From 43ff37bed954d0d3aaad4f9f285a6aa31a95dfd6 Mon Sep 17 00:00:00 2001 From: AAA <35992542+TNTcraftHIM@users.noreply.github.com> Date: Tue, 25 Aug 2026 17:07:53 +0800 Subject: [PATCH] fix(viewer): align lifecycle and native controls (#260) --- src/client/media/decoded-frame-stall.ts | 4 + src/client/media/viewer-presentation.ts | 10 +- src/client/media/viewer-volume.ts | 63 ----- src/client/pages/HostPage.tsx | 32 +-- src/client/pages/ViewerPage.tsx | 329 +++++++++--------------- src/client/styles.css | 91 ++----- src/client/webrtc/viewer-peer.ts | 6 +- tests/decoded-frame-stall.test.ts | 21 ++ tests/viewer-peer-generation.test.ts | 7 + tests/viewer-presentation.test.ts | 42 ++- tests/viewer-volume.test.ts | 65 ----- 11 files changed, 219 insertions(+), 451 deletions(-) delete mode 100644 src/client/media/viewer-volume.ts delete mode 100644 tests/viewer-volume.test.ts diff --git a/src/client/media/decoded-frame-stall.ts b/src/client/media/decoded-frame-stall.ts index 64c701b..332eb09 100644 --- a/src/client/media/decoded-frame-stall.ts +++ b/src/client/media/decoded-frame-stall.ts @@ -9,6 +9,10 @@ export class DecodedFrameStallDetector { setPaused(paused: boolean, nowMs = Date.now()): void { if (this.paused === paused) return; this.paused = paused; + this.rebaseline(nowMs); + } + + rebaseline(nowMs = Date.now()): void { this.lastDecodedAt = nowMs; this.reported = false; } diff --git a/src/client/media/viewer-presentation.ts b/src/client/media/viewer-presentation.ts index e12d8b7..86fa2f9 100644 --- a/src/client/media/viewer-presentation.ts +++ b/src/client/media/viewer-presentation.ts @@ -134,7 +134,6 @@ export interface ViewerPresentation { message: string; notice: string | null; overlay: "none" | "status" | "blocking"; - showPlay: boolean; retryAvailable: boolean; hasCurrentFrame: boolean; hasRetainedFrame: boolean; @@ -310,6 +309,7 @@ export function reduceViewerPresentation( } return { ...state, + connection: "connected", media: { ...state.media, framePresented: true }, retainedFrame: false, failure: @@ -486,10 +486,7 @@ export function deriveViewerPresentation( state.autoplayBlockedGeneration === state.media.generation && state.connection === "connected" ) { - return { - ...presentation("needs-play", "需要点击播放", "blocking", state), - showPlay: true, - }; + return presentation("needs-play", "点击播放", "status", state); } if (currentFrame) { @@ -505,7 +502,7 @@ export function deriveViewerPresentation( state, ), notice: state.host === "offline" - ? "分享者连接已中断,画面仍然可用" + ? "分享者连接已中断,画面可能冻结" : signalRecovering ? "服务器连接正在恢复,画面仍在播放" : mediaRecovering @@ -666,7 +663,6 @@ function presentation( message, notice: null, overlay, - showPlay: false, retryAvailable: state.access === "ready" && state.host !== "paused" && state.retryAvailable, hasCurrentFrame: hasCurrentFrame(state), diff --git a/src/client/media/viewer-volume.ts b/src/client/media/viewer-volume.ts deleted file mode 100644 index be54c4b..0000000 --- a/src/client/media/viewer-volume.ts +++ /dev/null @@ -1,63 +0,0 @@ -export interface ViewerVolumeState { - volumePercent: number; - muted: boolean; - lastNonZeroVolumePercent: number; -} - -interface ViewerVolumeTarget { - volume: number; - muted: boolean; -} - -export const DEFAULT_VIEWER_VOLUME_STATE: ViewerVolumeState = { - volumePercent: 100, - muted: false, - lastNonZeroVolumePercent: 100, -}; - -function normalizeVolumePercent(value: number): number { - if (!Number.isFinite(value)) { - return 0; - } - return Math.min(100, Math.max(0, Math.round(value))); -} - -export function setViewerVolume( - current: ViewerVolumeState, - requestedPercent: number, -): ViewerVolumeState { - const volumePercent = normalizeVolumePercent(requestedPercent); - if (volumePercent === 0) { - return { ...current, volumePercent: 0, muted: true }; - } - return { - volumePercent, - muted: false, - lastNonZeroVolumePercent: volumePercent, - }; -} - -export function toggleViewerMuted( - current: ViewerVolumeState, -): ViewerVolumeState { - if (!current.muted) { - return { ...current, muted: true }; - } - const volumePercent = - current.volumePercent > 0 - ? current.volumePercent - : Math.max(1, current.lastNonZeroVolumePercent); - return { - volumePercent, - muted: false, - lastNonZeroVolumePercent: volumePercent, - }; -} - -export function applyViewerVolume( - target: ViewerVolumeTarget, - state: ViewerVolumeState, -): void { - target.volume = state.volumePercent / 100; - target.muted = state.muted; -} diff --git a/src/client/pages/HostPage.tsx b/src/client/pages/HostPage.tsx index 247538b..be7a4f1 100644 --- a/src/client/pages/HostPage.tsx +++ b/src/client/pages/HostPage.tsx @@ -7,7 +7,6 @@ import { KeyRound, Link2Off, LockKeyhole, - Maximize2, MonitorUp, Network, Pause, @@ -884,24 +883,6 @@ export function HostPage({ onAuthorizationRequired }: HostPageProps = {}) { ); } - async function enterPreviewFullscreen(): Promise { - const video = videoRef.current as - | (HTMLVideoElement & { webkitEnterFullscreen?: () => void }) - | null; - if (!video) { - return; - } - try { - if (video.requestFullscreen) { - await video.requestFullscreen(); - } else { - video.webkitEnterFullscreen?.(); - } - } catch { - setNotice("当前浏览器无法放大本地预览"); - } - } - function removePeer(peerId: string): void { clearViewerQualityEvidence(peerId); const peer = peersRef.current.get(peerId); @@ -2102,18 +2083,7 @@ export function HostPage({ onAuthorizationRequired }: HostPageProps = {}) { aria-label="分享或加入房间" > {stream ? ( - <> -