配置依赖

     <properties><java.version>11</java.version><ffmpeg.version>0.6.2</ffmpeg.version><hutool.version>5.7.15</hutool.version><aliyun-sdk-oss.version>3.13.2</aliyun-sdk-oss.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--  javacv --><dependency><groupId>org.bytedeco</groupId><artifactId>javacv-platform</artifactId><version>1.5.8</version></dependency><!--        &lt;!&ndash; Additional dependencies required to use CUDA and cuDNN &ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>org.bytedeco</groupId>-->
<!--            <artifactId>opencv-platform-gpu</artifactId>-->
<!--            <version>4.6.0-1.5.8</version>-->
<!--        </dependency>--><!-- Optional GPL builds with (almost) everything enabled --><dependency><groupId>org.bytedeco</groupId><artifactId>ffmpeg-platform-gpl</artifactId><version>5.1.2-1.5.8</version></dependency><dependency><groupId>net.bramp.ffmpeg</groupId><artifactId>ffmpeg</artifactId><version>${ffmpeg.version}</version></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>${hutool.version}</version></dependency><!-- 阿里云oss --><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>${aliyun-sdk-oss.version}</version></dependency></dependencies>

注意: ffmpeg-platformjavacv-platform 版本要对应

application.yml 项目配置


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: 

configproperties配置相关类

SpringAsyncConfig 配置信息


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;}
}

FilePath 配置信息


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/";}

AliOssProperties 配置信息


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 包 相关组件类

OssComponent 组件

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<PartETag> 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<MultipartFile> 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;}
}

M3u8Component 组件

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;}
}

工具包

m3u8Util 工具类


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("."));}}

FileUtil 工具类


import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;public class FileUtil {public static void deleteFiles(String path) {File file = new File(path);if (file.exists()) {if (file.isDirectory()) {File[] temp = file.listFiles(); //获取该文件夹下的所有文件for (File value : temp) {deleteFile(value.getAbsolutePath());}} else {file.delete(); //删除子文件}file.delete(); //删除文件夹}}public static void deleteFile(String path){File dest = new File(path);if (dest.isFile() && dest.exists()) {dest.delete();}}public static void replaceTextContent(String path,String srcStr,String replaceStr) throws IOException {// 读File file = new File(path);FileReader in = new FileReader(file);BufferedReader bufIn = new BufferedReader(in);// 内存流, 作为临时流CharArrayWriter tempStream = new CharArrayWriter();// 替换String line = null;while ( (line = bufIn.readLine()) != null) {// 替换每行中, 符合条件的字符串line = line.replaceAll(srcStr, replaceStr);// 将该行写入内存tempStream.write(line);// 添加换行符tempStream.append(System.getProperty("line.separator"));}// 关闭 输入流bufIn.close();// 将内存中的流 写入 文件FileWriter out = new FileWriter(file);tempStream.writeTo(out);out.close();System.out.println("====path:"+path);}
}

FfmpegUtil 工具类


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);}}
}

创建service包 并创建 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<File> 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 "";}
}

Result 通用返回类


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");}
}

创建测试 TestController


@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("上传失败");}
}

springboot 通过javaCV 实现mp4转m3u8 上传oss相关推荐

  1. SpringBoot 二维码生成base64并上传OSS

    SpringBoot 二维码生成base64并上传OSS 基础环境 SpringBoot.Maven 代码实现 1.添加依赖 <!--二维码生成 --> <dependency> ...

  2. SpringBoot中在配置文件中限制文件上传的大小

    场景 在SpringBoot项目的application.properties中配置上传文件的限制大小 实现 Spring Boot 1.4以下 multipart.maxFileSize = 10M ...

  3. Springboot集成七牛云,实现图片上传功能

    七牛云的使用 1.进入官网,注册一个账号 2.找到对象存储 3.新建存储空间 4.管理存储空间 5.到此基本操作就完成了,接下来我们要在项目中使用 方式一:官方的帮助文档,很详细: 步骤一:找到开发者 ...

  4. kindeditor支持视频flv, mp4格式视频上传播放

    首先声明本人用的版本为4.1.12 1.mp4视频格式上传: 修改kindeditor-all.js  _mediaImg()方法 原代码 function _mediaImg(blankPath, ...

  5. springboot项目打包为docker镜像并上传nexus私服

    springboot项目docker打包镜像上传Nexus私服 1.springboot项目打包为docker镜像并上传nexus私服 1.0. 必要条件 1.1.开启docker远程访问 1.2.配 ...

  6. 通过url链接将图片上传oss图片显示不完整问题

    通过url链接将图片上传oss图片显示不完整问题 问题:在之前通过链接上传图片的时候,都是先获取inputStream流,然后通过available()方法获取文件大小.但是通过这种方法获取到的文件大 ...

  7. uedit php,laravel uedit上传oss

    laravel ueditor上传oss 首先下载ueditor包安装好! ueditor包没有?好吧,看这里laravel ueditor教程 oss包没有?好吧,看这里laravel oss教程 ...

  8. PHP版微信权限验证配置,音频文件下载,FFmpeg转码,上传OSS和删除转存服务器本地文件...

    2019独角兽企业重金招聘Python工程师标准>>> 一.概述和通过config接口注入权限验证配置 由于微信的临时素材(如:录音文件)上传到微信服务器上,微信后台保存时间为3天. ...

  9. springboot 静态资源访问,和文件上传 ,以及路径问题

    springboot 静态资源访问: 这是springboot 默认的静态资源访问路径  访问顺序依次从前到后(http://localhost:8080/bb.jpg) spring.resourc ...

最新文章

  1. 拆分工作簿为多个文件_刻意地练习Excel快速拆分工作簿「例07-4」
  2. 一个关于HashCode 的追问!
  3. C++ 虚函数与纯虚函数
  4. CF1413F. Roads and Ramen(树的直径,线段树)
  5. Android.View.InflateException: Binary XML File Line #异常的解决
  6. 京东商城上市带来的利与益
  7. 【Alpha版本】冲刺随笔汇总
  8. 「AI原生」时代来临?百度智能云提出AI-Native,发布新一代云基础架构「太行」
  9. 华为手机像素密度排行_最新手机性能排行榜出炉:高通骁龙865霸榜,前十不见华为!...
  10. 数值分析共轭梯度法matlab程序,数值分析11(共轭梯度法).ppt
  11. Redis详解(五)——Redis多实例
  12. HTML5 - WebSQL
  13. 计算机二级C语言选择题怎么做?要注意些什么?(易错点难点总结笔记)
  14. 网络工程师十个常见面试问题
  15. Orcad Pspice仿真
  16. 有源蜂鸣器与无源蜂鸣器的驱动方式详解(精华版)
  17. linux 配置dns域名服务器,linux下DNS配置及域名解析服务
  18. 白光迈克尔逊干涉仪的仿真
  19. QQ一来消息或一些提示声,媒体、视频、音频、游戏就会卡顿【解决方法】
  20. 华为鸿蒙系统界面亮相,比Android操作更简单。

热门文章

  1. Matlab eval用法
  2. TileColor快速指导:注册ocx文件
  3. 【java】程序启动后, 可以从键盘输入接收多个整数, 直到输入quit时结束输入. 把所有输入的整数倒序排列打印.
  4. 语音识别,语义理解一站式解决之智能照相机(人脸识别,olami)
  5. 移动 电信 联通 APN cmwap cmnet ctwap ctnet 3gwap uniwap 3gnet uninet
  6. linux 双线接入配置(电信联通双线)+三线接入配置
  7. GitHub 全域数字年报:携手推动开源世界的超级协作
  8. jQueryanimation实现漂流瓶扔和写动画效果
  9. 循环问“老婆,你爱我吗?”,如果回答的是“爱”, 那么就结束循环,否则就继续问。用程序描述这个故事!
  10. 水经注,bigemap 功能对比