前言
本帖旨在共享Linux上的mp4视频上传和播放,不包含视频安全加密,对视频加密感兴趣的可以查看lz的这篇帖子,十分详细欢迎查阅:Java实现视频加密及播放
有问题可以留言,看到后一定尽快回复。代码由于是按照方法的方式共享所有在复制到这边时可能会有点问题,希望大家可以留言提出来,一起成长。创作不易,欢迎点赞收藏!
随着现在数据处理能力越来越强大,对视频及文件的存储和安全需求越来越多,很多项目中都涉及视频的存储和安全,据lz观察就像两年前FastDFS(阿里巴巴的非关系型数据库,可以用来存储图片、视频、压缩包等几乎任意文件)的使用还没有那么多查找资料也比较困难,现在FastDFS相关的资料以及越来越多了,从侧面也映射出用户对视频等文件的开发需求增加。视频的存储随之而来的就是视频的安全问题,这是一个成本比较高的事,普通公司没办法去实现自己的视频加密逻辑,同样这个问题也困扰着我,大概有两年的时间了一直也没有找到很好的解决方案,今天总算是解决了。这个方法不能保证视频的绝对安全,但是一般的浏览器视频下载插件无法在下载后直接查看视频,对于开发人员我觉得一般超过2年的开发经验才有能力去处理这些视频,但是也需要花大量的时间去解密视频并且合成,工作量之大,接下来看一下相关的技术实现吧。由于项目安全无法直接贡献类文件只能通过分享方法的方式共享,感兴趣的可以留言我会尽快回复,希望大家勿喷,认为有参考价值的可以点个赞,确实也是花了很多的时间整理资料。
代码
技术实现:FFmpeg + OSS + video.js
从ffmpeg工具转码,ffmpeg视频播放,java语言操控ffmpeg转码,转码后视频上传阿里云oss,四个方面完整记录下这个流程,内容是基于我项目中的需求而定,不能使用所有情况,仅供参考。与大部分网上使用ffmpeg转m3u8不同的是,lz使用的是form表单提交上传后处理MultipartFile对象来操作。相关操作我就不说了大家可以查看这篇文章,我是来贡献代码的
Java 利用ffmpeg工具实现视频MP4转m3u8
ConvertM3U8
package com.luntek.app.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* mp4转换m3u8工具类
*
* @author: Czw
* @create: 2021-01-26 14:19
**/
@Slf4j
@Component
public class ConvertM3U8 {
// ffmPeg.exe的目录
private final String ffmPegPath = "D:\\ffmPeg\\ffmPeg-20180702-3c4af57-win64-shared\\bin\\ffmPeg";
public boolean convertOss(String folderUrl, String fileName) {
if (!checkFile(folderUrl + fileName)) {
System.out.println("文件不存在!");
return false;
}
//验证文件后缀
String suffix = StringUtils.substringAfter(fileName, ".");
String fileFullName = StringUtils.substringBefore(fileName, ".");
if (!validFileType(suffix)) {
return false;
}
return processM3U8(folderUrl, fileName, fileFullName);
}
/**
* 验证上传文件后缀
*
* @param type 类型
* @return 是否为mp4
*/
private boolean validFileType(String type) {
return "mp4".equals(type);
}
/**
* 验证是否是文件格式
*
* @param path 路径
* @return 是否存在
*/
private boolean checkFile(String path) {
File file = new File(path);
return file.isFile();
}
//
/**
* ffmPeg程序转换m3u8
* ffmPeg -i vue.mp4 -c:v libx264 -hls_time 20 -hls_list_size 0 -c:a aac -strict -2 -f hls vue.m3u8
* ffmPeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
*
* @param folderUrl 文件路径
* @param fileName 名称
* @param fileFullName 全名
* @return 是否成功
*/
private boolean processM3U8(String folderUrl, String fileName, String fileFullName) {
//这里就写入执行语句就可以了
List<String> commend = new ArrayList<>();
commend.add(ffmPegPath);
commend.add("-i");
commend.add(folderUrl + fileName);
commend.add("-c:v");
commend.add("libx264");
commend.add("-hls_time");
commend.add("20");
commend.add("-hls_list_size");
commend.add("0");
commend.add("-c:a");
commend.add("aac");
commend.add("-strict");
commend.add("-2");
commend.add("-f");
commend.add("hls");
commend.add(folderUrl + fileFullName + ".m3u8");
try {
ProcessBuilder builder = new ProcessBuilder();//java
builder.command(commend);
Process p = builder.start();
int i = doWaitFor(p);
log.info("***i=【{}】***", i);
p.destroy();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 监听ffmPeg运行过程
*
* @param p 进程
* @return 直接结果
*/
public int doWaitFor(Process p) {
InputStream in = null;
InputStream err = null;
int exitValue = -1; // returned to caller when p is finished
try {
log.info("***检测ffmPeg运行***");
in = p.getInputStream();
err = p.getErrorStream();
boolean finished = false; // Set to true when p is finished
while (!finished) {
try {
while (in.available() > 0) {
Character c = (char) in.read();
System.out.print(c);
}
while (err.available() > 0) {
Character c = (char) err.read();
System.out.print(c);
}
exitValue = p.exitValue();
finished = true;
} catch (IllegalThreadStateException e) {
Thread.sleep(500);
}
}
} catch (Exception e) {
log.error("doWaitFor();: unexpected exception - "
+ e.getMessage());
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
if (err != null) {
try {
err.close();
} catch (IOException e) {
log.error(e.getMessage());
}
}
}
return exitValue;
}
}
TestController
/**
* 将表单提交的MultipartFile上传到OSS中
* 实现思路:
* 先将文件本地上传
* 重命名,生成对应文件夹
* 调用ffmpeg切片转成m3u8
* 将mp4视频和转换后的m3u8以及ts放在一起,然后遍历文件目录
* 将文件上传后,删除本地文件夹和文件在将转换后文件上传到oss上
*
* @param file 上传的文件
* @return ResponseResult 结果
*/
@SysLog(value = "视频上传到oss上进行m3u8分片")
@PostMapping("/testM3U8InOSSUpload")
public ResponseResult<?> testM3U8InOSSUpload(MultipartFile file) {
log.info("***testOSSUpload***");
//oss域名
String endpoint = OSSUtil.endpoint;
//ossId
String accessKeyId = OSSUtil.accessKeyId;
//ossKey
String accessKeySecret = OSSUtil.accessKeySecret;
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
String f = file.getOriginalFilename();
//获取文件后缀
String suffix = StringUtils.substringAfter(f, ".");
//获取文件名后重命名
String filename = StringUtils.substringBefore(f, ".") + (new SecureRandom().nextInt(8999) + 1000);
/*本地上传*/
//转储路径
String localPath = "C:\\Users\\Administrator\\Desktop\\m3u8\\";
//完整转储路径
String folderUrl = localPath + filename;
//文件名带后缀
String fileName = filename + "." + suffix;
//上传oss后路径
String uploadPath = folderUrl + "/" + fileName;
File fileFolder = new File(folderUrl);
if (!fileFolder.exists()) {
fileFolder.mkdirs();
}
File newFile = new File(uploadPath);
file.transferTo(newFile);
//mp4转m3u8
boolean b = convertM3U8.convertOss(folderUrl + "/", fileName);
if (!b) {
return new ResponseResult<>(300, "上传失败!系统转码异常!");
}
//访问本地上传文件夹所有文件,依次上传至oss服务器
File[] files = fileFolder.listFiles();
if (null == files || files.length == 0) {
return null;
}
boolean flag = true;
for (int i = 0; i < files.length; i++) {
if (!files[i].isDirectory()) {
//上传
String name = files[i].getName();
String suf = StringUtils.substringAfter(name, ".");
String pre = StringUtils.substringBefore(name, ".");
FileInputStream fis = new FileInputStream(files[i]);
if ("m3u8".equals(suf)) {
if (flag && filename.equals(pre)) {
//这是封装的上传阿里云oss的方法
ossClient.putObject("You Bucket Name",
"m3u8/" + filename + "/" + name,
fis);
flag = false;
}
} else if ("ts".equals(suf)) {
ossClient.putObject("You Bucket Name",
"m3u8/" + filename + "/" + name,
fis);
}
fis.close();
if (files[i].exists()) {
files[i].delete();
}
}
}
//删除文件夹
fileFolder.delete();
/*********本地上传(Tomcat配置映射C:/upload/file)*********/
} catch (Exception e) {
e.printStackTrace();
log.error("上传异常");
}
return ResponseResult.success();
}
效果
注:将下面代码删除后可以查看在本地切分的ts文件
if (files[i].exists()) {
files[i].delete();
}
本地截图
oss截图
访问
此时可以直接复制.m3u8文件的地址访问即可播放,如果无法访问可以检查是否是以下原因造成
1.m3u8链接无法播放,video.js提示无法访问对应资源,需要在阿里云oss上设置跨域。bucket空间—>基础设置—>跨域设置
2.ts文件的读写权限需要公共读不然video.js访问不到的,然后为了防止视频被恶意下载可以设置防盗链
3.同一个视频的m3u8格式文件以及ts文件需要放在同一级目录。最好是一个视频的相关文件放入以文件名命名的路径下以示区别
4.bucket桶需要设置为空开读的权限
也可以使用video.js播放,代码如下(如果无法播放可能是引入的 video-js.css/video.js/videojs-contrib-hls.js无法下载,只需要访问地址下载下来修改引入方式即可)。如果资源过期或者无法下载,可以到本人csdn的资料中使用个积分下载。
<link href="https://unpkg.com/video.js@5.16.0/dist/video-js.css" rel="stylesheet">
<script src="https://unpkg.com/video.js@5.16.0/dist/video.js"></script>
<script src="https://unpkg.com/videojs-contrib-hls@4.1.1/dist/videojs-contrib-hls.js"></script>
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>videojs-contrib-hls embed</title>
<!--
Uses the latest versions of video.js and videojs-contrib-hls.
To use specific versions, please change the URLs to the form:
<link href="https://unpkg.com/video.js@5.16.0/dist/video-js.css" rel="stylesheet">
<script src="https://unpkg.com/video.js@5.16.0/dist/video.js"></script>
<script src="https://unpkg.com/videojs-contrib-hls@4.1.1/dist/videojs-contrib-hls.js"></script>
-->
<link href="./video-js.css" rel="stylesheet">
<script src="./video.js"></script>
<script src="./videojs-contrib-hls.js"></script>
</head>
<body>
<h1>Video.js Example Embed</h1>
<video id="my_video_1" class="video-js vjs-default-skin" controls preload="auto" width="640" height="268"
data-setup='{}'>
<source src="http://******/m3u8/1200MB5987/1200MB5987.m3u8" type="application/x-mpegURL">
</video>
</body>
</html>
总结不易记得点赞哦