优化视频背景手机端缩略图显示的功能
This commit is contained in:
parent
c25d7b446d
commit
23e3e68fa9
@ -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')
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user