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');