A high-level real-time audio playback, generation and recording library based on miniaudio. The library offers basic functionality and quite low latency. Supports MP3, WAV and FLAC formats.
Platform | Tested | Supposed to work | Unsupported |
---|---|---|---|
Android | SDK 31, 19 | SDK 16+ | SDK 15- |
iOS | None | Unknown | Unknown |
Windows | 11, 7 (x64) | Vista+ | XP- |
macOS | None | Unknown | Unknown |
Linux | Fedora 39-40, Mint 22 | Any | None |
Web | Chrome 93+, Firefox 79+, Safari 16+ | Browsers with an AudioWorklet support |
Browsers without an AudioWorklet support |
There was some pretty major changes in 2.0.0 version, see the migration guide down below.
While the main script is quite large, there is a loader script provided. Include it in the web/index.html
file like this
<script src="assets/packages/minisound_web/build/minisound_web.loader.js"></script>
It is highly recommended NOT to make the script
defer
, as loading may not work properly. Also, it is very small (only 18 lines).
And at the bottom, at the body's <script>
do like this
// ADD 'async'
window.addEventListener('load', async function (ev) {
{{flutter_js}}
{{flutter_build_config}}
// ADD THIS LINE TO LOAD THE LIBRARY
await _minisound.loader.load();
// LEAVE THE REST IN PLACE
// Download main.dart.js
_flutter.loader.load({
serviceWorker: {
serviceWorkerVersion: {{flutter_service_worker_version}},
},
onEntrypointLoaded: function (engineInitializer) {
engineInitializer.initializeEngine().then(function (appRunner) {
appRunner.runApp();
});
},
});
}
);
Minisound
depends on SharedArrayBuffer
feature, so you should enable cross-origin isolation on your site.
To use this plugin, add minisound
as a dependency in your pubspec.yaml file.
// if you are using flutter, use
import "package:minisound/engine_flutter.dart" as minisound;
// and with plain dart use
import "package:minisound/engine.dart" as minisound;
// the difference is that flutter version allows you to load from assets, which is a concept specific to flutter
void main() async {
final engine = minisound.Engine();
// engine initialization
{
// you can pass `periodMs` as an argument, to change determines the latency (does not affect web). can cause crackles if too low
await engine.init();
// for web: this should be executed after the first user interaction due to browsers' autoplay policy
await engine.start();
}
// there is a base `Sound` interface that is implemented by `LoadedSound` (which reads data from a defined length memory location)
final LoadedSound sound;
// sound loading
{
// there are also `loadSoundFile` and `loadSound` methods to load sounds from file (by filename) and `TypedData` respectfully
final sound = await engine.loadSoundAsset("asset/path.ext");
// you can get and set sound's volume (1 by default)
sound.volume *= 0.5;
}
// playing, pausing and stopping
{
sound.play();
await Future.delayed(sound.duration * .5); // waiting while the first half plays
sound.pause();
// when sound is paused, `resume` will continue the sound and `play` will start from the beginning
sound.resume();
sound.stop();
}
// looping
{
final loopDelay = const Duration(seconds: 1);
sound.playLooped(delay: loopDelay); // sound will be looped with one second period
// btw, sound duration does not account loop delay
await Future.delayed((sound.duration + loopDelay) * 5); // waiting for sound to loop 5 times (with all the delays)
sound.stop();
}
// engine and sounds will be automatically disposed when gets garbage-collected
}
// you may want to read previous example first for more detailed explanation
import "package:minisound/engine_flutter.dart" as minisound;
void main() async {
final engine = minisound.Engine();
await engine.init();
await engine.start();
// `Sound` is also implemented by a `GeneratedSound` which is extended by `WaveformSound`, `NoiseSound` and `PulseSound`
// there are four types of a waveform: sine, square, triangle and sawtooth; the type can be changed later
final WaveformSound wave = engine.genWaveform(WaveformType.sine);
// and three types of a noise: white, pink and brownian; CANNOT be changed later
final NoiseSound noise = engine.genNoise(NoiseType.white);
// pulsewave is basically a square wave with a different ratio between high and low levels (which is represented by the `dutyCycle`)
final PulseSound pulse = engine.genPulse(dutyCycle: 0.25);
wave.play();
noise.play();
pulse.play();
// generated sounds have no duration, which makes sense if you think about it; for this reason they cannot be looped
await Future.delayed(const Duration(seconds: 1))
wave.stop();
noise.stop();
pulse.stop();
}
import "package:minisound/recorder.dart" as minisound;
void main() async {
// recorder records into memory using the wav format
final recorder = minisound.Recorder();
// recording format characteristics can be changed via this function params
recorder.init();
// just starts the engine
await recorder.start();
await Future.delayed(const Duration(seconds: 1));
// returns what've been recorded
final recording = await recorder.stop();
// all data is provided via buffer; sound can be used from it via `engine.loadSound(recording.buffer)`
print(recording.buffer);
// recordings will be automatically disposed when gets garbage-collected
}
-
Recording and generation APIs got heavily changed. See examples for new usage.
-
Sound autounloading logic got changed, now they depend on the sound object itself, rather than the engine.
// remove
// sound.unload();
As a result, when Sound
objects get garbage collected (which may be immediately after or not at the moment they go out of scope), they stop and unload. If you want to prevent this, you are probably doing something wrong, as this means you are creating an indefenetely played sound with no way to access it. Though this behaviour can still be disabled via the doAddToFinalizer
parameter to sound loading and generation methods of the Engine
class. However, it disables any finalization, so you'll need to manage Sound
s completely yourself. If you believe your usecase is valid, create a github issue and provide the code. Maybe it will change my mind.
- The main file (
minisound.dart
) becameengine_flutter.dart
.
// import "package:minisound/minisound.dart";
// becomes two files
import "package:minisound/engine_flutter.dart";
import "package:minisound/engine.dart";
A Makefile is provided with recipes to build the project and ease development. Type make help
to see a list of available commands.
To manually build the project, follow these steps:
-
Initialize the submodules:
git submodule update --init --recursive
-
Run the following commands to build the project using emcmake:
emcmake cmake -S ./minisound_ffi/src/ -B ./minisound_web/lib/build/cmake_stuff cmake --build ./minisound_web/lib/build/cmake_stuff
If you encounter issues or want to start fresh, clean the
build
folder and rerun the cmake commands:rm -rf * emcmake cmake -S ./minisound_ffi/src/ -B ./minisound_web/lib/build/cmake_stuff cmake --build ./minisound_web/lib/build/cmake_stuff
-
For development work, it's useful to run
ffigen
from theminisound_ffi
directory:cd ./minisound_ffi/ dart run ffigen