Java视频流在线播放(分断下载,断点续传)
短信预约 -IT技能 免费直播动态提醒
思路:前端使用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) { } } } }
第二步: 完成前端
大坑注意:正常情况到这里就已经正常播放视频了,但是博主却发现了一个问题,我本地录制的视频可以播放,从摄像头截取下来的视频却不可以播放,点击播放时后台一直报java.io.IOException: 您的主机中的软件中止了一个已建立的连接,这种情况一般是前端主动断开连接造成的。
百思不得其解时突然想到是不是视频的问题,果然,发现视频监控截取下来的虽然是.mp4后缀,但是视频编码是MPEG-PS的编码格式,而vdieo标签目前只支持三种视频格式
由于前端不能播放这种视频,所以主动断开了链接,所以这里有涉及到了视频转码。
首先引入依赖:
org.bytedeco javacv 1.4.1 org.bytedeco javacpp 1.4.1
编写转码工具类
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 { 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异常"); } } }}
来源地址:https://blog.csdn.net/dongcsdn123/article/details/132207675
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341