背景:紧接着上篇文章,博主已经完成了海康摄像头的视频截取,现在则需要实现视频播放功能。
思路:前端使用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异常");
}
}
}
}