Ask iOS for the playback audio session on a page with a video
CI / Typecheck, test, build (pull_request) Successful in 12s

A TikTok post played perfectly and said nothing on an iPhone. The file was
not the problem: every rendition TikTok offers for it carries a full AAC
track, and the page as served measures loud audio in WebKit and Chromium
alike, straight through the /m/ proxy.

iOS is the problem. A video playing inline gets the "ambient" audio session,
which the Ring/Silent switch mutes; only going fullscreen gets the sound
back. Claiming "playback" says what is true of this page -- the sound is the
point, not decoration -- and the switch stops applying.

Declared only where there is a video, so an ordinary text post never claims
it, and the session activates when something plays rather than on load, so
it interrupts nothing on a page nobody presses play on.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B3RUMiXa6eAW3okw9nB9NF
This commit is contained in:
2026-09-02 21:06:12 -03:00
co-authored by Claude Opus 5
parent d9fa2f5768
commit 4c78ce8a27
3 changed files with 23 additions and 0 deletions
+14
View File
@@ -155,6 +155,19 @@ function setupMedia(media) {
}
}
// On iOS a video playing inline gets the "ambient" audio session, which the
// Ring/Silent switch mutes -- so the post plays perfectly and says nothing,
// and the only way to hear it is to go fullscreen. Claiming "playback" says
// what is true here: the sound is the point, not decoration. The session is
// not activated until something actually plays, so declaring it up front
// interrupts nothing; it is declared only on a page that has a video so an
// ordinary text post never claims it at all.
function setupAudioSession() {
if (!('audioSession' in navigator)) return;
if (!document.querySelector('video')) return;
navigator.audioSession.type = 'playback';
}
// Every comment is a <details open>, so folding one already works with this
// file missing. All this adds is doing the whole page at once, which is why
// the button ships hidden and is only revealed here.
@@ -189,6 +202,7 @@ setupCopy();
applyBrowser(storedBrowser());
setupBrowserPicker();
for (const media of document.querySelectorAll('.media')) setupMedia(media);
setupAudioSession();
setupComments();
// --- Verification puzzles -------------------------------------------------