Foreground audio is a demo. Background audio is a product. The gap between the two is where most of the genuinely hard mobile engineering lives — and it's almost entirely invisible until real users hit it on real devices.
When audio has to keep playing after the user locks the phone, switches apps, walks into a dead zone, or leaves the app alone long enough for the OS to get hungry, you are no longer writing a player. You're negotiating with two operating systems that have different, often undocumented opinions about what your app is allowed to do when it's not on screen.
The happy path is a trap
The dangerous thing about background audio is that the happy path works perfectly. You build the player, you test it in the foreground, you even lock the phone once and hear it keep going, and you ship. The bugs don't show up in your test session. They show up as a slow trickle of reports — "audio cuts out sometimes," "the lock-screen buttons don't work," "it stopped when I switched apps" — that you can't reproduce at your desk.
That's because the failures are conditional on state you don't normally exercise: a specific transition, a specific platform, a specific moment of memory pressure. Background reliability isn't one feature; it's a collection of edge cases that each need explicit, tested handling.
Four edge cases, eight behaviors
The reason background audio resists a clean abstraction is that the same four concerns behave differently on iOS and Android:
- Foreground/background transitions. Going to the background, coming back, and the in-between states each need handling — and what each platform permits while backgrounded differs.
- Lock-screen and remote controls. A "next" or "pause" from the lock screen, headphones, or a car head unit can arrive while your JS thread is effectively asleep. These events have to be treated as first-class inputs to your playback state, not UI callbacks that assume the app is awake.
- Connectivity changes. The network drops and returns while you're backgrounded, and the player has to react without a foreground render loop to lean on.
- The OS reclaiming your process. This is the worst one, and it's mostly an Android reality: under memory pressure the system terminates your background process outright. Your audio dies, and there was no error to catch — the whole process simply ceased to exist.
Eight distinct behaviors hiding behind four innocent-sounding features. Pretending the platforms are the same is the original sin of cross-platform background media.
Treat remote controls as real input
A common mistake is to wire lock-screen and headphone controls as if they were buttons inside your UI. They aren't. They fire when your app may have no active render cycle, and they have to mutate the same playback state that the on-screen player reads.
The clean model is a single source of truth for "what's playing," with remote-control events feeding into it on equal footing with taps inside the app. The lock screen, the mini-player, and the full player are all just views of that one state. When they share a source of truth, a pause from the car stereo and a pause from the in-app button are the same operation — and they can never drift out of sync.
Recovering from a process that no longer exists
You cannot catch an exception for "the OS killed your process," because your code stopped running. Recovery has to happen on the way back in: when the app is relaunched or restored, it has to detect that a session was interrupted and rebuild it — restoring the queue, the track, and the position from persisted state rather than assuming a clean start.
This is why durable, persisted playback state matters so much for background reliability. If the only record of "the user was halfway through chapter four" lives in memory, an OS kill erases it. If it's persisted, a background-kill becomes a recoverable blip instead of a lost session. The recovery module's whole job is to make process death survivable.
Notice the stalls, not just the errors
The other silent failure is the stall: playback that's nominally active but producing nothing. No exception, no crash — just a frozen scrubber and silence. In the background, with no one watching the screen, a stall can sit indefinitely.
The answer is a watchdog: something that expects progress and acts when it doesn't see any, re-buffering or re-selecting the source instead of waiting forever. Background reliability is as much about detecting the absence of progress as it is about handling explicit failures.
Make it visible before you make it perfect
You will not reason your way to every background edge case from first principles, because some of them are specific to a phone model, an OS point-release, or a network you've never tested on. The only way to find them is to instrument the player and watch the field.
Custom playback telemetry — what state the engine was in, what transition just happened, what the network was doing — is what turns "audio cuts out sometimes" into an actionable bug. Add it early. The field is where background audio is actually tested, and telemetry is how you get to watch the test.
The takeaway
Background audio is hard because it forces you to design for a world where your app isn't in control: it's backgrounded, the controls are external, the network is unreliable, and the OS can end you at any moment. Get it right and the experience feels effortless — audio that simply follows the user around without ever asking for attention. Getting there means giving each platform edge case the explicit, tested handling it deserves, instead of hoping the happy path holds.