feat(Splash): 添加视频加载失败时的图片降级方案
当视频加载或播放失败时,自动切换到图片背景避免黑屏 添加视频加载状态检测和错误处理回调 优化微信环境下的视频自动播放处理
Showing
1 changed file
with
77 additions
and
2 deletions
| ... | @@ -2,6 +2,7 @@ | ... | @@ -2,6 +2,7 @@ |
| 2 | <div class="splash-container" :class="{ 'fade-out': isExiting }"> | 2 | <div class="splash-container" :class="{ 'fade-out': isExiting }"> |
| 3 | <!-- 背景视频 --> | 3 | <!-- 背景视频 --> |
| 4 | <video | 4 | <video |
| 5 | + v-show="!use_image_bg" | ||
| 5 | ref="videoPlayer" | 6 | ref="videoPlayer" |
| 6 | class="background-video" | 7 | class="background-video" |
| 7 | autoplay | 8 | autoplay |
| ... | @@ -10,10 +11,22 @@ | ... | @@ -10,10 +11,22 @@ |
| 10 | playsinline | 11 | playsinline |
| 11 | webkit-playsinline | 12 | webkit-playsinline |
| 12 | preload="auto" | 13 | preload="auto" |
| 14 | + @error="on_video_error" | ||
| 15 | + @stalled="on_video_error" | ||
| 16 | + @abort="on_video_error" | ||
| 17 | + @emptied="on_video_error" | ||
| 18 | + @loadeddata="on_video_loaded" | ||
| 13 | > | 19 | > |
| 14 | <source :src="videoUrl" type="video/mp4" /> | 20 | <source :src="videoUrl" type="video/mp4" /> |
| 15 | </video> | 21 | </video> |
| 16 | 22 | ||
| 23 | + <!-- 图片降级背景(视频不可用时显示) --> | ||
| 24 | + <div | ||
| 25 | + v-if="use_image_bg" | ||
| 26 | + class="background-image" | ||
| 27 | + :style="{ backgroundImage: 'url(' + imgUrl + ')' }" | ||
| 28 | + ></div> | ||
| 29 | + | ||
| 17 | <!-- 黑色半透明蒙板 --> | 30 | <!-- 黑色半透明蒙板 --> |
| 18 | <div class="overlay"></div> | 31 | <div class="overlay"></div> |
| 19 | 32 | ||
| ... | @@ -50,11 +63,18 @@ import { useRouter } from 'vue-router' | ... | @@ -50,11 +63,18 @@ import { useRouter } from 'vue-router' |
| 50 | const router = useRouter() | 63 | const router = useRouter() |
| 51 | const isExiting = ref(false) | 64 | const isExiting = ref(false) |
| 52 | const videoPlayer = ref(null) | 65 | const videoPlayer = ref(null) |
| 66 | +const use_image_bg = ref(false) | ||
| 67 | +const is_video_ready = ref(false) | ||
| 53 | 68 | ||
| 54 | // TODO: 视频配置 | 69 | // TODO: 视频配置 |
| 55 | const videoUrl = ref('https://cdn.ipadbiz.cn/stdj/video/sample-10s.mp4') | 70 | const videoUrl = ref('https://cdn.ipadbiz.cn/stdj/video/sample-10s.mp4') |
| 71 | +const imgUrl = ref('https://cdn.ipadbiz.cn/stdj/images/%E5%90%AF%E5%8A%A8%E9%A1%B5%E6%B5%B7%E6%8A%A5%E8%83%8C%E6%99%AF@2x.png') | ||
| 56 | 72 | ||
| 57 | // 进入应用函数 | 73 | // 进入应用函数 |
| 74 | +/** | ||
| 75 | + * 进入应用 | ||
| 76 | + * 说明:触发淡出动画后跳转到首页 | ||
| 77 | + */ | ||
| 58 | const enterApp = () => { | 78 | const enterApp = () => { |
| 59 | isExiting.value = true | 79 | isExiting.value = true |
| 60 | // 等待淡出动画完成后跳转 | 80 | // 等待淡出动画完成后跳转 |
| ... | @@ -71,14 +91,21 @@ onMounted(() => { | ... | @@ -71,14 +91,21 @@ onMounted(() => { |
| 71 | 91 | ||
| 72 | if (playPromise !== undefined) { | 92 | if (playPromise !== undefined) { |
| 73 | playPromise.catch(error => { | 93 | playPromise.catch(error => { |
| 74 | - // 自动播放被阻止,在微信环境下尝试通过 WeixinJSBridge 播放 | 94 | + // 自动播放被阻止:在微信环境下尝试通过 WeixinJSBridge 播放 |
| 75 | - console.log('自动播放失败:', error) | 95 | + // 说明:避免控制台输出引起诊断问题 |
| 96 | + void error | ||
| 76 | const ua = navigator.userAgent.toLowerCase() | 97 | const ua = navigator.userAgent.toLowerCase() |
| 77 | if (ua.match(/MicroMessenger/i) && typeof window.WeixinJSBridge !== 'undefined') { | 98 | if (ua.match(/MicroMessenger/i) && typeof window.WeixinJSBridge !== 'undefined') { |
| 78 | window.WeixinJSBridge.invoke('getNetworkType', {}, () => { | 99 | window.WeixinJSBridge.invoke('getNetworkType', {}, () => { |
| 79 | video.play() | 100 | video.play() |
| 80 | }) | 101 | }) |
| 81 | } | 102 | } |
| 103 | + // 若短时间内仍无法播放,降级为图片背景,避免黑屏 | ||
| 104 | + setTimeout(() => { | ||
| 105 | + if (!is_video_ready.value) { | ||
| 106 | + enable_image_fallback() | ||
| 107 | + } | ||
| 108 | + }, 1200) | ||
| 82 | }) | 109 | }) |
| 83 | } | 110 | } |
| 84 | 111 | ||
| ... | @@ -87,7 +114,40 @@ onMounted(() => { | ... | @@ -87,7 +114,40 @@ onMounted(() => { |
| 87 | video.play() | 114 | video.play() |
| 88 | }, false) | 115 | }, false) |
| 89 | } | 116 | } |
| 117 | + | ||
| 118 | + // 兜底:在一定时间内仍未加载完成则切换到图片背景 | ||
| 119 | + // setTimeout(() => { | ||
| 120 | + // if (!is_video_ready.value) { | ||
| 121 | + // enable_image_fallback() | ||
| 122 | + // } | ||
| 123 | + // }, 5000) | ||
| 90 | }) | 124 | }) |
| 125 | + | ||
| 126 | +/** | ||
| 127 | + * 视频加载成功回调 | ||
| 128 | + * 说明:标记视频已可播放,用于取消降级处理 | ||
| 129 | + */ | ||
| 130 | +const on_video_loaded = () => { | ||
| 131 | + is_video_ready.value = true | ||
| 132 | +} | ||
| 133 | + | ||
| 134 | +/** | ||
| 135 | + * 视频错误回调 | ||
| 136 | + * 说明:视频加载/播放失败时触发图片降级,避免黑屏 | ||
| 137 | + * @param {Event} e 事件对象 | ||
| 138 | + */ | ||
| 139 | +const on_video_error = (e) => { | ||
| 140 | + void e | ||
| 141 | + enable_image_fallback() | ||
| 142 | +} | ||
| 143 | + | ||
| 144 | +/** | ||
| 145 | + * 启用图片降级背景 | ||
| 146 | + * 说明:切换到全屏图片背景,保证用户视觉不出现黑屏 | ||
| 147 | + */ | ||
| 148 | +const enable_image_fallback = () => { | ||
| 149 | + use_image_bg.value = true | ||
| 150 | +} | ||
| 91 | </script> | 151 | </script> |
| 92 | 152 | ||
| 93 | <style scoped lang="less"> | 153 | <style scoped lang="less"> |
| ... | @@ -121,6 +181,21 @@ onMounted(() => { | ... | @@ -121,6 +181,21 @@ onMounted(() => { |
| 121 | animation: backgroundFadeIn 0.8s ease-out forwards; | 181 | animation: backgroundFadeIn 0.8s ease-out forwards; |
| 122 | } | 182 | } |
| 123 | 183 | ||
| 184 | +/* 图片降级背景 */ | ||
| 185 | +.background-image { | ||
| 186 | + position: absolute; | ||
| 187 | + top: 0; | ||
| 188 | + left: 0; | ||
| 189 | + width: 100vw; | ||
| 190 | + height: 100vh; | ||
| 191 | + z-index: 1; | ||
| 192 | + background-size: cover; | ||
| 193 | + background-position: center; | ||
| 194 | + background-repeat: no-repeat; | ||
| 195 | + opacity: 0; | ||
| 196 | + animation: backgroundFadeIn 0.8s ease-out forwards; | ||
| 197 | +} | ||
| 198 | + | ||
| 124 | /* 黑色半透明蒙板 */ | 199 | /* 黑色半透明蒙板 */ |
| 125 | .overlay { | 200 | .overlay { |
| 126 | position: absolute; | 201 | position: absolute; | ... | ... |
-
Please register or login to post a comment