代码改变世界

Re:从零开始学流媒体(一):视频跳转、206、chunked、动态URL、断点续传

2021-08-16 14:33  查士丁尼  阅读(633)  评论(0编辑  收藏  举报

一、静态路由视频播放

首先,不考虑用最新的技术,不考虑m3u8,用最传统的方式,如何实现页面播放视频?

最简单的是直接引用资源路径。

Demo如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="https://vjs.zencdn.net/7.5.4/video-js.css" rel="stylesheet">
</head>
<body>
<video id="example_video_1" class="video-js vjs-default-skin" controls preload="none" width="640" height="264"
       poster="http://vjs.zencdn.net/v/oceans.png">
<!--    <source src="/frag_bunny.mp4" type="video/mp4">-->
    <source src="/getMovie" type="video/mp4">

</video>
</body>
</html>
<script src='https://vjs.zencdn.net/7.5.4/video.js'></script>
<script type="text/javascript">
    var player1 = videojs('example_video_1');
    player1.play();
</script>

其中src填入的是你项目静态资源路由根目录.

 

二、动态URL播放视频

由于不是所有资源都在静态路由的,资源可能是云存储,可能是其他文件路径。

使用静态路由,太不方便,nginx可以映射到其他位置。

但考虑到权限、云存储等问题。动态路由很有必要。

如何实现动态路由?

看上去很简单,我只需要设置HttpResponse的表头setContentType、 FileInputStream填入文件,用OutputStream读取文件就可以了

网上有很多例子。

但是这样做有个弊端:按顺序读取文件。也就是视频你无法跳转播放!他只能按顺序缓冲。

为什么使用动态URL无法跳转视频,静态URL却可以跳转播放?

这就涉及到了Http协议的问题了。

 

通过对比静态Url和动态Url的HttpResponse,我们知道:

如果是动态路由,默认的传输方式是Transfer-Encoding为chunked,成功类型为200,这样的方式是无法支持视频跳转和断点续传等功能的。

所以我们需要手动修改返回类型和请求头。

我们只需要设置Content-Length和Content-Range,并设置状态为206,之后Transfer-Encoding:chunked就会自动被取消掉(需要注意的一个细节,设置Content-Length和Content-Range必须要在inputStream执行之前)

而Range需要根据HttpRequest来具体的数字处理和拼接。

服务器端代码如下:

@ResponseBody
    @RequestMapping("getMovie")
    public void getMovie(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String path = "/Users/Desktop/movie/aaa.mp4";
        response.setContentType("video/mp4");
        response.setHeader("Accept-Ranges", "bytes");
        response.setStatus(javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT);

        long fSize=new File(path).length();

        long pos = 0, last = fSize - 1, sum = 0;//pos开始读取位置;  last最后读取位置;  sum记录总共已经读取了多少字节
        if (null != request.getHeader("Range")) {
         response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            try {
                String numRang = request.getHeader("Range").replaceAll("bytes=", "");
                String[] strRange = numRang.split("-");
                if (strRange.length == 2) {
                    pos = Long.parseLong(strRange[0].trim());
                    last = Long.parseLong(strRange[1].trim());
                } else {
                    pos = Long.parseLong(numRang.replaceAll("-", "").trim());
                }
            } catch (NumberFormatException e) {
                pos = 0;
            }
        }
        long rangLength = last - pos + 1;// 总共需要读取的字节

        response.setHeader("Content-Range", "bytes " + pos + "-" + last + "/" + fSize);
        response.setHeader("Content-Length", String.valueOf(rangLength));

        FileInputStream in = new FileInputStream(new File(path));
        in.skip(pos);
        OutputStream out = response.getOutputStream();

        byte[] b = new byte[512];

        int length = 0;
        while (sum < rangLength) {
            length = in.read(b, 0, ((rangLength - sum) <= b.length ? ((int) (rangLength - sum)) : b.length));
            sum = sum + length;
            out.write(b, 0, length);
        }

        out.flush();
        in.close();
        out.close();

然后,就可以通过/getMovie来随意读取文件,可以实现跳转播放了。