To use native code, bindings from Dart to C/C++ are generated from the header file src/bindings.h using package:ffigen. src/bindings.h is the single source of truth for the native ABI.
To regenerate Dart bindings:
- Declare your new native C/C++ functions with
FFI_PLUGIN_EXPORTinsrc/bindings.hand implement them insrc/bindings.cpp.
Run in terminal:
dart run ffigen --config ffigen.yaml(On macOS if system headers are missing: CPATH="$(xcrun --show-sdk-path)/usr/include" dart run ffigen --config ffigen.yaml)
- The bindings will be automatically placed into
lib/src/bindings/flutter_soloud_ffigen.dart.
- Wrap and expose the generated functions in
lib/src/bindings/bindings_player_ffi.dartandlib/src/soloud.dart.
This plugin uses the following structure:
lib: Contains the Dart API code and bindings.src: Contains the native C/C++ source code, filters, audio buffers, and SoLoud engine.src/bindings.h: The single source of truth and ffigen entry point for the native C ABI.src/soloud: Contains the SoLoud sources of the fork.hook: Contains the Dart build hooks (hook/build.dart,hook/sources.dart,hook/xiph.dart) that compile native code automatically for Android, iOS, macOS, Windows, and Linux into Code Assets.web: Contains the scripts and assets for compiling and initializing the WebAssembly build on the Web platform.xiph: Contains the prebuilt libraries and scripts forogg,opus,vorbis, andflac.
The flutter_soloud plugin utilizes a forked repository of SoLoud, where the miniaudio audio backend is used by default.
I have provided the necessary settings in the .vscode directory for debugging native C++ code on Linux, macOS, and Windows. To debug on Android, please use Android Studio and open the project located in the example/android directory. On iOS, please use Xcode.
When debugging the package using the example/ app, you might want to change the logging level to something more granular. For example, in main():
// Capture even the finest log messages.
Logger.root.level = Level.ALL;- Apple (macOS/iOS): Uses
miniaudiowith CoreAudio / AVFAudio. - Linux: Uses
miniaudiowith ALSA (MA_NO_PULSEAUDIOis enabled by default to prevent stutters). - Windows: Uses
miniaudiowith WASAPI. - Android: Uses
miniaudio, which automatically selects AAudio on Android 11.0+ or OpenSL|ES on older versions.
The default audio backend is miniaudio, which will automatically select the appropriate audio backend based on your Android version:
- AAudio with Android 11.0 and newer.
- OpenSL|ES for older Android versions.
In the web directory, there is a compile_wasm.sh script that generates the .js and .wasm files for the native C code located in the src dir. Run it after installing emscripten. There is also a compile_worker_and_init_module.sh to compile the web worker needed by native code to communicate with Dart and the init_soloud.dart which initializes the WASM module. The default emscripten Module name is Module_soloud instead of the default Module to prevent some other WASM plugins from conflicting.
The generated files are already provided, but if it is needed to modify C/C++ code or the web/worker.dart code, the scripts must be run to reflect the changes.
The compile_wasm.sh script uses the -O3 code optimization flag. To see a better errors logs, use -O0 -g -s ASSERTIONS=1 in compile_wasm.sh.
The AudioIsolate has been removed and all the logic has been implemented natively. Events like voice ended are sent from C back to Dart. However, since it is not possible to call Dart from a native thread (the audio thread), a new web worker is created using the WASM EM_ASM directive. This allows sending the voice ended event back to Dart via the worker.

#1. This function is called while initializing the player with FlutterSoLoudWeb.setDartEventCallbacks().
It creates a Web Worker in the WASM Module using the compiled web/worker.dart. After calling this, the WASM Module will have a new variable called Module_soloud.wasmWorker which will be used in Dart to receive messages.
By doing this it will be easy to use the Worker to send messages from within the CPP code.
#2. This function, like #1, uses EM_ASM to inline JS. This JS code uses the Module_soloud.wasmWorker created in #1 to send a message.
#3. This is the JS used and created in #1. Every messages sent by #2 are managed here and sent to #4.
#4. Here when the event message has been received, a new event is added to a Stream. This Stream is listened by the SoLoud API.
#5. Here we listen to the event messages coming from the WorkerController stream. Currently, only the "voice ended" event is supported. The Stream is listened in SoLoud._initializeNativeCallbacks().