Java视频流在线播放(分断下载,断点续传)

思路:前端使用video标签,访问后端视频接口地址,并附带视频路径参数,进行播放(后端接口需要支持分段,否则前端视频不能拖动进度条)。

第一步:完成后端方法。

public void play(String path, HttpServletRequest request, HttpServletResponse response) {        
        String fileName ="测试.mp4";
        RandomAccessFile targetFile = null;
        OutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            response.reset();
            //获取请求头中Range的值
            String rangeString = request.getHeader("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=" + fileName + "");
                    //设置文件长度
                    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) {
 
 
                }
            } else {
                throw new RuntimeException("文件路劲有误");
            }
            outputStream.flush();
        } catch (Exception e) {
 
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
 
                }
            }
            if (targetFile != null) {
                try {
                    targetFile.close();
                } catch (IOException e) {
 
                }
            }
        }
 
    }

第二步: 完成前端

<template>
  <div class="video-monitor-wrapper">
    <video width="100%" id="see" controls="controls">
      <source src="接口地址" type="video/mp4" />
    </video>
  </div>
</template>

大坑注意:正常情况到这里就已经正常播放视频了,但是博主却发现了一个问题,我本地录制的视频可以播放,从摄像头截取下来的视频却不可以播放,点击播放时后台一直报java.io.IOException: 您的主机中的软件中止了一个已建立的连接,这种情况一般是前端主动断开连接造成的。

百思不得其解时突然想到是不是视频的问题,果然,发现视频监控截取下来的虽然是.mp4后缀,但是视频编码是MPEG-PS的编码格式,而vdieo标签目前只支持三种视频格式

由于前端不能播放这种视频,所以主动断开了链接,所以这里有涉及到了视频转码。

首先引入依赖:

<dependency>
  <groupId>org.bytedeco</groupId>
  <artifactId>javacv</artifactId>
  <version>1.4.1</version>
</dependency>

<dependency>
  <groupId>org.bytedeco</groupId>
  <artifactId>javacpp</artifactId>
  <version>1.4.1</version>
</dependency>

编写转码工具类 

package com.video.tool.hik;

import cn.hutool.http.HttpUtil;
import org.bytedeco.javacpp.avcodec;
import org.bytedeco.javacv.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* 视频转码工具类
*/
public class VideoEncodeUtil {
    /**
* @param inputFile  文件原始路径
* @param outputFile 文件输出路径
* @param id
* @throws Exception
*/
    public static void encode(String inputFile, String outputFile, String id) throws Exception {

        final Logger logger = LoggerFactory.getLogger(DownLoadCallBack.class);
        FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(inputFile);
        Frame captured_frame;
        FFmpegFrameRecorder recorder = null;

        try {
            grabber.start();

            recorder = new FFmpegFrameRecorder(outputFile, grabber.getImageWidth(), grabber.getImageHeight(),
                                               grabber.getAudioChannels());
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            recorder.setFormat("mp4");
            recorder.setFrameRate(grabber.getFrameRate());
            recorder.setSampleRate(grabber.getSampleRate());
            recorder.setVideoBitrate(grabber.getVideoBitrate());
            recorder.setAspectRatio(grabber.getAspectRatio());
            recorder.setAudioBitrate(grabber.getAudioBitrate());
            recorder.setAudioOptions(grabber.getAudioOptions());
            recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
            recorder.start();

            while (true) {
                captured_frame = grabber.grabFrame();
                if (captured_frame == null) {
                    logger.info("转码成功");
                    break;
                }
                recorder.record(captured_frame);
            }

        } catch (FrameRecorder.Exception e) {
            e.printStackTrace();
        } finally {
            if (recorder != null) {
                try {
                    recorder.close();
                } catch (Exception e) {
                    logger.info("recorder.close异常");
                }
            }

            try {
                grabber.close();
            } catch (FrameGrabber.Exception e) {
                logger.info("frameGrabber.close异常");
            }
        }
    }
}

 

 

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值