DEV Community

BellSal
BellSal

Posted on

The audio bugs nobody warns you about when you build a mobile looper

I spent the last few months building an MPC-style pad sampler and looper for Android in Flutter. The UI was the easy part. The part that ate the time was audio — specifically, all the ways audio quietly breaks on a phone, without a crash, without a log, without any hint until a user tells you their work disappeared.

Here are the ones that cost me the most, and what actually fixed them. If you're building anything that records, loops, or plays samples on mobile, at least a few of these will bite you too.

1. Requesting audio focus at launch kills other apps' music

The most embarrassing one. I asked the audio session for focus (setActive with a gain focus type) during startup, before the user had produced a single sound. On Android that means: the moment someone opens your app, whatever music they were listening to in another app stops forever.

The fix is a one-liner in spirit but easy to get wrong: don't request focus until you actually make a sound, and request it exactly once. Opening the app, tapping through onboarding, switching tabs — none of that should touch another app's audio. Focus is a promise you make when you're about to be loud, not when you launch.

2. Your MP3 encoder will hang on shutdown if you don't time-box close()

I had already learned to put a timeout on every encode() and flush() call, because an encoder running in a background isolate can stall. What I missed: close() was still waiting without any limit for the same isolate — an isolate that, in some failure paths, had never fully started. So the encode loop was safe, and the shutdown was the thing that hung, leaving the user staring at an infinite spinner after a successful-looking export.

Lesson: if you time-box the hot path, time-box the teardown too. The cleanup call is exactly where you stop paying attention, and exactly where a stalled worker will wait forever.

3. 24-bit WAVs from a DAW are "unreadable" unless you handle WAVE_FORMAT_EXTENSIBLE

Users kept importing perfectly valid WAV files exported from their desktop DAW and getting "file not readable." The reason: those files use WAVE_FORMAT_EXTENSIBLE (format tag 0xFFFE) rather than plain PCM, which is what 24-bit and 32-bit exports typically are. My parser only understood the simple PCM header and bailed on anything else.

If you accept user audio, you cannot assume 16-bit PCM. You have to read the extensible header, find the real sub-format, and handle the bit depth. "It's just a WAV" is a trap.

4. Stopping the mic too soon freezes the UI thread for 20 seconds

This one showed up as an ANR ("app not responding"), reproducible every time: open the microphone sheet and close it again within about a tenth of a second. The capture device had just started; the native stop call, issued ~100 ms after start, blocked the UI thread for around 20 seconds before returning.

The fix was to never stop the capture device within 500 ms of starting it, and to do the stop asynchronously so the UI never blocks on it regardless. Audio hardware has a warm-up cost; tearing it down mid-warm-up is where the native layer punishes you. Treat "start then immediately stop" as a first-class case, because users do it constantly (open the wrong sheet, close it).

5. A single exception in the audio clock doubles every hit

Live looping means adding and removing layers while the transport is running. Two operations — "undo last layer" and "double the loop length" — could throw inside the clock callback while a loop was playing. The exception didn't crash the app; it caused the clock to re-schedule clicks and note hits it had already fired, so everything doubled up. On a real device this sounded like the whole beat suddenly playing twice.

Two fixes: don't throw from those operations in the first place, and harden the clock so that one misbehaving subscriber can't re-book the scheduling window for the others. The audio thread is not a place to let exceptions bubble — a thrown error there doesn't stop the world, it corrupts timing.

6. Auto-connecting MIDI at launch crashes some devices, unrecoverably

Connecting to any already-attached USB MIDI controller during startup caused a native launch crash on at least one tablet — the kind you can't catch, because it happens below your Dart code.

The safe pattern: do not auto-connect at launch. Connect automatically only to a device that is plugged in while the app is already open, and let the user tap anything that was already attached from a list. Startup is fragile enough without probing hardware you don't control.

7. The bug that actually loses work: a save that fails silently

The scariest one had no sound at all. My autosave (_doSave()) was written to never throw, on the theory that an autosave blowing up shouldn't take down the app. Reasonable — except that openProject and createProject then went ahead even when the underlying write had failed, so the edits you'd just made vanished as you switched projects. The "three failures in a row" warning I'd added arrived far too late: by the first failure, the work was already gone.

The fix: when a save fails, cancel the thing that depended on it. Don't switch projects, keep the user on what they were editing, and tell them. Silent success is worse than a loud error every time.


None of these were exotic. They were the boring seam between Dart and the platform's audio stack — focus, teardown, file formats, hardware timing, the clock, native launch, and the write that "can't fail." That seam is where a music app lives or dies, and it's almost entirely invisible until a real device and a real user find it.

Full disclosure: the app is my own, so treat this as biased. It's called PadLoop — an MPC-style pad sampler plus a looper for Android: load or record your own samples, tap patterns on pads, stack loop layers, quantize reversibly, and export WAV/MP3/stems. No ads, no subscription (free to try, then a single one-time unlock), and it runs fully offline. Every bug above is one I fixed in it.

If you're building in this space and want to compare notes on any of these, I'm genuinely happy to — the mic-teardown ANR in particular took embarrassingly long to pin down.

PadLoop on Google Play: https://play.google.com/store/apps/details?id=es.positivevibration.padloop

Top comments (0)