From d71a37f84ae984ff191ab9fc69ab389344550bd3 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Thu, 4 Jun 2026 13:01:37 -0400 Subject: [PATCH] =?UTF-8?q?zebra-spaces:=20Media=20Session=20API=20on=20li?= =?UTF-8?q?stener=20attach=20=E2=80=94=20OS=20keeps=20backgrounded=20tab?= =?UTF-8?q?=20alive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pairs with the signal-server listener alive-ttl exemption (c16f6c1). When the AudioContext listener path binds a remote stream, declare a MediaSession to the OS: metadata + playbackState=playing + no-op play/pause action handlers. On Android Firefox + iOS Safari this: - keeps the tab in media-priority mode (less aggressive JS throttling, AudioContext stays running) - surfaces lock-screen / notification-area transport controls - signals to the OS scheduler not to freeze this tab Together with the server exemption, mobile listeners can tab away to email / browser / chat and zebra-spaces audio keeps playing. --- web/zebra-spaces.html | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index dee4665..8e7b5dd 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -1680,6 +1680,33 @@ function attachListenerStreamViaAudioContext(uuid, stream){ gain.connect(audioCtx.destination); listenerAudioNodes.set(uuid, { src, gain, stream }); logLine('', 'listener audio via AudioContext '+uuid.slice(0,4)+' ctxState='+audioCtx.state); + /* Media Session API — tell the OS this tab is playing media. On + * Android Firefox + iOS Safari, this: + * - keeps the tab in media-priority mode (less aggressive JS + * throttling, AudioContext stays running) + * - surfaces lock-screen / notification-area transport controls + * - signals "do not freeze this tab" to the OS scheduler + * Combined with the server-side skip of alive-ttl for listeners, + * a mobile user can tab away to email / browser / chat and the + * zebra-spaces audio keeps playing in the background. Fox + * 2026-06-04. */ + try { + if (navigator.mediaSession){ + const title = 'zebra-spaces'; + const artist = roomID ? ('room ' + roomID.slice(0,8)) : 'live'; + navigator.mediaSession.metadata = new MediaMetadata({ title, artist }); + navigator.mediaSession.playbackState = 'playing'; + /* Provide a no-op pause handler so the OS knows we accept the + * 'pause' action — without one, some platforms refuse to keep + * the session active. */ + navigator.mediaSession.setActionHandler('pause', () => { + navigator.mediaSession.playbackState = 'paused'; + }); + navigator.mediaSession.setActionHandler('play', () => { + navigator.mediaSession.playbackState = 'playing'; + }); + } + } catch(_){} return true; } function detachListenerStream(uuid){ @@ -5475,8 +5502,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');