Keep iPhone audio playing through background handoffs
This commit is contained in:
88
docs/fullscreen-background-audio.md
Normal file
88
docs/fullscreen-background-audio.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Fullscreen orientation and iPhone audio continuity
|
||||
|
||||
## Task A — done
|
||||
|
||||
Settings → Listening → Video & fullscreen offers Auto (follow device), Landscape,
|
||||
and Portrait. The choice persists on this device and can be cycled from the
|
||||
fullscreen controls. Supported browsers request fullscreen and then lock the
|
||||
selected orientation; Auto leaves orientation unlocked. Exit releases our lock.
|
||||
|
||||
When orientation locking or element fullscreen is unavailable, the player uses a
|
||||
viewport overlay. A portrait viewport with Landscape selected rotates the existing
|
||||
stage and its controls 90 degrees; Portrait in a landscape viewport rotates it
|
||||
in the opposite direction. Safe-area padding follows the rotated axes. Touch
|
||||
seeking, double-tap seeking, volume/brightness swipes and pinch movement use
|
||||
inverse coordinates. The video stays in its existing DOM position to avoid
|
||||
interrupting WebKit playback. Controls and browser fullscreen exit restore the
|
||||
inline surface, including when a native orientation lock was rejected.
|
||||
|
||||
Safari's custom overlay fills the available dynamic viewport; it cannot force
|
||||
Safari to hide browser chrome or change the phone's physical orientation. An
|
||||
installed PWA has no browser toolbar occupying that viewport.
|
||||
|
||||
Files: `frontend/fullscreen-orientation.js`, `.css`, `.test.js`, small hooks in
|
||||
`app.js`, `index.html`, `sw.js`, `tests/fullscreen-orientation.spec.js` and
|
||||
`playwright.fullscreen.config.js`.
|
||||
|
||||
## Task B — done; physical iPhone verification remains
|
||||
|
||||
The previous progressive handoff loaded the audio source and sought it only when
|
||||
the page was hidden, then paused the video before transferring master ownership.
|
||||
That required a cold audio decoder start and allowed pause-handler re-entry.
|
||||
Foreground recovery also sought the video, whose shared seeking handler then
|
||||
sought the still-playing audio. Repeated play/playing/focus/watchdog handlers
|
||||
could independently issue the same audio play/seek requests.
|
||||
|
||||
On iPhone, progressive playback now uses the existing dual playback path from the
|
||||
start: a muted video and an audible audio element. Saved files use the same local
|
||||
source; streams prefer their audio-only URL, falling back to the muxed source.
|
||||
EQ-render playback already used this path and remains unchanged. Locking promotes
|
||||
the already-playing audio to master without loading, seeking or restarting it.
|
||||
Unlocking aligns only the silent video; its internal seek does not propagate to
|
||||
the audible audio. Pending play requests are shared, small drift does not cause
|
||||
seeks, and the watchdog never seeks an element to itself. Hidden focus events do
|
||||
not restore the video prematurely. The audio clock still owns background progress
|
||||
and ended events, preserving auto-advance and lock-screen playback intent.
|
||||
|
||||
This uses an audio decoder alongside video while visible. Audio-only playback
|
||||
and tracks that start while already backgrounded retain their existing paths.
|
||||
Browsers other than iPhone retain their foreground progressive path. Their cold
|
||||
handoff fallback now sets master ownership before media events and waits for audio
|
||||
to play before pausing video; returning keeps that audio playing while video resumes.
|
||||
|
||||
Files: `frontend/audio-continuity.js`, `.test.js`, player/event hooks in `app.js`,
|
||||
`index.html`, `sw.js`, `tests/audio-continuity.spec.js`, and
|
||||
`playwright.audio-continuity.config.js`.
|
||||
|
||||
## Validation and hardware review
|
||||
|
||||
- 132 frontend unit tests pass, including orientation mapping, source selection,
|
||||
small-drift policy, concurrent play deduplication, autoplay rejection retry, and
|
||||
stale pending requests after pause/source reset.
|
||||
- `node --check frontend/app.js` and `bun build --no-bundle server/server.js` pass.
|
||||
- Eight fullscreen browser cases pass in Chromium/WebKit at 390 × 844, covering
|
||||
Classic and Glass Stage overlays, visible controls, quick toggle, exit/restore,
|
||||
lock/unlock calls and browser exit after lock rejection.
|
||||
- Four audio browser cases use real local PCM media in Chromium/WebKit, with an
|
||||
iPhone user agent, covering saved and streamed progressive attachments. Repeated
|
||||
visibility/pagehide/focus/pageshow events keep the same advancing audio element
|
||||
with zero audio loads, pauses, seeks or additional play calls.
|
||||
- All 155 server tests pass with `YTDLP_PATH=/tmp/ytplayer-test-yt-dlp` (the existing
|
||||
local test ZIPAPP). The initial default run failed because the system Python
|
||||
environment lacked `yt_dlp`; no server changes were necessary.
|
||||
|
||||
Review on an actual iPhone in Safari and installed PWA: saved MP4, streamed songs,
|
||||
EQ renders, multiple lock/unlock and app switches, auto-advance while locked,
|
||||
Media Session controls, seeking and PiP. Test Landscape/Portrait with safe areas,
|
||||
rotation and touch swipes in both themes. On Android/PWA, verify an actual native
|
||||
orientation lock and release; browser tests verify API calls with a controlled
|
||||
orientation implementation.
|
||||
|
||||
iOS may suspend JavaScript, pause media or interrupt the audio session during lock,
|
||||
app switching, route changes or incoming calls. A web app cannot guarantee a zero
|
||||
OS-level gap. These tests demonstrate removal of application-induced source
|
||||
reloads and audio seeks; desktop WebKit cannot simulate physical iOS suspension.
|
||||
Tracks first started in the background still reattach their picture on return;
|
||||
that existing transition also needs hardware review.
|
||||
|
||||
No server imports were added, and nothing was pushed or deployed.
|
||||
Reference in New Issue
Block a user