Playing background audio
Flutter sounds open-architecture provides the ability to easily integrate a host of 3rd party plugins. We are strong advocates in keeping things simple (KISS) & where a robust solution already exists, we would rather use that than trying to re-invent the wheel. With this in mind, we have provided an an example within our demo app
that shows how to play background audio & more importantly - how the audio can be controlled from your phones lock screen.
Setting up your app to play background audio
The first thing you will want to do is to amend your pubspec.yaml
file and include a reference to audio service
:
audio_service: ^0.18.3
iOS - Amend info.plist
Next you will need to make some adjustments to your info.plist so that it supports background mode :
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
Android - Amend AndroidManifest.xml
For Android to work correctly - a number of important changes needs to be made in your AndroidManifest file (Note , the info below assumes your Android project is 1.12 , if your project is prior to that then you will need to update your project to follow the new Flutter Android project structure (https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects)
Add permissions
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
Change Activity name
android:name="com.ryanheise.audioservice.AudioServiceActivity"
Add the audio service & receiver intents
<service android:name="com.ryanheise.audioservice.AudioService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver" >
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
A completed AndroidManifest would look something like this
<manifest xmlns:tools="http://schemas.android.com/tools" ...>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application ...>
<activity android:name="com.ryanheise.audioservice.AudioServiceActivity" ...>
...
</activity>
<service android:name="com.ryanheise.audioservice.AudioService"
android:exported="true" tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver"
android:exported="true" tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>
Provide callbacks from the Lock Screen to your Audio Handler
We need a way for our code to handle lock-screen audio callbacks (Play, Pause, Stop, Previous etc) - The way we do this is to initialise the AudioService
and customize accordingly (Android is different to iOS), we also specify a plain vanila Dart class file that houses our callback code :
Future<AudioHandler> initAudioService() async {
return await AudioService.init(
builder: () => AudioPlayerHandler(),
config: AudioServiceConfig(
androidNotificationChannelId: 'com.fluttersound.audio',
androidNotificationChannelName: 'Flutter Sound Audio Service Demo',
/// The next two, specific to Android allows you to dismiss the Audio controls
androidNotificationOngoing: true,
androidStopForegroundOnPause: true,
),
);
}
The configuration for your AudioService
requires a completed AudioServiceConfig
. This belongs to the Audio Service Plugin
and allows you to easily and completely initialise iOS and Android :
AudioServiceConfig AudioServiceConfig({
bool androidResumeOnClick = true,
String? androidNotificationChannelId,
String androidNotificationChannelName = 'Notifications',
String? androidNotificationChannelDescription,
Color? notificationColor,
String androidNotificationIcon = 'mipmap/ic_launcher',
bool androidShowNotificationBadge = false,
bool androidNotificationClickStartsActivity = true,
bool androidNotificationOngoing = false,
bool androidStopForegroundOnPause = true,
int? artDownscaleWidth,
int? artDownscaleHeight,
Duration fastForwardInterval = const Duration(seconds: 10),
Duration rewindInterval = const Duration(seconds: 10),
bool preloadArtwork = false,
Map<String, dynamic>? androidBrowsableRootExtras,
})
package:audio_service/audio_service.dart
Creates a configuration object.
As you can see from the above snippet - you have full control for configuring Android.
We can see from the above initAudioService
method that the audio handler is AudioPlayerHandler
. This handler is what you will create and whereby you will ensure that it extends from BaseAudioHandler
. This class is provided by the AudioService
plugin - it will expect you to override base methods from this class so that you can control how your app behaves when control buttons like Pause, Play, Seek, Stop etc are triggered from your devices lock screen.
Ultimately you will simply override these base methods and delegate to your chosen AudioPlayer (Just Audio, Flutter Sound etc).
For the purpose of the demo, we have used JustAudio player within our AudioPlayerHandler
but with some changes this could also be amended to include the callback code for Play, Stop, Seek, Pause etc for any Player (Flutter Sound etc).
With respect to AudioPlayerHandler
- Here you will see how we override the base methods of BaseAudioHandler
:
@override
Future<void> play() => _useFlutterSound
? _flutterSoundAudioPlayer.play()
: _justAudioPlayer.play();
@override
Future<void> pause() => _useFlutterSound
? _flutterSoundAudioPlayer.pause()
: _justAudioPlayer.pause();
@override
Future<void> seek(Duration position) => _useFlutterSound
? _flutterSoundAudioPlayer.seekTo(position)
: _justAudioPlayer.seek(position);
@override
Future<void> stop() => _useFlutterSound
? _flutterSoundAudioPlayer.stop()
: _justAudioPlayer.stop();
From the above code fragment - we use a simple switch _useFlutterSound
to determine which player we wish to use and then delegate our callbacks accordingly.
Initialising your Audio Service
You are free to choose how you want to initialise your audio service (Provider, Get_x etc) but this should be one of the first things your app does. In our demo code, we have chosen to use a simple DI plugin flutter_simple_dependency_injection
and we initialise this in main()
as well as create our audio service :
Future<void> main() async {
/// Lets add some DI to this demo !
final injector = Injector();
/// We want Audio template to be able to run in the background
var audioService = await initAudioService();
injector.map((i) => audioService, isSingleton: true);
runApp(ExamplesApp());
}
This will then allow any code to reference audioservice
easily :
Play, Pause & Stop buttons
StreamBuilder<bool>(
stream: Injector()
.get<AudioHandler>()
.playbackState
.map((state) => state.playing)
.distinct(),
builder: (context, snapshot) {
final playing = snapshot.data ?? false;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_button(Icons.fast_rewind,
Injector().get<AudioHandler>().rewind),
if (playing)
_button(Icons.pause, Injector().get<AudioHandler>().pause)
else
_button(Icons.play_arrow,
Injector().get<AudioHandler>().play),
_button(Icons.stop, Injector().get<AudioHandler>().stop),
_button(Icons.fast_forward,
Injector().get<AudioHandler>().fastForward),
],
);
},
),
Summary
Integrating AudioService
into Flutter Sound consists of :
1- Adding the plugin in your pubspec.yaml
2- Amending your AndroidManifest.xml
3- Amending your info.plist
4- Creating an AudioHandler that descends from BaseAudioHandler
5- Providing implementation code for Play, Pause, Seek, Stop etc