优化视频背景手机端缩略图显示的功能

This commit is contained in:
taiyi 2026-04-12 15:30:18 +08:00
parent c25d7b446d
commit 23e3e68fa9
2 changed files with 207 additions and 0 deletions

View File

@ -40,10 +40,13 @@
<video
v-if="background.resource_type === 2"
:src="background.resource_url || defaultThumbnail"
:poster="getVideoPoster(background)"
:alt="background.name"
class="background-video"
muted
loop
preload="metadata"
playsinline
@mouseenter="handleVideoHover(true)"
@mouseleave="handleVideoHover(false)"
/>
@ -252,6 +255,7 @@ export default {
return {
backgrounds: [],
filteredBackgrounds: [],
videoPosterMap: {},
showCreateDialog: false,
showPreviewDialog: false,
previewBackgroundData: null,
@ -286,6 +290,7 @@ export default {
const responseData = response.data;
this.backgrounds = (responseData && responseData.data) ? responseData.data : [];
this.filterBackgrounds();
await this.ensureVideoPosters(this.filteredBackgrounds);
} catch (error) {
console.error('Failed to load backgrounds:', error)
this.backgrounds = [];
@ -321,6 +326,103 @@ export default {
this.showPreviewDialog = true
},
async generateVideoPoster(background) {
return new Promise((resolve) => {
if (!background?.id || !background?.resource_url) {
resolve('')
return
}
const cacheKey = String(background.id)
if (this.videoPosterMap[cacheKey]) {
resolve(this.videoPosterMap[cacheKey])
return
}
const video = document.createElement('video')
video.crossOrigin = 'anonymous'
video.muted = true
video.playsInline = true
video.preload = 'metadata'
let settled = false
let timeoutId = null
const cleanUp = () => {
video.pause()
video.removeAttribute('src')
video.load()
}
const done = (value = '') => {
if (settled) return
settled = true
if (timeoutId) {
window.clearTimeout(timeoutId)
}
cleanUp()
resolve(value)
}
const captureFrame = () => {
try {
const canvas = document.createElement('canvas')
canvas.width = video.videoWidth || 360
canvas.height = video.videoHeight || 640
const ctx = canvas.getContext('2d')
if (!ctx) {
done('')
return
}
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
const poster = canvas.toDataURL('image/jpeg', 0.82)
this.videoPosterMap[cacheKey] = poster
done(poster)
} catch (error) {
done('')
}
}
timeoutId = window.setTimeout(() => done(''), 5000)
video.addEventListener('loadeddata', () => {
if (video.readyState >= 2) {
video.currentTime = 0
} else {
captureFrame()
}
}, { once: true })
video.addEventListener('seeked', () => {
captureFrame()
}, { once: true })
video.addEventListener('error', () => {
done('')
}, { once: true })
video.src = background.resource_url
})
},
async ensureVideoPosters(backgrounds) {
const videoList = (backgrounds || []).filter(bg => bg.resource_type === 2)
await Promise.all(videoList.map(async (bg) => {
const key = String(bg.id)
if (!this.videoPosterMap[key]) {
const generatedPoster = await this.generateVideoPoster(bg)
if (generatedPoster) {
this.videoPosterMap[key] = generatedPoster
}
}
}))
},
getVideoPoster(background) {
const key = String(background?.id || '')
return background?.thumbnail || this.videoPosterMap[key] || background?.resource_url || this.defaultThumbnail
},
handleVideoHover(isHover) {
// /
const videoElements = document.querySelectorAll('.background-video')

View File

@ -116,10 +116,13 @@
<video
v-if="background.resource_type === 2"
:src="background.resource_url"
:poster="getVideoPoster(background)"
:alt="background.name"
class="background-video"
muted
loop
preload="metadata"
playsinline
@mouseenter="handleVideoHover(true, $event)"
@mouseleave="handleVideoHover(false, $event)"
/>
@ -445,6 +448,7 @@ export default {
//
const userInfo = ref({})
const userBackgrounds = ref([])
const videoPosterMap = ref({})
const loading = ref(false)
const showPreviewDialog = ref(false)
const previewBackgroundData = ref(null)
@ -557,6 +561,7 @@ export default {
...bg,
is_default: false
}))
await ensureVideoPosters(userBackgrounds.value)
} catch (error) {
console.error('Failed to load user backgrounds:', error)
if (error.response?.status === 401 || error.response?.status === 403) {
@ -579,6 +584,105 @@ export default {
ElMessage.success('刷新完成')
}
//
const generateVideoPoster = (background) => {
return new Promise((resolve) => {
if (!background?.id || !background?.resource_url) {
resolve('')
return
}
const cacheKey = String(background.id)
if (videoPosterMap.value[cacheKey]) {
resolve(videoPosterMap.value[cacheKey])
return
}
const video = document.createElement('video')
video.crossOrigin = 'anonymous'
video.muted = true
video.playsInline = true
video.preload = 'metadata'
let settled = false
let timeoutId = null
const done = (value = '') => {
if (settled) return
settled = true
if (timeoutId) {
window.clearTimeout(timeoutId)
}
cleanUp()
resolve(value)
}
const cleanUp = () => {
video.pause()
video.removeAttribute('src')
video.load()
}
const captureFrame = () => {
try {
const canvas = document.createElement('canvas')
canvas.width = video.videoWidth || 360
canvas.height = video.videoHeight || 640
const ctx = canvas.getContext('2d')
if (!ctx) {
done('')
return
}
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
const poster = canvas.toDataURL('image/jpeg', 0.82)
videoPosterMap.value[cacheKey] = poster
done(poster)
} catch (e) {
done('')
}
}
timeoutId = window.setTimeout(() => done(''), 5000)
video.addEventListener('loadeddata', () => {
if (video.readyState >= 2) {
video.currentTime = 0
} else {
captureFrame()
}
}, { once: true })
video.addEventListener('seeked', () => {
captureFrame()
}, { once: true })
video.addEventListener('error', () => {
done('')
}, { once: true })
video.src = background.resource_url
})
}
//
const ensureVideoPosters = async (backgrounds) => {
const videoList = (backgrounds || []).filter(bg => bg.resource_type === 2)
await Promise.all(videoList.map(async (bg) => {
const key = String(bg.id)
if (!videoPosterMap.value[key]) {
const generatedPoster = await generateVideoPoster(bg)
if (generatedPoster) {
videoPosterMap.value[key] = generatedPoster
}
}
}))
}
// 使thumbnail使
const getVideoPoster = (background) => {
const key = String(background?.id || '')
return background?.thumbnail || videoPosterMap.value[key] || background?.resource_url || ''
}
//
const handleVideoHover = (isHover, event) => {
const video = event.target
@ -708,6 +812,7 @@ export default {
handleMenuSelect,
handleAvatarClick,
refreshBackgrounds,
getVideoPoster,
handleVideoHover,
enterChat,
previewBackground,