audio_in_app

A Flutter package for playing audio files. Ideal for games or applications with sound.

Supports Android, iOS, macOS, Windows, Linux, and Web.

Getting started and Usage

1 - We import the Audio_in_app package.

import 'package:audio_in_app/audio_in_app.dart';

2 - We create a variable of the AudioInApp class.

AudioInApp _audioInApp = AudioInApp();

3 - We load the audios that we are going to use later with the createNewAudioCache() method. Here we will have to insert an id to call it later of type String, the path is of type String where the audio being inside the "assets" folder and the type of audio, punctual or background. (See following examples).

3.1 - If it is a punctual audio (used in button presses, character jumps or shots).

await _audioInApp.createNewAudioCache(playerId: 'button', route: 'audio/button.wav', audioInAppType: AudioInAppType.determined);

3.2 - If it is background audio (used during gameplay). Background audio has several differences from spot audio. The differences are as follows.

  • Background audio plays infinitely in a loop until you decide to stop it.
  • Multiple background audios can play simultaneously with independent volume control.
  • Use stopBackground() to stop all background audios at once, or stop(playerId:) to stop a specific one.
await _audioInApp.createNewAudioCache(playerId: 'intro1', route: 'audio/intro_1.wav', audioInAppType: AudioInAppType.background);

4 - Play audio.

await _audioInApp.play(playerId: 'button');

or

await _audioInApp.play(playerId: 'intro1');

5 - Stop a specific background audio.

await _audioInApp.stopBackground(playerId: 'intro1');

6 - Stop all background audios at once.

await _audioInApp.stopBackground();

7 - Change volume of a specific audio (0.0 to 1.0).

await _audioInApp.setVol('intro1', 0.5);

Load audio from a local file

Since 4.1.0 you can play a file that lives on the device filesystem (downloaded, recorded, etc.) instead of a bundled asset. Pass source: AudioInAppSource.file and an absolute path as route. This is non-breaking: source defaults to AudioInAppSource.asset, so any existing call keeps loading from assets exactly as before.

// 'route' must be an ABSOLUTE path to a local file on the device.
await _audioInApp.createNewAudioCache(
  playerId: 'note',
  route: '/data/user/0/com.example.app/files/note.m4a',
  audioInAppType: AudioInAppType.determined,
  source: AudioInAppSource.file,
);

await _audioInApp.play(playerId: 'note');

Web: loading from a local file is not supported on Web (it relies on SoLoud.loadFile, which is unavailable there). On Web, source: AudioInAppSource.file fails gracefully and createNewAudioCache returns false — use bundled assets on Web.

Once cached, you can play it as many times as you want, exactly like an asset (the source only matters when caching).

Bundled assets keep the same call as always (no source needed):

await _audioInApp.createNewAudioCache(
  playerId: 'button',
  route: 'audio/button.wav',
  audioInAppType: AudioInAppType.determined,
);

Check if an audio is still playing

Since 4.1.0 you can ask whether the last started voice for a playerId is still sounding with isPlaying(playerId). It returns false once the voice has finished, which is handy to detect the end of a one-shot (determined) sound, e.g. to reset a play button.

final bool stillPlaying = _audioInApp.isPlaying('note');
if (!stillPlaying) {
  // The one-shot finished: update your UI here.
}

Background channels (crossfade)

Since 4.2.0 you can group background audio into logical channels. A channel plays one track at a time and crossfades smoothly when you switch tracks — ideal for game music that reacts to state (calm / tension / danger) or for ambient weather that changes without an abrupt cut. Each channel is backed by a real flutter_soloud mixing bus, so its master volume and fades are handled natively.

This is fully additive: the existing API (play, stop, stopBackground, setVol) is unchanged. Channels are a separate, opt-in layer.

1 - Create a channel once (idempotent; calling it again just updates its volume):

await _audioInApp.createChannel(channelId: 'music', volume: 0.8);

2 - Play a cached background track into the channel. With no transition it switches instantly; with a transitionDuration the outgoing and incoming tracks crossfade:

// Instant.
await _audioInApp.playChannel(channelId: 'music', playerId: 'calm');

// 2-second crossfade to another track.
await _audioInApp.playChannel(
  channelId: 'music',
  playerId: 'tension',
  transitionDuration: const Duration(seconds: 2),
);

The returned Future completes once the transition is safely started, not after the whole fade. Rapid changes (calm → tension → danger) are handled correctly: only the last requested track survives and the others are cleaned up.

3 - Master volume of the channel, without cancelling any ongoing crossfade:

await _audioInApp.setChannelVolume(channelId: 'music', volume: 0.5);

4 - Pause / resume the whole channel (composes with the automatic background/foreground pause):

await _audioInApp.pauseChannel(channelId: 'music');
await _audioInApp.resumeChannel(channelId: 'music');

5 - Stop the channel. With a fadeOutDuration the tracks fade out; the channel stays reusable afterwards:

await _audioInApp.stopChannel(
  channelId: 'music',
  fadeOutDuration: const Duration(milliseconds: 800),
);

6 - Queries:

final String? active = _audioInApp.activePlayerIdInChannel('music'); // current target or null
final bool playing = _audioInApp.isChannelPlaying('music');
final bool paused = _audioInApp.isChannelPaused('music');

Notes:

  • Only cached background audio can be played into a channel.
  • The per-track volume set with setVol(playerId, v) still applies inside a channel; the channel volume and the track volume multiply together.
  • stop, stopBackground(playerId:), setVol and isPlaying also reach voices playing inside channels, so the same playerId can safely play both as a plain background and inside a channel at the same time.
  • Invalid arguments (empty id, volume outside 0..1, negative duration) throw ArgumentError; engine/operational errors return false.

Example

There is a basic example in the example folder of the project.
But here we add a quick example based on the example that is in the project.


1 - In this case we have a loading page, where we wait 1.5 seconds and then proceed to load all the audios that are going to be used on the next page.

import 'package:audio_in_app/audio_in_app.dart';
import 'package:flutter/material.dart';

class LoadingActivity extends StatefulWidget {
  @override
  State<LoadingActivity> createState() => _LoadingActivityState();
}

class _LoadingActivityState extends State<LoadingActivity> {
  AudioInApp _audioInApp = AudioInApp();

  @override
  void initState() {
    super.initState();
    Future.delayed(Duration(milliseconds: 1500)).then((value) => goToMain());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text('Loading Activity'),
      ),
    );
  }

  Future<void> goToMain() async {
    await _audioInApp.createNewAudioCache(playerId: 'intro1', route: 'audio/intro_1.wav', audioInAppType: AudioInAppType.background);
    await _audioInApp.createNewAudioCache(playerId: 'intro2', route: 'audio/intro_2.wav', audioInAppType: AudioInAppType.background);
    await _audioInApp.createNewAudioCache(playerId: 'button', route: 'audio/button.wav', audioInAppType: AudioInAppType.determined);
    Navigator.pushReplacementNamed(context, 'main');
  }
}

2 - This would be the next page where these sounds will be used depending on the buttons pressed.

import 'package:audio_in_app/audio_in_app.dart';
import 'package:flutter/material.dart';

class MainActivity extends StatefulWidget {
  @override
  State<MainActivity> createState() => _MainActivityState();
}

class _MainActivityState extends State<MainActivity> {
  AudioInApp _audioInApp = AudioInApp();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
                margin: EdgeInsets.only(bottom: 30),
                child: Text('Main Activity')),
            OutlinedButton(
              onPressed: () => _audioInApp.play(playerId: 'intro1'),
              child: Text("Play background intro 1"),
            ),
            OutlinedButton(
              onPressed: () => _audioInApp.play(playerId: 'intro2'),
              child: Text("Play background intro 2"),
            ),
            OutlinedButton(
              onPressed: () => _audioInApp.stopBackground(playerId: 'intro1'),
              child: Text("Stop intro 1 only"),
            ),
            OutlinedButton(
              onPressed: () => _audioInApp.setVol('intro1', 0.3),
              child: Text("Lower volume intro 1"),
            ),
            OutlinedButton(
              onPressed: () => _audioInApp.play(playerId: 'button'),
              child: Text("Play Button Sound"),
            ),
            OutlinedButton(
              onPressed: () => _audioInApp.stopBackground(),
              child: Text("Stop all background sounds"),
            ),
          ],
        ),
      ),
    );
  }
}

Platform setup

Since 4.0.0 the audio engine is flutter_soloud (SoLoud C++ via FFI). Some platforms need extra setup:

Web

Add these two scripts to your web/index.html, inside <head>:

<script src="assets/packages/flutter_soloud/web/libflutter_soloud_plugin.js" defer></script>
<script src="assets/packages/flutter_soloud/web/init_module.dart.js" defer></script>

Linux

Install the ALSA development library:

sudo apt-get install libasound2-dev   # Debian/Ubuntu
# alsa-lib (Arch) · alsa-devel (openSUSE)

iOS / macOS

When creating release archives in Xcode, set the Runner target's Strip Style to Non-Global Symbols (Build Settings → Deployment), so the native FFI symbols are not stripped.

Android / Windows

No extra setup: the native engine is built automatically via CMake.

Additional information

This package uses the flutter_soloud engine under the hood and tries to make things easier for new users. Feel free to collaborate to add new features or improve part of the code currently created. Any help will be welcome.

Libraries

audio_in_app