124 lines
4.1 KiB
JavaScript
124 lines
4.1 KiB
JavaScript
|
|
/**
|
|||
|
|
* 缩略图生成辅助函数
|
|||
|
|
* 当后端 FFmpeg 生成失败时,使用浏览器原生能力作为兜底
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 使用浏览器原生 API 生成视频缩略图
|
|||
|
|
* @param {File} file - 视频文件对象
|
|||
|
|
* @param {number} seekTime - 截取时间点(秒)
|
|||
|
|
* @returns {Promise<string>} base64 图片数据
|
|||
|
|
*/
|
|||
|
|
export async function generateVideoThumbnailNative(file, seekTime = 0.1) {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const video = document.createElement('video');
|
|||
|
|
const canvas = document.createElement('canvas');
|
|||
|
|
const ctx = canvas.getContext('2d');
|
|||
|
|
|
|||
|
|
// 设置视频源
|
|||
|
|
const url = URL.createObjectURL(file);
|
|||
|
|
video.src = url;
|
|||
|
|
video.muted = true;
|
|||
|
|
video.playsInline = true;
|
|||
|
|
|
|||
|
|
video.addEventListener('loadeddata', () => {
|
|||
|
|
// seek 到指定时间
|
|||
|
|
video.currentTime = Math.min(seekTime, video.duration * 0.1 || 0.1);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
video.addEventListener('seeked', () => {
|
|||
|
|
try {
|
|||
|
|
// 计算缩放后的尺寸(最大宽度 320)
|
|||
|
|
const maxWidth = 320;
|
|||
|
|
const scale = Math.min(1, maxWidth / video.videoWidth);
|
|||
|
|
canvas.width = video.videoWidth * scale;
|
|||
|
|
canvas.height = video.videoHeight * scale;
|
|||
|
|
|
|||
|
|
// 绘制帧
|
|||
|
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|||
|
|
|
|||
|
|
// 转换为 base64
|
|||
|
|
const dataUrl = canvas.toDataURL('image/jpeg', 0.85);
|
|||
|
|
URL.revokeObjectURL(url);
|
|||
|
|
resolve(dataUrl);
|
|||
|
|
} catch (e) {
|
|||
|
|
URL.revokeObjectURL(url);
|
|||
|
|
reject(e);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
video.addEventListener('error', (e) => {
|
|||
|
|
URL.revokeObjectURL(url);
|
|||
|
|
reject(new Error('视频加载失败'));
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 开始加载
|
|||
|
|
video.load();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 使用浏览器原生 API 生成图片缩略图
|
|||
|
|
* @param {File} file - 图片文件对象
|
|||
|
|
* @returns {Promise<string>} base64 图片数据
|
|||
|
|
*/
|
|||
|
|
export async function generateImageThumbnailNative(file) {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const img = new Image();
|
|||
|
|
const canvas = document.createElement('canvas');
|
|||
|
|
const ctx = canvas.getContext('2d');
|
|||
|
|
|
|||
|
|
const url = URL.createObjectURL(file);
|
|||
|
|
|
|||
|
|
img.onload = () => {
|
|||
|
|
try {
|
|||
|
|
// 计算缩放后的尺寸(最大宽度 320)
|
|||
|
|
const maxWidth = 320;
|
|||
|
|
const scale = Math.min(1, maxWidth / img.naturalWidth);
|
|||
|
|
canvas.width = img.naturalWidth * scale;
|
|||
|
|
canvas.height = img.naturalHeight * scale;
|
|||
|
|
|
|||
|
|
// 绘制图片
|
|||
|
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|||
|
|
|
|||
|
|
// 转换为 base64
|
|||
|
|
const dataUrl = canvas.toDataURL('image/jpeg', 0.85);
|
|||
|
|
URL.revokeObjectURL(url);
|
|||
|
|
resolve(dataUrl);
|
|||
|
|
} catch (e) {
|
|||
|
|
URL.revokeObjectURL(url);
|
|||
|
|
reject(e);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
img.onerror = () => {
|
|||
|
|
URL.revokeObjectURL(url);
|
|||
|
|
reject(new Error('图片加载失败'));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
img.src = url;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 智能缩略图生成
|
|||
|
|
* 优先使用后端 FFmpeg,失败时尝试浏览器原生方案
|
|||
|
|
*/
|
|||
|
|
export async function generateThumbnailSmart(filePath, fileType, fileObj = null) {
|
|||
|
|
// 首先尝试使用后端 FFmpeg(如果有 file 对象的话)
|
|||
|
|
if (fileObj && fileObj instanceof File) {
|
|||
|
|
try {
|
|||
|
|
if (fileType === 'video') {
|
|||
|
|
return await generateVideoThumbnailNative(fileObj);
|
|||
|
|
} else if (fileType === 'image') {
|
|||
|
|
return await generateImageThumbnailNative(fileObj);
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.log('浏览器原生缩略图生成失败,使用后端:', e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 返回 null,让后端处理
|
|||
|
|
return null;
|
|||
|
|
}
|