RORK LABJP
TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the roundTOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
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.

Rork515expo-av6ambient soundhealing app2meditation app3React Native209background 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 $10 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 →