陈同学
微服务
Accelerator
About
# 移动端无法播放视频问题 ## 问题描述 今天前端小伙伴抛出一个微信服务号开发时遇到的视频播放问题,一个页面中要播放一段视频,视频存储在我们自己的内容管理系统(简称CMS)中。他可以播放网络上的视频,但是CMS中的视频无法播放。CMS报错如下: ```java Caused by: ClientAbortException: java.net.SocketException: Broken pipe at com.hscf.common.utl.FileUtil.copyByNIO(FileUtil.java:499) at com.hscf.common.utl.FileUtil.copyByNIO(FileUtil.java:540) at com.hscf.media.pub.FileUtlService.$tt__readFile(FileUtlService.groovy:92) ... 13 more Caused by: java.net.SocketException: Broken pipe at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:113) at java.net.SocketOutputStream.write(SocketOutputStream.java:159) ... 16 more ``` ## 问题排查 从异常来看,是Client主动close了connection. 因此往 HttpServletResponse中写数据时会报错。 ### 排查1: 视频用PC可以正常播放 加大验证范围后,发现PC端浏览器可以正常播放视频,但移动端的浏览器无法播放。 移动端和PC端访问效果如下: ![](https://blog-1256695615.cos.ap-shanghai.myqcloud.com/2018/05/16/899d2d714a6f4d0da3560c3e5e99b019.png) 说明在PC端下提供视频的API本身没有问题。 ### 排查2: 检查PC端和移动端的请求参数 后端日志打印了下PC端和服务端发起的请求和响应数据,核心区别如下: 移动端发起了两次请求,第一次和PC端没太大区别,第二次多了个Http Header:`key:range value:bytes=0-1` 复习了下利用HTTP Range作为断点下载的知识后,可以判断是CMS没有支持断点下载。 ### 排查3: 用Charles模拟请求 Mac下用Charles抓了来自手机的数据包,发现网络上的正常资源下载过程如下: ![](https://blog-1256695615.cos.ap-shanghai.myqcloud.com/2018/05/16/ca52ad61566049879ba2c44d8389b385.png) 由于移动端的特性,资源往往是分片下载。通过上图可以看到视频分4次下来,第一次的数据量为 45.73KB,第二次为2字节,然后继续下载完成。 **第一次请求** 第一个请求Request没设置分片信息,但是Response中返回了两个重要的Header属性: `Accept-Ranges:bytes` 和`Content-Length:214541`. 这两个属性告诉客户端了数据总量。此时,客户端立马中断了TCP连接,因此ClientAbortException就得到了解释。 此时,我用curl命令尝试下载一个50M的文件,并且立马手工中断,也复现了ClientAbortException异常 **第二次请求** 第二次客户端请求了2个字节,利用`Range: bytes=0-1`告诉服务端仅获取2字节,通过这个请求拿到了属性:`Content-Range:bytes 0-1/214541` 即真正的数量大小信息,因为第一次服务端不一定返回`Content-Length`属性 **后两次请求** 第三次,客户端发起的请求包含属性:`Range: bytes=0-214540`,告诉服务端要下载所有数据,但是只下载了45.68KB就主动中断,控制每次下载数据的量。 第四次,客户端下载了`Range: bytes=20629-214540`,下载完成。 ### 排查4: 模拟断点下载 写了个简单程序模拟断点下载: ```java @GetMapping("/file") public void read(HttpServletRequest request, HttpServletResponse response) throws IOException { long size = 214541; // hardcode文件大小 response.setHeader("Content-Transfer-Encoding", "binary"); response.setHeader("Accept-Ranges", "bytes"); response.setHeader("Content-type", "video/mp4"); response.setHeader("Content-Disposition", "inline; filename=\"1002.mp4\";"); FileInputStream fis = new FileInputStream(new File("/Users/cyj/Downloads/1002.mp4")); OutputStream os = response.getOutputStream(); String range = request.getHeader("range"); if (!"".equals(range) && range != null) { // 简单解析Range属性,正常开发需要支持Range各种情景 String rangeValue = range.replace("bytes=", ""); String[] rangeArr = rangeValue.split("-"); int start = Integer.parseInt(rangeArr[0]); int end = Integer.parseInt(rangeArr[1]); response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + size); IOUtils.copyLarge(fis, os, start, end - start + 1); if (end != (size - 1)) { response.setStatus(206); // 206表示数据仅部分下载完成 } } else { IOUtils.copy(fis, os); } } ``` 在移动端访问以上程序提供的视频,可以正常播放,问题得以解决。 ## 总结 以前CMS做过分片上传,但是两年前,太过久远。同时CMS没有为移动端提供过视频资源,缺少处理经验。
本文由
cyj
创作,可自由转载、引用,但需署名作者且注明文章出处。
文章标题:
移动端无法播放视频问题
文章链接:
https://chenyongjun.vip/articles/31
扫码或搜索 cyjrun 关注微信公众号, 结伴学习, 一起努力