diff --git a/CodeGenerator/Lib/CloudBuilder.Core.dll b/CodeGenerator/Lib/CloudBuilder.Core.dll index b976e5d..6212e7a 100644 Binary files a/CodeGenerator/Lib/CloudBuilder.Core.dll and b/CodeGenerator/Lib/CloudBuilder.Core.dll differ diff --git a/WebVue/src/components/basic/VideoPlayer.vue b/WebVue/src/components/basic/VideoPlayer.vue index e43493f..a70cbb3 100644 --- a/WebVue/src/components/basic/VideoPlayer.vue +++ b/WebVue/src/components/basic/VideoPlayer.vue @@ -8,12 +8,18 @@
+ -
@@ -80,6 +86,8 @@
+ +
@@ -139,6 +147,7 @@ @@ -594,6 +746,23 @@ onBeforeUnmount(() => { transform: translate(-50%, -50%) scale(0.94); } +/* 缓冲提示:让「正在加载」和「卡死」区分开。 + 不加 pointer-events: none 会挡住画面区的点击(播放/暂停) */ +.vp-loading { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 2; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + font-size: 13px; + color: rgba(255, 255, 255, 0.9); + pointer-events: none; +} + /* ---------- 画面区 ---------- */ .vp-stage { position: relative; @@ -792,6 +961,16 @@ onBeforeUnmount(() => { background: rgba(255, 255, 255, 0.32); } +/* 已缓冲区间:比已播进度浅,能看出加载走到哪了 */ +.vp-bar-buffer { + position: absolute; + left: 0; + top: 0; + bottom: 0; + border-radius: 2px; + background: rgba(255, 255, 255, 0.5); +} + .vp-bar-fill { position: absolute; left: 0; diff --git a/WebVue/src/components/basic/VinylAudioPlayer.vue b/WebVue/src/components/basic/VinylAudioPlayer.vue index d6aacf7..c53223b 100644 --- a/WebVue/src/components/basic/VinylAudioPlayer.vue +++ b/WebVue/src/components/basic/VinylAudioPlayer.vue @@ -386,10 +386,25 @@ const seekTo = (t: number) => { /** 两个 audio 都会触发事件,只有当前在播的那个才需要响应 */ const isActive = (el: EventTarget | null) => !!el && el === activeAudio.value; +/** + * 直接从当前在播的 audio 读时长。 + * + * 不能只依赖 loadedmetadata 事件:切歌若是换到了「后台已经预载好的那一个」, + * 它的 loadedmetadata 早在预载阶段就触发过了(那时它还不是主角,被 isActive 挡掉), + * 成为主角后不会再触发第二次,duration 就永远停在 0—— + * 总时长显示 00:00、进度条不动、也没法拖动。 + * 这条路径只在「会交换角色的情况」下出现,比如手机连 WiFi 时预载开启。 + */ +const syncDuration = () => { + const el = activeAudio.value; + if (el && isFinite(el.duration) && el.duration > 0) duration.value = el.duration; +}; + const onTimeUpdate = (e: Event) => { if (!isActive(e.target) || dragRatio.value !== null) return; const a = e.target as HTMLAudioElement; currentTime.value = a.currentTime; + syncDuration(); syncLyric(); }; const onLoadedMetadata = (e: Event) => { @@ -636,6 +651,9 @@ watch(current, async (t) => { lyricIndex.value = -1; if (!t) return; await nextTick(); + // 换到预载好的那一个时不会再有 loadedmetadata,这里主动读一次, + // 否则总时长停在 00:00、进度条也不走 + syncDuration(); loadLrc(t.lrc); if (resumeAfterSwitch) { resumeAfterSwitch = false; @@ -664,6 +682,8 @@ watch([volume, muted, rate], () => applyAudioSettings()); watch(activeSlot, async () => { await nextTick(); applyAudioSettings(); + // 交换角色后补读时长:这个元素的 loadedmetadata 可能已经在预载时被丢掉了 + syncDuration(); }); let timerId = 0; @@ -920,7 +940,7 @@ onBeforeUnmount(() => { 比自然位置高一点,又不至于像之前 -160px 那样贴着唱片下沿。 用 relative + top 只挪视觉位置,不影响下面标题、按钮的排布 */ position: relative; - top: -60px; + top: -20px; height: 102px; margin-top: 30px; overflow: hidden;