Sautiflow
High-fidelity, cross-platform Dart/Flutter audio engine for audiophiles. Powered by miniaudio and native C++ FFI.
Zero Latency
Native C++ binding ensures instant playback and response times.
Gapless Playlist
Seamless transitions between tracks with advanced queue management.
Pro FX Chain
Built-in EQ suite, reverb, delay, low/high/band-pass, notch, peak EQ, and shelf filters.
Cross-Platform
Support for Windows, Linux, Android, iOS, and macOS.
Changes Made So Far
Quick implementation summary of the latest work in this package:
- Added mixed multiband FX chain API with per-band types via
EqBandTypeandEqBandConfig. - Added realtime analyzer frame APIs, including stream and pull-style access.
- Added crossfade controls:
setCrossfadeEnabled(bool)andsetCrossfadeDurationMs(int). - Updated sample-rate behavior so
setOutputSampleRate(0)uses native device rate mode. - Updated example app to test: mixed EQ mode, analyzer visualization, output format/rate/channels, and crossfade controls.
- Updated docs with implementation snippets for mixed multiband FX, realtime analyzer, crossfade, and output sample-rate native mode.
Installation
Add sautiflow to your pubspec.yaml:
flutter pub add sautiflow
Or manually:
dependencies:
sautiflow: ^0.2.0
Quick Start
Initialize the player, set a playlist, and start playing.
import 'package:sautiflow/sautiflow.dart';
void main() async {
final player = MiniAudioPlayer();
// 1. Initialize the engine
player.init();
// 2. Define playlist
final playlist = [
AudioSource.network('https://example.com/track1.mp3'),
AudioSource.file('/path/to/local/track2.mp3'),
];
// 3. Load and play
player.setAudioSources(playlist);
player.play();
// 4. Control playback
await Future.delayed(Duration(seconds: 5));
player.seekToNext();
}
Initialization
The MiniAudioPlayer is the main entry point.
init
bool init({int sampleRate, int channels, bool enableSystemAudio})Initializes the underlying C++ audio engine. Must be called before any other operation.
| Parameter | Type | Description |
|---|---|---|
| sampleRate | int | Output sample rate (default: 48000) |
| channels | int | Output channels (default: 2) |
| enableSystemAudio | bool | Integrate with OS media controls (Android/iOS) |
dispose
void dispose()Releases all native resources and stops the audio engine. Always call this when the player is no longer needed.
Playback Controls
play / pause / stop
bool play() / bool pause() / bool stop()Basic transport controls. Returns true if the operation was successful.
seek
bool seek(Duration position, {int? index})Seeks to a specific position. If index is provided, it jumps to that track in
the playlist first.
seekToNext / seekToPrevious
bool seekToNext() / bool seekToPrevious()Skips to the next or previous track in the playlist.
setVolume / setGain
void setVolume(double volume)Sets the master volume gain. 1.0 is unity gain.
Playlist Management
setAudioSources
bool setAudioSources(List<AudioSource> sources, ...)Replaces the current playlist with a new list of sources.
player.setAudioSources([
AudioSource.network('https://stream.url/audio.mp3'),
AudioSource.file('assets/music.mp3')
]);
Playlist Mutation
addAudioSource(AudioSource source): Append a track.insertAudioSource(int index, AudioSource source): Insert at position.removeAudioSourceAt(int index): Remove track.moveAudioSource(int from, int to): Reorder playback.clearAudioSources(): Clear playlist.
Modes
setLoopMode(LoopMode mode):off,all,one.setShuffleModeEnabled(bool enabled): Toggles shuffle.reshuffle(): Re-randomizes the shuffle order.
Player Status
You can listen to the player status stream for real-time UI updates.
StreamBuilder<PlayerStatus>(
stream: player.statusStream,
builder: (context, snapshot) {
final status = snapshot.data;
if (status == null) return CircularProgressIndicator();
return Column(
children: [
Text("Time: ${status.positionSeconds} / ${status.durationSeconds}"),
LinearProgressIndicator(
value: status.positionSeconds / status.durationSeconds
),
],
);
},
);
Equalizer (EQ)
Sautiflow includes a 3-band parametric EQ and a multiband equalizer.
3-Band EQ
player.setEqEnabled(true);
player.setEq(low: 1.2, mid: 0.8, high: 1.1);
Multiband EQ
Initialize with custom frequencies.
player.initMultibandEq([32, 64, 125, 250, 500, 1000, 2000, 4000, 8000, 16000]);
player.setMultibandEqEnabled(true);
player.setMultibandEqBandGain(0, 2.0); // Boost 32Hz by 2dB
Mixed Multiband FX (Peak + Shelf + Bandpass + Notch)
Build a single, ordered chain where each band can use a different filter type.
final bands = <EqBandConfig>[
EqBandConfig(type: EqBandType.lowshelf, frequencyHz: 120, gainDb: 4.0, slope: 1.0),
EqBandConfig(type: EqBandType.peak, frequencyHz: 1000, gainDb: 2.0, q: 1.2),
EqBandConfig(type: EqBandType.notch, frequencyHz: 60, q: 10.0),
EqBandConfig(type: EqBandType.bandpass, frequencyHz: 2500, q: 0.9),
EqBandConfig(type: EqBandType.highshelf, frequencyHz: 9000, gainDb: 2.5, slope: 1.0),
];
// Initialize and enable the mixed chain.
player.initMultibandFx(bands, enabled: true);
// Update all bands later (for dynamic user-added bands in UI).
player.setMultibandFxBands(bands);
// Toggle or clear.
player.setMultibandFxEnabled(false);
player.clearMultibandFx();
Realtime Analyzer Frames + fl_chart
Expose frame values as a stream and plug into any pub chart library (example below uses
fl_chart).
// pubspec.yaml (app)
// fl_chart: ^0.69.0
player.configureAnalyzer(frameSize: 1024);
player.setAnalyzerEnabled(true);
StreamSubscription? sub;
List<double> bins = List.filled(96, 0.0);
sub = player.analyzerStream.listen((frame) {
// Convert raw frame samples to chart bins.
const targetBins = 96;
final out = List<double>.filled(targetBins, 0.0);
for (var i = 0; i < targetBins; i++) {
final from = (i * frame.length / targetBins).floor();
final to = ((i + 1) * frame.length / targetBins).ceil();
var sum = 0.0;
var count = 0;
for (var j = from; j < to && j < frame.length; j++) {
sum += frame[j].abs();
count++;
}
out[i] = count > 0 ? sum / count : 0.0;
}
bins = out;
// setState(() {});
});
// Pull API also exists:
final latest = player.getLatestAnalyzerFrame();
Reverb
Add spatial depth to your audio.
player.setReverbEnabled(true);
player.setReverb(
mix: 0.3, // Wet/Dry mix (0.0 - 1.0)
feedback: 0.5, // Decay tail
delayMs: 50.0 // Pre-delay
);
Delay (Echo)
player.setDelay(
enabled: true,
mix: 0.4,
feedback: 0.6,
delayMs: 250.0 // Quarter second echo
);
Filters & Advanced EQ
Sautiflow provides a suite of professional filters for sound design and mixing.
// High-pass (Remove rumble)
player.setHighpass(enabled: true, cutoffHz: 120.0);
// Low-pass (Lo-fi effect)
player.setLowpass(enabled: true, cutoffHz: 3000.0);
// Band-pass (Telephone effect / Focus)
player.setBandpass(
enabled: true,
cutoffHz: 1000.0,
q: 1.0 // Q-factor (width)
);
// Notch Filter (Remove specific frequency)
player.setNotch(
enabled: true,
frequencyHz: 60.0, // Remove 60Hz hum
q: 10.0
);
// Peak EQ (Parametric Boost/Cut)
player.setPeakEq(
enabled: true,
frequencyHz: 2500.0,
gainDb: 3.0,
q: 1.0
);
// Low Shelf (Bass Boost)
player.setLowshelf(
enabled: true,
frequencyHz: 100.0,
gainDb: 4.0,
slope: 1.0
);
// High Shelf (Treble Boost)
player.setHighshelf(
enabled: true,
frequencyHz: 10000.0,
gainDb: 2.0,
slope: 1.0
);
Crossfade
Enable transition fades between tracks and control duration in milliseconds.
// Enable track transition fade.
player.setCrossfadeEnabled(true);
// Duration is clamped in native layer to 0..10000 ms.
player.setCrossfadeDurationMs(300);
// Disable if you want hard cuts.
player.setCrossfadeEnabled(false);
Output Format
Configure the audio engine output format.
// Set to Floating Point 32-bit (highest quality)
player.setOutputFormat(AudioFormat.f32);
// Set Sample Rate to 44.1kHz
player.setOutputSampleRate(44100);
// Use native device sample rate mode
player.setOutputSampleRate(0);
// Set to Mono
player.setOutputChannels(1);
Stream Pushing
For custom streaming implementations or raw data handling.
pushStream
Future<void> pushStream({required String url})Manually fetches chunks from a URL and pushes them to the engine. Useful for environments where native networking is restricted.
Android Full Setup
For a production-ready app with background playback, lockscreen controls, and notifications, follow this checklist.
1. Dependencies
Ensure your pubspec.yaml includes:
dependencies:
sautiflow: ^0.2.0
audio_session: ^0.1.21
audio_service: ^0.18.18
2. AndroidManifest.xml
Add the following permissions and components to
android/app/src/main/AndroidManifest.xml.
FlutterActivity with
AudioServiceActivity or Ensure your MainActivity extends it.
<manifest ...>
<!-- Required Permissions -->
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<!-- Android 13+ Notifications -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application ...>
<!-- Main Activity (Must use AudioServiceActivity) -->
<activity
android:name="com.ryanheise.audioservice.AudioServiceActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Audio Service -->
<service
android:name="com.ryanheise.audioservice.AudioService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<!-- Media Button Receiver -->
<receiver
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>
3. Dart Initialization
Initialize the audio session and system integration in your main() function.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. Configure Audio Session
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.music());
// 2. Initialize Player with System Audio
final player = MiniAudioPlayer();
await player.init(enableSystemAudio: true);
runApp(const MyApp());
}
4. Update Metadata & State
For the notification to show correct info, update the now playing info whenever the track changes.
await player.updateNowPlaying(
title: "Song Title",
artist: "Artist Name",
album: "Album Name",
duration: Duration(minutes: 3, seconds: 45),
// id, artwork can also be added if supported
);
Platform Specifics
Android Network Streaming
Native properties can be overridden in your `android/gradle.properties`:
MINIAUDIODART_ENABLE_CURL=ON
MINIAUDIODART_CURL_INCLUDE_DIR=/path/to/curl/include
MINIAUDIODART_CURL_LIBRARY=/path/to/libcurl.so
iOS / macOS
Linked statically. Ensure `ffiPlugin` is enabled in your `pubspec.yaml`.