RORK LABJP
EVENT — Apple holds its Surprise and Shine event today, September 9, starting at 10:00 Pacific. That lands in the small hours of September 10 in JapanEXPECT — Expected are the iPhone 18 Pro and Pro Max, a foldable, the 2nm A20 Pro chip, and release dates for iOS 27 and its sibling updatesWAIT — As this is written the event has not happened yet. Rumor-stage writing and post-announcement writing look identical once they are mixed togetherMAX — Since Rork Max generates native Swift, Apple news is not somebody else's problem. Worth repeating that the standard product still writes React NativeSIMULATOR — Rork Max compiles on cloud Macs and lets you check the result in a streaming iOS simulator inside the browser, with no Xcode and no Mac hardwareSEASON — A new OS is when automated build pipelines wobble most. An article selling convenience owes its readers a word about that wobbleEVENT — Apple holds its Surprise and Shine event today, September 9, starting at 10:00 Pacific. That lands in the small hours of September 10 in JapanEXPECT — Expected are the iPhone 18 Pro and Pro Max, a foldable, the 2nm A20 Pro chip, and release dates for iOS 27 and its sibling updatesWAIT — As this is written the event has not happened yet. Rumor-stage writing and post-announcement writing look identical once they are mixed togetherMAX — Since Rork Max generates native Swift, Apple news is not somebody else's problem. Worth repeating that the standard product still writes React NativeSIMULATOR — Rork Max compiles on cloud Macs and lets you check the result in a streaming iOS simulator inside the browser, with no Xcode and no Mac hardwareSEASON — A new OS is when automated build pipelines wobble most. An article selling convenience owes its readers a word about that wobble
Articles/Dev Tools
Dev Tools/2026-08-31Intermediate

Three Minutes After the Screen Locked, My expo-audio Ambient Sound Went Silent

Why expo-audio stops playing when the screen locks, diagnosed as three separate layers: build config, audio session, and lock screen integration. The three-minute stop on Android is documented behavior.

expo-audio4background audio3React Native236Expo203Android48

As an indie developer, I run ambient sound apps for sleep. A user taps play on rain sounds and drifts off; the screen locks within a minute, and only the audio is supposed to remain until morning. For this kind of app, everything that matters happens after the screen goes dark.

While testing a build that had migrated from expo-av to expo-audio, I ran into a strange failure mode on a physical Android device. The audio kept playing right after the screen locked — then, roughly three minutes later, it stopped. No error, no crash. Just silence.

If playback dies the instant you lock the screen, you suspect a missing setting. But "plays for a while, then stops" is much harder to reason about. It turned out this wasn't a bug or a quirk of my test device. It's a stopping behavior written plainly into Expo's documentation. Here is how I traced it, and how I now think about background audio as three separate layers.

Your expo-av knowledge does not carry over to expo-audio

Some context first. expo-av was deprecated in SDK 52 and removed in SDK 55. Since apps generated with Rork are built on Expo, rebuilding on a current SDK means your audio code lives in expo-audio now, whether you planned the migration or not.

In the expo-av era, background playback hinged on a single boolean: staysActiveInBackground, passed to setAudioModeAsync. Set it, and you were mostly done.

That field does not exist in expo-audio's AudioMode. You search for the name, fail to find it, spot the plausible-looking shouldPlayInBackground, set it to true, and move on — which is exactly what I did. On iOS, that works. On Android, your audio stops after about three minutes.

What changed is that one boolean became three layers. That structural shift is the real trap.

The three layers, and how each one fails

Here is the map I wish I'd had. Background playback in expo-audio only works when all three of these layers are in place.

LayerWhere it livesWhat to setSymptom when missing
1. Build configConfig plugin in app.jsonenableBackgroundPlayback (default true)iOS stops instantly on lock; Android lacks the service declaration
2. Audio sessionsetAudioModeAsync at runtimeshouldPlayInBackground: true (default false)Both platforms stop immediately on lock or backgrounding
3. Lock screen integrationsetActiveForLockScreen at runtimeEffectively required on Android (optional on iOS)Android only: playback stops after roughly 3 minutes

The table doubles as a reverse lookup. Audio dies the moment the screen locks: check layers one and two. Audio plays for a while and then goes quiet: layer three. The three-minute mark is not noise — it's a fingerprint telling you which layer to inspect.

Layers one and two: the build-time declaration and the runtime session

Layer one is the config plugin. In app.json:

{
  "expo": {
    "plugins": [
      ["expo-audio", { "enableBackgroundPlayback": true }]
    ]
  }
}

This adds two Android permissions — FOREGROUND_SERVICE and FOREGROUND_SERVICE_MEDIA_PLAYBACK — plus the media playback foreground service declaration (AudioControlsService) to your manifest, and adds the audio entry to UIBackgroundModes on iOS. You never touch the native files yourself.

The subtle part: enableBackgroundPlayback defaults to true, so this layer is usually fine without you doing anything. Meanwhile, layer two's shouldPlayInBackground defaults to false. The two defaults point in opposite directions. That's why "the manifest has everything, yet the audio still stops" is such a common state of confusion during this migration.

Layer two is the session setup, called once before playback starts:

import { setAudioModeAsync } from 'expo-audio';
 
await setAudioModeAsync({
  playsInSilentMode: true,      // keep playing under the iOS silent switch
  shouldPlayInBackground: true, // the core switch; defaults to false
  interruptionMode: 'doNotMix', // required for lock screen controls
});

The default interruptionMode is 'mixWithOthers', but the docs are explicit that lock screen controls need 'doNotMix' — otherwise the OS may not associate those controls with your player. Also note that the old interruptionModeAndroid is deprecated; interruptionMode now applies to both platforms. Older tutorials will steer you toward field names that no longer do what they once did.

Layer three: without setActiveForLockScreen, Android stops at about three minutes

This is the heart of the matter. Expo's documentation states that on Android, if you do not enable lock screen controls, background playback stops after approximately three minutes — an OS limitation, not a bug to be fixed on your side.

My build stopping quietly at the three-minute mark was this sentence playing out verbatim. The fix is to activate lock screen controls when playback starts:

player.loop = true;
player.setActiveForLockScreen(true, {
  title: 'Rain Sounds',
  artist: 'Ambient Sounds',
});
player.play();

This call posts a media notification, and the foreground service tied to that notification is what keeps playback alive. The notification in the shade is your visible proof that the audio will survive. Put the other way around: if you lock the screen and there's no media notification, your playback is sitting on a three-minute hourglass.

One design note: only one player can own the lock screen at a time. In an ambient app that switches between multiple sound sources, you either re-activate the new player on each switch or keep one active player and refresh the display with updateLockScreenMetadata.

The minimal working setup, and why my test checklist now includes three minutes

All three layers in one place:

import { useAudioPlayer, setAudioModeAsync } from 'expo-audio';
import { useEffect } from 'react';
import { Button } from 'react-native';
 
const rainSound = require('./assets/rain.mp3');
 
export function AmbientPlayer() {
  const player = useAudioPlayer(rainSound);
 
  useEffect(() => {
    setAudioModeAsync({
      playsInSilentMode: true,
      shouldPlayInBackground: true,
      interruptionMode: 'doNotMix',
    });
  }, []);
 
  const start = () => {
    player.loop = true;
    player.setActiveForLockScreen(true, {
      title: 'Rain Sounds',
      artist: 'Ambient Sounds',
    });
    player.play();
  };
 
  return <Button title="Play" onPress={start} />;
}

This incident also added one step to my release checklist: start playback on a physical Android device, lock the screen, confirm the media notification is showing, and then keep listening for more than three minutes. A thirty-second check in a simulator will never surface this problem.

For a sleep app, three minutes is a brutal number. The audio dies at the exact moment the user is most defenseless — half asleep. And the review they leave will only say "the sound stops"; nobody writes down the three-minute detail that would point you at the cause. Waiting out those three minutes by hand is unglamorous testing, but it protects more than it appears to.

Other ambient-audio pitfalls — gapless loop seams, silence under the ring/silent switch — are collected in Three Things I Got Stuck on When Implementing Ambient Sound in a Healing App. And once lock-screen playback is solid, the next stage is timing: my premium article My Rork sleep timer faded out on time — but the sound didn't covers designing fades driven by playback position rather than wall-clock time.

Start with three minutes on a real device. If your audio survives that, the foundation for playing until morning is in place.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-06-26
Keep Audio Playing in the Background and Add Lock Screen Controls in a Rork App
How to make a Rork-generated Expo app keep playing music or healing sounds in the background and expose lock screen and Control Center controls, with working expo-audio code and the platform-specific gotchas.
Dev Tools2026-05-05
Taking a Rork Podcast App to Production — Migrating to expo-audio, Recovering from Interruptions, and Resumable Downloads
Implementation notes for making a Rork podcast app survive real use: migrating off expo-av before SDK 55 removes it, recovering from call interruptions, resuming broken downloads, and budgeting storage against iCloud backup.
Dev Tools2026-05-04
Microphone and Audio Recording Not Working in Rork — A Symptom-Based Troubleshooting Guide
When microphone or audio recording stops working in a Rork-generated app, the root cause is often not obvious. This guide walks through common failure patterns — permissions, audio mode setup, simulator limits, and async pitfalls — with working code fixes.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links