Browse Source

视频预览

master
han\hanst 1 week ago
parent
commit
e083d45598
  1. 3
      build.gradle
  2. 1
      src/main/java/com/xujie/sys/config/ShiroConfig.java
  3. 163
      src/main/java/com/xujie/sys/modules/longchuang/service/impl/ProductionPlanServiceImpl.java
  4. 320
      src/main/java/com/xujie/sys/modules/oss/controller/OssController.java

3
build.gradle

@ -147,6 +147,9 @@ dependencies {
implementation 'com.google.zxing:core:3.5.3' implementation 'com.google.zxing:core:3.5.3'
implementation 'com.google.zxing:javase:3.5.3' implementation 'com.google.zxing:javase:3.5.3'
// FFmpeg nativeffmpeg
implementation 'org.bytedeco:javacv-platform:1.5.10'
} }
test { test {

1
src/main/java/com/xujie/sys/config/ShiroConfig.java

@ -48,6 +48,7 @@ public class ShiroConfig {
filterMap.put("/tcp", "anon"); filterMap.put("/tcp", "anon");
filterMap.put("/ckp-file/**", "anon"); filterMap.put("/ckp-file/**", "anon");
filterMap.put("/oss/2/**", "anon"); filterMap.put("/oss/2/**", "anon");
filterMap.put("/oss/video/**", "anon");
filterMap.put("/oss/previewOssFile", "anon"); filterMap.put("/oss/previewOssFile", "anon");
// filterMap.put("/**", "authc"); // filterMap.put("/**", "authc");
filterMap.put("/webjars/**", "anon"); filterMap.put("/webjars/**", "anon");

163
src/main/java/com/xujie/sys/modules/longchuang/service/impl/ProductionPlanServiceImpl.java

@ -27,6 +27,11 @@ import com.xujie.sys.modules.oss.entity.SysOssEntity;
import com.xujie.sys.modules.oss.service.SysOssService; import com.xujie.sys.modules.oss.service.SysOssService;
import com.xujie.sys.modules.sys.entity.SysUserEntity; import com.xujie.sys.modules.sys.entity.SysUserEntity;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -41,6 +46,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
@ -69,6 +75,8 @@ public class ProductionPlanServiceImpl implements ProductionPlanService {
private static final String NODE_REPORT_MODE_PARALLEL = "PARALLEL"; private static final String NODE_REPORT_MODE_PARALLEL = "PARALLEL";
private static final String NODE_REPORT_MODE_SEQUENTIAL = "SEQUENTIAL"; private static final String NODE_REPORT_MODE_SEQUENTIAL = "SEQUENTIAL";
private static final String NODE_MEDIA_REF_TYPE = "LONGCHUANG_NODE_REPORT_MEDIA"; private static final String NODE_MEDIA_REF_TYPE = "LONGCHUANG_NODE_REPORT_MEDIA";
private static final String MEDIA_INFO_VIDEO_STANDARD = "VIDEO_STD_MP4_H264_AAC";
private static final String MEDIA_INFO_VIDEO_RAW = "VIDEO_RAW";
@Autowired @Autowired
private ProductionPlanMapper productionPlanMapper; private ProductionPlanMapper productionPlanMapper;
@ -79,6 +87,9 @@ public class ProductionPlanServiceImpl implements ProductionPlanService {
@Value("${longchuang.production-report.media-root:D:\\longchuang}") @Value("${longchuang.production-report.media-root:D:\\longchuang}")
private String workReportMediaRootPath; private String workReportMediaRootPath;
@Value("${longchuang.production-report.enable-video-transcode:true}")
private boolean enableVideoTranscode;
@Override @Override
public PageUtils queryHomeLiftOrderPage(ProductionPlanOrderQueryData data) { public PageUtils queryHomeLiftOrderPage(ProductionPlanOrderQueryData data) {
return queryOrderPage(data, ORDER_TYPE_HOME_LIFT); return queryOrderPage(data, ORDER_TYPE_HOME_LIFT);
@ -856,18 +867,42 @@ public class ProductionPlanServiceImpl implements ProductionPlanService {
if (file == null || file.isEmpty()) { if (file == null || file.isEmpty()) {
continue; continue;
} }
String suffix = resolveMediaSuffix(file.getOriginalFilename(), file.getContentType());
String newFileName = buildMediaFileName(suffix);
String originalFileName = file.getOriginalFilename();
String suffix = resolveMediaSuffix(originalFileName, file.getContentType());
boolean videoFile = isVideoMedia(file.getContentType(), suffix);
boolean transcodeVideo = videoFile && enableVideoTranscode;
String targetSuffix = transcodeVideo ? "mp4" : (StringUtils.hasText(suffix) ? suffix : (videoFile ? "mp4" : "bin"));
String newFileName = buildMediaFileName(targetSuffix);
File targetFile = new File(nodeDateFolder, newFileName); File targetFile = new File(nodeDateFolder, newFileName);
file.transferTo(targetFile);
if (transcodeVideo) {
String sourceSuffix = StringUtils.hasText(suffix) ? suffix : "bin";
File sourceFile = File.createTempFile("lc_media_src_", "." + sourceSuffix);
try {
file.transferTo(sourceFile);
transcodeVideoToMp4(sourceFile, targetFile);
} finally {
if (sourceFile.exists()) {
sourceFile.delete();
}
}
} else {
file.transferTo(targetFile);
}
if (!targetFile.exists() || targetFile.length() <= 0) {
throw new XJException("报工影像上传失败,请重试");
}
savedFileList.add(targetFile); savedFileList.add(targetFile);
SysOssEntity ossEntity = new SysOssEntity(); SysOssEntity ossEntity = new SysOssEntity();
ossEntity.setUrl(targetFile.getAbsolutePath()); ossEntity.setUrl(targetFile.getAbsolutePath());
ossEntity.setCreateDate(new Date()); ossEntity.setCreateDate(new Date());
ossEntity.setFileName(StringUtils.hasText(file.getOriginalFilename()) ? file.getOriginalFilename() : newFileName);
String displayFileName = StringUtils.hasText(originalFileName) ? originalFileName : newFileName;
if (transcodeVideo) {
displayFileName = normalizeVideoDisplayName(displayFileName);
}
ossEntity.setFileName(displayFileName);
ossEntity.setNewFileName(newFileName); ossEntity.setNewFileName(newFileName);
ossEntity.setFileType(suffix);
ossEntity.setFileType(targetSuffix);
ossEntity.setCreatedBy(userName); ossEntity.setCreatedBy(userName);
ossEntity.setOrderRef1(data.getOrderNo()); ossEntity.setOrderRef1(data.getOrderNo());
ossEntity.setOrderRef2(node.getNodeCode()); ossEntity.setOrderRef2(node.getNodeCode());
@ -876,7 +911,11 @@ public class ProductionPlanServiceImpl implements ProductionPlanService {
ossEntity.setOrderRef5(String.valueOf(userId)); ossEntity.setOrderRef5(String.valueOf(userId));
ossEntity.setOrderRef6(node.getNodeName()); ossEntity.setOrderRef6(node.getNodeName());
ossEntity.setOrderReftype(NODE_MEDIA_REF_TYPE); ossEntity.setOrderReftype(NODE_MEDIA_REF_TYPE);
ossEntity.setCAdditionalInfo(resolveMediaCategory(file.getContentType()));
String mediaAdditionalInfo = resolveMediaCategory(file.getContentType());
if (videoFile) {
mediaAdditionalInfo = transcodeVideo ? MEDIA_INFO_VIDEO_STANDARD : MEDIA_INFO_VIDEO_RAW;
}
ossEntity.setCAdditionalInfo(mediaAdditionalInfo);
ossEntity.setConclusion(userName); ossEntity.setConclusion(userName);
sysOssService.save(ossEntity); sysOssService.save(ossEntity);
uploadCount++; uploadCount++;
@ -887,6 +926,11 @@ public class ProductionPlanServiceImpl implements ProductionPlanService {
savedFile.delete(); savedFile.delete();
} }
} }
log.error("报工影像保存失败,orderNo={}, nodeCode={}, logNo={}, error={}",
data.getOrderNo(), data.getNodeCode(), logNo, e.getMessage(), e);
if (e instanceof XJException) {
throw (XJException) e;
}
throw new XJException("报工影像上传失败,请重试"); throw new XJException("报工影像上传失败,请重试");
} }
if (uploadCount <= 0) { if (uploadCount <= 0) {
@ -901,6 +945,28 @@ public class ProductionPlanServiceImpl implements ProductionPlanService {
return folderName.replaceAll("[\\\\/:*?\"<>|]", "_").trim(); return folderName.replaceAll("[\\\\/:*?\"<>|]", "_").trim();
} }
private boolean isVideoMedia(String contentType, String suffix) {
if (StringUtils.hasText(contentType) && contentType.toLowerCase().startsWith("video/")) {
return true;
}
if (!StringUtils.hasText(suffix)) {
return false;
}
String lowerSuffix = suffix.toLowerCase();
return Arrays.asList("mp4", "webm", "mov", "avi", "m4v", "3gp", "mkv").contains(lowerSuffix);
}
private String normalizeVideoDisplayName(String originalFileName) {
if (!StringUtils.hasText(originalFileName)) {
return "video.mp4";
}
int dotIndex = originalFileName.lastIndexOf(".");
if (dotIndex >= 0) {
return originalFileName.substring(0, dotIndex) + ".mp4";
}
return originalFileName + ".mp4";
}
private String resolveMediaSuffix(String originalFileName, String contentType) { private String resolveMediaSuffix(String originalFileName, String contentType) {
if (StringUtils.hasText(originalFileName)) { if (StringUtils.hasText(originalFileName)) {
int dotIndex = originalFileName.lastIndexOf("."); int dotIndex = originalFileName.lastIndexOf(".");
@ -924,6 +990,15 @@ public class ProductionPlanServiceImpl implements ProductionPlanService {
if (lowerType.contains("mp4")) { if (lowerType.contains("mp4")) {
return "mp4"; return "mp4";
} }
if (lowerType.contains("quicktime")) {
return "mov";
}
if (lowerType.contains("avi")) {
return "avi";
}
if (lowerType.contains("3gpp")) {
return "3gp";
}
return "bin"; return "bin";
} }
@ -933,6 +1008,82 @@ public class ProductionPlanServiceImpl implements ProductionPlanService {
return "LCM_" + timeText + "_" + randomText + "." + suffix; return "LCM_" + timeText + "_" + randomText + "." + suffix;
} }
private void transcodeVideoToMp4(File sourceFile, File targetFile) {
if (sourceFile == null || !sourceFile.exists()) {
throw new XJException("视频源文件不存在,无法转码");
}
if (targetFile.exists() && !targetFile.delete()) {
throw new XJException("视频转码目标文件清理失败");
}
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(sourceFile);
FFmpegFrameRecorder recorder = null;
try {
grabber.start();
int width = grabber.getImageWidth();
int height = grabber.getImageHeight();
if (width <= 0 || height <= 0) {
throw new XJException("视频转码失败:无法识别视频分辨率");
}
int audioChannels = Math.max(grabber.getAudioChannels(), 0);
recorder = new FFmpegFrameRecorder(targetFile, width, height, audioChannels);
recorder.setInterleaved(true);
recorder.setFormat("mp4");
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setVideoOption("preset", "veryfast");
recorder.setVideoOption("crf", "23");
recorder.setVideoOption("movflags", "+faststart");
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
double frameRate = grabber.getVideoFrameRate();
if (frameRate > 0) {
recorder.setFrameRate(frameRate);
} else {
recorder.setFrameRate(25);
}
int videoBitrate = grabber.getVideoBitrate();
recorder.setVideoBitrate(videoBitrate > 0 ? videoBitrate : 1500_000);
if (audioChannels > 0) {
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
int sampleRate = grabber.getSampleRate();
recorder.setSampleRate(sampleRate > 0 ? sampleRate : 44100);
recorder.setAudioChannels(audioChannels);
int audioBitrate = grabber.getAudioBitrate();
recorder.setAudioBitrate(audioBitrate > 0 ? audioBitrate : 128000);
}
recorder.start();
Frame frame;
while ((frame = grabber.grabFrame()) != null) {
recorder.record(frame);
}
recorder.stop();
grabber.stop();
if (!targetFile.exists() || targetFile.length() <= 0) {
throw new XJException("视频转码失败:目标文件为空");
}
} catch (XJException e) {
throw e;
} catch (Exception e) {
log.error("视频转码失败,source={}, target={}, error={}",
sourceFile.getAbsolutePath(), targetFile.getAbsolutePath(), e.getMessage(), e);
throw new XJException("视频转码失败,请重试");
} finally {
try {
if (recorder != null) {
recorder.release();
}
} catch (Exception ignored) {
}
try {
grabber.release();
} catch (Exception ignored) {
}
}
}
private String resolveMediaCategory(String contentType) { private String resolveMediaCategory(String contentType) {
if (!StringUtils.hasText(contentType)) { if (!StringUtils.hasText(contentType)) {
return "OTHER"; return "OTHER";

320
src/main/java/com/xujie/sys/modules/oss/controller/OssController.java

@ -5,13 +5,20 @@ import com.xujie.sys.common.utils.OfficeConverter;
import com.xujie.sys.common.utils.R; import com.xujie.sys.common.utils.R;
import com.xujie.sys.modules.oss.entity.SysOssEntity; import com.xujie.sys.modules.oss.entity.SysOssEntity;
import com.xujie.sys.modules.oss.service.SysOssService; import com.xujie.sys.modules.oss.service.SysOssService;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.*; import java.io.*;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
@ -20,14 +27,25 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@RestController @RestController
@RequestMapping("/oss") @RequestMapping("/oss")
public class OssController { public class OssController {
private static final Logger logger = LoggerFactory.getLogger(OssController.class); private static final Logger logger = LoggerFactory.getLogger(OssController.class);
private static final String MEDIA_INFO_VIDEO_STANDARD = "VIDEO_STD_MP4_H264_AAC";
@Value("${sys-file.mes-url}") @Value("${sys-file.mes-url}")
private String mesUrl; private String mesUrl;
@Value("${longchuang.production-report.enable-playback-transcode:false}")
private boolean enablePlaybackTranscode;
@Value("${longchuang.production-report.playback-transcode-force-mp4:false}")
private boolean playbackTranscodeForceMp4;
private final ConcurrentHashMap<String, Object> playbackTranscodeLockMap = new ConcurrentHashMap<>();
@Autowired @Autowired
private SysOssService sysOssService; private SysOssService sysOssService;
@ -75,6 +93,74 @@ public class OssController {
sysOssService.previewOssFileById2(id,response); sysOssService.previewOssFileById2(id,response);
} }
@GetMapping("/video/{id}")
public void previewVideoStream(@PathVariable("id") Long id, HttpServletRequest request, HttpServletResponse response) throws IOException {
try {
SysOssEntity attachment = sysOssService.getById(id);
if (attachment == null || !StringUtils.hasText(attachment.getUrl())) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "视频不存在");
return;
}
File sourceFile = new File(attachment.getUrl());
if (!sourceFile.exists() || !sourceFile.isFile()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "视频文件不存在");
return;
}
File videoFile = sourceFile;
boolean playbackTranscoded = false;
if (shouldPlaybackTranscode(attachment, sourceFile)) {
videoFile = ensurePlaybackVideoFile(sourceFile);
playbackTranscoded = !sourceFile.getAbsolutePath().equals(videoFile.getAbsolutePath());
}
long fileLength = videoFile.length();
if (fileLength <= 0) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "视频文件为空");
return;
}
String contentType = playbackTranscoded ? "video/mp4" : resolveVideoContentType(attachment, videoFile);
response.setContentType(contentType);
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
response.setHeader("Content-Disposition", "inline");
String rangeHeader = request.getHeader("Range");
if (!StringUtils.hasText(rangeHeader) || !rangeHeader.startsWith("bytes=")) {
response.setStatus(HttpServletResponse.SC_OK);
response.setHeader("Content-Length", String.valueOf(fileLength));
writeVideoRange(videoFile, 0, fileLength - 1, response);
return;
}
long[] range = resolveVideoRange(rangeHeader, fileLength);
if (range == null) {
response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
response.setHeader("Content-Range", "bytes */" + fileLength);
return;
}
long start = range[0];
long end = range[1];
long contentLength = end - start + 1;
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileLength);
response.setHeader("Content-Length", String.valueOf(contentLength));
writeVideoRange(videoFile, start, end, response);
} catch (Exception e) {
if (isClientAbortException(e)) {
logger.debug("视频流已由客户端主动断开,id={}, msg={}", id, e.getMessage());
return;
}
if (!response.isCommitted()) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "视频预览失败");
return;
}
throw e;
}
}
/** /**
* 通过OSS服务代理预览或下载附件 * 通过OSS服务代理预览或下载附件
* OSS接口POST http://172.26.68.17:8091//oss/2/{id} * OSS接口POST http://172.26.68.17:8091//oss/2/{id}
@ -285,9 +371,243 @@ public class OssController {
case "rar": return "application/x-rar-compressed"; case "rar": return "application/x-rar-compressed";
case "7z": return "application/x-7z-compressed"; case "7z": return "application/x-7z-compressed";
case "mp4": return "video/mp4"; case "mp4": return "video/mp4";
case "m4v": return "video/mp4";
case "webm": return "video/webm";
case "mov": return "video/quicktime";
case "3gp": return "video/3gpp";
case "avi": return "video/x-msvideo"; case "avi": return "video/x-msvideo";
case "mkv": return "video/x-matroska";
case "mp3": return "audio/mpeg"; case "mp3": return "audio/mpeg";
default: return "application/octet-stream"; default: return "application/octet-stream";
} }
} }
private String resolveVideoContentType(SysOssEntity attachment, File videoFile) {
String ext = "";
if (attachment != null && StringUtils.hasText(attachment.getFileType())) {
ext = attachment.getFileType();
}
if (!StringUtils.hasText(ext) && attachment != null && StringUtils.hasText(attachment.getFileName())) {
ext = resolveExtByName(attachment.getFileName());
}
if (!StringUtils.hasText(ext) && videoFile != null) {
ext = resolveExtByName(videoFile.getName());
}
String resolved = getContentType(ext);
if (StringUtils.hasText(resolved) && !"application/octet-stream".equalsIgnoreCase(resolved)) {
return resolved;
}
return "video/mp4";
}
private String resolveExtByName(String fileName) {
String raw = fileName == null ? "" : fileName.trim();
int index = raw.lastIndexOf(".");
if (index < 0 || index >= raw.length() - 1) {
return "";
}
return raw.substring(index + 1);
}
private boolean shouldPlaybackTranscode(SysOssEntity attachment, File sourceFile) {
if (!enablePlaybackTranscode || sourceFile == null) {
return false;
}
if (attachment != null && StringUtils.hasText(attachment.getCAdditionalInfo())) {
String additionalInfo = attachment.getCAdditionalInfo().trim().toUpperCase();
if (additionalInfo.contains(MEDIA_INFO_VIDEO_STANDARD)) {
return false;
}
}
String sourceName = sourceFile.getName();
if (sourceName.endsWith("_stream.mp4")) {
return false;
}
String ext = resolveExtByName(sourceName);
if (!StringUtils.hasText(ext)) {
return true;
}
String lowerExt = ext.toLowerCase();
if ("mp4".equals(lowerExt)) {
return playbackTranscodeForceMp4;
}
return true;
}
private File ensurePlaybackVideoFile(File sourceFile) {
if (sourceFile == null || !sourceFile.exists() || !sourceFile.isFile()) {
return sourceFile;
}
String sourceName = sourceFile.getName();
int dotIndex = sourceName.lastIndexOf(".");
String baseName = dotIndex > 0 ? sourceName.substring(0, dotIndex) : sourceName;
File cachedFile = new File(sourceFile.getParentFile(), baseName + "_stream.mp4");
if (cachedFile.exists() && cachedFile.length() > 0 && cachedFile.lastModified() >= sourceFile.lastModified()) {
return cachedFile;
}
String lockKey = sourceFile.getAbsolutePath();
Object lock = playbackTranscodeLockMap.computeIfAbsent(lockKey, key -> new Object());
synchronized (lock) {
try {
if (cachedFile.exists() && cachedFile.length() > 0 && cachedFile.lastModified() >= sourceFile.lastModified()) {
return cachedFile;
}
transcodeVideoForPlayback(sourceFile, cachedFile);
if (cachedFile.exists() && cachedFile.length() > 0) {
return cachedFile;
}
return sourceFile;
} catch (Exception e) {
logger.error("播放转码失败,source={}, cache={}, error={}",
sourceFile.getAbsolutePath(), cachedFile.getAbsolutePath(), e.getMessage(), e);
return sourceFile;
} finally {
playbackTranscodeLockMap.remove(lockKey);
}
}
}
private void transcodeVideoForPlayback(File sourceFile, File targetFile) throws Exception {
if (targetFile.exists() && !targetFile.delete()) {
throw new IOException("播放转码目标文件清理失败");
}
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(sourceFile);
FFmpegFrameRecorder recorder = null;
try {
grabber.start();
int width = grabber.getImageWidth();
int height = grabber.getImageHeight();
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("视频分辨率无效");
}
int audioChannels = Math.max(grabber.getAudioChannels(), 0);
recorder = new FFmpegFrameRecorder(targetFile, width, height, audioChannels);
recorder.setInterleaved(true);
recorder.setFormat("mp4");
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setVideoOption("preset", "veryfast");
recorder.setVideoOption("crf", "23");
recorder.setVideoOption("movflags", "+faststart");
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
double frameRate = grabber.getVideoFrameRate();
recorder.setFrameRate(frameRate > 0 ? frameRate : 25);
int videoBitrate = grabber.getVideoBitrate();
recorder.setVideoBitrate(videoBitrate > 0 ? videoBitrate : 1500_000);
if (audioChannels > 0) {
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
int sampleRate = grabber.getSampleRate();
recorder.setSampleRate(sampleRate > 0 ? sampleRate : 44100);
recorder.setAudioChannels(audioChannels);
int audioBitrate = grabber.getAudioBitrate();
recorder.setAudioBitrate(audioBitrate > 0 ? audioBitrate : 128000);
}
recorder.start();
Frame frame;
while ((frame = grabber.grabFrame()) != null) {
recorder.record(frame);
}
recorder.stop();
grabber.stop();
if (!targetFile.exists() || targetFile.length() <= 0) {
throw new IOException("播放转码结果为空");
}
} finally {
try {
if (recorder != null) {
recorder.release();
}
} catch (Exception ignored) {
}
try {
grabber.release();
} catch (Exception ignored) {
}
}
}
private long[] resolveVideoRange(String rangeHeader, long fileLength) {
try {
String rangeValue = rangeHeader.substring("bytes=".length()).trim();
int commaIndex = rangeValue.indexOf(",");
if (commaIndex > -1) {
rangeValue = rangeValue.substring(0, commaIndex).trim();
}
int dashIndex = rangeValue.indexOf("-");
if (dashIndex < 0) {
return null;
}
String startText = rangeValue.substring(0, dashIndex).trim();
String endText = rangeValue.substring(dashIndex + 1).trim();
long start;
long end;
if (!StringUtils.hasText(startText)) {
long suffixLength = Long.parseLong(endText);
if (suffixLength <= 0) {
return null;
}
start = suffixLength >= fileLength ? 0 : fileLength - suffixLength;
end = fileLength - 1;
} else {
start = Long.parseLong(startText);
end = StringUtils.hasText(endText) ? Long.parseLong(endText) : fileLength - 1;
}
if (start < 0 || end < 0 || start >= fileLength) {
return null;
}
if (end >= fileLength) {
end = fileLength - 1;
}
if (end < start) {
return null;
}
return new long[]{start, end};
} catch (Exception e) {
return null;
}
}
private void writeVideoRange(File file, long start, long end, HttpServletResponse response) throws IOException {
OutputStream outputStream = response.getOutputStream();
try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
randomAccessFile.seek(start);
byte[] buffer = new byte[8192];
long remain = end - start + 1;
while (remain > 0) {
int readLength = randomAccessFile.read(buffer, 0, (int) Math.min(buffer.length, remain));
if (readLength < 0) {
break;
}
outputStream.write(buffer, 0, readLength);
remain -= readLength;
}
outputStream.flush();
}
}
private boolean isClientAbortException(Throwable throwable) {
Throwable current = throwable;
while (current != null) {
String className = current.getClass().getName();
String message = current.getMessage();
if (className.contains("ClientAbortException") || className.contains("AsyncRequestNotUsableException")) {
return true;
}
if (message != null) {
String lowerMessage = message.toLowerCase();
if (lowerMessage.contains("broken pipe")
|| lowerMessage.contains("connection reset")
|| lowerMessage.contains("connection reset by peer")
|| lowerMessage.contains("forcibly closed")) {
return true;
}
}
current = current.getCause();
}
return false;
}
} }
Loading…
Cancel
Save