1、问题
前几天有一个视频截取的需求,七牛云直播回放地址是m3u8格式的,客户让把m3u8转成mp4格式后再进行视频截取。
2、思路
1.拿到前端传的截取起止时间,先简单判断参数是否合法
2.根据m3u8地址获取文件信息,并解析出直播推流的起止时间
3.判断前端传的截取时间是否在直播推流起止时间范围内,不判断的话后面截取会报错
4.获取key,生成新的key,给七牛云发送转码指令,拿到转码任务ID
5.根据转码任务ID定时查询转码结果
6.转码失败更新表字段状态,转码成功继续发送视频截取指令,拿到截取任务ID
7.根据任务ID轮询查询截取状态
8.截取成功更新状态和截取后的地址
3.关键代码实现
3.1 解析m3u8文件
replayUrl地址是m3u8格式的直播回放地址
文件解析的正则表达式具体还要根据文件生成格式来定
public static final Pattern TS_PATTERN = Pattern.compile("#EXTINF:(.*?),.*?(\\d+-\\d+).ts");
public VideoTsInfo getM3u8Info(String replayUrl){
VideoTsInfo videoTsInfo = new VideoTsInfo();
if (StringUtils.isNotBlank(replayUrl)) {
try {
URL url = new URL(replayUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(3000);
connection.setReadTimeout(10000);
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder content = new StringBuilder();
while ((line = reader.readLine()) != null) {
content.append(line);
}
reader.close();
connection.disconnect();
String m3u8Content = content.toString();
if (StringUtils.isBlank(m3u8Content)) {
return null;
}
Matcher matcher = TS_PATTERN.matcher(m3u8Content);
double totalDuration = 0;
VideoTsInfo start = null;
VideoTsInfo end = null;
while (matcher.find()) {
String[] strings = matcher.group(2).split("-");
if (start == null) {
start = new VideoTsInfo(strings);
}
end = new VideoTsInfo(strings);
String durationString = matcher.group(1);
double duration = Double.parseDouble(durationString);
totalDuration += duration;
}
if (start != null) {
videoTsInfo.setStartTime(start.getStartTime());
}
if (end != null) {
videoTsInfo.setEndTime(end.getEndTime());
}
videoTsInfo.setDuration((long)totalDuration);
return videoTsInfo;
} catch (Exception e) {
logger.info("获取视频的时间信息异常", e);
}
}
return null;
}
3.2 发送转码指令
这里key,和新key先写死方便测试,key就是回放地址域名后面的部分
livecast是直播桶名
public static String transCodeVideoMp4(String key, String mp4Key) {
OperationManager operationManager = new OperationManager(auth, new Configuration(Region.region0()));
key = "recordings/z1.hljtv-live.fmzb1682668851843/fmzb1682668851843.m3u8";
mp4Key = "recordings/z1.hljtv-live.fmzb1682668851843/cutVideo/fmzb1682668851843.mp4";
if (StringUtils.isBlank(key) || StringUtils.isBlank(mp4Key)) {
logger.info("transCodeVideo,key或者mp4Key为空");
return null;
}
// 设置持久化处理操作参数
String saveAs = UrlSafeBase64.encodeToString("livecast" + ":" + mp4Key);
String fops = "avthumb/mp4|saveas/" + saveAs; // 转换并保存为新文件名
logger.info("直播桶:{},transCodeVideoMp4,key:{},newsKey:{},fops:{}",QiNiuConfig.LIVE_HUB,key,mp4Key,fops);
try {
return QiNiuConfig.operationManager.pfop("livecast", key, fops, pipeline,true);
} catch (QiniuException e) {
logger.error("transCodeVideo", e);
}
return null;
}
运行测试一下,成功拿到任务id
3.3 查询转码结果
定时任务这里就不写了,简单说一下怎么获取任务结果
直接在浏览器直接访问七牛云的这个地址,后面参数就是任务id
看到code:0代表任务执行成功;items中的key就是我们的转码后的key
3.4 MP4视频截取
转码成功后,我们拿到items中的key去发送视频截取指令,代码如下:
发送成功后会获取到截取的任务id,然后再根据这个任务id轮询获取截取结果就可以了。
指令中的ss代表截取指令,后面跟截取的开始时间,/t代表时间单位秒,后面跟截取的结束时间。
截取成功后的key在拼接直播域名就是一个完整的访问地址。
在代码中可以看到operationManager.prefop(persistId)也是可以获取到任务执行结果的。
public static boolean interceptPart(){
String path = "recordings/z1.hljtv-live.fmzb1682668851843/fmzb1682668851843.mp4";
try {
String streamKey = "fmzb1682668851843";
logger.info("streamKey{}:",streamKey);
String saveAsKey = new StringBuilder("recordings/z1.")
.append(QiNiuConfig.LIVE_HUB)
.append(".")
.append(streamKey)
.append("/")
.append(streamKey)
.append("cutVideo")
.append(System.currentTimeMillis())
.append(".mp4")
.toString();
String encodedSaveAs = Base64.getUrlEncoder().encodeToString(("livecast" + ":" + saveAsKey).getBytes());
StringBuilder sb = new StringBuilder();
sb.append("avthumb/mp4/ss/");
sb.append(0);
sb.append("/t/").append(60).append("|saveas/").append(encodedSaveAs);
String fops = sb.toString();
String persistId = QiNiuConfig.operationManager.pfop("livecast", path, fops, pipeline,true);
logger.info("mp4视频截取任务id:{}",persistId);
//轮询截取进度
// 查询持久化进度
boolean isProcessing = true;
while (isProcessing) {
Thread.sleep(3000); // 每隔3秒查询一次
OperationStatus status = QiNiuConfig.operationManager.prefop(persistId);
System.out.println("mp4视频截取任务结果: " + status.code);
logger.info("mp4视频截取任务结果:{}",JSON.toJSONString(status));
if (status.code == 0 || status.code == 3) {
isProcessing = false;
if(status.code == 0){
String newUrl = new StringBuilder("https://")
.append("/").append(saveAsKey)
.toString();
System.out.println(newUrl);
}
}
}
} catch (Exception e) {
logger.error("生成回放地址出错", e);
}
return false;
}
说明:代码中的QiNiuConfig
是七牛的一些配置,包括ak
,sk
,operationManager
,bucketManager
等信息。