11 0.6.2 5.7.15 3.13.2 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-configuration-processor true org.projectlombok lombok true org.bytedeco javacv-platform 1.5.8
org.bytedeco ffmpeg-platform-gpl 5.1.2-1.5.8 net.bramp.ffmpeg ffmpeg ${ffmpeg.version} cn.hutool hutool-all ${hutool.version} com.aliyun.oss aliyun-sdk-oss ${aliyun-sdk-oss.version}
注意: ffmpeg-platform
与 javacv-platform
版本要对应
spring:servlet:multipart:max-file-size: 200MBmax-request-size: 500MB#m3u8视频转换配置
m3u8:convertor:base-path: /file/m3u8/temp-path: /file/temp/big-path: /file/big/proxy: m3u8/ali:oss:#oss end-pointend-point: #oss access-key-idaccess-key-id: #oss access-key-secretaccess-key-secret: #oss bucket-namebucket-name: #访问地址 可以与 ali-url my-host-url 一致 例如 https://12312312.oss-cn-shenzhen.aliyuncs.com/url: #访问地址 可以与 my-host-url url 一致 例如 https://12312312.oss-cn-shenzhen.aliyuncs.com/ali-url: get-file-url: ${aliyun.oss.url}${aliyun.oss.fileDir}#访问地址 可以与 ali-url url 一致 例如 https://12312312.oss-cn-shenzhen.aliyuncs.com/my-host-url:
config
包 properties
配置相关类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;/*** 线程池配置*/
@Configuration
@EnableAsync
public class SpringAsyncConfig {/*** oss async* @return*/@Bean("ossUploadTreadPool")public ThreadPoolTaskExecutor asyncServiceExecutorForOss() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 设置核心线程数,采用IO密集 h/(1-拥塞)executor.setCorePoolSize(8);// 设置最大线程数,由于minIO连接数量有限,此处尽力设计大点executor.setMaxPoolSize(120);// 设置线程活跃时间(秒)executor.setKeepAliveSeconds(30);// 设置默认线程名称executor.setThreadNamePrefix("ossUploadTask-");// 等待所有任务结束后再关闭线程池executor.setWaitForTasksToCompleteOnShutdown(true);//执行初始化executor.initialize();return executor;}
}
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Data
@Component
@ConfigurationProperties(prefix = "m3u8.convertor")
public class FilePath {/*** 文件上传临时路径 (本地文件转换不需要)*/private String tempPath = "/file/tmp/";/*** m3u8文件转换后,储存的根路径*/private String basePath = "/file/m3u8/";/*** m3u8文件转换后,储存的根路径*/private String bigPath = "/file/big/";private String proxy = "m3u8/";}
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Component
@ConfigurationProperties(prefix = "ali.oss")
@Data
public class AliOssProperties {/*** OSS配置信息*/private String endpoint;private String accessKeyId;private String accessKeySecret;private String bucketName;private String myHostUrl;private String url;private String aliUrl;
}
component
包 相关组件类import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.CompleteMultipartUploadRequest;
import com.aliyun.oss.model.CompleteMultipartUploadResult;
import com.aliyun.oss.model.InitiateMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadResult;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PartETag;
import com.aliyun.oss.model.PutObjectResult;
import com.aliyun.oss.model.UploadPartRequest;
import com.aliyun.oss.model.UploadPartResult;
import com.laowei.ffmpegm3u8demo.config.AliOssProperties;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;/*** 阿里云 OSS 工具类** */
@Component
@Slf4j
@Getter
public class OssComponent{@Resourceprivate AliOssProperties aliOssProperties;/* -----------------对外功能---------------- *//*** 本地文件切片上传** @param objectName:文件名* @param path : 本地完整路径,xxx/xxx.txt* @return :异常*/public String uploadSlice(String objectName, String localPath,String path) throws IOException {OSS ossClient = new OSSClientBuilder().build(aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(), aliOssProperties.getAccessKeySecret());String keyPath = path+objectName;// 创建InitiateMultipartUploadRequest对象。InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(aliOssProperties.getBucketName(), keyPath);// 如果需要在初始化分片时设置请求头,请参考以下示例代码。ObjectMetadata metadata = new ObjectMetadata();// 指定该Object的网页缓存行为。metadata.setCacheControl("no-cache");// 指定该Object被下载时的名称。metadata.setContentDisposition("attachment;filename=" + objectName);// 指定初始化分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object。metadata.setHeader("x-oss-forbid-overwrite", "true");// 初始化分片。InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);// 返回uploadId,它是分片上传事件的唯一标识。您可以根据该uploadId发起相关的操作,例如取消分片上传、查询分片上传等。String uploadId = result.getUploadId();List partETags = new ArrayList<>();// 每个分片的大小,用于计算文件有多少个分片。单位为字节。final long partSize = 5 * 1024 * 1024L; //1 MB。// 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。final File sampleFile = new File(localPath);long fileLength = sampleFile.length();int partCount = (int) (fileLength / partSize);if (fileLength % partSize != 0) {partCount++;}// 遍历分片上传。for (int i = 0; i < partCount; i++) {long startPos = i * partSize;long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;try (InputStream inStream = new FileInputStream(sampleFile)) {// 跳过已经上传的分片。long skip = inStream.skip(startPos);UploadPartRequest uploadPartRequest = new UploadPartRequest();uploadPartRequest.setBucketName(aliOssProperties.getBucketName());uploadPartRequest.setKey(keyPath);uploadPartRequest.setUploadId(uploadId);uploadPartRequest.setInputStream(inStream);// 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。uploadPartRequest.setPartSize(curPartSize);// 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,OSS将返回InvalidArgument错误码。uploadPartRequest.setPartNumber(i + 1);// 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);// 每次上传分片之后,OSS的返回结果包含PartETag。PartETag将被保存在partETags中。partETags.add(uploadPartResult.getPartETag());}catch (Exception e){log.error("OSS切片上传异常,e:{}",e.getMessage());}}// 创建CompleteMultipartUploadRequest对象。// 在执行完成分片上传操作时,需要提供所有有效的partETags。OSS收到提交的partETags后,会逐一验证每个分片的有效性。当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。CompleteMultipartUploadRequest completeMultipartUploadRequest =new CompleteMultipartUploadRequest(aliOssProperties.getBucketName(),keyPath , uploadId, partETags);// 完成分片上传。CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);log.info(completeMultipartUploadResult.getETag());// 关闭OSSClient。ossClient.shutdown();return path+objectName;}/*** 单个文件上传** @param file 文件* @return 返回完整URL地址*/public String uploadFile(String fileDir, MultipartFile file) {String fileUrl = upload2Oss(fileDir, file);String str = getFileUrl(fileDir, fileUrl);return str.trim();}/*** 单个文件上传(指定文件名(带后缀))** @param inputStream 文件* @param fileName 文件名(带后缀)* @return 返回完整URL地址*/public String uploadFile(String fileDir, InputStream inputStream, String fileName) {try {this.uploadFile2Oss(fileDir, inputStream, fileName);String url = getFileUrl(fileDir, fileName);if (url != null && url.length() > 0) {return url;}} catch (Exception e) {throw new RuntimeException("获取路径失败");}return "";}/*** 多文件上传** @param fileList 文件列表* @return 返回完整URL,逗号分隔*/public String uploadFile(String fileDir, List fileList) {String fileUrl;String str;StringBuilder photoUrl = new StringBuilder();for (int i = 0; i < fileList.size(); i++) {fileUrl = upload2Oss(fileDir, fileList.get(i));str = getFileUrl(fileDir, fileUrl);if (i == 0) {photoUrl = new StringBuilder(str);} else {photoUrl.append(",").append(str);}}return photoUrl.toString().trim();}public boolean deleteFile(String fileDir, String fileName) {OSS ossClient = new OSSClientBuilder().build(aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(), aliOssProperties.getAccessKeySecret());// 删除文件ossClient.deleteObject(aliOssProperties.getBucketName(), fileDir + fileName);// 判断文件是否存在boolean found = ossClient.doesObjectExist(aliOssProperties.getBucketName(), fileDir + fileName);// 如果文件存在则删除失败return !found;}/*** 通过文件名获取文完整件路径** @param fileUrl 文件名* @return 完整URL路径*/public String getFileUrl(String fileDir, String fileUrl) {if (fileUrl != null && fileUrl.length() > 0) {String[] split = fileUrl.replaceAll("\\\\","/").split("/");String url = aliOssProperties.getMyHostUrl() + fileDir + split[split.length - 1];return Objects.requireNonNull(url);}return null;}public File getFile(String url) {//对本地文件命名String fileName = url.substring(url.lastIndexOf("."));File file = null;try {file = File.createTempFile("net_url", fileName);} catch (Exception e) {log.error("创建默认文件夹net_url失败!原因e:{}", e.getMessage());}if (file != null) {try (InputStream inStream = new URL(url).openStream();OutputStream os = new FileOutputStream(file)) {int bytesRead;byte[] buffer = new byte[8192];while ((bytesRead = inStream.read(buffer, 0, 8192)) != -1) {os.write(buffer, 0, bytesRead);}} catch (Exception e) {e.printStackTrace();}}return file;}/* -----------内部辅助功能------------------------ *//*** 获取去掉参数的完整路径** @param url URL* @return 去掉参数的URL*/private String getShortUrl(String url) {String[] imgUrls = url.split("\\?");return imgUrls[0].trim();}/*** 获得url真实外网链接* 不提供使用,因为会产生公网OOS流量下行费用** @param key 文件名* @return URL*/@Deprecatedprivate String getUrl(String key) {OSS ossClient = new OSSClientBuilder().build(aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(), aliOssProperties.getAccessKeySecret());// 设置URL过期时间为20年 3600l* 1000*24*365*20Date expiration = new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 365 * 20);URL url = ossClient.generatePresignedUrl(aliOssProperties.getBucketName(), key, expiration);if (url != null) {String replaceUrl = url.toString().replace(aliOssProperties.getAliUrl(), aliOssProperties.getUrl());return getShortUrl(replaceUrl);}ossClient.shutdown();return null;}/*** 上传文件** @param file 文件* @return 文件名*/private String upload2Oss(String fileDir, MultipartFile file) {// 2、重命名文件String fileName = Objects.requireNonNull(file.getOriginalFilename(), "文件名不能为空");// 文件后缀String suffix = fileName.substring(fileName.lastIndexOf(".")).toLowerCase(Locale.ENGLISH);String uuid = UUID.randomUUID().toString();String name = uuid + suffix;try {InputStream inputStream = file.getInputStream();this.uploadFile2Oss(fileDir, inputStream, name);return name;} catch (Exception e) {throw new RuntimeException("上传失败");}}/*** 上传文件(指定文件名)** @param inputStream 输入流* @param fileName 文件名*/private void uploadFile2Oss(String fileDir, InputStream inputStream, String fileName) {OSS ossClient = new OSSClientBuilder().build(aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(), aliOssProperties.getAccessKeySecret());String ret;try {//创建上传Object的MetadataObjectMetadata objectMetadata = new ObjectMetadata();objectMetadata.setContentLength(inputStream.available());objectMetadata.setCacheControl("no-cache");objectMetadata.setHeader("Pragma", "no-cache");objectMetadata.setContentType(getContentType(fileName.substring(fileName.lastIndexOf("."))));objectMetadata.setContentDisposition("inline;filename=" + fileName);//上传文件PutObjectResult putResult = ossClient.putObject(aliOssProperties.getBucketName(), fileDir + fileName, inputStream, objectMetadata);ret = putResult.getETag();if (StringUtils.isEmpty(ret)) {log.error("上传失败,文件ETag为空");}ossClient.shutdown();} catch (IOException e) {log.error(e.getMessage(), e);} finally {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}}/*** 请求类型** @param filenameExtension :* @return :*/private static String getContentType(String filenameExtension) {if (FileNameSuffixEnum.BMP.getSuffix().equalsIgnoreCase(filenameExtension)) {return "image/bmp";}if (FileNameSuffixEnum.GIF.getSuffix().equalsIgnoreCase(filenameExtension)) {return "image/gif";}if (FileNameSuffixEnum.JPEG.getSuffix().equalsIgnoreCase(filenameExtension) ||FileNameSuffixEnum.JPG.getSuffix().equalsIgnoreCase(filenameExtension) ||FileNameSuffixEnum.PNG.getSuffix().equalsIgnoreCase(filenameExtension)) {return "image/jpeg";}if (FileNameSuffixEnum.HTML.getSuffix().equalsIgnoreCase(filenameExtension)) {return "text/html";}if (FileNameSuffixEnum.TXT.getSuffix().equalsIgnoreCase(filenameExtension)) {return "text/plain";}if (FileNameSuffixEnum.VSD.getSuffix().equalsIgnoreCase(filenameExtension)) {return "application/vnd.visio";}if (FileNameSuffixEnum.PPTX.getSuffix().equalsIgnoreCase(filenameExtension) ||FileNameSuffixEnum.PPT.getSuffix().equalsIgnoreCase(filenameExtension)) {return "application/vnd.ms-powerpoint";}if (FileNameSuffixEnum.DOCX.getSuffix().equalsIgnoreCase(filenameExtension) ||FileNameSuffixEnum.DOC.getSuffix().equalsIgnoreCase(filenameExtension)) {return "application/msword";}if (FileNameSuffixEnum.XML.getSuffix().equalsIgnoreCase(filenameExtension)) {return "text/xml";}if (FileNameSuffixEnum.PDF.getSuffix().equalsIgnoreCase(filenameExtension)) {return "application/pdf";}return "image/jpeg";}}@Getter
enum FileNameSuffixEnum {/*** 文件后缀名*/BMP(".bmp", "bmp文件"),GIF(".gif", "gif文件"),JPEG(".jpeg", "jpeg文件"),JPG(".jpg", "jpg文件"),PNG(".png", "png文件"),HTML(".html", "HTML文件"),TXT(".txt", "txt文件"),VSD(".vsd", "vsd文件"),PPTX(".pptx", "PPTX文件"),DOCX(".docx", "DOCX文件"),PPT(".ppt", "PPT文件"),DOC(".doc", "DOC文件"),XML(".xml", "XML文件"),PDF(".pdf", "PDF文件");/*** 后缀名*/private final String suffix;/*** 描述*/private final String description;FileNameSuffixEnum(String suffix, String description) {this.suffix = suffix;this.description = description;}
}
import javax.annotation.Resource;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;@Slf4j
@Component
public class M3u8Component {@Resourceprivate FilePath filePath;public String mediaFileToJavaM3u8(MultipartFile file) throws Exception{if (file.isEmpty()) {throw new RuntimeException("未发现文件");}log.info("开始解析视频");long start = System.currentTimeMillis();//临时目录创建String path = new File(System.getProperty("user.dir")).getAbsolutePath();String tempFilePath = path+ filePath.getTempPath();if (!FileUtil.exist(tempFilePath)) {FileUtil.mkdir(tempFilePath);}String filePathName = tempFilePath + file.getOriginalFilename();File dest = new File(filePathName);try {file.transferTo(dest);}catch (Exception e){log.error("视频转m3u8格式存在异常,异常原因e:{}",e.getMessage());}//m3u8文件 存储路径String filePath = m3u8Util.generateFilePath(this.filePath.getBasePath());if (!FileUtil.exist(filePath)) {FileUtil.mkdir(filePath);}long end = System.currentTimeMillis();log.info("临时文件上传成功......耗时:{} ms", end - start);String m3u8FilePath = FfmpegUtil.mp4ToM3u8(filePathName,filePath);log.info("视频转换已完成 !");return m3u8FilePath;}
}
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;import java.time.LocalDateTime;public class m3u8Util {/***@Description 根据基础路径,生成文件存储路径*@param basePath 基础路径(根路径)*@Return */public static String generateFilePath(String basePath){String temp = basePath;if(StrUtil.isNotBlank(basePath)){if(basePath.endsWith("/")){temp = basePath.substring(0,basePath.lastIndexOf("/"));}}return temp+"/"+generateDateDir()+"/";}/***@Description 根据当前时间,生成下级存储目录*@Return*/public static String generateDateDir(){LocalDateTime now = LocalDateTime.now();return DateUtil.format(now, "yyyyMMdd/HH/mm/ss");}/***@Description 根据文件全路径,获取文件主名称*@param fullPath 文件全路径(包含文件名)*@Return*/public static String getFileMainName(String fullPath){String fileName = FileUtil.getName(fullPath);return fileName.substring(0,fileName.lastIndexOf("."));}}
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
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.FFmpegLogCallback;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameRecorder;import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.UUID;/*** javacv ffmpeg 工具类*/
@Slf4j
public class FfmpegUtil {/*** mp4, m3u8** @param filePathName 需要转换文件* @param toFilePath 需要转换的文件路径*/public static String mp4ToM3u8(String filePathName, String toFilePath) throws Exception {avutil.av_log_set_level(avutil.AV_LOG_INFO);FFmpegLogCallback.set();boolean isStart = true;// 该变量建议设置为全局控制变量,用于控制录制结束//加载文件FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(filePathName);//grabber.setAudioChannels(1);grabber.start();String fileName = UUID.randomUUID().toString().replaceAll("-", "");File tempFile3 = new File(toFilePath, fileName + ".m3u8");String prefixName = toFilePath + File.separator + fileName;//生成加密keyString secureFileName = prefixName + ".key";byte[] secureRandom = getSecureRandom();FileUtil.writeBytes(secureRandom,secureFileName);String toHex = Convert.toHex(secureRandom);String keyInfoPath = toFilePath + File.separator +"key.keyinfo";//写入加密文件writeKeyInfo(keyInfoPath,fileName + ".key",secureFileName,toHex);FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(tempFile3, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());//格式方式recorder.setFormat("hls");//关于hls_wrap的说明,hls_wrap表示重复覆盖之前ts切片,这是一个过时配置,ffmpeg官方推荐使用hls_list_size 和hls_flags delete_segments代替hls_wrap//设置单个ts切片的时间长度(以秒为单位)。默认值为2秒recorder.setOption("hls_time", "10");//不根据gop间隔进行切片,强制使用hls_time时间进行切割ts分片recorder.setOption("hls_flags", "split_by_time");//设置播放列表条目的最大数量。如果设置为0,则列表文件将包含所有片段,默认值为5// 当切片的时间不受控制时,切片数量太小,就会有卡顿的现象recorder.setOption("hls_list_size", "0");//自动删除切片,如果切片数量大于hls_list_size的数量,则会开始自动删除之前的ts切片,只保留hls_list_size个数量的切片recorder.setOption("hls_flags", "delete_segments");//ts切片自动删除阈值,默认值为1,表示早于hls_list_size+1的切片将被删除recorder.setOption("hls_delete_threshold", "1");/*hls的切片类型:* 'mpegts':以MPEG-2传输流格式输出ts切片文件,可以与所有HLS版本兼容。* 'fmp4':以Fragmented MP4(简称:fmp4)格式输出切片文件,类似于MPEG-DASH,fmp4文件可用于HLS version 7和更高版本。*/recorder.setOption("hls_segment_type", "mpegts");//指定ts切片生成名称规则,按数字序号生成切片,例如'file%03d.ts',就会生成file000.ts,file001.ts,file002.ts等切片文件//recorder.setOption("hls_segment_filename", toFilePath + "-%03d.ts");recorder.setOption("hls_segment_filename", toFilePath + File.separator + fileName + "-%5d.ts");//加密recorder.setOption("hls_key_info_file", keyInfoPath);// 设置第一个切片的编号
// recorder.setOption("start_number", String.valueOf(tsCont));
// recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);// 转码log.info("{} | 启动Hls转码录制器……", toFilePath);// 设置零延迟//recorder.setVideoOption("tune", "zerolatency");recorder.setVideoOption("tune", "fastdecode");// 快速recorder.setVideoOption("preset", "ultrafast");
// recorder.setVideoOption("crf", "26");recorder.setVideoOption("threads", "12");recorder.setVideoOption("vsync", "2");recorder.setFrameRate(grabber.getFrameRate());// 设置帧率
// recorder.setGopSize(25);// 设置gop,与帧率相同,相当于间隔1秒chan's一个关键帧
// recorder.setVideoBitrate(100 * 1000);// 码率500kb/srecorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
// recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);//如果想截取规定时间段视频
// recorder.start();
// Frame frame;
// while ((frame = grabber.grabImage()) != null) {
// try {
// recorder.record(frame);
// } catch (FrameRecorder.Exception e) {
// log.error("转码异常:{}", e);
// }
// }recorder.start(grabber.getFormatContext());AVPacket packet;while ((packet = grabber.grabPacket()) != null) {try {recorder.recordPacket(packet);} catch (FrameRecorder.Exception e) {log.error("转码异常:{}", e);}}recorder.setTimestamp(grabber.getTimestamp());recorder.stop();recorder.release();grabber.stop();grabber.release();File dest = new File(filePathName);if (dest.isFile() && dest.exists()) {dest.delete();log.warn("临时文件 {}已删除", dest.getName());}log.info("转码m3u8:{}", tempFile3.getAbsolutePath());return tempFile3.getAbsolutePath();}/*** 安全安全随机** @return {@link byte[]}*/public static byte[] getSecureRandom(){byte[] bytes = new byte[16];new SecureRandom().nextBytes(bytes);return bytes;}/*** 写入关键文件数据** @param keyInfoPath 路径* @param decrypt 解密* @param encrypt 加密*/public static void writeKeyInfo(String keyInfoPath,String decrypt,String encrypt,String IV) throws IOException {try (BufferedWriter writer = new BufferedWriter(new FileWriter(keyInfoPath));){writer.write(decrypt);writer.newLine();writer.write(encrypt);writer.newLine();if(StringUtils.isNotBlank(IV)){writer.write(IV);}writer.flush();} catch (IOException e) {throw new RuntimeException(e);}}
}
FileService
服务/*** 文件服务**/
public interface FileService {/*** 上传java video2 m3u8** @param file 文件* @return {@link String}* @throws Exception 异常*/String uploadJavaVideo2M3u8(MultipartFile file) throws Exception;
}
/*** 文件服务impl**/@Slf4j
@Service
public class FileServiceImpl implements FileService {@Autowiredprivate M3u8Component m3U8ComponentTemplate;@Autowiredprivate AliOssProperties aliOssProperties;@Autowiredprivate OssComponent ossComponent;@Autowiredprivate FilePath filePath;@Resource(name = "ossUploadTreadPool")private ThreadPoolTaskExecutor poolTaskExecutor;private static final String projectUrl = System.getProperty("user.dir").replaceAll("\\\\", "/");@Overridepublic String uploadJavaVideo2M3u8(MultipartFile file) throws Exception {String path = m3U8ComponentTemplate.mediaFileToJavaM3u8(file);return uploadJava2M3u8(path);}public String uploadJava2M3u8(String path) throws Exception {File pathFile = new File(path);String realPath = pathFile.getParent();log.info("视频解析后的 realPath {}", realPath);String name = pathFile.getName();log.info("解析后视频 name {}", name);return uploadFile(path, realPath, name);}/*** 上传文件** @param path 路径* @param realPath 真正路径* @param name 名字* @return {@link String}* @throws Exception 异常*/public String uploadFile(String path, String realPath, String name) throws Exception {File allFile = new File(realPath);File[] files = allFile.listFiles();if (null == files || files.length == 0) {return null;}String patch = DateUtil.format(LocalDateTime.now(), "yyyy/MM/") + name.substring(0, name.lastIndexOf(".")) + "/";log.info("uploadfile--->path:{}", patch);List errorFile = new ArrayList<>();long start = System.currentTimeMillis();//String fileName = UUID.randomUUID().toString().replaceAll("-","");//替换m3u8文件中的路径FileUtil.replaceTextContent(path, name.substring(0, name.lastIndexOf(".")),aliOssProperties.getMyHostUrl() + filePath.getProxy() + patch +name.substring(0, name.lastIndexOf(".")));//开始上传CountDownLatch countDownLatch = new CountDownLatch(files.length);Arrays.stream(files).forEach(li -> poolTaskExecutor.execute(() -> {try (FileInputStream fileInputStream = new FileInputStream(li)) {//minioComponent.FileUploaderExist("m3u8", patch + li.getName(), fileInputStream);ossComponent.uploadFile(filePath.getProxy() + patch, fileInputStream, li.getName());log.info("文件:{} 正在上传", li.getName());} catch (Exception e) {errorFile.add(li);e.printStackTrace();} finally {countDownLatch.countDown();}}));countDownLatch.await();long end = System.currentTimeMillis();log.info("解析文件上传成功,共计:{} 个文件,失败:{},共耗时: {}ms", files.length, errorFile.size(), end - start);// try {// minioComponent.mkBucket("m3u8");// } catch (Exception e) {// log.error("创建Bucket失败!");// }//异步移除所有文件poolTaskExecutor.execute(() -> {FileUtil.deleteFile(projectUrl + filePath.getTempPath());});if (CollectionUtils.isEmpty(errorFile)) {return aliOssProperties.getMyHostUrl() + filePath.getProxy() + patch + name;}return "";}
}
import lombok.Data;
import lombok.experimental.Accessors;@Data
@Accessors(chain = true)
public class Result {public String code ;public String msg;public Object data;public static Result success(String msg,String data){return new Result().setCode("200").setData(data).setMsg(msg);}public static Result success(String msg){return new Result().setCode("200").setMsg(msg);}public static Result fileBuild(){return new Result().setCode("101");}public static Result fileSuccess(String data){return new Result().setCode("201").setData(data);}public static Result error(String msg){return new Result().setCode("500").setMsg(msg);}public static Result error(){return new Result().setCode("500").setMsg("服务器出错");}public static Result fileOver() {return new Result().setCode("202");}
}
@Slf4j
@RestController
@RequestMapping("/")
public class TestController {@Autowiredprivate FileService fileService;@PostMapping("/uploadJavaVideo")public Result uploadJavaVideo(@RequestPart("file") MultipartFile file) {try {String path = fileService.uploadJavaVideo2M3u8(file);if (StringUtils.isNotBlank(path)) {return Result.success("上传成功",path);}}catch (Exception e){log.error("视频上传转码异常,异常原因e:{}",e);}return Result.error("上传失败");}
}