Files
ytplayer/docs/fullscreen-background-audio.md
2026-10-04 02:29:10 +08:00

5.3 KiB
Raw Permalink Blame History

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.