feat(每日充值活动): 抽离方法,调整奖励区域vap视频一次获取不到就永久展示封面的问题

This commit is contained in:
hzj 2026-05-08 18:13:19 +08:00
parent 9f349d3c87
commit 5acaae8a19
2 changed files with 336 additions and 221 deletions

View File

@ -0,0 +1,312 @@
import { ref } from 'vue'
import { clearVapConfigCache, preloadVapConfig } from '@/utils/vapMp4Config.js'
import { resolveProtectedAssetUrl } from '@/utils/protectedAssets.js'
const REWARD_VAP_MAX_RETRY_COUNT = 3
const isEmptySourceValue = (value) => {
if (typeof value !== 'string' && typeof value !== 'number') {
return true
}
return ['', 'null', 'undefined'].includes(String(value || '').trim().toLowerCase())
}
const getRewardRawSourceUrl = (reward = {}) => {
const sourceCandidates = [
reward.sourceUrl,
reward.mp4Url,
reward.vapUrl,
reward.animationUrl,
reward.resourceUrl,
reward.dynamicUrl,
reward.url,
reward?.source?.url,
reward?.resource?.url,
reward?.extValues?.sourceUrl,
reward?.extValues?.mp4Url,
reward?.extValues?.vapUrl,
]
return sourceCandidates.find((value) => !isEmptySourceValue(value)) || ''
}
export function useDailyRechargeRewardVap(options = {}) {
const { isAndroidDevice, getCurrentMainRewardVapSourceUrl = () => '' } = options
const rewardVapPreloadElements = new Map()
const criticalVapPreloadElements = new Map()
const rewardVapRetryTimers = new Map()
const failedRewardVapUrls = ref(new Set())
const rewardVapRetryCounts = ref({})
const rewardVapRetryVersions = ref({})
let rewardVapPreloadTimer = null
let rewardVapPreloadIdleCallbackId = null
let rewardVapPreloadStarted = false
const normalizeVapSourceUrl = (sourceUrl = '') => {
const resolvedUrl = String(resolveProtectedAssetUrl(sourceUrl || '') || '').trim()
if (!resolvedUrl) {
return ''
}
if (resolvedUrl.startsWith('//')) {
const protocol =
typeof window !== 'undefined' && window.location?.protocol ? window.location.protocol : 'https:'
return `${protocol}${resolvedUrl}`
}
if (/^http:\/\//i.test(resolvedUrl)) {
return resolvedUrl.replace(/^http:\/\//i, 'https://')
}
if (/^https?:\/\//i.test(resolvedUrl)) {
return resolvedUrl
}
if (resolvedUrl.startsWith('/oss/')) {
return resolvedUrl
}
if (resolvedUrl.startsWith('/')) {
return process.env.NODE_ENV === 'development'
? `/oss${resolvedUrl}`
: `https://tkm-likei.oss-ap-southeast-1.aliyuncs.com${resolvedUrl}`
}
return `https://${resolvedUrl}`
}
const isValidVapSourceUrl = (sourceUrl = '') => {
if (!sourceUrl) {
return false
}
return /^https?:\/\/.+\.mp4(?:[?#].*)?$/i.test(sourceUrl) || /^\/oss\/.+\.mp4(?:[?#].*)?$/i.test(sourceUrl)
}
const getRewardVapSourceUrl = (reward = {}) => {
const sourceUrl = normalizeVapSourceUrl(getRewardRawSourceUrl(reward))
return isValidVapSourceUrl(sourceUrl) ? sourceUrl : ''
}
const getRewardVapUrls = (rewardTopList) => {
return (rewardTopList || [])
.flatMap((rewardTop) => rewardTop?.propsGroup?.activityRewardProps || [])
.map((reward) => getRewardVapSourceUrl(reward))
.filter(Boolean)
}
const getRewardVapRetryVersion = (sourceUrl) => {
return sourceUrl ? rewardVapRetryVersions.value[sourceUrl] || 0 : 0
}
const preloadRewardVap = (sourceUrl) => {
if (!isAndroidDevice?.value || !sourceUrl || rewardVapPreloadElements.has(sourceUrl)) {
return
}
preloadVapConfig(sourceUrl)
if (typeof document === 'undefined') {
return
}
const preloadVideo = document.createElement('video')
preloadVideo.preload = 'auto'
preloadVideo.muted = true
preloadVideo.defaultMuted = true
preloadVideo.playsInline = true
preloadVideo.setAttribute('playsinline', '')
preloadVideo.setAttribute('webkit-playsinline', '')
preloadVideo.setAttribute('x5-playsinline', '')
preloadVideo.src = sourceUrl
preloadVideo.style.position = 'fixed'
preloadVideo.style.left = '-9999px'
preloadVideo.style.top = '0'
preloadVideo.style.width = '1px'
preloadVideo.style.height = '1px'
preloadVideo.style.opacity = '0'
preloadVideo.style.pointerEvents = 'none'
document.body.appendChild(preloadVideo)
preloadVideo.load()
rewardVapPreloadElements.set(sourceUrl, preloadVideo)
}
const preloadRewardVaps = (rewardTopList) => {
getRewardVapUrls(rewardTopList).forEach(preloadRewardVap)
}
const preloadCriticalVap = (sourceUrl) => {
if (!isAndroidDevice?.value || !sourceUrl) {
return Promise.resolve()
}
const resolvedSourceUrl = resolveProtectedAssetUrl(sourceUrl)
const configPromise = preloadVapConfig(resolvedSourceUrl)
if (criticalVapPreloadElements.has(resolvedSourceUrl) || typeof document === 'undefined') {
return configPromise
}
const preloadVideo = document.createElement('video')
preloadVideo.preload = 'auto'
preloadVideo.muted = true
preloadVideo.playsInline = true
preloadVideo.src = resolvedSourceUrl
preloadVideo.style.position = 'fixed'
preloadVideo.style.left = '-9999px'
preloadVideo.style.top = '-9999px'
preloadVideo.style.width = '1px'
preloadVideo.style.height = '1px'
preloadVideo.style.opacity = '0'
preloadVideo.style.pointerEvents = 'none'
document.body.appendChild(preloadVideo)
preloadVideo.load()
criticalVapPreloadElements.set(resolvedSourceUrl, preloadVideo)
return configPromise
}
const startRewardVapPreload = (rewardTopList) => {
if (!isAndroidDevice?.value || rewardVapPreloadStarted || !rewardTopList?.length) {
return
}
rewardVapPreloadStarted = true
preloadRewardVaps(rewardTopList)
}
const queueRewardVapPreload = (rewardTopList) => {
if (!isAndroidDevice?.value || rewardVapPreloadStarted || typeof window === 'undefined') {
return
}
if (typeof window.requestIdleCallback === 'function') {
rewardVapPreloadIdleCallbackId = window.requestIdleCallback(() => {
rewardVapPreloadIdleCallbackId = null
startRewardVapPreload(rewardTopList)
})
return
}
rewardVapPreloadTimer = window.setTimeout(() => {
rewardVapPreloadTimer = null
startRewardVapPreload(rewardTopList)
}, 0)
}
const clearRewardVapRetryTimer = (sourceUrl) => {
const retryTimer = rewardVapRetryTimers.get(sourceUrl)
if (retryTimer && typeof window !== 'undefined') {
window.clearTimeout(retryTimer)
}
rewardVapRetryTimers.delete(sourceUrl)
}
const clearRewardVapRetryState = () => {
rewardVapRetryTimers.forEach((retryTimer) => {
if (typeof window !== 'undefined') {
window.clearTimeout(retryTimer)
}
})
rewardVapRetryTimers.clear()
rewardVapRetryCounts.value = {}
rewardVapRetryVersions.value = {}
}
const scheduleRewardVapRetry = (sourceUrl, retryCount) => {
if (typeof window === 'undefined') {
return
}
clearRewardVapRetryTimer(sourceUrl)
clearVapConfigCache(sourceUrl)
const retryDelay = Math.min(500 + retryCount * 500, 2000)
const retryTimer = window.setTimeout(() => {
rewardVapRetryTimers.delete(sourceUrl)
if (getCurrentMainRewardVapSourceUrl() !== sourceUrl || failedRewardVapUrls.value.has(sourceUrl)) {
return
}
preloadVapConfig(sourceUrl)
rewardVapRetryVersions.value = {
...rewardVapRetryVersions.value,
[sourceUrl]: (rewardVapRetryVersions.value[sourceUrl] || 0) + 1,
}
}, retryDelay)
rewardVapRetryTimers.set(sourceUrl, retryTimer)
}
const handleRewardVapError = (error, sourceUrl = '') => {
console.error('Reward VAP playback failed:', error)
const normalizedSourceUrl = normalizeVapSourceUrl(sourceUrl)
if (!normalizedSourceUrl) {
return
}
const retryCount = (rewardVapRetryCounts.value[normalizedSourceUrl] || 0) + 1
rewardVapRetryCounts.value = {
...rewardVapRetryCounts.value,
[normalizedSourceUrl]: retryCount,
}
if (retryCount <= REWARD_VAP_MAX_RETRY_COUNT) {
scheduleRewardVapRetry(normalizedSourceUrl, retryCount)
return
}
clearRewardVapRetryTimer(normalizedSourceUrl)
failedRewardVapUrls.value = new Set([...failedRewardVapUrls.value, normalizedSourceUrl])
}
const resetRewardVapState = () => {
failedRewardVapUrls.value = new Set()
clearRewardVapRetryState()
rewardVapPreloadStarted = false
}
const cleanupRewardVap = () => {
if (rewardVapPreloadTimer !== null && typeof window !== 'undefined') {
window.clearTimeout(rewardVapPreloadTimer)
rewardVapPreloadTimer = null
}
if (
rewardVapPreloadIdleCallbackId !== null &&
typeof window !== 'undefined' &&
typeof window.cancelIdleCallback === 'function'
) {
window.cancelIdleCallback(rewardVapPreloadIdleCallbackId)
rewardVapPreloadIdleCallbackId = null
}
clearRewardVapRetryState()
rewardVapPreloadElements.forEach((video) => {
video.pause()
video.removeAttribute('src')
video.load()
video.parentNode && video.parentNode.removeChild(video)
})
rewardVapPreloadElements.clear()
criticalVapPreloadElements.forEach((video) => {
video.pause()
video.removeAttribute('src')
video.load()
video.parentNode && video.parentNode.removeChild(video)
})
criticalVapPreloadElements.clear()
}
return {
failedRewardVapUrls,
getRewardVapRetryVersion,
getRewardVapSourceUrl,
handleRewardVapError,
preloadCriticalVap,
queueRewardVapPreload,
resetRewardVapState,
cleanupRewardVap,
}
}

View File

@ -340,6 +340,7 @@ justify-content: flex-start;
>
<VapMp4Player
v-if="shouldPlayMainRewardVap"
:key="mainRewardVapRenderKey"
:model-value="true"
:src="mainRewardVapSourceUrl"
mode="inline"
@ -527,17 +528,16 @@ import { getMp4Url, getPngUrl } from '@/config/imagePaths.js'
import { preloadImages } from '@/utils/image/imagePreloader.js'
import { useLangStore } from '@/stores/lang'
import { showError } from '@/utils/toast.js'
import { preloadVapConfig } from '@/utils/vapMp4Config.js'
import {
apiGetActivityResource,
claimDailyRechargeTask1,
claimDailyRechargeTask2,
getDailyRechargeInfo,
} from '@/api/activity.js'
import { resolveProtectedAssetUrl } from '@/utils/protectedAssets.js'
import { handleRewardImageError } from '@/utils/image/imageHandler.js'
import { formatRewardDisplay } from '@/utils/rewardFormatter.js'
import { useThrottle } from '@/utils/useDebounce.js'
import { useDailyRechargeRewardVap } from './composables/useDailyRechargeRewardVap.js'
import BackgroundLayer from '@/components/BackgroundLayer.vue'
import itemCenter from '@/components/itemCenter.vue'
@ -781,6 +781,19 @@ const selectedRewardList = computed(() => {
const mainReward = computed(() => {
return selectedRewardList.value[0] || null
})
const {
failedRewardVapUrls,
getRewardVapRetryVersion,
getRewardVapSourceUrl,
handleRewardVapError,
preloadCriticalVap,
queueRewardVapPreload,
resetRewardVapState,
cleanupRewardVap,
} = useDailyRechargeRewardVap({
isAndroidDevice,
getCurrentMainRewardVapSourceUrl: () => mainRewardVapSourceUrl.value,
})
const mainRewardVapSourceUrl = computed(() => {
return getRewardVapSourceUrl(mainReward.value)
})
@ -797,12 +810,12 @@ const rewardGridList = computed(() => {
const mainRewardRenderKey = computed(() => {
return mainReward.value ? getRewardRenderKey(mainReward.value, 0) : 'empty-main-reward'
})
const rewardVapPreloadElements = new Map()
const criticalVapPreloadElements = new Map()
const failedRewardVapUrls = ref(new Set())
let rewardVapPreloadTimer = null
let rewardVapPreloadIdleCallbackId = null
let rewardVapPreloadStarted = false
const mainRewardVapRetryVersion = computed(() => {
return getRewardVapRetryVersion(mainRewardVapSourceUrl.value)
})
const mainRewardVapRenderKey = computed(() => {
return `${mainRewardRenderKey.value}-vap-${mainRewardVapRetryVersion.value}`
})
let pageInitializationStarted = false
const isLocalRewardAsset = (reward) => {
@ -813,79 +826,6 @@ const getRewardDisplayText = (reward) => {
return formatRewardDisplay(reward.type, reward)
}
const isEmptySourceValue = (value) => {
if (typeof value !== 'string' && typeof value !== 'number') {
return true
}
return ['', 'null', 'undefined'].includes(String(value || '').trim().toLowerCase())
}
const getRewardRawSourceUrl = (reward = {}) => {
const sourceCandidates = [
reward.sourceUrl,
reward.mp4Url,
reward.vapUrl,
reward.animationUrl,
reward.resourceUrl,
reward.dynamicUrl,
reward.url,
reward?.source?.url,
reward?.resource?.url,
reward?.extValues?.sourceUrl,
reward?.extValues?.mp4Url,
reward?.extValues?.vapUrl,
]
return sourceCandidates.find((value) => !isEmptySourceValue(value)) || ''
}
const normalizeVapSourceUrl = (sourceUrl = '') => {
const resolvedUrl = String(resolveProtectedAssetUrl(sourceUrl || '') || '').trim()
if (!resolvedUrl) {
return ''
}
if (resolvedUrl.startsWith('//')) {
const protocol =
typeof window !== 'undefined' && window.location?.protocol ? window.location.protocol : 'https:'
return `${protocol}${resolvedUrl}`
}
if (/^http:\/\//i.test(resolvedUrl)) {
return resolvedUrl.replace(/^http:\/\//i, 'https://')
}
if (/^https?:\/\//i.test(resolvedUrl)) {
return resolvedUrl
}
if (resolvedUrl.startsWith('/oss/')) {
return resolvedUrl
}
if (resolvedUrl.startsWith('/')) {
return process.env.NODE_ENV === 'development'
? `/oss${resolvedUrl}`
: `https://tkm-likei.oss-ap-southeast-1.aliyuncs.com${resolvedUrl}`
}
return `https://${resolvedUrl}`
}
const isValidVapSourceUrl = (sourceUrl = '') => {
if (!sourceUrl) {
return false
}
return /^https?:\/\/.+\.mp4(?:[?#].*)?$/i.test(sourceUrl) || /^\/oss\/.+\.mp4(?:[?#].*)?$/i.test(sourceUrl)
}
const getRewardVapSourceUrl = (reward = {}) => {
const sourceUrl = normalizeVapSourceUrl(getRewardRawSourceUrl(reward))
return isValidVapSourceUrl(sourceUrl) ? sourceUrl : ''
}
const getRewardRenderKey = (reward, index) => {
return [
selectedRewardLevel.value,
@ -897,109 +837,6 @@ const getRewardRenderKey = (reward, index) => {
].join('-')
}
const getRewardVapUrls = (rewardTopList) => {
return (rewardTopList || [])
.flatMap((rewardTop) => rewardTop?.propsGroup?.activityRewardProps || [])
.map((reward) => getRewardVapSourceUrl(reward))
.filter(Boolean)
}
const preloadRewardVap = (sourceUrl) => {
if (!isAndroidDevice.value || !sourceUrl || rewardVapPreloadElements.has(sourceUrl)) {
return
}
preloadVapConfig(sourceUrl)
if (typeof document === 'undefined') {
return
}
const preloadVideo = document.createElement('video')
preloadVideo.preload = 'auto'
preloadVideo.muted = true
preloadVideo.defaultMuted = true
preloadVideo.playsInline = true
preloadVideo.setAttribute('playsinline', '')
preloadVideo.setAttribute('webkit-playsinline', '')
preloadVideo.setAttribute('x5-playsinline', '')
preloadVideo.src = sourceUrl
preloadVideo.style.position = 'fixed'
preloadVideo.style.left = '-9999px'
preloadVideo.style.top = '0'
preloadVideo.style.width = '1px'
preloadVideo.style.height = '1px'
preloadVideo.style.opacity = '0'
preloadVideo.style.pointerEvents = 'none'
document.body.appendChild(preloadVideo)
preloadVideo.load()
rewardVapPreloadElements.set(sourceUrl, preloadVideo)
}
const preloadRewardVaps = (rewardTopList) => {
getRewardVapUrls(rewardTopList).forEach(preloadRewardVap)
}
const preloadCriticalVap = (sourceUrl) => {
if (!isAndroidDevice.value || !sourceUrl) {
return Promise.resolve()
}
const resolvedSourceUrl = resolveProtectedAssetUrl(sourceUrl)
const configPromise = preloadVapConfig(resolvedSourceUrl)
if (criticalVapPreloadElements.has(resolvedSourceUrl) || typeof document === 'undefined') {
return configPromise
}
const preloadVideo = document.createElement('video')
preloadVideo.preload = 'auto'
preloadVideo.muted = true
preloadVideo.playsInline = true
preloadVideo.src = resolvedSourceUrl
preloadVideo.style.position = 'fixed'
preloadVideo.style.left = '-9999px'
preloadVideo.style.top = '-9999px'
preloadVideo.style.width = '1px'
preloadVideo.style.height = '1px'
preloadVideo.style.opacity = '0'
preloadVideo.style.pointerEvents = 'none'
document.body.appendChild(preloadVideo)
preloadVideo.load()
criticalVapPreloadElements.set(resolvedSourceUrl, preloadVideo)
return configPromise
}
const startRewardVapPreload = () => {
if (!isAndroidDevice.value || rewardVapPreloadStarted || !activityRewards.value.length) {
return
}
rewardVapPreloadStarted = true
preloadRewardVaps(activityRewards.value)
}
const queueRewardVapPreload = () => {
if (!isAndroidDevice.value || rewardVapPreloadStarted || typeof window === 'undefined') {
return
}
if (typeof window.requestIdleCallback === 'function') {
rewardVapPreloadIdleCallbackId = window.requestIdleCallback(() => {
rewardVapPreloadIdleCallbackId = null
startRewardVapPreload()
})
return
}
rewardVapPreloadTimer = window.setTimeout(() => {
rewardVapPreloadTimer = null
startRewardVapPreload()
}, 0)
}
const getExtraRewardsByLevel = (level) => {
switch (Number(level)) {
case 8:
@ -1097,14 +934,6 @@ const openPopup = (type) => {
}
}
const handleRewardVapError = (error, sourceUrl = '') => {
console.error('Reward VAP playback failed:', error)
const normalizedSourceUrl = normalizeVapSourceUrl(sourceUrl)
if (normalizedSourceUrl) {
failedRewardVapUrls.value = new Set([...failedRewardVapUrls.value, normalizedSourceUrl])
}
}
const handleMainRewardPreviewClick = () => {
if (!mainRewardVapSourceUrl.value) {
return
@ -1174,8 +1003,7 @@ const getActivityRewards = async () => {
const res = await apiGetActivityResource('DAILY_RECHARGE_TASK2')
if (res.status && res.body) {
activityRewards.value = Array.isArray(res.body) ? appendExtraRewards(res.body) : []
failedRewardVapUrls.value = new Set()
rewardVapPreloadStarted = false
resetRewardVapState()
return true
}
@ -1261,7 +1089,7 @@ const completePreloading = async () => {
console.error('棰勫姞杞借繃绋嬩腑鍙戠敓閿欒:', error)
} finally {
isLoading.value = false
queueRewardVapPreload()
queueRewardVapPreload(activityRewards.value)
}
}
@ -1297,32 +1125,7 @@ onBeforeUnmount(() => {
delete window.__setDailyRechargeDevice
delete window.__dailyRechargeDeviceMode
}
if (rewardVapPreloadTimer !== null && typeof window !== 'undefined') {
window.clearTimeout(rewardVapPreloadTimer)
rewardVapPreloadTimer = null
}
if (
rewardVapPreloadIdleCallbackId !== null &&
typeof window !== 'undefined' &&
typeof window.cancelIdleCallback === 'function'
) {
window.cancelIdleCallback(rewardVapPreloadIdleCallbackId)
rewardVapPreloadIdleCallbackId = null
}
rewardVapPreloadElements.forEach((video) => {
video.pause()
video.removeAttribute('src')
video.load()
video.parentNode && video.parentNode.removeChild(video)
})
rewardVapPreloadElements.clear()
criticalVapPreloadElements.forEach((video) => {
video.pause()
video.removeAttribute('src')
video.load()
video.parentNode && video.parentNode.removeChild(video)
})
criticalVapPreloadElements.clear()
cleanupRewardVap()
})
</script>