The Java backend interface returns the video stream, uses the video component to play the video, and realizes segmented download

Video files are stored in unknown places, which cannot be directly accessed by the front-end. They need to be taken out through the back-end interface and returned to the front-end.
The front end plays like this

<video controls="controls" controls="controls">
  <source src="http://ip:port/xxxxxxx" type="video/mp4" />
</video>

src=backend interface
If the backend is directly written like this

	//path为本地文件路劲
    public void play(String path, HttpServletRequest request, HttpServletResponse response) {
        //获取视频文件流
        FileInputStream fileInputStream = null;
        OutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            fileInputStream = new FileInputStream(new File(path));
        byte[] cache = new byte[1024];
            response.setHeader(HttpHeaders.CONTENT_TYPE, "video/mp4");
            response.setHeader(HttpHeaders.CONTENT_LENGTH, fileInputStream.available()+"");
        int flag;
        while ((flag = fileInputStream.read(cache)) != -1) {
            outputStream.write(cache, 0, flag);
        }
        outputStream.flush();
        outputStream.close();
        }catch (Exception e){
            log.error("文件传输错误", e);
            throw new RuntimeException("文件传输错误");
        } finally{
            if(outputStream != null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    log.error("流释放错误", e);
                }
            }
            if(fileInputStream != null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    log.error("文件流释放错误", e);
                }
            }
        }
    }

Small videos are not a big problem. If the video is large, it will freeze for a long time. If you check the request, you will first request to download the full video and then start playing. And you can't drag the progress bar to play. The reason for catching up is because video will first send a probe request to obtain information such as the size and length of the video, and then start playing. The progress bar cannot be dragged to play because the acquisition video interface does not support breakpoint transmission, so it can only be watched in sequence.
The http request has a protocol related to breakpoint transmission. The http protocol status code 206 is the protocol for realizing breakpoint transmission. The Http request header needs to specify the range of data to be obtained: Range: bytes=first-end, first, the index position of the starting data , end, the index position of the end data. The Http response needs to specify the range response header: content-range bytes first-end, and the HTTP status code is set to 206.
Therefore, the general idea of ​​​​realizing video breakpoint playback is to first identify whether the http request header carries the range request header. If it does, it means that the request needs to be downloaded with a breakpoint. After identifying the location of the breakpoint, use the random read file class RandomAccessFile to perform a certain range. Read and write, and then return to set the Content-Range request header to indicate the range of the returned data, and set the http status code to 206 to indicate the breakpoint transmission. code show as below

	//path为本地文件路劲
	public void play(String path, HttpServletRequest request, HttpServletResponse response) {

        RandomAccessFile targetFile = null;
        OutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            response.reset();
            //获取请求头中Range的值
            String rangeString = request.getHeader(HttpHeaders.RANGE);

            //打开文件
            File file = new File(path);
            if (file.exists()) {
                //使用RandomAccessFile读取文件
                targetFile = new RandomAccessFile(file, "r");
                long fileLength = targetFile.length();
                long requestSize = (int) fileLength;
                //分段下载视频
                if (StringUtils.hasText(rangeString)) {
                    //从Range中提取需要获取数据的开始和结束位置
                    long requestStart = 0, requestEnd = 0;
                    String[] ranges = rangeString.split("=");
                    if (ranges.length > 1) {
                        String[] rangeDatas = ranges[1].split("-");
                        requestStart = Integer.parseInt(rangeDatas[0]);
                        if (rangeDatas.length > 1) {
                            requestEnd = Integer.parseInt(rangeDatas[1]);
                        }
                    }
                    if (requestEnd != 0 && requestEnd > requestStart) {
                        requestSize = requestEnd - requestStart + 1;
                    }
                    //根据协议设置请求头
                    response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
                    response.setHeader(HttpHeaders.CONTENT_TYPE, "video/mp4");
                    if (!StringUtils.hasText(rangeString)) {
                        response.setHeader(HttpHeaders.CONTENT_LENGTH, fileLength + "");
                    } else {
                        long length;
                        if (requestEnd > 0) {
                            length = requestEnd - requestStart + 1;
                            response.setHeader(HttpHeaders.CONTENT_LENGTH, "" + length);
                            response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + requestStart + "-" + requestEnd + "/" + fileLength);
                        } else {
                            length = fileLength - requestStart;
                            response.setHeader(HttpHeaders.CONTENT_LENGTH, "" + length);
                            response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + requestStart + "-" + (fileLength - 1) + "/"
                                    + fileLength);
                        }
                    }
                    //断点传输下载视频返回206
                    response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                    //设置targetFile,从自定义位置开始读取数据
                    targetFile.seek(requestStart);
                } else {
                    //如果Range为空则下载整个视频
                    response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=test.mp4");
                    //设置文件长度
                    response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileLength));
                }

                //从磁盘读取数据流返回
                byte[] cache = new byte[4096];
                try {
                    while (requestSize > 0) {
                        int len = targetFile.read(cache);
                        if (requestSize < cache.length) {
                            outputStream.write(cache, 0, (int) requestSize);
                        } else {
                            outputStream.write(cache, 0, len);
                            if (len < cache.length) {
                                break;
                            }
                        }
                        requestSize -= cache.length;
                    }
                } catch (IOException e) {
                    // tomcat原话。写操作IO异常几乎总是由于客户端主动关闭连接导致,所以直接吃掉异常打日志
                    //比如使用video播放视频时经常会发送Range为0- 的范围只是为了获取视频大小,之后就中断连接了
                    log.info(e.getMessage());
                }
            } else {
                throw new RuntimeException("文件路劲有误");
            }
            outputStream.flush();
        } catch (Exception e) {
            log.error("文件传输错误", e);
            throw new RuntimeException("文件传输错误");
        }finally {
            if(outputStream != null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    log.error("流释放错误", e);
                }
            }
            if(targetFile != null){
                try {
                    targetFile.close();
                } catch (IOException e) {
                    log.error("文件流释放错误", e);
                }
            }
        }
    }

Guess you like

Origin blog.csdn.net/m0_67401134/article/details/126738028