iOS Background Audio
Overview
Section titled “Overview”iOS Background Audio solves a common pain point for web audio developers: on iOS, flipping the hardware ringer/silent switch mutes all audio played through the browser. This project demonstrates how to use the navigator.audioSession API to override that behaviour, ensuring audio continues to play regardless of the switch position — critical for music apps, games, and any web experience where audio is part of the core product.
The technique boils down to a single API call, but getting it right across iOS versions and browsers requires careful testing. This repo packages the solution as a clean, deployable demo with a live GitHub Pages site for on-device verification.
The Problem
Section titled “The Problem”By default, Safari on iOS treats web audio as “ambient” — meaning the ringer switch silences it, just like a notification sound. For media-focused web apps (music players, DAWs, interactive audio experiences), that’s a deal-breaker. Users expect audio to behave like Spotify or YouTube, not like a ringtone.
The Fix
Section titled “The Fix”The key is the Audio Session API, supported from Safari 16.4+. Setting the session type to "playback" tells the OS to treat the page’s audio as intentional media output:
function backgroundAudioFix(): void { interface AudioSession { type: "ambient" | "playback" | "auto"; } type Nav = Navigator & { audioSession: AudioSession }; function hasAudioSession(nav: Navigator): nav is Nav { return (nav as Nav).audioSession !== undefined; } if (typeof window !== "undefined" && hasAudioSession(window.navigator)) { window.navigator.audioSession.type = "playback"; }}The function includes a runtime feature check, so it’s safe to call on any platform — it simply no-ops where the API isn’t available.
Compatibility Testing
Section titled “Compatibility Testing”The project includes detailed cross-browser, cross-version test results gathered on BrowserStack:
| Platform | Browser | Supported |
|---|---|---|
| iOS 18.1 | Chrome | ✅ |
| iOS 18.1 | Safari | ✅ |
| iOS 17.6.1 | Firefox 133.3 | ✅ |
| iOS 16 | Safari 16 | ✅ |
| iOS 16 | Chrome 92 | ✅ |
| iOS 15 | Chrome 92 | ❌ |
| iOS 15 | Safari 15 | ❌ |
Features
Section titled “Features”- Silent switch bypass — Audio plays through the hardware ringer switch via the Audio Session API
- Build-time audio encoding — WAV sources are transcoded to Opus at 48 kHz using FFmpeg during build, with content-addressed filenames derived from Git LFS OIDs for cache busting
- Bun macros for asset manifests —
macros.tsreads the encoded asset manifest at compile time, embedding the sound file mapping directly into the bundle with zero runtime I/O - SoundManager with lazy caching — A lightweight
SoundManagerclass fetches, decodes, and cachesAudioBufferinstances on demand, avoiding redundant network requests - can-start-audio-context integration — Uses the
can-start-audio-contextlibrary to handle the iOS autoplay policy, ensuring theAudioContextis resumed after a user gesture - GitHub Pages CI/CD — Automated deployment via GitHub Actions with Git LFS checkout and FFmpeg setup
How It Works
Section titled “How It Works”The build pipeline in build.ts is worth noting. It pulls WAV files tracked by Git LFS, transcodes them to Opus with FFmpeg, and writes a JSON manifest to .cache/encoded.json. The macros.ts file then reads this manifest at compile time using Bun’s macro system — meaning the final bundle knows exactly which hashed filenames to fetch, with no runtime file-system access needed.
At runtime, SoundManager.fromContext() creates an audio manager preloaded with the macro-embedded manifest. When sm.fetch("rosa10") is called, it looks up the hashed Opus filename, fetches and decodes it, and caches the resulting AudioBuffer for reuse.
Tech Stack
Section titled “Tech Stack”| Language | TypeScript, HTML |
| Runtime | Bun |
| Build | Bun bundler with HTML entrypoints, FFmpeg for audio encoding |
| Audio | Web Audio API, Audio Session API |
| Asset Pipeline | Git LFS + Bun macros for compile-time asset manifests |
| Deployment | GitHub Actions → GitHub Pages |
| Platform | Web (Safari / iOS primarily) |
Live Demo
Section titled “Live Demo”Try it on an iOS device: jadujoel.github.io/ios-background-audio
Flip your ringer switch to silent and tap to start — the audio should play through.
Source Code
Section titled “Source Code”The source code is available on the project’s GitHub repository.