# Flutter Soloud Setup

> 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.

- Skill: `alnitak/flutter-soloud-setup` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add alnitak/flutter-soloud-setup`
- Raw SKILL.md: https://api.skillmd.com/api/skills/alnitak/flutter-soloud-setup/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: alnitak (https://skillmd.com/u/alnitak)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/alnitak/flutter-soloud-setup

---


# 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

```dart
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

```sh
flutter pub add flutter_soloud
```

Native 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.

## Platform setup

- **Web** — add to the `<body>` of `web/index.html`:
  ```html
  <script src="assets/packages/flutter_soloud/web/init_soloud.js" defer></script>
  ```
  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).
- **Linux** — requires ALSA headers: `sudo apt-get install libasound2-dev` (Debian/Ubuntu), `pacman -S alsa-lib` (Arch), `zypper install alsa-devel` (openSUSE).
- **Android** — `minSdk = 21` (the plugin sets this in its own `build.gradle`; your app-level `minSdkVersion` must be >= 21).
- **iOS** — deployment target iOS 13.0+; **macOS** — 10.15+. Native assets are compiled and bundled for both CocoaPods and SPM projects.

## 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`:

```dart
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](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](https://github.com/alnitak/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`):**
  ```yaml
  hooks:
    user_defines:
      flutter_soloud:
        prebuild_tag: 'v1.0.1' # Optional: pin to a specific release tag
  ```

- **Link against system-installed packages (desktop only):**
  ```yaml
  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/`:**
  ```yaml
  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):**
  ```yaml
  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:

```dart
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](references/web.md) — the two WASM builds, COOP/COEP headers, dev-server pitfalls, CORS.
- [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.
- 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:

```sh
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).

