注册
web

通过调试技术,我理清了 b 站视频播放很快的原理

b 站视频播放的是很快的,基本是点哪就播放到哪。


而且如果你上次看到某个位置,下次会从那个位置继续播放。


那么问题来了:如果一个很大的视频,下载下来需要很久,怎么做到点哪个位置快速播放那个位置的视频呢?


前面写过一篇 range 请求的文章,也就是不下载资源的全部内容,只下载 range 对应的范围的部分。


那视频的快速播放,是不是也是基于 range 来实现的呢?


我们先复习下 range 请求:



请求的时候带上 range:



服务端会返回 206 状态码,还有 Content-Range 的 header 代表当前下载的是整个资源的哪一部分:



这里的 Content-Length 是当前内容的长度,而 Content-Range 里是资源总长度和当前资源的范围。


更多关于 Range 的介绍可以看这篇文章:基于 HTTP Range 实现文件分片并发下载!


那 b 站视频是不是用 Range 来实现的快速播放呢?


我们先在知乎的视频试一下:


随便打开一个视频页面,比如这个:



然后打开 devtools,刷新页面,拖动下进度条,可以看到确实有 206 的状态码:



我们可以在搜索框输入 status-code:206 把它过滤出来:



这是一种叫过滤器的技巧:



可以根据 method、domain、mime-type 等过滤。




  • has-response-header:过滤响应包含某个 header 的请求




  • method:根据 GET、POST 等请求方式过滤请求




  • domain: 根据域名过滤




  • status-code:过滤响应码是 xxx 的请求,比如 404、500 等




  • larger-than:过滤大小超过多少的请求,比如 100k,1M




  • mime-type:过滤某种 mime 类型的请求,比如 png、mp4、json、html 等




  • resource-type:根据请求分类来过滤,比如 document 文档请求,stylesheet 样式请求、fetch 请求,xhr 请求,preflight 预检请求




  • cookie-name:过滤带有某个名字的 cookie 的请求




当然,这些不需要记,输入一个 - 就会提示所有的过滤器:



但是这个减号之后要去掉,它是非的意思:



和右边的 invert 选项功能一样。


然后点开状态码为 206 的请求看一下:




确实,这是标准的 range 请求。


我点击进度条到后面的位置,可以看到发出了新的 range 请求:



那这些 range 请求有什么关系呢?


我们需要分析下 Content-Range,但是一个个点开看不直观。


这时候可以自定义显示的列:


右键单击列名,可以勾选展示的 header,不过这里面没有我们想要的 header,需要自定义:



点击 Manage Header Columns



添加自定义的 header,输入 Content-Range:



这时候就可以直观的看出这些 range 请求的范围之间的关系:



点击 Content-Range 这一列,升序排列。


我们刷新下页面,从头来试一下:


随着视频的播放,你会看到一个个 range 请求发出:



这些 range 请求是能连起来的,也就是说边播边下载后面的部分。


视频进度条这里的灰条也在更新:



当你直接点击后面的进度条:



观察下 range,是不是新下载的片段和前面不连续了?


也就是说会根据进度来计算出 range,再去请求。


那这个 range 是完全随意的么?


并不是。


我们当前点击的是 15:22 的位置:



我刷新下页面,点击 15:31 的位置:



如果是任意的 range,下载的部分应该和之前的不同吧。


但是你观察下两次的 range,都是 2097152-3145727


也就是说,视频分成多少段是提前就确定的,你点击进度条的时候,会计算出在哪个 range,然后下载对应 range 的视频片段来播放。


那有了这些视频片段,怎么播放呢?


浏览器有一个 SourceBuffer 的 api,我们在 MDN 看一下:



大概是这样用的:



也就是说,可以一部分一部分的下载视频片段,然后 append 上去。


拖动进度条的时候,可以把之前的部分删掉,再 append 新的:



我们验证下,搜索下代码里是否有 SourceBuffer:


按住 command + f 可以搜索请求内容:



可以看到搜索出 3 个结果。


在其中搜索下 SourceBuffer:



可以看到很多用到 SourceBuffer 的方法,基本可以确认就是基于 SourceBuffer 实现的。


也就是说,知乎视频是通过 range 来请求部分视频片段,通过 SourceBuffer 来动态播放这个片段,来实现的快速播放的目的。具体的分段是提前确定好的,会根据进度条来计算出下载哪个 range 的视频。


那服务端是不是也要分段存储这些视频呢?


确实,有这样一种叫做 m3u8 的视频格式,它的存储就是一个个片段 ts 文件来存储的,这样就可以一部分一部分下载。



不过知乎没用这种格式,还是 mp4 存储的,这种就需要根据 range 来读取部分文件内容来返回了:



再来看看 b 站,它也是用的 range 请求的方式来下载视频片段:



大概 600k 一个片段:


下载 600k 在现在的网速下需要多久?这样播放能不快么?


相比之下,知乎大概是 1M 一个片段:



网速不快的时候,体验肯定是不如 b 站的。


而且 b 站用的是一种叫做 m4s 的视频格式:



它和 m3u8 类似,也是分段存储的,这样提前分成不同的小文件,然后 range 请求不同的片段文件,速度自然会很快。


然后再 command + f 搜索下代码,同样是用的 SourceBuffer:



这样,我们就知道了为什么 b 站视频播放的那么快了:


m4s 分段存储视频,通过 range 请求动态下载某个视频片段,然后通过 SourceBuffer 来动态播放这个片段。


总结


我们分析了 b 站、知乎视频播放速度很快的原因。


结论是通过 range 动态请求视频的某个片段,然后通过 SourceBuffer 来动态播放这个片段。


这个 range 是提前确定好的,会根据进度条来计算下载哪个 range 的视频。


播放的时候,会边播边下载后面的 range,而调整进度的时候,也会从对应的 range 开始下载。


服务端存储这些视频片段的方式,b 站使用的 m4s,当然也可以用 m3u8,或者像知乎那样,动态读取 mp4 文件的部分内容返回。


除了结论之外,调试过程也是很重要的:


我们通过 status-code 的过滤器来过滤除了 206 状态码的请求。



通过自定义列在列表中直接显示了 Content-Range:



通过 command + f 搜索了响应的内容:



这篇文章就是对这些调试技巧的综合运用。


以后再看 b 站和知乎视频的时候,你会不会想起它是基于 range 来实现的分段下载和播放呢?



更多调试技术可以看我的调试小册《前端调试通关秘籍》


作者:zxg_神说要有光
来源:juejin.cn/post/7255110638154072120

0 个评论

要回复文章请先登录注册