From ab96d5821fe9cda4f31b3389e1c6fb3e6010682d Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 26 Aug 2026 11:35:31 +0200 Subject: feat(video): hold a Screen Wake Lock while a video is open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Purely additive and isolated from the streaming/transport code: touches no DataChannel, SourceBuffer, or playback state, so it cannot itself cause a stall or regress existing playback (PC/Electron included) — feature-detected and try/caught, a no-op wherever unsupported or refused. Sidesteps the commonest real-world trigger for the WebRTC-drop recovery (the phone auto-locking on its own idle timer while someone just watches). Does nothing for a deliberate power-button lock or backgrounding the tab — released automatically in both cases per spec — so the reconnect path is still what handles those. --- .../src/meshbay_hub/static/video-player.js | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'packages/meshbay-hub/src') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/video-player.js b/packages/meshbay-hub/src/meshbay_hub/static/video-player.js index 78760d8..0c0c6c7 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/video-player.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/video-player.js @@ -164,6 +164,10 @@ function VideoPlayer({ entry, transportRef, gekRef, onClose, onDownload }) { const castDeviceRef = useRef(null); const castRestartGenRef = useRef(0); const landingPlayheadRef = useRef(false); + // The current Screen Wake Lock sentinel, if the browser granted one — see + // the effect below. Null on any platform/context that does not support it, + // which playback has never depended on. + const wakeLockRef = useRef(null); /** * The buffered range the playhead is actually in, or null. @@ -780,11 +784,49 @@ function VideoPlayer({ entry, transportRef, gekRef, onClose, onDownload }) { if (t && t.connected) t.stopStream(); }; const onPageHide = () => leave('pagehide'); + + // Screen Wake Lock: keeps the display on while this page is open and + // visible, purely so the phone stops auto-locking mid-film on its own + // idle timer — the commonest real-world trigger for the WebRTC-drop + // recovery above, and the one case it can sidestep entirely rather than + // recover from. Unrelated to streaming/transport in every direction: + // requesting, holding, or losing this lock touches no DataChannel, no + // SourceBuffer, no playback state, so it cannot itself cause a stall or + // a regression in the existing pipeline. It also does nothing at all on + // a phone the user locks with the power button, or once the tab is + // backgrounded (the spec releases it automatically) — the reconnect path + // above is still the one that has to handle those. + const releaseWakeLock = () => { + const wl = wakeLockRef.current; + wakeLockRef.current = null; + if (wl) { try { wl.release(); } catch { /* already released */ } } + }; + const acquireWakeLock = async () => { + if (!('wakeLock' in navigator)) return; + try { + const wl = await navigator.wakeLock.request('screen'); + // The effect may have torn down while this was in flight. + if (cancelled) { try { wl.release(); } catch { /* ignore */ } return; } + wakeLockRef.current = wl; + wl.addEventListener('release', () => { wakeLockRef.current = null; }); + } catch (e) { + // Battery saver, no permission, an insecure context — playback has + // never depended on this, so there is nothing to fall back to. + console.warn('[MeshBay] Wake lock request failed:', e.message); + } + }; + acquireWakeLock(); + // NOT wired to stopStream. Android fires visibilitychange when a video goes // fullscreen, so cutting the stream here killed the film the moment it was // watched properly. Logged only, until that is confirmed or ruled out. const onVisibility = () => { console.log('[MeshBay] visibilitychange:', document.visibilityState); + // The lock is released automatically the moment the page goes hidden + // (spec behaviour, not something to undo) — re-requesting it here is + // what makes it hold again once the film is actually back on screen, + // including the fullscreen transition this handler already exists for. + if (document.visibilityState === 'visible') acquireWakeLock(); }; window.addEventListener('pagehide', onPageHide); document.addEventListener('visibilitychange', onVisibility); @@ -838,6 +880,7 @@ function VideoPlayer({ entry, transportRef, gekRef, onClose, onDownload }) { clearInterval(pumpTimer); clearInterval(diagTimer); clearTimeout(seekTimerRef.current); + releaseWakeLock(); // Closing the player is the commonest way to stop watching, so this is // the write that matters most. if (videoRef.current) { -- cgit v1.2.3