Merge pull request 'Ask iOS for the playback audio session on a page with a video' (#14) from video-audio-session-on-ios into main
CI / Typecheck, test, build (push) Successful in 46s
Publish / Build and push (push) Successful in 28s
Publish / Move the working version on (push) Successful in 4s

Reviewed-on: #14
This commit was merged in pull request #14.
This commit is contained in:
2026-09-03 00:56:04 +00:00
3 changed files with 23 additions and 0 deletions
+5
View File
@@ -100,6 +100,11 @@ Things worth knowing before editing:
`<video>` with no data has a natural size of 300x150, and WebKit sizes it from that — `<video>` with no data has a natural size of 300x150, and WebKit sizes it from that —
so one with no poster of its own gets an empty SVG of the right shape as a stand-in, so one with no poster of its own gets an empty SVG of the right shape as a stand-in,
without which a portrait video sits in a squat landscape box until you press play. without which a portrait video sits in a squat landscape box until you press play.
- **A page with a video claims the `playback` audio session** (`public/app.js`). iOS
hands inline video the ambient session, which the Ring/Silent switch mutes, so
without this the video plays and says nothing until it goes fullscreen — and the
report that arrives is "this one platform has no sound". The session only activates
once something plays, so declaring it costs nothing on a page nobody presses play on.
- **Never a bare error page.** A failed resolve renders a card carrying the platform, the - **Never a bare error page.** A failed resolve renders a card carrying the platform, the
original URL and the copy button. A broken adapter must still leave the link one tap original URL and the copy button. A broken adapter must still leave the link one tap
away. away.
+4
View File
@@ -171,6 +171,10 @@ The one exception is Bluesky video, which is an HLS playlist — proxying it wou
rewriting the manifest and every segment, so it is linked directly. Safari plays HLS rewriting the manifest and every segment, so it is linked directly. Safari plays HLS
natively; other browsers show a note. natively; other browsers show a note.
A page carrying a video asks iOS for the playback audio session. Without it a video
playing inline is treated as ambience and the Ring/Silent switch mutes it, so the post
plays perfectly and says nothing unless you go fullscreen.
Resolved posts are cached in memory for an hour, so a reload or a back button doesn't Resolved posts are cached in memory for an hour, so a reload or a back button doesn't
drive the browser again. drive the browser again.
+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 // 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 // file missing. All this adds is doing the whole page at once, which is why
// the button ships hidden and is only revealed here. // the button ships hidden and is only revealed here.
@@ -189,6 +202,7 @@ setupCopy();
applyBrowser(storedBrowser()); applyBrowser(storedBrowser());
setupBrowserPicker(); setupBrowserPicker();
for (const media of document.querySelectorAll('.media')) setupMedia(media); for (const media of document.querySelectorAll('.media')) setupMedia(media);
setupAudioSession();
setupComments(); setupComments();
// --- Verification puzzles ------------------------------------------------- // --- Verification puzzles -------------------------------------------------