Contribute
Learn how to contribute to flutter_soloud
Contribute
To use native code, bindings from Dart to C/C++ are needed. To avoid writing these manually, they are generated from the header file (src/ffi_gen_tmp.h) using package:ffigen and temporarily stored in lib/flutter_soloud_FFIGEN.dart. You can generate the bindings by running dart run ffigen.
Since I needed to modify the generated .dart file, I followed this workflow:
- Copy the C function declarations to be generated into
src/ffi_gen_tmp.h. - Run
dart run ffigen --config ffigen.yamlin teminal. - The file
lib/flutter_soloud_FFIGEN.dartwill be generated. - Copy the relevant code for the new functions from
lib/flutter_soloud_FFIGEN.dartintolib/flutter_soloud_bindings_ffi.dart.
Project structure
This plugin uses the following structure:
lib: Contains the Dart code that defines the API of the plugin relative to all platforms.src: Contains the native source code. Linux, Android and Windows have their own CmakeFile.txt file in their own subdir to build the code into a dynamic library.src/soloud: Contains the SoLoud sources of my forkweb: Contains the scripts to build the plugin module on the web platform.xiph: Contains the script to buildogg,opus,vorbis, andflaclibraries for all the platforms.
Each platform folders contains its own CMakeLists.txt file to build the code into a dynamic library. A common CMakeLists.txt file is stored in src/.
On macOS and iOS, the Swift Package Manager (SPM) can be used to build the plugin module. If SPM is not enabled, the build will be done using CocoaPods (using cmake).
The flutter_soloud plugin utilizes a forked repository of SoLoud, where the miniaudio audio backend is used by default.
Debugging
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.
Logging
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;
Linux
If you encounter any glitches, they might be caused by PulseAudio. To troubleshoot this issue, you can try disabling PulseAudio within the linux/src.cmake file. Look for the line add_definitions(-DMA_NO_PULSEAUDIO) and uncomment it (now it is the default behavior).
Android
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.
Web
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_module.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.
Here a sketch to show the step used:

#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().