// 从视频截取首帧并上传为封面图(POST /common/upload,返回图片 fileName;失败返回 null)
// coverImage 必须是图片文件,不能直接传视频地址(否则封面加载不出来)
async function generateVideoCover(videoSrc) {
try {
const frameBlob = await new Promise((resolve, reject) => {
const video = document.createElement('video')
video.muted = true
video.crossOrigin = 'anonymous'
video.preload = 'auto'
let settled = false
const done = (fn, arg) => {
if (settled) return
settled = true
video.removeAttribute('src')
try { video.load() } catch { /* 忽略 */ }
fn(arg)
}
video.onloadeddata = () => {
try { video.currentTime = Math.min(0.1, video.duration || 0.1) } catch { done(resolve, null) }
}
video.onseeked = () => {
try {
const canvas = document.createElement('canvas')
canvas.width = video.videoWidth || 1280
canvas.height = video.videoHeight || 720
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height)
canvas.toBlob((blob) => {
if (blob) done(resolve, blob)
else done(reject, new Error('封面帧生成失败'))
}, 'image/jpeg', 0.85)
} catch (e) {
done(reject, e)
}
}
video.onerror = () => done(reject, new Error('视频加载失败'))
setTimeout(() => done(reject, new Error('视频封面生成超时')), 15000)
video.src = videoSrc
})
if (!frameBlob) return null
const file = new File([frameBlob], `cover_${Date.now()}.jpg`, { type: 'image/jpeg' })
return await upload(file)
} catch {
return null
}
}