IOS/Mac/Safari无法播放MP4文件流及Http1.1 Range分段请求相关

故事背景

公司项目需要将多媒体文件迁移至阿里OSS,原本直接从后端静态文件的<img> <video> 等标签都要改成从OSS获取展示。

于是乎拦截了所有静态链接,调用了OSS接口获取了文件流返回到前台进行展示,简单粗暴,但是也遇到了问题 —— ios的 <video> 标签全军覆没。

由于项目里视频比较少,这问题还是在上线前三天发现的,那个急的,一开始还找不到是什么问题,找到老版本的代码后发现前端代码毫无区别(亏得还在前端调试了半天),最后发现使用静态文件就没什么问题。

确定下来大致是文件流的缘故没跑了,百度+谷歌发现safari不支持整个文件流,服务器必须支持分段请求,也就是下面要说的Range分段请求了。


请求头 Range 和 响应头 Content-Range

Http协议从1.1开始支持获取文件的部分内容,这为并行下载以及断点续传提供了技术支持。该标准分为两个Header,分别为一个请求头Range, 和一个响应头Content-Range


请求头 Range

先来看看请求头,safari对于文件流的请求会带上这个头,服务器需要正确的对其作出响应,浏览器才能正确的展示文件。之前没法播放文件流正是因为没有正确响应这个请求头。

  • 格式
    该请求头的格式为 Range: bytes=开始字节-结束字节
    示例: Range:bytes=0-499 表示请求开始的500个字节

    除了基本格式以外,Range头还有可能是以下格式:
    表示第二个500字节:Range:bytes=500-999
    表示最后500个字节:Range:bytes=-500
    表示500字节以后的范围:Range:bytes=500-
    第一个和最后一个字节:Range:bytes=0-0,-1
    同时指定几个范围:Range:bytes=500-600,601-999

  • 如何回复
    对于Range头,服务器需要作出正确的响应,返回对应的状态码来告知客户端,服务器是否支持分段请求:

    200:不支持分段请求,但是能正常响应。
    206:支持分段请求 ,并返回分段结果,此时还需要在响应头中添加Content-Range头,并且返回对应的字节片段。

即:如果我们需要正确的响应分段请求,需要做以下几步

  1. 解析Range请求头,获取客户端想要的文件片段范围
  2. 返回206状态码
  3. 响应头中Content-Type头需要填写为文件mime类型,如 video/mp4
  4. 响应头中添加Content-Range头,告诉客户端字节片段的信息(包含字节起止位置及文件流总大小)
  5. 响应体中添加客户端所请求的字节片段

响应头 Content-Range

上一步讲到我们除了需要返回206状态码及返回字节片段以外,还需要返回一个Content-Range头,来告诉客户端我们返回了哪一部分的字节片段,来看一下这个响应头相关的一些信息。

  • 格式
    还是一样先来看格式,Content-Range: bytes 起始字节-末尾字节/总字节数
    示例:Content-Range:bytes 0-499/13521 表示文件总共有13521字节, 本次返回了0-499字节

    针对不同格式的Range头,我们的响应内容也会有相应的变化(*注意,这里一定不是照搬Range头的内容)
    对于 Range:bytes=500-999,应该返回 Content-Range:bytes 500-999/13521
    对于 Range:bytes=-500,应该返回 Content-Range:bytes 13021-13520/13521
    对于 Range:bytes=500-,应该返回 Content-Range:bytes 500-13520/13521

    对于 Range:bytes=0-0,-1Range:bytes=500-600,601-999 这样的多个范围, Content-Type 头需要修改为multipart/byteranges ,并且在响应体内返回每个范围对应的多个 Content-RangeContent-Type,这里具体是怎样实现,由于上线比较着急,用到多个范围的场景也非常少,所以没有时间验证及研究,后面再来做补充,如果有大牛能告知这里应该怎么做的,希望能在评论区告知一下,提前谢谢啦!


代码我就不上了,每个语言每个框架都不一样,大家理解了之后按照以下步骤来书写代码,应该不会有什么大问题的。

  1. 判断请求中是否有 Range 头,如果没有,就按普通请求处理,返回整个流
  2. 如果有Range头,则解析Range的内容,获得所需片段的起止位置
  3. 设置响应状态码为 206
  4. 设置响应头 Content-Type为文件mime类型
  5. 设置响应头 Content-Range为正确格式的分段起止位置与文件流总字节数
  6. 根据分段起止位置截取文件流,写入响应体
  7. 结束该次请求

有什么问题欢迎在下面评论,我会及时回复www