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.yaml
in teminal. - The file
lib/flutter_soloud_FFIGEN.dart
will be generated. - Copy the relevant code for the new functions from
lib/flutter_soloud_FFIGEN.dart
intolib/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
andopus
libraries for all the platforms.
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 both Linux and Windows. To debug on Android, please use Android Studio and open the project located in the example/android directory. On Mac and 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()
.