●ACQUISITION — Rork makes its first acquisition, buying Paperline, a macOS app that generates native Swift apps with AI●FUNDING — The $15M seed led by Left Lane Capital backs Rork's push to redefine how mobile apps are built and monetized●GROWTH — Rork Max reportedly hit $1.5M ARR within three days of launch and doubled annual revenue in two weeks●ENGINE — Rork Max runs on Claude Code and Claude Opus 4.6, the first web Swift builder aiming to replace Xcode●SPLIT — Standard Rork uses React Native (Expo); Rork Max generates native Swift across the whole Apple ecosystem●PRICING — Start free; paid plans begin at $25/month, with Rork Max at $200/month●ACQUISITION — Rork makes its first acquisition, buying Paperline, a macOS app that generates native Swift apps with AI●FUNDING — The $15M seed led by Left Lane Capital backs Rork's push to redefine how mobile apps are built and monetized●GROWTH — Rork Max reportedly hit $1.5M ARR within three days of launch and doubled annual revenue in two weeks●ENGINE — Rork Max runs on Claude Code and Claude Opus 4.6, the first web Swift builder aiming to replace Xcode●SPLIT — Standard Rork uses React Native (Expo); Rork Max generates native Swift across the whole Apple ecosystem●PRICING — Start free; paid plans begin at $25/month, with Rork Max at $200/month
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.
The first thing that betrays you when you build a sound app is the moment the audio that played perfectly in preview goes silent the instant you return to the home screen or lock the device. When I rebuilt Relaxing Healing — a sleep and meditation sound app I have run as a personal developer for years — in Rork, the freshly generated code only played in the foreground. For an app people use right before falling asleep, going silent the moment the screen turns off is a fatal flaw.
Rork outputs React Native (Expo) code, so this is not a Rork-specific problem; it is solved in Expo's audio configuration. But pasting the official minimal sample as-is gives you no lock screen play button, and on Android the audio quietly stops after a few minutes. Here is the implementation that makes both background playback and lock screen controls work, using expo-audio (Expo SDK 54), laid out in the order I hit each wall.
Why generated code stops in the background
"Just playing" audio and "keeping audio playing while the app is backgrounded" are, from the OS's point of view, two different permissions. The former works with the default audio session state; the latter only works once three things are all in place.
First, on iOS, unless you declare audio in the background execution modes, the audio session is suspended the moment the app moves to the background. Second, you need to switch the audio session itself into a mode that plays in the background. Third, on Android, unless you actively post a lock screen (media) notification, the OS stops the process's audio after about three minutes. Generated code usually satisfies none of these three.
1. Add expo-audio and declare background mode
Add expo-audio to the project you exported from Rork, and enable iOS background audio through the plugin configuration in app.json (or app.config.js). Writing it via the plugin sets UIBackgroundModes without you touching the native Info.plist.
Build a dev build without this, and you get a low-reproducibility bug: it appears to work in the iOS simulator but stops the instant you background it on a real device. I burned half a day on exactly this gap, so I strongly recommend declaring it up front. After changing the config you need npx expo prebuild and a rebuild (UIBackgroundModes is not honored in Expo Go).
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Turn a sound or music app that goes silent on screen lock into one that keeps playing with full lock screen controls
✦You'll learn the correct pairing of expo-audio's setAudioModeAsync and setActiveForLockScreen, with code that runs
✦Ship with the iOS/Android divergences already handled — the Android 3-minute cutoff and interruption recovery
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
2. Put the audio session into a background-playing mode
Call setAudioModeAsync once at app launch (before you play anything) to set the session's character. What matters here is shouldPlayInBackground: true and, to make lock screen controls work, interruptionMode: 'doNotMix'.
// audioSession.ts — call once at app launchimport { setAudioModeAsync } from 'expo-audio';export async function configureAudioSession() { await setAudioModeAsync({ // Play even with the silent (mute) switch on — essential for healing/music apps playsInSilentMode: true, // Keep playing in the background and on the lock screen shouldPlayInBackground: true, // Do not mix with other apps' audio. Without this, lock screen controls // won't bind to your player interruptionMode: 'doNotMix', // Android: pause when another app starts playing, leaving room to resume interruptionModeAndroid: 'doNotMix', });}
Leave interruptionMode at its default (equivalent to 'mixWithOthers') and the sound plays, but the lock screen transport controls bind to no player, so the buttons do nothing. The "audio plays in the background but only the lock screen buttons don't respond" symptom is almost always caused by this one line.
3. Create the player and subscribe to playback state
In expo-audio you create a player with useAudioPlayer and subscribe to playback state with useAudioPlayerStatus. Unlike the old expo-av Audio.Sound, the hook handles releasing on unmount for you, so the manual unloadAsync that used to be a memory-leak hotspot is gone.
Gating the button on status.isLoaded quietly pays off. If you call play() before the source finishes loading, the first tap does nothing, which tends to turn into "the button doesn't work" one-star reviews.
4. Surface info on the lock screen and Control Center
This is the heart of it. Even once it plays in the background, if no track title or play button appears on the lock screen, the user has no choice but to wake the screen and reopen the app. Pass metadata to setActiveForLockScreen to light up the lock screen media controls.
import { useAudioPlayer, useAudioPlayerStatus } from 'expo-audio';async function startWithLockScreen(player) { player.play(); // ⚠️ Call it slightly after playback starts. // Called before playback, the session isn't active yet, so the // lock screen controls sometimes fail to appear setTimeout(() => { player.setActiveForLockScreen(true, { title: 'Rain - 8 Hour Loop', artist: 'Relaxing Healing', albumTitle: 'Sleep Sounds', artworkUrl: 'https://example.com/artwork/rain.jpg', // Whether to show seek (fast-forward/rewind). false is natural for ambient loops showSeekForward: false, showSeekBackward: false, }); }, 250);}
Pass artworkUrl either a remote URL or a path resolved from a local asset via Asset.fromModule().localUri. Leave it empty and the lock screen thumbnail falls back to the default icon, breaking the mood you worked for. For ambient loops, dropping the seek buttons and keeping only play/pause reduces operational hesitation.
And on Android, unless you call setActiveForLockScreen to post this media notification, the OS stops the background audio after about three minutes. iOS keeps playing with just shouldPlayInBackground: true, but on Android showing the lock screen notification and continuing background playback are linked — this is where you absorb that asymmetry.
The iOS/Android behavior gap on one page
Here are just the differences that directly drive implementation decisions.
Item
iOS
Android
Condition to keep background playback
audio in UIBackgroundModes + shouldPlayInBackground
The above + a lock screen notification (setActiveForLockScreen) is required
If you post no notification
Keeps playing
Stops after ~3 minutes
Playback with the mute switch on
Plays with playsInSilentMode: true
No such switch, so little impact
Binding lock screen controls
Requires interruptionMode: 'doNotMix'
Requires FOREGROUND_SERVICE permission
Verifying in Expo Go
UIBackgroundModes not honored, not possible
Same — needs a dev build
Recovering from interruptions (calls, other apps' audio)
Leave a sleep app running all night and a call, an alarm, or another app's audio will inevitably interrupt it midway. During the interruption it auto-pauses, and when it ends you decide whether to resume based on your own playback policy. For healing use, "auto-resume once the interruption ends" is usually closer to user expectations.
import { useEffect, useRef } from 'react';import { AppState } from 'react-native';function useResumeAfterInterruption(player, status) { // Flag recording whether the user intentionally stopped playback const shouldAutoResume = useRef(true); useEffect(() => { const sub = AppState.addEventListener('change', (next) => { // On returning to foreground, resume if the user didn't stop it if (next === 'active' && !status.playing && shouldAutoResume.current) { player.play(); } }); return () => sub.remove(); }, [player, status.playing]); return shouldAutoResume;}
The thing to watch here is not confusing "a pause the user intended" with "a pause caused by an interruption." I once implemented auto-resume without tracking this distinction with a flag, which produced the disappointing behavior of restarting every time the app came back even though the user had stopped it. It is safer to always record the reason playback stopped with an explicit flag like shouldAutoResume.
Pitfalls easy to hit before release
Finally, here are the points beyond what I covered above that tend to trip you up at real-device release.
Deciding "it stops in the background" from Expo Go without making a dev build is the most common way to burn time. Neither UIBackgroundModes nor the Android foreground service is honored in Expo Go. Always verify with an eas build dev build or a production build.
The other is calling setActiveForLockScreen even after the player has been released, leaving lock screen controls lingering as debris on every navigation. Calling setActiveForLockScreen(false) to explicitly tear it down when you leave a screen keeps the display from crossing wires even in an app that switches between multiple sources.
Background playback is less about "making sound" and more about "making the right promise to the OS." Only once the three pieces — declaring the background mode, characterizing the session, and posting the lock screen notification — are all in place does it become an app where users can keep the screen off and fall asleep without worry. Start by getting a single sound file to keep playing after the screen locks, today.
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.