最近在做公司的视频业务,涉及到大的视频文件的上传和播放。针对大文件,无论是上传和下载都需要分片处理,不能像以前处理小图片一样直接将整个文件流上传到服务器,服务器也不能直接响应整个文件给客户端。
大文件的分片上传可以看笔者前面的文章:大文件分片上传前后端实现。
这篇文章,主要记录一下,服务端如何将一个大的视频文件做切分,分段响应给客户端,让浏览器可以渐进式的播放。
为什么需要分段播放?
如果一个视频文件很大,例如一部1GB的电影,服务端直接将整个文件响应给客户端是会抛异常的,浏览器也没办法一下子接收这么大的文件,视频播放会出问题。
其次,直接响应一个完整的视频,无疑会浪费服务器的带宽,用户点击播放,很少会完整的观看完视频,可能看一下片头不感兴趣就不看了,亦或是想直接快进到高潮部分,跳过前面的情节等等,服务端应该根据用户的需求,只响应用户真正需要的视频片段就可以了。
服务器带宽是很珍贵的稀缺资源,应该尽可能的节约。
Http请求头Range
如下是一个HTTP请求头示例:
1 2 3 4 5 6 | Accept: */* Accept-Encoding: identity;q=1, *;q=0 Accept-Language: zh-CN,zh;q=0.9 Connection: keep-alive Host: localhost:8080 Range: bytes=0-1024 |
Range请求头的意思是告诉服务端,这次请求客户端只需要资源的第0-1024个字节的区间数据,服务端只需要响应这部分数据就可以了。
使用
实战
话不多说,直接上后端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | /** * @author: pch * @description: 视频分段播放 * @date: 2020/10/27 **/ @RestController public class VideoController {<!-- --> @GetMapping("play") public void play(HttpServletRequest request, HttpServletResponse response) throws IOException{<!-- --> response.reset(); File file = new File("/Users/panchanghe/Downloads/阳光电影www.ygdy8.com.北极狗.BD.1080p.国英双语中字.mkv"); long fileLength = file.length(); // 随机读文件 RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r"); //获取从那个字节开始读取文件 String rangeString = request.getHeader("Range"); long range=0; if (StrUtil.isNotBlank(rangeString)) {<!-- --> range = Long.valueOf(rangeString.substring(rangeString.indexOf("=") + 1, rangeString.indexOf("-"))); } //获取响应的输出流 OutputStream outputStream = response.getOutputStream(); //设置内容类型 response.setHeader("Content-Type", "video/mp4"); //返回码需要为206,代表只处理了部分请求,响应了部分数据 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 移动访问指针到指定位置 randomAccessFile.seek(range); // 每次请求只返回1MB的视频流 byte[] bytes = new byte[1024 * 1024]; int len = randomAccessFile.read(bytes); //设置此次相应返回的数据长度 response.setContentLength(len); //设置此次相应返回的数据范围 response.setHeader("Content-Range", "bytes "+range+"-"+(fileLength-1)+"/"+fileLength); // 将这1MB的视频流响应给客户端 outputStream.write(bytes, 0, len); outputStream.close(); randomAccessFile.close(); System.out.println("返回数据区间:【"+range+"-"+(range+len)+"】"); } } |
读取视频文件的指定位置数据,主要还是用到了JDK提供的
在浏览器中直接键入播放地址,视频的请求过程是这样的:
再来看看后端控制台输出:
尾巴
利用文件分段下载的特点,除了可以做视频的渐进式播放,还有很多其他的用处。
例如:文件的断点续传、文件多线程并发下载(迅雷就是这么玩的)等。