Posted in: 前端

基于 HTTP Range 实现视频分片快速播放!

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 一个片段: 1db8681da83b4c3e8f86949dbb58794d~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0

下载 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_神说要有光
链接:https://juejin.cn/post/7255110638154072120
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

订阅
提醒
guest

0 评论
在线反馈
显示所有评论
Back to Top