折腾中成长
一:源起:
某天,A君话ios视频播放不了,但是Android和Web是可以播放的,一如当年听到 IE XXX 不行那样,可手头上并没iphone,一直用android机开发和调试,于是一头雾水的我,搜索到可能视频格式问题,结果什么格式都试过了,始终无果,直到搜索的关键词到一些帖子看到可能和请求头Range有关,但是解决过程还是跌跌撞撞,毕竟很多并没提到如何解决,当前的语言解决方案,当然解决后的自己在回看这些帖子时,才会觉得,实在也就这些,只不过当时雾中的自己看不清而已,最后,设备才是关键,一直没iphone,靠猜真是浪费青春。。。
一:问题起因排查:
- 视频格式问题,其实验证这个,只需要将视频地址改为本地存在视频的地址即可,不能单看后缀名,这也是为何要验证这个的原因,需要排查,但是造成的可能性不大
- 排查接口是否为本地资源还是视频流,若是本地资源,这个就好办了,检查路径是否正确以及是否存在该文件,若不是,才考虑后台流传输逻辑(比如下文所提到的解决方法)
- 检查请求头,这也是我一开始忽略导致方向迷失的原因,网络传输,这些很重要,需要分析出现的非常规的参数,或者说,自己应该清楚这些参数代表的意义
- 检查页面是否存在报错导致,检查video标签是否正确,其实这个正确在第一点也能一并验证出了
- 平台差异的确存在,Windos和Android的浏览器大部分(其实还没看见,稳妥点)对video标签是采取常规加载,而Safari则是采取分段加载,当然要不要分段这个选择权要不要该交给服务端,还是终端去确定这个又是另一个话题了
二:Iphone-H5-视频流播放:
- 第一次请求头,必包含 Range:bytes=0-1 (其实重点仅为Range这个key)
- 响应头必需要包含:
// 视频流格式,iphone支持mp4,但是注意码率这些,第一次必须返回此参数,后续的分段可不返回,省事的就所有都默认发送
Content-Type: video/mp4
// 该参数必须且不能错误,极其重要的参数
Content-Range: bytes 0-1/25536
// 该参数必须且不能错误,极其重要的参数
ContentLength: 1
- .NET 4.0 WebApi后台视频流分段传输参考源码(适用支持http协议且该协议带有Range规范的软件,也适合断点下载的场景,对于资源来说,不分是不是video还是文本,当然按上面的提到的点或者看http关于Range的规范也是不分语言或者该语言的哪种版本,此处仅仅为当时项目用到4.0而已):
public HttpResponseMessage ResourceSegmentLoad()
{
// 每次请求响应状态码均为 206,无须判断最后一次分段时返回200,省事
var response = new HttpResponseMessage(HttpStatusCode.PartialContent);
// 获取range,若是无返回的对象为null
var range = Request.Headers.Range?.Ranges.FirstOrDefault();
// 分段的起始位置,若为null,默认0
long? from = range == null ? 0 : range.From;
// 分段的结束位置,若为null,默认1
long? to = range == null ? 1 : range.To;
byte[] bytes = null;
// 具体如何获取,自行解决
string path = "你的资源文件地址";
// 具体如何获取,自行解决
string mediaType = "资源的文件类型";
long fileLength = 0;
var file = new FileInfo(path);
if (file.Exists)
{
using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
fileLength = fileStream.Length;
// 对应的文件流跳转到指定的分段起始位置开始读操作,Begin为从资源流0开始位置计算
fileStream.Seek(from.Value, SeekOrigin.Begin);
using (BinaryReader binaryReader = new BinaryReader(fileStream))
{
to = to == null ? fileLength - 1 : to.Value;
// 分段大小
int length = Convert.ToInt32(to - from) + 1;
// 仅仅加载分段指定范围的数据
bytes = binaryReader.ReadBytes(length);
}
}
}
// 切勿使用StreamContent
response.Content = new ByteArrayContent(bytes);
// 该类型谨谨遵循 http 规范
response.Content.Headers.ContentType = new MediaTypeHeaderValue(mediaType);
// 对应生成的header参数为 $"Content-Range: bytes {from}-{to}/{fileLength}"
response.Content.Headers.ContentRange = new ContentRangeHeaderValue(from.Value, to.Value, fileLength);
// 此处很容易误填fileLength,实际上是你放进Content 的bytes的大小
response.Content.Headers.ContentLength = bytes?.Length ?? 0;
return response;
}