flutter_soloud setup
flutter_soloud is an FFI plugin around the SoLoud C++ engine: the native code is compiled automatically by Dart build hooks when you depend on the package, so setup is mostly pubspec + a few platform bits (one <script> tag on web, ALSA dev package on Linux, minimum SDK versions). Unlike audioplayers/just_audio there is no per-player instance — everything goes through the singleton SoLoud.instance, which must be init()ed before use and deinit()ed on shutdown.
Minimal example
import 'package:flutter_soloud/flutter_soloud.dart';
Future<void> main() async {
// Optional: pick the output device before init.
final devices = SoLoud.instance.listPlaybackDevices(); // works pre-init
await SoLoud.instance.init(
// device: devices.firstWhere((d) => d.isDefault),
sampleRate: 44100,
bufferSize: 2048,
channels: Channels.stereo,
lowLatency: true,
automaticCleanup: false,
);
final sound = await SoLoud.instance.loadAsset('assets/audio/click.mp3');
SoLoud.instance.play(sound);
// Later, on shutdown:
SoLoud.instance.deinit();
}
Adding the package
flutter pub add flutter_soloud
Native C/C++ sources are compiled by Dart build hooks (hooks/code_assets/native_toolchain_c are transitive deps of the package). No CMake, CocoaPods script phases, or compiler flags are needed.
Platform setup
The API shape
All of these live on the singleton SoLoud.instance (import 'package:flutter_soloud/flutter_soloud.dart').
Future<void> init({PlaybackDevice? device, bool automaticCleanup = false, int sampleRate = 44100, int bufferSize = 2048, Channels channels = Channels.stereo, bool lowLatency = true, AndroidAAudioAttributes androidAAudioAttributes = AndroidAAudioAttributes.mediaMusic, int? devicePeriodFrames, int? renderAheadFrames}) — initializes the engine. Throws on failure (e.g. SoLoudCppException, SoLoudNoPlaybackDevicesFoundCppException); it does not return a PlayerErrors status, so await it in try/catch.
void deinit() / Future<void> deinitAsync() — stops the engine and disposes all resources including sounds. deinit blocks the calling thread; prefer deinitAsync where you can await it.
bool get isInitialized — synchronous readiness check.
List<PlaybackDevice> listPlaybackDevices() — safe to call before init(). Returns PlaybackDevice(id, isDefault, name).
Future<void> changeDevice({PlaybackDevice? newDevice}) — switches output while running; omit newDevice to select the system default. Await it — the swap runs off the UI isolate.
Future<void> stopAudioDevice({bool force = false}) / Future<void> startAudioDevice() — stop/start only the output device; loaded sounds, voices, and filter state are preserved and playback resumes where it left off.
AudioDeviceState getAudioDeviceState() — cheap sync read: uninitialized | stopped | started | starting | stopping. Safe before init().
void setAudioDeviceIdleTimeout(Duration? timeout) — when no unpaused voices remain: Duration.zero stops the device ASAP, a positive duration keeps it alive that long (default 500 ms), null keeps it running indefinitely (Android wakelock). No effect on web.
Divergences from what models trained on audioplayers/just_audio assume:
- One global engine, no
AudioPlayer() instances. Call SoLoud.instance.init() once, early; every other call throws SoLoudNotInitializedException before that.
play() is synchronous and returns a SoundHandle immediately — it cannot report device-start failures; subscribe to SoLoud.instance.audioDeviceStartFailures for those.
init() while already initialized deinitializes and reinitializes, stopping all sounds and unloading all files.
init() options that are native-only (silently ignored on web): lowLatency, androidAAudioAttributes (Android-only, and only when lowLatency: false), devicePeriodFrames, renderAheadFrames.
automaticCleanup: true makes the engine purge its temp directory of loaded sound files occasionally — relevant for apps that load many files from the network.
Render-Ahead Ring (ultra-low latency with large mix buffers)
On native platforms (Android, iOS, macOS, Windows, Linux), you can decouple the hardware output period from the engine mix buffer by setting renderAheadFrames > 0:
await SoLoud.instance.init(
bufferSize: 2048, // Large DSP/mixing quantum for CPU headroom
devicePeriodFrames: 512, // Hardware device callback period (~11 ms @ 44.1kHz)
renderAheadFrames: 1536, // Mix-ahead depth (e.g. bufferSize - devicePeriodFrames)
);
- How it works: The engine pre-mixes audio
renderAheadFrames ahead into an internal ring buffer. When reactive calls like play() or playScheduled() occur, audio is mixed retroactively into the not-yet-played section of the ring, giving near-instantaneous keypress-to-sound latency (~11 ms) without risking audio underruns from tiny mix buffers.
- Inspection getters:
bool get isRenderAheadEnabled — true when enabled on native platforms.
Duration getPlayheadTime() — true playhead time reaching the speaker right now (equals getEngineTime() when disabled or on web).
Duration getOutputLatency() — estimated output latency (ring depth + device period; Duration.zero when disabled or on web).
- Caveats: Ignored on web. Ended-voice callbacks may fire up to
renderAheadFrames earlier than without the ring. Unseekable/un-snapshotable streams (released push streams, pull streams, speechText) degrade gracefully to standard buffer boundaries.
Traps
- Don't await a return code from
init(). It returns Future<void> and throws; older docs suggest a PlayerErrors return. Trust the code.
- Calling
init() again (e.g. after hot restart) wipes all loaded sounds — guard with isInitialized if you only want to init once.
- Web without the
<script> tag fails at init() with confusing WASM-module errors. The tag must be present, and the WASM assets only load from assets/packages/flutter_soloud/web/….
- Web: don't pass
--web-header COOP/COEP flags together with flutter run --wasm — the dev server already sends COOP/COEP for WasmGC and the conflicting duplicated headers block the plugin's worker threads (ERR_BLOCKED_BY_RESPONSE). See references/web.md.
changeDevice is desktop-mostly: Android, iOS, and Web support only the default output device; listPlaybackDevices() there returns just the default.
- A device stopped via
stopAudioDevice() or the idle timeout stays stopped across changeDevice() — the replacement device only starts if the old one was running.
- Linux build fails with
alsa/asoundlib.h: No such file — install libasound2-dev; the error is from the native build hook, not Dart.
- On web,
loadUrl() hits CORS (Access-Control-Allow-Origin missing) unless the server allows it, and local files can't be read — use loadMem() instead.
- Per-sound filters are not supported on web (global filters are).
Xiph audio libraries (Ogg, Vorbis, Opus, FLAC)
The Xiph audio decoders provide compressed audio playback, streaming, and master output capture (600–3000 KB per binary). To keep the pub package lightweight, tested precompiled static and shared libraries are hosted in the companion repository flutter_soloud_prebuilds.
By default, Dart Native Assets build hooks automatically download the matching prebuilt archive on first build and cache it under .dart_tool/flutter_soloud/xiph/prebuild/:
- Android: 4 ABIs (
arm64-v8a, armeabi-v7a, x86, x86_64)
- iOS: Universal
arm64 device + arm64/x86_64 simulator static libraries
- macOS: Universal Apple Silicon (
arm64) + Intel (x86_64) static libraries
- Windows:
x64 and arm64 DLLs and import libraries
- Linux:
x86_64 and aarch64 shared .so libraries
This ensures that packaged apps (Android AAB/APK, iOS IPA, macOS APP, Windows EXE) work out of the box with zero external build dependencies.
You can customize this in the app's pubspec.yaml under hooks.user_defines.flutter_soloud:
Pin a specific prebuild release version (default is latest):
hooks:
user_defines:
flutter_soloud:
prebuild_tag: 'v1.0.1' # Optional: pin to a specific release tag
Link against system-installed packages (desktop only):
hooks:
user_defines:
flutter_soloud:
linux_use_system_libs: true
macos_use_system_libs: true
windows_use_system_libs: true
Force building from source via CMake into .dart_tool/:
hooks:
user_defines:
flutter_soloud:
linux_force_build_libs: true
macos_force_build_libs: true
windows_force_build_libs: true
android_force_build_libs: true
ios_force_build_libs: true
Exclude Xiph libraries entirely (shrinking binary size):
hooks:
user_defines:
flutter_soloud:
no_xiph_libs: true
When excluded, setBufferStream()/readSamplesFrom*() with Opus/Vorbis/FLAC throw SoLoudXiphLibsNotAvailableException; WAV, MP3, and synthesis work normally. On web, edit web/compile_wasm.sh in the package (NO_XIPH_LIBS="1") and rebuild the WASM yourself (requires Emscripten, Linux/macOS).
Logging
The plugin logs via package:logging (logger name flutter_soloud.SoLoud). Nothing is printed until you attach a listener:
import 'package:logging/logging.dart';
import 'dart:developer' as dev;
Logger.root.level = Level.FINE;
Logger.root.onRecord.listen((r) => dev.log(r.message, name: r.loggerName));
More depth
- references/web.md — the two WASM builds, COOP/COEP headers, dev-server pitfalls, CORS.
- references/audio_context.md — background playback with
audio_service + audio_session (media notification, lock-screen controls, ducking), with required iOS/Android manifest changes.
- Demos:
example/lib/output_device/output_device.dart (device enumeration and switching), example/lib/audio_context/audio_context.dart (full audio_service integration), example/lib/main.dart (minimal init/play).
Keeping this skill current
This skill ships inside the flutter_soloud package, so upgrading flutter_soloud can carry a newer revision of it than the copy installed in the project. To check, run:
dart run flutter_soloud:skills --check
It reports the installed and bundled skill versions and exits non-zero when an update is available. Offer to update with dart run flutter_soloud:skills (which touches only the skills, never pubspec or build files).
1---2name: flutter-soloud-setup3description: Teaches how to add flutter_soloud to a Flutter app, configure each platform (web script tag and COOP/COEP headers, Linux ALSA, Android/iOS/macOS minimum versions), initialize and deinitialize the engine, shrink binaries by excluding the Xiph libs, set up logging, and enumerate/switch output devices. Use when a user asks to install flutter_soloud, initialize SoLoud, set up web/background-audio prerequisites, reduce binary size, or switch the audio output device.4---56# flutter_soloud setup78flutter_soloud is an FFI plugin around the SoLoud C++ engine: the native code is compiled automatically by Dart build hooks when you depend on the package, so setup is mostly pubspec + a few platform bits (one `<script>` tag on web, ALSA dev package on Linux, minimum SDK versions). Unlike audioplayers/just_audio there is no per-player instance — everything goes through the singleton `SoLoud.instance`, which must be `init()`ed before use and `deinit()`ed on shutdown.910## Minimal example1112```dart13import 'package:flutter_soloud/flutter_soloud.dart';1415Future<void> main() async {16 // Optional: pick the output device before init.17 final devices = SoLoud.instance.listPlaybackDevices(); // works pre-init1819 await SoLoud.instance.init(20 // device: devices.firstWhere((d) => d.isDefault),21 sampleRate: 44100,22 bufferSize: 2048,23 channels: Channels.stereo,24 lowLatency: true,25 automaticCleanup: false,26 );2728 final sound = await SoLoud.instance.loadAsset('assets/audio/click.mp3');29 SoLoud.instance.play(sound);3031 // Later, on shutdown:32 SoLoud.instance.deinit();33}34```3536## Adding the package3738```sh39flutter pub add flutter_soloud40```4142Native C/C++ sources are compiled by [Dart build hooks](https://dart.dev/tools/hooks) (`hooks`/`code_assets`/`native_toolchain_c` are transitive deps of the package). No CMake, CocoaPods script phases, or compiler flags are needed.4344## Platform setup4546- **Web** — add to the `<body>` of `web/index.html`:47 ```html48 <script src="assets/packages/flutter_soloud/web/init_soloud.js" defer></script>49 ```50 The script auto-picks between the multi-threaded (AudioWorklet) and single-threaded (ScriptProcessorNode) WASM builds based on whether the page is cross-origin isolated. Details in [references/web.md](references/web.md).51- **Linux** — requires ALSA headers: `sudo apt-get install libasound2-dev` (Debian/Ubuntu), `pacman -S alsa-lib` (Arch), `zypper install alsa-devel` (openSUSE).52- **Android** — `minSdk = 21` (the plugin sets this in its own `build.gradle`; your app-level `minSdkVersion` must be >= 21).53- **iOS** — deployment target iOS 13.0+; **macOS** — 10.15+. Native assets are compiled and bundled for both CocoaPods and SPM projects.5455## The API shape5657All of these live on the singleton `SoLoud.instance` (`import 'package:flutter_soloud/flutter_soloud.dart'`).5859- `Future<void> init({PlaybackDevice? device, bool automaticCleanup = false, int sampleRate = 44100, int bufferSize = 2048, Channels channels = Channels.stereo, bool lowLatency = true, AndroidAAudioAttributes androidAAudioAttributes = AndroidAAudioAttributes.mediaMusic, int? devicePeriodFrames, int? renderAheadFrames})` — initializes the engine. **Throws on failure** (e.g. `SoLoudCppException`, `SoLoudNoPlaybackDevicesFoundCppException`); it does not return a `PlayerErrors` status, so `await` it in try/catch.60- `void deinit()` / `Future<void> deinitAsync()` — stops the engine and disposes all resources including sounds. `deinit` blocks the calling thread; prefer `deinitAsync` where you can await it.61- `bool get isInitialized` — synchronous readiness check.62- `List<PlaybackDevice> listPlaybackDevices()` — **safe to call before `init()`**. Returns `PlaybackDevice(id, isDefault, name)`.63- `Future<void> changeDevice({PlaybackDevice? newDevice})` — switches output while running; omit `newDevice` to select the system default. Await it — the swap runs off the UI isolate.64- `Future<void> stopAudioDevice({bool force = false})` / `Future<void> startAudioDevice()` — stop/start only the output device; loaded sounds, voices, and filter state are preserved and playback resumes where it left off.65- `AudioDeviceState getAudioDeviceState()` — cheap sync read: `uninitialized | stopped | started | starting | stopping`. Safe before `init()`.66- `void setAudioDeviceIdleTimeout(Duration? timeout)` — when no unpaused voices remain: `Duration.zero` stops the device ASAP, a positive duration keeps it alive that long (default 500 ms), `null` keeps it running indefinitely (Android wakelock). No effect on web.6768Divergences from what models trained on audioplayers/just_audio assume:6970- One global engine, no `AudioPlayer()` instances. Call `SoLoud.instance.init()` once, early; every other call throws `SoLoudNotInitializedException` before that.71- `play()` is synchronous and returns a `SoundHandle` immediately — it cannot report device-start failures; subscribe to `SoLoud.instance.audioDeviceStartFailures` for those.72- `init()` while already initialized **deinitializes and reinitializes**, stopping all sounds and unloading all files.73- `init()` options that are **native-only** (silently ignored on web): `lowLatency`, `androidAAudioAttributes` (Android-only, and only when `lowLatency: false`), `devicePeriodFrames`, `renderAheadFrames`.74- `automaticCleanup: true` makes the engine purge its temp directory of loaded sound files occasionally — relevant for apps that load many files from the network.7576### Render-Ahead Ring (ultra-low latency with large mix buffers)7778On native platforms (Android, iOS, macOS, Windows, Linux), you can decouple the hardware output period from the engine mix buffer by setting `renderAheadFrames > 0`:7980```dart81await SoLoud.instance.init(82 bufferSize: 2048, // Large DSP/mixing quantum for CPU headroom83 devicePeriodFrames: 512, // Hardware device callback period (~11 ms @ 44.1kHz)84 renderAheadFrames: 1536, // Mix-ahead depth (e.g. bufferSize - devicePeriodFrames)85);86```8788- **How it works**: The engine pre-mixes audio `renderAheadFrames` ahead into an internal ring buffer. When reactive calls like `play()` or `playScheduled()` occur, audio is mixed **retroactively** into the not-yet-played section of the ring, giving near-instantaneous keypress-to-sound latency (~11 ms) without risking audio underruns from tiny mix buffers.89- **Inspection getters**:90 - `bool get isRenderAheadEnabled` — true when enabled on native platforms.91 - `Duration getPlayheadTime()` — true playhead time reaching the speaker right now (equals `getEngineTime()` when disabled or on web).92 - `Duration getOutputLatency()` — estimated output latency (ring depth + device period; `Duration.zero` when disabled or on web).93- **Caveats**: Ignored on web. Ended-voice callbacks may fire up to `renderAheadFrames` earlier than without the ring. Unseekable/un-snapshotable streams (released push streams, pull streams, `speechText`) degrade gracefully to standard buffer boundaries.9495## Traps9697- **Don't await a return code from `init()`.** It returns `Future<void>` and throws; older docs suggest a `PlayerErrors` return. Trust the code.98- Calling `init()` again (e.g. after hot restart) wipes all loaded sounds — guard with `isInitialized` if you only want to init once.99- **Web without the `<script>` tag fails at `init()`** with confusing WASM-module errors. The tag must be present, and the WASM assets only load from `assets/packages/flutter_soloud/web/…`.100- **Web: don't pass `--web-header` COOP/COEP flags together with `flutter run --wasm`** — the dev server already sends COOP/COEP for WasmGC and the conflicting duplicated headers block the plugin's worker threads (`ERR_BLOCKED_BY_RESPONSE`). See [references/web.md](references/web.md).101- **`changeDevice` is desktop-mostly**: Android, iOS, and Web support only the default output device; `listPlaybackDevices()` there returns just the default.102- A device stopped via `stopAudioDevice()` or the idle timeout **stays stopped across `changeDevice()`** — the replacement device only starts if the old one was running.103- **Linux build fails with `alsa/asoundlib.h: No such file`** — install `libasound2-dev`; the error is from the native build hook, not Dart.104- On web, `loadUrl()` hits CORS (`Access-Control-Allow-Origin` missing) unless the server allows it, and local files can't be read — use `loadMem()` instead.105- Per-sound filters are not supported on web (global filters are).106107## Xiph audio libraries (Ogg, Vorbis, Opus, FLAC)108109The Xiph audio decoders provide compressed audio playback, streaming, and master output capture (600–3000 KB per binary). To keep the pub package lightweight, tested precompiled static and shared libraries are hosted in the companion repository [flutter_soloud_prebuilds](https://github.com/alnitak/flutter_soloud_prebuilds).110111By default, Dart Native Assets build hooks automatically download the matching prebuilt archive on first build and cache it under `.dart_tool/flutter_soloud/xiph/prebuild/`:112- **Android**: 4 ABIs (`arm64-v8a`, `armeabi-v7a`, `x86`, `x86_64`)113- **iOS**: Universal `arm64` device + `arm64`/`x86_64` simulator static libraries114- **macOS**: Universal Apple Silicon (`arm64`) + Intel (`x86_64`) static libraries115- **Windows**: `x64` and `arm64` DLLs and import libraries116- **Linux**: `x86_64` and `aarch64` shared `.so` libraries117118This ensures that packaged apps (Android AAB/APK, iOS IPA, macOS APP, Windows EXE) work out of the box with zero external build dependencies.119120You can customize this in the **app's** `pubspec.yaml` under `hooks.user_defines.flutter_soloud`:121122- **Pin a specific prebuild release version (default is `latest`):**123 ```yaml124 hooks:125 user_defines:126 flutter_soloud:127 prebuild_tag: 'v1.0.1' # Optional: pin to a specific release tag128 ```129130- **Link against system-installed packages (desktop only):**131 ```yaml132 hooks:133 user_defines:134 flutter_soloud:135 linux_use_system_libs: true136 macos_use_system_libs: true137 windows_use_system_libs: true138 ```139140- **Force building from source via CMake into `.dart_tool/`:**141 ```yaml142 hooks:143 user_defines:144 flutter_soloud:145 linux_force_build_libs: true146 macos_force_build_libs: true147 windows_force_build_libs: true148 android_force_build_libs: true149 ios_force_build_libs: true150 ```151152- **Exclude Xiph libraries entirely (shrinking binary size):**153 ```yaml154 hooks:155 user_defines:156 flutter_soloud:157 no_xiph_libs: true158 ```159 When excluded, `setBufferStream()`/`readSamplesFrom*()` with Opus/Vorbis/FLAC throw `SoLoudXiphLibsNotAvailableException`; WAV, MP3, and synthesis work normally. On web, edit `web/compile_wasm.sh` in the package (`NO_XIPH_LIBS="1"`) and rebuild the WASM yourself (requires Emscripten, Linux/macOS).160161162## Logging163164The plugin logs via `package:logging` (logger name `flutter_soloud.SoLoud`). Nothing is printed until you attach a listener:165166```dart167import 'package:logging/logging.dart';168import 'dart:developer' as dev;169170Logger.root.level = Level.FINE;171Logger.root.onRecord.listen((r) => dev.log(r.message, name: r.loggerName));172```173174## More depth175176- [references/web.md](references/web.md) — the two WASM builds, COOP/COEP headers, dev-server pitfalls, CORS.177- [references/audio_context.md](references/audio_context.md) — background playback with `audio_service` + `audio_session` (media notification, lock-screen controls, ducking), with required iOS/Android manifest changes.178- Demos: `example/lib/output_device/output_device.dart` (device enumeration and switching), `example/lib/audio_context/audio_context.dart` (full `audio_service` integration), `example/lib/main.dart` (minimal init/play).179180## Keeping this skill current181182This skill ships inside the flutter_soloud package, so upgrading flutter_soloud can carry a newer revision of it than the copy installed in the project. To check, run:183184```sh185dart run flutter_soloud:skills --check186```187188It reports the installed and bundled skill versions and exits non-zero when an update is available. Offer to update with `dart run flutter_soloud:skills` (which touches only the skills, never pubspec or build files).