Spring boot实现视频播放断点续传

Spring boot提供HDFS上的视频播放接口,支持断点续传

视频播放断点续传

HTTP1.1中优化了网络连接的使用,支持在Header中设置range字段,允许请求资源的一部分,并使用返回码 206 “Partial Content” 标识

后端简单实现

    @GetMapping("/file/video-preview")
    public void videoPreview(HttpServletResponse response,
                             @RequestParam String filePath,
                             @RequestHeader String range) throws IOException {
        hugeFileService.videoPreview(filePath, range, response);
    }

    public void videoPreview(String filePath, String range, HttpServletResponse response) throws IOException {
        response.reset();
        // 自定义getHdfsPath函数生成文件在hdfs上的路径
        Path srcPath = new Path(getHdfsPath(filePath));
        FSDataInputStream resource = fs.open(srcPath);

        // 每次返回的最大数据块大小
        long partLen = 1024 * 1024 * 2;
        byte[] bytes = new byte[(int) partLen];
        
        // 计算本次返回的数据范围 [start, end]
        long fileLen = hdfsFileService.getHdfsFileStatus(srcPath.toString()).getLen();
        long start = Long.parseLong(range.substring(range.indexOf("=") + 1, range.indexOf("-")));
        long end = Math.min(start + partLen - 1, fileLen-1);

        BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
        // 随机寻址 定位数据块头
        resource.seek(start);
        // 计算本次返回的数据块大小
        int len = (int) (end - start + 1);
        // 由于每次最多读 65535 循环读入数据
        int ptr = 0;
        while (ptr < len) {
            int readLen = Math.min(65535, len - ptr);
            resource.readFully(bytes, ptr, readLen);
            ptr += readLen;
        }

        //返回码需要为206,代表只处理了部分请求,响应了部分数据
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        response.setHeader("Content-Type", "video/mp4");
        //设置此次相应返回的数据长度
        response.setContentLength(len);
        //设置此次相应返回的数据范围
        response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileLen);
        response.setHeader("Accept-Ranges", "bytes");

        // 写入数据到OutputStream
        out.write(bytes, 0, len);

        out.flush();
        out.close();
        resource.close();
    }
}
前端简单实现(假设点击播放按钮,在一个Modal内部,生成播放视频的代码)

buildVideoShowData = () => {
    const { file } = this.props;
    const { path } = file;
    const url = `http://ip:port/file/video-preview?filePath=${path}`;
    return (
        <video width="840" height="630"
                controls='controls'
                preload='auto'
                autoPlay={true}
                src={url}
                loop={true}
        >
        </video>
    )
}

可能遇到的问题

如果视频从开头播放一直没问题,直到最后一段卡死。需要检查后端返回的Http响应中的range是否设置正确
range的end最大为fileLen-1,最后一段需设置为 xxx-fileLen-1/fileLen

posted @ 2021-03-27 11:30  BuptWade  阅读(2985)  评论(1)    收藏  举报