|
|
@@ -323,7 +323,7 @@
|
|
|
:show-text="false"
|
|
|
:width="30"
|
|
|
:status="currentUploadProgress.line1Status === 'success' ? 'success' :
|
|
|
- currentUploadProgress.line1Status === 'failed' ? 'exception' : ''"
|
|
|
+ currentUploadProgress.line1Status === 'failed' ? 'exception' : undefined"
|
|
|
></el-progress>
|
|
|
<span class="line-status-text">{{ Math.round(currentUploadProgress.line1) }}%</span>
|
|
|
</div>
|
|
|
@@ -334,7 +334,7 @@
|
|
|
:show-text="false"
|
|
|
:width="30"
|
|
|
:status="currentUploadProgress.line2Status === 'success' ? 'success' :
|
|
|
- currentUploadProgress.line2Status === 'failed' ? 'exception' : ''"
|
|
|
+ currentUploadProgress.line2Status === 'failed' ? 'exception' : undefined"
|
|
|
></el-progress>
|
|
|
<span class="line-status-text">{{ Math.round(currentUploadProgress.line2) }}%</span>
|
|
|
</div>
|
|
|
@@ -1374,13 +1374,35 @@ export default {
|
|
|
async clipVideoFirstTwoSeconds(file) {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
const video = document.createElement('video');
|
|
|
- video.src = URL.createObjectURL(file);
|
|
|
+ const objectUrl = URL.createObjectURL(file);
|
|
|
+ video.src = objectUrl;
|
|
|
video.muted = true;
|
|
|
video.playsInline = true;
|
|
|
|
|
|
- video.onloadedmetadata = () => {
|
|
|
- video.currentTime = 0; // 定位到第一帧
|
|
|
- video.onseeked = () => {
|
|
|
+ let timeoutId = null;
|
|
|
+ let seeked = false;
|
|
|
+
|
|
|
+ // 清理函数
|
|
|
+ const cleanup = () => {
|
|
|
+ if (timeoutId) {
|
|
|
+ clearTimeout(timeoutId);
|
|
|
+ timeoutId = null;
|
|
|
+ }
|
|
|
+ URL.revokeObjectURL(objectUrl);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 超时处理
|
|
|
+ timeoutId = setTimeout(() => {
|
|
|
+ cleanup();
|
|
|
+ reject(new Error('视频截取超时'));
|
|
|
+ }, 10000); // 10秒超时
|
|
|
+
|
|
|
+ // 先设置 onseeked 监听器,防止在设置 currentTime 之前触发 seeked 事件
|
|
|
+ video.onseeked = () => {
|
|
|
+ if (seeked) return; // 防止重复触发
|
|
|
+ seeked = true;
|
|
|
+
|
|
|
+ try {
|
|
|
const canvas = document.createElement('canvas');
|
|
|
canvas.width = video.videoWidth;
|
|
|
canvas.height = video.videoHeight;
|
|
|
@@ -1389,17 +1411,24 @@ export default {
|
|
|
|
|
|
canvas.toBlob(
|
|
|
(blob) => {
|
|
|
- URL.revokeObjectURL(video.src);
|
|
|
+ cleanup();
|
|
|
resolve(blob); // 返回 JPEG Blob
|
|
|
},
|
|
|
'image/jpeg',
|
|
|
0.8 // 质量
|
|
|
);
|
|
|
- };
|
|
|
+ } catch (error) {
|
|
|
+ cleanup();
|
|
|
+ reject(new Error('canvas操作失败: ' + error.message));
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ video.onloadedmetadata = () => {
|
|
|
+ video.currentTime = 0; // 定位到第一帧
|
|
|
};
|
|
|
|
|
|
video.onerror = () => {
|
|
|
- URL.revokeObjectURL(video.src);
|
|
|
+ cleanup();
|
|
|
reject(new Error('视频加载失败'));
|
|
|
};
|
|
|
});
|
|
|
@@ -1564,7 +1593,15 @@ export default {
|
|
|
this.currentUploadProgress = { total: 0, line1: 0, line2: 0, line1Status: 'pending', line2Status: 'pending' };
|
|
|
|
|
|
try {
|
|
|
- await this.getFirstThumbnail(file, this.form);
|
|
|
+ // 获取封面,10秒超时,失败后继续上传
|
|
|
+ try {
|
|
|
+ await Promise.race([
|
|
|
+ this.getFirstThumbnail(file, this.form),
|
|
|
+ new Promise((_, reject) => setTimeout(() => reject(new Error('获取封面超时')), 10000))
|
|
|
+ ]);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取封面失败:', error);
|
|
|
+ }
|
|
|
const [line1Result, line2Result] = await Promise.allSettled([
|
|
|
this.uploadVideoToTxPcdn(file, this.form, options.onProgress),
|
|
|
//this.uploadVideoToHwObs(file, this.form, options.onProgress)
|
|
|
@@ -1612,6 +1649,10 @@ export default {
|
|
|
video.preload = 'metadata';
|
|
|
video.onloadedmetadata = () => {
|
|
|
this.form.duration = Math.round(video.duration);
|
|
|
+ URL.revokeObjectURL(video.src);
|
|
|
+ };
|
|
|
+ video.onerror = () => {
|
|
|
+ URL.revokeObjectURL(video.src);
|
|
|
};
|
|
|
video.src = URL.createObjectURL(file);
|
|
|
},
|
|
|
@@ -1727,8 +1768,14 @@ export default {
|
|
|
this.$message.success('批量添加成功');
|
|
|
this.batchAddVisible = false;
|
|
|
this.getList();
|
|
|
+ } else {
|
|
|
+ this.$message.error(response.msg || '批量添加失败');
|
|
|
}
|
|
|
- })
|
|
|
+ }).catch(error => {
|
|
|
+ this.$message.error('批量添加失败: ' + (error.message || '网络错误'));
|
|
|
+ }).finally(() => {
|
|
|
+ this.add = false;
|
|
|
+ });
|
|
|
},
|
|
|
/** 获取分类名称 */
|
|
|
getTypeName(typeId) {
|
|
|
@@ -1931,7 +1978,7 @@ export default {
|
|
|
};
|
|
|
|
|
|
this.uploadQueue.push(tempVideo);
|
|
|
- this.videoList.unshift(tempVideo);
|
|
|
+ this.videoList.push(tempVideo);
|
|
|
|
|
|
// 获取视频时长
|
|
|
const video = document.createElement('video');
|
|
|
@@ -1941,6 +1988,10 @@ export default {
|
|
|
if (index !== -1) {
|
|
|
tempVideo.duration = Math.round(video.duration);
|
|
|
}
|
|
|
+ URL.revokeObjectURL(video.src);
|
|
|
+ };
|
|
|
+ video.onerror = () => {
|
|
|
+ URL.revokeObjectURL(video.src);
|
|
|
};
|
|
|
video.src = URL.createObjectURL(file);
|
|
|
|
|
|
@@ -1973,18 +2024,22 @@ export default {
|
|
|
const index = this.videoList.findIndex(item => item.tempId === video.tempId);
|
|
|
if (index !== -1) {
|
|
|
this.videoList[index].uploadStatus = 'uploading';
|
|
|
+ // 重置上传进度,防止下一个视频显示旧进度
|
|
|
+ this.videoList[index].progress = 0;
|
|
|
+ this.videoList[index].uploadDetails = {
|
|
|
+ line1: 0,
|
|
|
+ line2: 0,
|
|
|
+ line1Status: 'pending',
|
|
|
+ line2Status: 'pending'
|
|
|
+ };
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// Process current batch in parallel
|
|
|
const batchPromises = currentBatch.map(video => this.uploadSingleVideo(video));
|
|
|
|
|
|
- try {
|
|
|
- await Promise.allSettled(batchPromises);
|
|
|
- this.$message.success(`批次上传完成,已处理 ${currentBatch.length} 个视频`);
|
|
|
- } catch (error) {
|
|
|
- this.$message.error(`批次上传过程中发生错误: ${error.message}`);
|
|
|
- }
|
|
|
+ await Promise.allSettled(batchPromises);
|
|
|
+ this.$message.success(`批次上传完成,已处理 ${currentBatch.length} 个视频`);
|
|
|
|
|
|
// Update queue positions for remaining videos
|
|
|
this.updateQueuePositions();
|
|
|
@@ -2003,17 +2058,41 @@ export default {
|
|
|
|
|
|
async uploadSingleVideo(tempVideo) {
|
|
|
try {
|
|
|
- // 获取封面
|
|
|
- await this.getFirstThumbnail(tempVideo.file, tempVideo);
|
|
|
-
|
|
|
- // 并行上传到两个服务器
|
|
|
- const [line1Result, line2Result] = await Promise.allSettled([
|
|
|
- this.uploadVideoToTxPcdnBatch(tempVideo.file, tempVideo),
|
|
|
- // this.uploadVideoToHwObsBatch(tempVideo.file, tempVideo)
|
|
|
- this.uploadVideoToHSYBatch(tempVideo.file, tempVideo),
|
|
|
+ // 获取封面,10秒超时,重试3次
|
|
|
+ let thumbnailSuccess = false;
|
|
|
+ for (let i = 0; i < 3; i++) {
|
|
|
+ try {
|
|
|
+ await Promise.race([
|
|
|
+ this.getFirstThumbnail(tempVideo.file, tempVideo),
|
|
|
+ new Promise((_, reject) => setTimeout(() => reject(new Error('获取封面超时')), 10000))
|
|
|
+ ]);
|
|
|
+ thumbnailSuccess = true;
|
|
|
+ break;
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`获取封面失败,第${i + 1}次重试:`, error);
|
|
|
+ if (i < 2) {
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!thumbnailSuccess) {
|
|
|
+ console.error('获取封面失败,已重试3次,跳过封面获取继续上传');
|
|
|
+ }
|
|
|
|
|
|
+ // 并行上传到两个服务器,添加超时保护
|
|
|
+ const uploadPromise = Promise.allSettled([
|
|
|
+ Promise.race([
|
|
|
+ this.uploadVideoToTxPcdnBatch(tempVideo.file, tempVideo),
|
|
|
+ new Promise((_, reject) => setTimeout(() => reject(new Error('腾讯云上传超时')), 300000))
|
|
|
+ ]),
|
|
|
+ Promise.race([
|
|
|
+ this.uploadVideoToHSYBatch(tempVideo.file, tempVideo),
|
|
|
+ new Promise((_, reject) => setTimeout(() => reject(new Error('HSY上传超时')), 300000))
|
|
|
+ ])
|
|
|
]);
|
|
|
|
|
|
+ const [line1Result, line2Result] = await uploadPromise;
|
|
|
+
|
|
|
// 检查上传结果
|
|
|
const line1Success = line1Result.status === 'fulfilled' && line1Result.value.success;
|
|
|
const line2Success = line2Result.status === 'fulfilled' && line2Result.value.success;
|
|
|
@@ -2037,11 +2116,13 @@ export default {
|
|
|
|
|
|
return { success: line1Success && line2Success, tempVideo };
|
|
|
} catch (error) {
|
|
|
+ console.error('视频上传失败:', error);
|
|
|
const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
|
|
|
if (index !== -1) {
|
|
|
this.videoList[index].uploadStatus = 'failed';
|
|
|
}
|
|
|
- throw error;
|
|
|
+ // 不再抛出错误,避免影响队列中的其他视频
|
|
|
+ return { success: false, tempVideo, error: error.message };
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -2360,7 +2441,7 @@ export default {
|
|
|
async uploadVideoToHSYBatch(file, tempVideo) {
|
|
|
try {
|
|
|
const data = await uploadToHSY(file, (progress) => {
|
|
|
- const progressPercent = Math.floor(progress);
|
|
|
+ const progressPercent = Math.floor(progress.percent);
|
|
|
const index = this.videoList.findIndex(item => item.tempId === tempVideo.tempId);
|
|
|
if (index !== -1) {
|
|
|
this.videoList[index].uploadDetails.line2 = progressPercent;
|
|
|
@@ -2506,7 +2587,7 @@ export default {
|
|
|
|
|
|
if (needRetryLine2) {
|
|
|
uploadPromises.push(
|
|
|
- this.uploadVideoToHwObsBatch(tempVideo.file, tempVideo)
|
|
|
+ this.uploadVideoToHSYBatch(tempVideo.file, tempVideo)
|
|
|
.then(result => ({ line: 'line2', result }))
|
|
|
.catch(error => ({ line: 'line2', result: { success: false, error: error.message } }))
|
|
|
);
|
|
|
@@ -2567,19 +2648,6 @@ export default {
|
|
|
this.$message.error(`文件 ${tempVideo.fileName} 重试过程中发生错误:${error.message || '未知错误'}`);
|
|
|
}
|
|
|
},
|
|
|
- // 重试单个上传
|
|
|
- async retryUpload(row) {
|
|
|
- this.$confirm('确认要重新上传该视频吗?', '提示', {
|
|
|
- confirmButtonText: '确定',
|
|
|
- cancelButtonText: '取消',
|
|
|
- type: 'warning'
|
|
|
- }).then(async () => {
|
|
|
- // 这里需要重新触发上传,但由于原始文件可能已经不存在
|
|
|
- // 建议用户重新选择文件上传
|
|
|
- this.$message.info('请重新选择文件进行上传');
|
|
|
- this.handleUpdate(row);
|
|
|
- }).catch(() => {});
|
|
|
- },
|
|
|
// 获取上传状态图标
|
|
|
getUploadStatusIcon(status) {
|
|
|
switch (status) {
|
|
|
@@ -2638,7 +2706,7 @@ export default {
|
|
|
} else if (row.uploadStatus === 'failed') {
|
|
|
return 'exception';
|
|
|
}
|
|
|
- return '';
|
|
|
+ return undefined;
|
|
|
},
|
|
|
}
|
|
|
}
|