--- name: flutter-soloud-setup version: 4 description: Teaches how to add flutter_soloud to a Flutter app, configure each platform (web script tag and COOP/COEP headers, Linux audio backends ALSA/PulseAudio/JACK, 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 or Linux audio backends. Use when a user asks to install flutter_soloud, initialize SoLoud, set up web/background-audio prerequisites, configure Linux audio backends, reduce binary size, or switch the audio output device. --- # 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 ` ``` 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** — audio playback uses `miniaudio` with dynamic runtime loading for ALSA (`libasound.so.2`), PulseAudio (`libpulse.so`), and JACK (`libjack.so`). No extra C/C++ development packages or compile-time headers are required to build. Ensure runtime libraries are present on the host system (e.g. `libasound2`, `libpulse0`). To decode extended OS-native formats (**M4A**, **MP4 audio tracks**, **AAC**, **AC-3**, and **E-AC-3**), the engine dynamically loads FFmpeg shared libraries (`libavcodec` and `libavformat`) at runtime; ensure FFmpeg is installed on the user system (e.g. `sudo apt install ffmpeg libavcodec-extra` on Debian/Ubuntu, `sudo pacman -S ffmpeg` on Arch, `sudo dnf install ffmpeg-free` on Fedora). Core formats (MP3, WAV, OGG, FLAC) work without FFmpeg. - **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 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, LinuxAudioBackend linuxAudioBackend = LinuxAudioBackend.auto})` — 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 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 listPlaybackDevices()` — **safe to call before `init()`**. Returns `PlaybackDevice(id, isDefault, name)`. - `Future changeDevice({PlaybackDevice? newDevice})` — switches output while running; omit `newDevice` to select the system default. Await it — the swap runs off the UI isolate. - `Future setLinuxAudioBackend(LinuxAudioBackend backend)` — Linux only: selects or dynamically switches the audio backend (`LinuxAudioBackend.auto` [ALSA -> PulseAudio -> JACK], `.alsa`, `.pulseAudio`, `.jack`). Safe to call before `init()` or while the engine is running. - `Future stopAudioDevice({bool force = false})` / `Future 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` 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 `