RORK LABJP
PLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updatesPLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updates
Articles/Dev Tools
Dev Tools/2026-05-13Intermediate

Three Things I Got Stuck on When Implementing Ambient Sound in a Healing App with Rork

Three common pitfalls when implementing ambient looping audio in meditation and healing apps built with Rork: loop clicks, iOS silent mode, and battery drain — with working expo-av code.

Rork547expo-av6ambient soundhealing app2meditation app3React Native234background music

I've been building apps independently since 2014, and my wellness, wallpaper, and manifestation apps have crossed 50 million cumulative downloads. In this category, sound is everything. The moment ambient audio stutters or cuts out, users snap back to reality — and a lot of them quietly uninstall.

Recently, I was prototyping a new meditation app in Rork and spent more time than I expected fighting ambient sound implementation. Rork handles screen layout and API wiring beautifully, but the code it generates for audio has a few recurring weak spots. Since I kept running into the same three problems, I want to write them down — with the fixes that actually worked.

Problem 1: A Click at the Loop Point

The first issue: a small "pop" or "click" sound every time the audio loops. When you ask Rork to implement looping ambient audio, it usually produces something like this:

// Pattern Rork commonly generates (prone to loop clicks)
const sound = await Audio.Sound.createAsync(
  require('./assets/rain.mp3'),
  { isLooping: true }
);
await sound.playAsync();

Setting isLooping: true should work in theory, but if there's any silence encoded at the tail of an MP3 file — which there almost always is — you'll hear a click on every loop. This isn't a Rork bug or a React Native bug; it's a fundamental quirk of the MP3 format.

Fix: Use WAV or m4a + manual loop control via PlaybackStatus

import { Audio } from 'expo-av';
import { useEffect, useRef } from 'react';
 
export function useAmbientSound(assetSource) {
  const soundRef = useRef(null);
 
  useEffect(() => {
    let isMounted = true;
 
    const loadAndPlay = async () => {
      await Audio.setAudioModeAsync({
        playsInSilentModeIOS: true,        // also covers Problem 2
        staysActiveInBackground: true,
        shouldDuckAndroid: false,
      });
 
      const { sound } = await Audio.Sound.createAsync(
        assetSource,
        { shouldPlay: true, volume: 0.6 }
      );
 
      if (!isMounted) return;
      soundRef.current = sound;
 
      // Loop fix: use onPlaybackStatusUpdate instead of isLooping
      sound.setOnPlaybackStatusUpdate(async (status) => {
        if (status.didJustFinish && isMounted) {
          await sound.replayAsync();
        }
      });
    };
 
    loadAndPlay();
    return () => {
      isMounted = false;
      soundRef.current?.unloadAsync();
    };
  }, []);
 
  return soundRef;
}

Listening for didJustFinish and calling replayAsync() is far more reliable on physical devices than relying on isLooping. For audio assets, use WAV or m4a (AAC) — avoid MP3 for anything that needs to loop seamlessly.

Problem 2: Silent Mode on iOS Kills All Audio

This one shows up in App Store reviews almost every time. A user opens your meditation app right before bed, their phone is on silent, and they hear nothing. It's one of those problems that's invisible in testing if you always test with sound on.

Rork's default generated code almost never includes the silent mode override. You have to add it explicitly:

// Call this at app startup or before loading any sound
await Audio.setAudioModeAsync({
  playsInSilentModeIOS: true,     // ← Without this, silent switch = no sound
  allowsRecordingIOS: false,
  staysActiveInBackground: true,
  interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
  interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
  shouldDuckAndroid: false,
  playThroughEarpieceAndroid: false,
});

This only needs to run once per session, but you'll also need corresponding entries in Info.plist for background audio mode. When prompting Rork, explicitly write "play audio even when iOS silent switch is on" — that phrasing significantly improves what it generates.

Problem 3: Battery Drain During Long Background Sessions

This one took me the longest to track down. During real-device testing, I noticed the app draining about 20% battery in an hour of background use — way more than expected for a simple ambient sound player.

Two causes turned out to be responsible. First, combining staysActiveInBackground: true with shouldDuckAndroid: false on a high-bitrate MP3 kept the audio decoder running at full priority the entire time. Second, the PlaybackStatus callback Rork generated was polling at the default 500ms interval — creating a constant drip of work on the JS thread even when nothing needed to change.

Fix: Optimize audio files + set progressUpdateIntervalMillis

const { sound } = await Audio.Sound.createAsync(
  assetSource,
  {
    shouldPlay: true,
    volume: 0.6,
    progressUpdateIntervalMillis: 1000,  // default 500ms → 1000ms
  }
);

For ambient audio, 96kbps AAC (m4a) is entirely sufficient. Streaming 320kbps MP3 or uncompressed WAV for hours of background playback is overkill. In my own apps, switching to 96kbps m4a reduced battery consumption by roughly 40%.

A Rork Prompt Pattern That Actually Works

Given these three problems, here's the prompt structure I now use when asking Rork to implement ambient audio:

Implement looping ambient sound with these requirements:
- Seamless looping — use didJustFinish + replayAsync(), NOT isLooping
- Play in iOS silent mode (playsInSilentModeIOS: true)
- Support background playback (staysActiveInBackground: true)
- Set progressUpdateIntervalMillis to 1000ms
- Asset format: m4a or WAV (not MP3)

Spelling out what to avoid, not just what to build, makes a significant difference in Rork's output quality.

The Part Rork Can't Do for You

Sound reaches users' emotions faster than visuals. After more than a decade building wellness apps, I'm more convinced of this than ever.

My grandfathers on both sides were shrine carpenters — craftsmen who believed that invisible joinery deserved the same care as what anyone could see. That feeling stuck with me. A single line of audio configuration that users will never notice is still worth getting right.

Rork lets you build the skeleton of an app at remarkable speed. But the texture of the experience — the parts that make someone keep coming back — still comes from the developer paying careful attention to exactly this kind of detail.

If you're building a meditation or wellness app with Rork, I hope the patterns here save you some time.

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-07-15
My Rork sleep timer faded out on time — but the sound didn't
Rork writes sleep-timer fades against the wall clock with setInterval. The numbers say 30 minutes, but the real audio fades early or cuts out abruptly. Here is how to drive the fade from the actual playback position, why a logarithmic curve sounds natural, and the honest limit of JS fades when the screen is locked.
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-03-28
Build a Music Player App with Rork — Audio Playback Implementation Guide Using expo-av
Learn how to build a music player app with Rork and expo-av. This step-by-step guide covers playback controls, seek bars, playlist management, and background audio with practical code examples.
📚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
See all →