diff --git a/lib/viewer-renderer.js b/lib/viewer-renderer.js index 502f7db..1eeae04 100644 --- a/lib/viewer-renderer.js +++ b/lib/viewer-renderer.js @@ -1383,7 +1383,7 @@ ${visitorMessages.BOX_CSS}${showButtons.CSS} ${cfg.debug_mic_measure === 1 ? '' : ''} - + `; if (html.includes('')) { html = html.replace('', injection + ''); diff --git a/public/rf-compat.js b/public/rf-compat.js index a6db80a..665ec3c 100644 --- a/public/rf-compat.js +++ b/public/rf-compat.js @@ -4560,6 +4560,9 @@ // a user gesture on iOS, which would break the resume-on-restart // flow. try { stopAudio(); } catch {} + // Stay awake for a while (a short stop between shows), then let + // the phone sleep. + try { scheduleKeepAwakeStop(); } catch {} // Hide the player content. notPlayingMsg has flex:1 so it takes // the textCol's place. Close (×) stays visible. @@ -4582,6 +4585,7 @@ minBtn.style.display = ''; if (ltBtn) ltBtn.style.display = ''; panel.classList.remove('sp-not-playing'); + try { setKeepAwake(!userPaused); } catch {} // If the user has the panel open when the show resumes, get audio // going. If audioCtx already exists (panel was opened during a // prior playing window), a syncOnce() picks up the new track. @@ -4678,6 +4682,162 @@ let _pendingGestureAudioCtx = null; let gainNode = null; let isMuted = false; + + // ---- Keep the page awake with the screen off (Android) ---- + // Chrome slows or pauses the code of a page you can't see once the tab + // stops playing sound, and the silent gap between songs counts as not + // playing. The code that starts the next song then doesn't run until + // the screen is turned back on. The -80 dBFS keep-alive level below + // (which keeps the speaker stream open) is under the level Chrome counts + // as sound (about -72 dBFS), so while the player is open and not paused + // the same constant level is raised to -60 dBFS: still a steady DC level + // no speaker can reproduce, but enough for Chrome to keep the tab + // running. It drops back to -80 dBFS on Pause, and 10 minutes after the + // show stops, so a phone left on the page can sleep. + // ?keepawake=0 turns this off; ?keepawake=1 turns it on off Android. + const SP_KEEPALIVE_QUIET = 0.0001; // -80 dBFS + const SP_KEEPALIVE_AWAKE = 0.001; // -60 dBFS + const SP_KEEPAWAKE_AFTER_STOP_MS = 10 * 60 * 1000; + let keepAliveSrc = null; + let keepAwakeStopTimer = null; + function spKeepAwakeOn() { + let q = null; + try { q = new URLSearchParams(window.location.search).get('keepawake'); } catch (_) {} + if (q === '0') return false; + if (q === '1') return true; + return /Android/i.test(navigator.userAgent || ''); + } + function setKeepAwake(on) { + if (keepAwakeStopTimer) { clearTimeout(keepAwakeStopTimer); keepAwakeStopTimer = null; } + try { if (on) spAnchorPlay(); else spAnchorPause(); } catch (_) {} + if (!keepAliveSrc || !audioCtx) return; + const level = (on && spKeepAwakeOn()) ? SP_KEEPALIVE_AWAKE : SP_KEEPALIVE_QUIET; + // A short ramp, so the level change can't click on analog outputs. + try { keepAliveSrc.offset.setTargetAtTime(level, audioCtx.currentTime, 0.05); } + catch (_) { try { keepAliveSrc.offset.value = level; } catch (_) {} } + try { if (spVerbose) console.log('[ShowPilot] keep awake:', level === SP_KEEPALIVE_AWAKE ? 'on (-60 dBFS)' : 'off (-80 dBFS)'); } catch (_) {} + } + // Let the phone sleep if the show stays stopped. + function scheduleKeepAwakeStop() { + if (keepAwakeStopTimer) clearTimeout(keepAwakeStopTimer); + keepAwakeStopTimer = setTimeout(() => { + keepAwakeStopTimer = null; + setKeepAwake(false); + }, SP_KEEPAWAKE_AFTER_STOP_MS); + } + + // ---- Media Session: notification and lock-screen controls ---- + // Browsers only show the media notification and lock-screen controls + // for a playing