|
|
@@ -1575,6 +1575,16 @@ export default {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ // 验证所有视频的元信息完整性
|
|
|
+ const invalidVideos = this.videoList.filter(item => {
|
|
|
+ return !this.validateVideoMetadata(item);
|
|
|
+ });
|
|
|
+ if (invalidVideos.length > 0) {
|
|
|
+ const invalidNames = invalidVideos.map(v => v.fileName).join('、');
|
|
|
+ this.$message.error(`以下视频元信息不完整,无法提交:${invalidNames}`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
if (this.add){
|
|
|
this.$message.warning("请勿重复提交")
|
|
|
return
|
|
|
@@ -1585,6 +1595,10 @@ export default {
|
|
|
const videoList = JSON.parse(JSON.stringify(this.videoList));
|
|
|
videoList.forEach(item => {
|
|
|
item.projectIds = item.projectIds.join(",");
|
|
|
+ // 确保duration是有效的数字
|
|
|
+ if (!item.duration || item.duration <= 0 || isNaN(item.duration)) {
|
|
|
+ console.error('视频时长无效,跳过:', item.fileName, item.duration);
|
|
|
+ }
|
|
|
});
|
|
|
batchAddVideoResource(videoList).then(response => {
|
|
|
if (response.code === 200) {
|
|
|
@@ -1592,6 +1606,10 @@ export default {
|
|
|
this.batchAddVisible = false;
|
|
|
this.getList();
|
|
|
}
|
|
|
+ }).catch(error => {
|
|
|
+ this.$message.error('批量添加失败: ' + (error.message || '未知错误'));
|
|
|
+ }).finally(() => {
|
|
|
+ this.add = false;
|
|
|
})
|
|
|
},
|
|
|
/** 获取分类名称 */
|
|
|
@@ -1771,22 +1789,45 @@ export default {
|
|
|
line2Status: 'pending'
|
|
|
},
|
|
|
file: file,
|
|
|
- queuePosition: this.uploadQueue.length + 1 // Track queue position
|
|
|
+ queuePosition: this.uploadQueue.length + 1, // Track queue position
|
|
|
+ metadataLoaded: false // 标记元数据是否已加载
|
|
|
};
|
|
|
|
|
|
this.uploadQueue.push(tempVideo);
|
|
|
this.videoList.unshift(tempVideo);
|
|
|
|
|
|
- // 获取视频时长
|
|
|
- const video = document.createElement('video');
|
|
|
- video.preload = 'metadata';
|
|
|
- video.onloadedmetadata = () => {
|
|
|
+ // 获取视频时长和元信息(确保完整)
|
|
|
+ try {
|
|
|
+ await this.getVideoMetadata(file, tempVideo);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取视频元信息失败:', error);
|
|
|
const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
|
|
|
if (index !== -1) {
|
|
|
- tempVideo.duration = Math.round(video.duration);
|
|
|
+ this.videoList[index].uploadStatus = 'failed';
|
|
|
+ this.$message.error(`视频 ${tempVideo.fileName} 元信息获取失败,请检查视频文件是否完整`);
|
|
|
}
|
|
|
- };
|
|
|
- video.src = URL.createObjectURL(file);
|
|
|
+ // 从队列中移除
|
|
|
+ const queueIndex = this.uploadQueue.findIndex(item => item.tempId === tempVideo.tempId);
|
|
|
+ if (queueIndex !== -1) {
|
|
|
+ this.uploadQueue.splice(queueIndex, 1);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证元信息完整性
|
|
|
+ if (!this.validateVideoMetadata(tempVideo)) {
|
|
|
+ const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
|
|
|
+ if (index !== -1) {
|
|
|
+ this.videoList[index].uploadStatus = 'failed';
|
|
|
+ this.$message.error(`视频 ${tempVideo.fileName} 元信息不完整(时长: ${tempVideo.duration}秒),请检查视频文件`);
|
|
|
+ }
|
|
|
+ // 从队列中移除
|
|
|
+ const queueIndex = this.uploadQueue.findIndex(item => item.tempId === tempVideo.tempId);
|
|
|
+ if (queueIndex !== -1) {
|
|
|
+ this.uploadQueue.splice(queueIndex, 1);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
// 关闭上传弹窗
|
|
|
this.showUpload = false;
|
|
|
@@ -1800,6 +1841,96 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
|
|
|
+ /** 获取视频元信息(Promise方式,确保完整) */
|
|
|
+ getVideoMetadata(file, tempVideo) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const video = document.createElement('video');
|
|
|
+ video.preload = 'metadata';
|
|
|
+ video.muted = true; // 静音以加快加载
|
|
|
+
|
|
|
+ let resolved = false;
|
|
|
+ const timeout = setTimeout(() => {
|
|
|
+ if (!resolved) {
|
|
|
+ resolved = true;
|
|
|
+ URL.revokeObjectURL(video.src);
|
|
|
+ reject(new Error('获取视频元信息超时'));
|
|
|
+ }
|
|
|
+ }, 30000); // 30秒超时
|
|
|
+
|
|
|
+ video.onloadedmetadata = () => {
|
|
|
+ if (resolved) return;
|
|
|
+ resolved = true;
|
|
|
+ clearTimeout(timeout);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const duration = video.duration;
|
|
|
+ if (isNaN(duration) || duration <= 0 || !isFinite(duration)) {
|
|
|
+ URL.revokeObjectURL(video.src);
|
|
|
+ reject(new Error('无法获取有效的视频时长'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
|
|
|
+ if (index !== -1) {
|
|
|
+ this.videoList[index].duration = Math.round(duration);
|
|
|
+ this.videoList[index].metadataLoaded = true;
|
|
|
+ }
|
|
|
+ tempVideo.duration = Math.round(duration);
|
|
|
+ tempVideo.metadataLoaded = true;
|
|
|
+
|
|
|
+ URL.revokeObjectURL(video.src);
|
|
|
+ resolve({
|
|
|
+ duration: Math.round(duration),
|
|
|
+ videoWidth: video.videoWidth,
|
|
|
+ videoHeight: video.videoHeight
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ URL.revokeObjectURL(video.src);
|
|
|
+ reject(error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ video.onerror = (error) => {
|
|
|
+ if (resolved) return;
|
|
|
+ resolved = true;
|
|
|
+ clearTimeout(timeout);
|
|
|
+ URL.revokeObjectURL(video.src);
|
|
|
+ reject(new Error('视频文件加载失败,请检查文件格式是否正确'));
|
|
|
+ };
|
|
|
+
|
|
|
+ video.src = URL.createObjectURL(file);
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 验证视频元信息完整性 */
|
|
|
+ validateVideoMetadata(tempVideo) {
|
|
|
+ // 检查时长是否有效
|
|
|
+ if (!tempVideo.duration || tempVideo.duration <= 0 || isNaN(tempVideo.duration)) {
|
|
|
+ console.error('视频时长无效:', tempVideo.duration);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查文件大小是否有效
|
|
|
+ if (!tempVideo.fileSize || tempVideo.fileSize <= 0) {
|
|
|
+ console.error('文件大小无效:', tempVideo.fileSize);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查文件名是否存在
|
|
|
+ if (!tempVideo.fileName || tempVideo.fileName.trim() === '') {
|
|
|
+ console.error('文件名无效:', tempVideo.fileName);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查元数据是否已加载
|
|
|
+ if (!tempVideo.metadataLoaded) {
|
|
|
+ console.error('元数据未加载完成');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ },
|
|
|
+
|
|
|
async processUploadQueue() {
|
|
|
if (this.isProcessingBatch || this.uploadQueue.length === 0) {
|
|
|
return;
|
|
|
@@ -1847,6 +1978,33 @@ export default {
|
|
|
|
|
|
async uploadSingleVideo(tempVideo) {
|
|
|
try {
|
|
|
+ // 再次验证元信息完整性(确保在上传前元信息完整)
|
|
|
+ if (!this.validateVideoMetadata(tempVideo)) {
|
|
|
+ const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
|
|
|
+ if (index !== -1) {
|
|
|
+ this.videoList[index].uploadStatus = 'failed';
|
|
|
+ this.$message.error(`视频 ${tempVideo.fileName} 元信息不完整,无法上传`);
|
|
|
+ }
|
|
|
+ throw new Error('视频元信息不完整');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果元数据未加载,尝试重新加载
|
|
|
+ if (!tempVideo.metadataLoaded || !tempVideo.duration || tempVideo.duration <= 0) {
|
|
|
+ console.warn('视频元信息不完整,尝试重新加载:', tempVideo.fileName);
|
|
|
+ try {
|
|
|
+ await this.getVideoMetadata(tempVideo.file, tempVideo);
|
|
|
+ if (!this.validateVideoMetadata(tempVideo)) {
|
|
|
+ throw new Error('重新加载后元信息仍不完整');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
|
|
|
+ if (index !== -1) {
|
|
|
+ this.videoList[index].uploadStatus = 'failed';
|
|
|
+ this.$message.error(`视频 ${tempVideo.fileName} 元信息加载失败: ${error.message}`);
|
|
|
+ }
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
// 获取封面
|
|
|
await this.getFirstThumbnail(tempVideo.file, tempVideo);
|
|
|
|
|
|
@@ -1860,7 +2018,7 @@ export default {
|
|
|
|
|
|
// 检查上传结果
|
|
|
const line1Success = line1Result.status === 'fulfilled' && line1Result.value.success;
|
|
|
- const line2Success = line1Result.status === 'fulfilled' && line1Result.value.success;
|
|
|
+ const line2Success = line2Result.status === 'fulfilled' && line2Result.value.success;
|
|
|
|
|
|
const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
|
|
|
if (index !== -1) {
|