feat(vap播放组件): 取消自动播放设置

This commit is contained in:
hzj 2026-05-01 23:28:21 +08:00
parent 14d32542fc
commit e535e0347d
3 changed files with 111 additions and 61 deletions

View File

@ -92,6 +92,7 @@ const loading = ref(false)
const errorMessage = ref('') const errorMessage = ref('')
let player = null let player = null
let playToken = 0 let playToken = 0
let cleanupPlayerVideoGuards = null
const teleportToBody = computed(() => props.mode === 'overlay') const teleportToBody = computed(() => props.mode === 'overlay')
const resolvedSrc = computed(() => resolveProtectedAssetUrl(props.src)) const resolvedSrc = computed(() => resolveProtectedAssetUrl(props.src))
@ -101,10 +102,36 @@ function enforceVideoPlaybackAttributes(video, shouldMute) {
return return
} }
video.autoplay = false
video.controls = false
video.playsInline = true video.playsInline = true
video.setAttribute('playsinline', '') video.setAttribute('playsinline', '')
video.setAttribute('webkit-playsinline', '') video.setAttribute('webkit-playsinline', '')
video.setAttribute('x5-playsinline', '') video.setAttribute('x5-playsinline', '')
video.setAttribute('controlslist', 'nofullscreen noplaybackrate noremoteplayback')
video.setAttribute('disablepictureinpicture', '')
video.setAttribute('x-webkit-airplay', 'deny')
video.setAttribute('preload', 'auto')
if ('disablePictureInPicture' in video) {
video.disablePictureInPicture = true
}
if ('disableRemotePlayback' in video) {
video.disableRemotePlayback = true
}
// Keep the source video alive for canvas sampling without letting Safari
// promote it into the native fullscreen player.
video.style.display = 'block'
video.style.position = 'fixed'
video.style.left = '-9999px'
video.style.top = '0'
video.style.width = '1px'
video.style.height = '1px'
video.style.opacity = '0'
video.style.pointerEvents = 'none'
video.style.zIndex = '-1'
if (shouldMute) { if (shouldMute) {
video.muted = true video.muted = true
@ -117,7 +144,51 @@ function enforceVideoPlaybackAttributes(video, shouldMute) {
} }
} }
function installInlinePlaybackGuards(video) {
if (!video) {
return () => {}
}
const keepInlinePlayback = () => {
enforceVideoPlaybackAttributes(video, props.mute)
if (typeof video.webkitSetPresentationMode === 'function') {
try {
video.webkitSetPresentationMode('inline')
} catch (error) {
console.warn('Failed to force inline presentation mode:', error)
}
}
}
keepInlinePlayback()
const guardEvents = [
'loadedmetadata',
'canplay',
'play',
'playing',
'webkitbeginfullscreen',
'webkitpresentationmodechanged',
]
guardEvents.forEach((eventName) => {
video.addEventListener(eventName, keepInlinePlayback)
})
return () => {
guardEvents.forEach((eventName) => {
video.removeEventListener(eventName, keepInlinePlayback)
})
}
}
function destroyPlayer() { function destroyPlayer() {
if (cleanupPlayerVideoGuards) {
cleanupPlayerVideoGuards()
cleanupPlayerVideoGuards = null
}
if (player) { if (player) {
player.destroy() player.destroy()
player = null player = null
@ -199,12 +270,7 @@ async function startPlay() {
}) })
enforceVideoPlaybackAttributes(player?.video, props.mute) enforceVideoPlaybackAttributes(player?.video, props.mute)
player?.video?.addEventListener('loadedmetadata', () => { cleanupPlayerVideoGuards = installInlinePlaybackGuards(player?.video)
enforceVideoPlaybackAttributes(player?.video, props.mute)
})
player?.video?.addEventListener('canplay', () => {
enforceVideoPlaybackAttributes(player?.video, props.mute)
})
player.on('ended', handleEnded) player.on('ended', handleEnded)
player.on('playing', () => { player.on('playing', () => {

View File

@ -1,21 +1,21 @@
<template> <template>
<div class="fullPage"> <div class="fullPage">
<!-- 棰勫姞杞界姸鎬?--> <!-- 预加载状态 -->
<div v-if="isLoading" class="loading-container"> <div v-if="isLoading" class="loading-container">
<!-- 页面背景 --> <!-- 页面背景 -->
<BackgroundLayer :useCanvas="true" :backgroundImages="[imageUrl('bg')]" /> <BackgroundLayer :useCanvas="true" :backgroundImages="[imageUrl('bg')]" />
<!-- 椤甸潰鑳屾櫙 --> <!-- 页面背景 -->
<!-- 鍔犺浇鍔ㄧ敾 --> <!-- 加载动画 -->
<LoadingSpinner /> <LoadingSpinner />
<p>{{ t('loading') }}...</p> <p>{{ t('loading') }}...</p>
</div> </div>
<!-- 涓昏鍐呭 --> <!-- 主要内容 -->
<div class="active-game-shell scroll"> <div class="active-game-shell scroll">
<div class="active-game-stage"> <div class="active-game-stage">
<!-- 澶撮儴鍔ㄧ敾锛堝甫澹伴煶锛?--> <!-- 头部背景图 -->
<VapBg class="vap-background-layer" /> <img :src="BACKGROUND_OVERLAY_IMAGE_URL" alt="" class="vap-background-layer" />
<div class="page-background-layer"> <div class="page-background-layer">
<BackgroundLayer <BackgroundLayer
:useCanvas="true" :useCanvas="true"
@ -23,11 +23,11 @@
:layerStyle="{ position: 'absolute', top: 0, left: 0, minHeight: 0, zIndex: 0 }" :layerStyle="{ position: 'absolute', top: 0, left: 0, minHeight: 0, zIndex: 0 }"
/> />
</div> </div>
<!-- 椤甸潰鑳屾櫙 --> <!-- 页面背景 -->
<!-- 椤甸潰鍐呭 --> <!-- 页面内容 -->
<div class="page-content"> <div class="page-content">
<!-- 濂栧姳 --> <!-- 累计奖励 -->
<itemCenter <itemCenter
style="padding: 0 3vw" style="padding: 0 3vw"
:imgUrl="imageUrl('totalRewardBg')" :imgUrl="imageUrl('totalRewardBg')"
@ -76,7 +76,7 @@
/> />
</itemCenter> </itemCenter>
<!-- 绛夌骇浠诲姟 --> <!-- 等级任务 -->
<itemCenter <itemCenter
style="" style=""
:imgUrl="imageUrl('lvTaskBg')" :imgUrl="imageUrl('lvTaskBg')"
@ -87,7 +87,7 @@ justify-content: flex-start;
gap: 8vw; gap: 8vw;
`" `"
> >
<!-- 鏍囬 --> <!-- 标题 -->
<div style="display: flex; justify-content: center; align-items: center; height: 11vw"> <div style="display: flex; justify-content: center; align-items: center; height: 11vw">
<img <img
v-smart-img v-smart-img
@ -97,9 +97,9 @@ justify-content: flex-start;
/> />
</div> </div>
<!-- 鍐呭 --> <!-- 内容 -->
<div style="width: 100%; display: flex; flex-direction: column; position: relative"> <div style="width: 100%; display: flex; flex-direction: column; position: relative">
<!-- 瑙勫垯寮圭獥灞曠ず鎸夐挳 --> <!-- 规则弹窗展示按钮 -->
<img <img
v-smart-img v-smart-img
src="/src/assets/icon/Azizi/helpWhite.png" src="/src/assets/icon/Azizi/helpWhite.png"
@ -108,9 +108,9 @@ justify-content: flex-start;
@click="openPopup('rule')" @click="openPopup('rule')"
/> />
<!-- 伅灞曠ず --> <!-- 信息展示 -->
<div style="height: 45vw; display: flex; flex-direction: column; gap: 5vw"> <div style="height: 45vw; display: flex; flex-direction: column; gap: 5vw">
<!-- 褰撳墠鐘舵?--> <!-- 当前状态 -->
<div style="display: flex; align-items: center"> <div style="display: flex; align-items: center">
<div <div
style=" style="
@ -137,7 +137,7 @@ justify-content: flex-start;
}} }}
</div> </div>
</div> </div>
<!-- 灞曠ず闅愯棌鎸夐挳 --> <!-- 展示隐藏按钮 -->
<img <img
v-smart-img v-smart-img
:src="imageUrl(amountVisible ? 'amountShowBt' : 'amountHiddenBt')" :src="imageUrl(amountVisible ? 'amountShowBt' : 'amountHiddenBt')"
@ -147,9 +147,9 @@ justify-content: flex-start;
/> />
</div> </div>
<!-- 浠诲姟璇 --> <!-- 任务详情 -->
<div style="display: flex; flex-direction: column; gap: 2vw"> <div style="display: flex; flex-direction: column; gap: 2vw">
<!-- 浠诲姟涓 --> <!-- 任务一 -->
<div :style="{ color: task1Completed ? '#1AFF00' : '#fff' }"> <div :style="{ color: task1Completed ? '#1AFF00' : '#fff' }">
{{ {{
t('active_game_task1_detail', { t('active_game_task1_detail', {
@ -165,7 +165,7 @@ justify-content: flex-start;
style="display: inline-block; width: 1em; object-fit: contain" style="display: inline-block; width: 1em; object-fit: contain"
/> />
</div> </div>
<!-- 浠诲姟浜?--> <!-- 任务二 -->
<div :style="{ color: task2Completed ? '#1AFF00' : '#fff' }"> <div :style="{ color: task2Completed ? '#1AFF00' : '#fff' }">
{{ {{
t('active_game_task2_detail', { t('active_game_task2_detail', {
@ -183,7 +183,7 @@ justify-content: flex-start;
</div> </div>
</div> </div>
<!-- 瀹濈灞曠ず --> <!-- 宝箱展示 -->
<div style="display: flex; flex-direction: column; align-items: center"> <div style="display: flex; flex-direction: column; align-items: center">
<img <img
v-smart-img v-smart-img
@ -192,12 +192,12 @@ justify-content: flex-start;
style="width: 38vw; display: block; object-fit: contain" style="width: 38vw; display: block; object-fit: contain"
/> />
<!-- 瀹濈绛夌骇 --> <!-- 宝箱等级 -->
<div style="font-size: 1.5em; font-weight: 700"> <div style="font-size: 1.5em; font-weight: 700">
{{ t('active_game_level_label', { level: currentLevel }) }} {{ t('active_game_level_label', { level: currentLevel }) }}
</div> </div>
<!-- 棰嗗彇鎸夐挳 --> <!-- 领取按钮 -->
<img <img
v-smart-img v-smart-img
:src="imageUrl(getImgName(taskRewardButtonImageName))" :src="imageUrl(getImgName(taskRewardButtonImageName))"
@ -210,7 +210,7 @@ justify-content: flex-start;
</div> </div>
</itemCenter> </itemCenter>
<!-- 绛夌骇濂栧姳 --> <!-- 等级奖励 -->
<itemCenter <itemCenter
style="" style=""
:imgUrl="imageUrl('LvRewardInfoBg')" :imgUrl="imageUrl('LvRewardInfoBg')"
@ -219,7 +219,7 @@ justify-content: flex-start;
padding: 8vw 7.5vw 14vw; padding: 8vw 7.5vw 14vw;
gap: 4vw;`" gap: 4vw;`"
> >
<!-- 鏍囬 --> <!-- 标题 -->
<div style="display: flex; justify-content: center; align-items: center; height: 11vw"> <div style="display: flex; justify-content: center; align-items: center; height: 11vw">
<img <img
v-smart-img v-smart-img
@ -229,7 +229,7 @@ justify-content: flex-start;
/> />
</div> </div>
<!-- 鍐呭 --> <!-- 内容 -->
<div <div
style=" style="
flex: 1; flex: 1;
@ -242,7 +242,7 @@ justify-content: flex-start;
position: relative; position: relative;
" "
> >
<!-- 绠辫繘搴 --> <!-- 开箱进度条 -->
<div <div
:class="[ :class="[
'scroll', 'scroll',
@ -251,9 +251,9 @@ justify-content: flex-start;
]" ]"
> >
<div :class="['chest-progress-row', { 'chest-progress-row-rtl': isRtlLayout }]"> <div :class="['chest-progress-row', { 'chest-progress-row-rtl': isRtlLayout }]">
<!-- 杩涘害鏉?--> <!-- 进度条 -->
<div class="chest-progress-track" :style="progressTrackStyle"> <div class="chest-progress-track" :style="progressTrackStyle">
<!-- 杩涘害鏉浘鐗?--> <!-- 进度条图片 -->
<div <div
:class="['chest-progress-fill', { 'chest-progress-fill-rtl': isRtlLayout }]" :class="['chest-progress-fill', { 'chest-progress-fill-rtl': isRtlLayout }]"
:style="progressFillStyle" :style="progressFillStyle"
@ -267,9 +267,9 @@ justify-content: flex-start;
</div> </div>
</div> </div>
<!-- 瀹濈鍒楄 --> <!-- 宝箱列表 -->
<div :class="['chest-progress-list', { 'chest-progress-list-rtl': isRtlLayout }]"> <div :class="['chest-progress-list', { 'chest-progress-list-rtl': isRtlLayout }]">
<!-- 澶村崰浣?--> <!-- 开头占位 -->
<div></div> <div></div>
<div <div
v-for="chest in chestProgressList" v-for="chest in chestProgressList"
@ -277,7 +277,7 @@ justify-content: flex-start;
style="width: 14vw; cursor: pointer" style="width: 14vw; cursor: pointer"
@click="selectRewardChest(chest.level)" @click="selectRewardChest(chest.level)"
> >
<!-- 揪鎴愭潯浠?--> <!-- 未达成条件 -->
<img <img
v-if="!isChestConditionReached(chest.level)" v-if="!isChestConditionReached(chest.level)"
v-smart-img v-smart-img
@ -286,7 +286,7 @@ justify-content: flex-start;
style="display: block; width: 100%; object-fit: contain" style="display: block; width: 100%; object-fit: contain"
/> />
<!-- 杈炬垚鏉 --> <!-- 达成条件 -->
<itemCenter <itemCenter
v-else v-else
style="width: 100%" style="width: 100%"
@ -305,7 +305,7 @@ justify-content: flex-start;
</div> </div>
</div> </div>
<!-- 濂栧姳灞曠ず --> <!-- 奖励展示 -->
<div class="reward-showcase"> <div class="reward-showcase">
<div v-if="mainReward" class="reward-showcase-main"> <div v-if="mainReward" class="reward-showcase-main">
<div <div
@ -436,9 +436,9 @@ justify-content: flex-start;
</itemCenter> </itemCenter>
</div> </div>
<!-- 寮圭獥閬僵灞?--> <!-- 弹窗遮罩层 -->
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup"> <maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
<!-- 灞呬腑寮圭獥 --> <!-- 居中弹窗 -->
<div <div
style=" style="
width: 100vw; width: 100vw;
@ -449,7 +449,7 @@ justify-content: flex-start;
" "
@click="closedPopup" @click="closedPopup"
> >
<!-- 瑙勫垯寮圭獥 --> <!-- 规则弹窗 -->
<itemCenter <itemCenter
style="" style=""
:imgUrl="imageUrl('ruleInfoBg')" :imgUrl="imageUrl('ruleInfoBg')"
@ -458,7 +458,7 @@ justify-content: flex-start;
padding: 8vw 7.5vw 5vw; padding: 8vw 7.5vw 5vw;
gap: 4vw;`" gap: 4vw;`"
> >
<!-- 鏍囬 --> <!-- 标题 -->
<div <div
style="display: flex; justify-content: center; align-items: center; height: 11vw" style="display: flex; justify-content: center; align-items: center; height: 11vw"
> >
@ -528,7 +528,6 @@ import BackgroundLayer from '@/components/BackgroundLayer.vue'
import itemCenter from '@/components/itemCenter.vue' import itemCenter from '@/components/itemCenter.vue'
import maskLayer from '@/components/MaskLayer.vue' import maskLayer from '@/components/MaskLayer.vue'
import VapMp4Player from '@/components/VapMp4Player.vue' import VapMp4Player from '@/components/VapMp4Player.vue'
import VapBg from './vapBg.vue'
const isLoading = ref(true) const isLoading = ref(true)
@ -545,6 +544,8 @@ const isRtlLayout = computed(() => {
locale.value && setDocumentDirection(locale.value) locale.value && setDocumentDirection(locale.value)
const imageUrl = (filename) => getPngUrl('Activities/DailyRecharge/', filename) const imageUrl = (filename) => getPngUrl('Activities/DailyRecharge/', filename)
const BACKGROUND_OVERLAY_IMAGE_URL =
'http://tkm-likei.oss-accelerate.aliyuncs.com/other/manager-a46bf46c-5aaa-481f-9b7b-08263f68b45e.png'
const CHEST_PROGRESS_GAP_VW = 2 const CHEST_PROGRESS_GAP_VW = 2
const CHEST_PROGRESS_ITEM_WIDTH_VW = 14 const CHEST_PROGRESS_ITEM_WIDTH_VW = 14
const CHEST_PROGRESS_FIRST_CENTER_VW = CHEST_PROGRESS_GAP_VW + CHEST_PROGRESS_ITEM_WIDTH_VW / 2 const CHEST_PROGRESS_FIRST_CENTER_VW = CHEST_PROGRESS_GAP_VW + CHEST_PROGRESS_ITEM_WIDTH_VW / 2

View File

@ -1,17 +0,0 @@
<template>
<img :src="BACKGROUND_IMAGE_URL" alt="" class="vap-image" />
</template>
<script setup>
const BACKGROUND_IMAGE_URL =
'http://tkm-likei.oss-accelerate.aliyuncs.com/other/manager-a46bf46c-5aaa-481f-9b7b-08263f68b45e.png'
</script>
<style lang="scss" scoped>
.vap-image {
display: block;
width: 100vw;
height: auto;
pointer-events: none;
}
</style>