Skip to content

Build1 publisher3 min readPublished

Requesting audio focus at startup stopped the user's music before the app made a sound

A Flutter looper developer has published seven Android audio bugs that produced no crash and no log entry. Most of them are ordering mistakes about when the app claims the hardware and when it lets go.

The Engineer · Build desk

What happened

  • A Flutter pad sampler asked the audio session for focus with a gain request during startup, which on Android stops whatever music the user was playing in another app the moment the sampler opens.
  • The encoder's close() call waited without any limit on a background isolate that in some failure paths had never started, so a successful-looking export left the user watching a spinner that never ended.
  • The autosave was written never to throw, and openProject and createProject then proceeded even when the underlying write had failed, so the user's recent edits disappeared on switching projects.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • cost Failures that emit no crash and no log move detection onto users, so the bug report arrives as "my work disappeared" weeks after the release that caused it.
  • decision Deferring the focus request to the first sound means the audio session now has to initialise on a code path that runs under a user's finger, with whatever latency that adds to the first pad hit.
  • constraint Timeouts on the hot path do not bound a session, so every teardown call needs its own budget and a decision about what to do when that budget expires.
  • exposure Any app accepting user audio inherits the whole WAV header space, and one that assumes 16-bit PCM will reject files produced by exactly the paying users who own a desktop DAW.

A gain focus request is a claim on the output, not a question about whether anyone else is using it. After the fix, the developer's rule was to ask at the moment the app first makes a sound, and to ask exactly once, so that onboarding taps and tab switches never reach another app's session [3]. "Focus is a promise you make when you're about to be loud, not when you launch," the developer wrote [4].

The export hang is the more instructive failure, because the obvious guard was already there. Every `encode()` and `flush()` call had a timeout, since an encoder running in a background isolate can stall [5]. The isolate that `close()` waited on had, in some failure paths, never fully started, so nothing was ever going to answer it [6]. The encoder path was bounded and still produced a hang; the write path failed differently, because a save written never to throw also never tells its caller that the write failed [14].

The microphone bug is the one with a ratio in it. The native stop call arrived about 100 ms after start and blocked the UI thread for around 20 seconds, roughly 200 times as long as the capture device had been running [8][15]. The developer attributes the block to the hardware's warm-up cost, and says start-then-immediately-stop deserves treatment as a first-class case because users do it constantly by opening the wrong sheet [20]. The guard is a 500 ms floor before any stop, five times the gap that reproduced the failure, with the stop moved off the UI thread [9][16]. The post does not name the device. For the 500 ms figure to transfer, your capture stack has to punish an early teardown the same way; it was measured once, on one native layer.

Two operations, "undo last layer" and "double the loop length", could throw inside the clock callback while a loop was playing [10]. The exception did not crash the app. It made the clock re-schedule clicks and note hits it had already fired, and on a real device the whole beat played twice [10]. The repairs were to stop those operations throwing, and to harden the clock so that one misbehaving subscriber cannot re-book the scheduling window for the others [11].

Input handling supplied the most ordinary bug of the set. Valid files exported from desktop DAWs came back as "file not readable" because 24-bit and 32-bit exports typically use WAVE_FORMAT_EXTENSIBLE, format tag 0xFFFE, and the parser understood only a plain PCM header [7].

Two of the seven failures fire during startup, before the user has asked for anything [19]. One is the focus request. The other is auto-connecting to an already-attached USB MIDI controller, which caused a native launch crash on at least one tablet, below the Dart code, where it cannot be caught [12]. The developer's pattern now is to auto-connect only devices plugged in while the app is already open, and to make the user tap anything already attached from a list [13]. The published account breaks off mid-sentence while describing a "three failures in a row" warning in the autosave path [17].

What to watch

  • Whether the developer publishes the truncated autosave section, including what the "three failures in a row" warning actually does when it fires.
  • Whether the 500 ms stop guard is needed on capture stacks other than the one Android device it was measured on.
  • Whether the USB MIDI launch crash reproduces beyond the single tablet named in the account.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories