【Easylive】transferVideoFile 方法详细解析
【Easylive】项目常见问题解答(自用&持续更新中…) 汇总版
transferVideoFile
方法是一个视频文件转码和处理的核心方法,主要完成视频文件从临时目录转移到正式目录、合并分片、获取视频信息、更新状态等一系列操作。下面我将详细解析这个方法的每个部分。
首先,方法被@Override注解标记,说明这是一个重写的方法。方法接收一个VideoInfoFilePost对象作为参数,这应该是一个包含视频文件相关信息的实体类。方法内部首先创建了一个updateFilePost对象,用于后续更新数据库中的文件信息。接着进入一个try块,开始主要的业务逻辑。
在try块中,首先通过redisComponent.getUploadVideoFile方法从Redis获取上传文件的信息,这里需要传入用户ID和上传ID。这表明系统使用Redis来临时存储上传过程中的文件信息,可能是为了支持断点续传或分片上传。
接下来,构建了临时文件路径tempFilePath和目标文件路径targetFilePath。这里用到了appConfig.getProjectFolder()获取项目根目录,然后拼接上常量FILE_FOLDER、FILE_FOLDER_TEMP和FILE_VIDEO,以及从Redis获取的fileDto.getFilePath()。这说明系统有明确的目录结构来管理临时文件和最终存储的视频文件。
检查目标目录是否存在,如果不存在则创建,确保目录结构正确。然后使用FileUtils.copyDirectory将临时目录下的文件复制到目标目录。这一步可能是将上传完成的文件从临时位置移动到正式存储位置。之后,删除临时目录,释放存储空间,并从Redis中删除对应的文件信息,避免数据冗余。
接下来调用this.union方法合并文件,生成完整的视频文件路径completeVideo。union方法可能负责将分片上传的视频文件合并成一个完整的文件。合并完成后,使用fFmpegUtils.getVideoInfoDuration获取视频时长,并更新updateFilePost的相关属性,包括时长、文件大小、文件路径和转码结果状态为成功。
然后调用this.convertVideo2Ts方法将视频转换为TS格式,可能是为了支持流媒体播放,比如HLS协议。转换完成后删除原始视频文件,保留转换后的TS分片。
如果在上述过程中出现异常,catch块会捕获异常并记录错误日志,同时将转码结果状态设置为失败。无论成功或失败,finally块都会执行,更新数据库中的文件状态。
在finally块中,首先通过videoInfoFilePostMapper.updateByUploadIdAndUserId更新文件信息。接着,构建查询条件,检查是否存在转码失败的文件。如果有失败的文件,更新整个视频的状态为STATUS1(可能表示转码失败)。如果没有失败的文件,再检查是否还有正在转码中的文件。如果所有文件都已处理完成,计算视频总时长并更新视频状态为STATUS2(转码完成),同时记录总时长。
方法概述
@Override
public void transferVideoFile(VideoInfoFilePost videoInfoFilePost) {
// 方法实现
}
功能:处理视频文件转码和转移流程,包括:
- 从临时目录转移视频到正式目录
- 合并视频分片(如果有)
- 获取视频元信息(时长等)
- 更新数据库状态
- 处理转码失败情况
- 检查并更新视频整体状态
1. 初始化与准备
VideoInfoFilePost updateFilePost = new VideoInfoFilePost();
try {
// 从Redis获取上传文件信息
UploadingFileDto fileDto = redisComponent.getUploadVideoFile(videoInfoFilePost.getUserId(), videoInfoFilePost.getUploadId());
// 设置临时文件路径
String tempFilePath = appConfig.getProjectFolder() + Constants.FILE_FOLDER + Constants.FILE_FOLDER_TEMP + fileDto.getFilePath();
File tempFile = new File(tempFilePath);
// 设置目标文件路径
String targetFilePath = appConfig.getProjectFolder() + Constants.FILE_FOLDER + Constants.FILE_VIDEO + fileDto.getFilePath();
File targetFile = new File(targetFilePath);
if (!targetFile.exists()) {
targetFile.mkdirs();
}
解析:
• 创建 updateFilePost
对象用于后续更新数据库
• 从Redis获取上传文件信息(包含文件路径等)
• 构造临时文件路径和目标文件路径
• 确保目标目录存在(不存在则创建)
2. 文件转移与处理
// 将文件从临时目录复制到正式目录
FileUtils.copyDirectory(tempFile, targetFile);
// 删除临时目录
FileUtils.forceDelete(tempFile);
// 从Redis删除文件信息
redisComponent.delVideoFileInfo(videoInfoFilePost.getUserId(), videoInfoFilePost.getUploadId());
// 合并文件(如果是分片上传)
String completeVideo = targetFilePath + Constants.TEMP_VIDEO_NAME;
this.union(targetFilePath, completeVideo, true);
解析:
• 使用 FileUtils.copyDirectory
将文件从临时目录复制到正式目录
• 删除临时目录(清理空间)
• 从Redis删除已处理的文件信息
• 调用 union
方法合并视频分片(如果有)
3. 获取视频信息
// 获取视频时长
Integer duration = fFmpegUtils.getVideoInfoDuration(completeVideo);
updateFilePost.setDuration(duration);
updateFilePost.setFileSize(new File(completeVideo).length());
updateFilePost.setFilePath(Constants.FILE_VIDEO + fileDto.getFilePath());
updateFilePost.setTransferResult(VideoFileTransferResultEnum.SUCCESS.getStatus());
// 将视频转换为TS格式(用于HLS流媒体)
this.convertVideo2Ts(completeVideo);
解析:
• 使用 fFmpegUtils
获取视频时长
• 设置文件大小、路径和转码结果为成功
• 调用 convertVideo2Ts
将视频转换为TS格式(用于HTTP Live Streaming)
4. 异常处理
} catch (Exception e) {
log.error("文件转码失败", e);
updateFilePost.setTransferResult(VideoFileTransferResultEnum.FAIL.getStatus());
}
解析:
• 捕获所有异常并记录日志
• 设置转码结果为失败状态
5. 状态更新与检查
finally {
// 更新文件状态
videoInfoFilePostMapper.updateByUploadIdAndUserId(updateFilePost, videoInfoFilePost.getUploadId(), videoInfoFilePost.getUserId());
// 检查是否有转码失败的视频文件
VideoInfoFilePostQuery fileQuery = new VideoInfoFilePostQuery();
fileQuery.setVideoId(videoInfoFilePost.getVideoId());
fileQuery.setTransferResult(VideoFileTransferResultEnum.FAIL.getStatus());
Integer failCount = videoInfoFilePostMapper.selectCount(fileQuery);
if (failCount > 0) {
// 如果有失败的,设置视频状态为STATUS1(可能是"转码失败"状态)
VideoInfoPost videoUpdate = new VideoInfoPost();
videoUpdate.setStatus(VideoStatusEnum.STATUS1.getStatus());
videoInfoPostMapper.updateByVideoId(videoUpdate, videoInfoFilePost.getVideoId());
return;
}
// 检查是否还有转码中的视频文件
fileQuery.setTransferResult(VideoFileTransferResultEnum.TRANSFER.getStatus());
Integer transferCount = videoInfoFilePostMapper.selectCount(fileQuery);
if (transferCount == 0) {
// 如果没有转码中的文件,计算总时长并更新视频状态为STATUS2(可能是"转码完成"状态)
Integer duration = videoInfoFilePostMapper.sumDuration(videoInfoFilePost.getVideoId());
VideoInfoPost videoUpdate = new VideoInfoPost();
videoUpdate.setStatus(VideoStatusEnum.STATUS2.getStatus());
videoUpdate.setDuration(duration);
videoInfoPostMapper.updateByVideoId(videoUpdate, videoInfoFilePost.getVideoId());
}
}
解析:
- 更新文件状态:无论成功失败,都更新当前文件状态
- 检查失败情况:
• 查询该视频下是否有转码失败的文件
• 如果有,设置视频整体状态为失败(STATUS1) - 检查转码完成情况:
• 查询是否还有转码中的文件
• 如果没有,计算视频总时长并更新状态为完成(STATUS2)
状态枚举说明
假设的状态枚举含义(根据代码推断):
枚举值 | 可能含义 |
---|---|
STATUS0 | 待审核/待处理 |
STATUS1 | 转码失败 |
STATUS2 | 转码完成 |
TRANSFER | 转码中 |
SUCCESS | 转码成功 |
FAIL | 转码失败 |
方法流程图
关键点总结
-
文件转移流程:
• 从临时目录 → 正式目录
• 清理临时文件
• 删除Redis缓存 -
视频处理:
• 合并分片(如需要)
• 获取元信息(时长、大小)
• 转换为TS格式(用于流媒体) -
状态管理:
• 单个文件状态更新
• 整体视频状态检查
• 失败处理和成功状态更新 -
事务完整性:
• 使用try-catch确保异常被捕获
• finally块确保状态一定会更新
• 全面的状态检查机制
可能的优化建议
- 异步处理:视频转码是耗时操作,可以考虑使用消息队列异步处理
- 进度反馈:可以增加转码进度反馈机制
- 重试机制:对于失败的转码任务可以加入重试逻辑
- 资源清理:确保在任何异常情况下都能正确清理临时资源
- 状态枚举:建议使用更具描述性的枚举名称,如"TRANSCODE_COMPLETED"而非"STATUS2"