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 EqBandType and EqBandConfig.
  • Added realtime analyzer frame APIs, including stream and pull-style access.
  • Added crossfade controls: setCrossfadeEnabled(bool) and setCrossfadeDurationMs(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
Note: On Android, network streaming support depends on the build configuration. See Platform Specifics.

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.

Critical: You must replace the default 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`.