This package relies on flutter_background_service. This documentation will walk through how the example app was configured. Refer to the flutter_background_service
package for the most accurate information.
Outlined below is the configuration used in the example app to enable the timer to execute in the background and play audio on Android.
- Add the following
<uses-permission>
and<service>
to android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<application
...>
<service
android:name="id.flutter.flutter_background_service.BackgroundService"
android:foregroundServiceType="mediaPlayback">
</service>
<activity
...>
</activity>
...
</application>
</manifest>
- On a production release, you may need to request the
SCHEDULE_EXACT_ALARM
permission from the user. This was left out of the example as when and how to request permissions is left up to the developer. You can use the permission_handler package:
if (Platform.isAndroid) {
await Permission.scheduleExactAlarm.isDenied.then((value) {
if (value) {
Permission.scheduleExactAlarm.request();
}
});
}
- Add the following to
ios/Runner/Info.plist
:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>dev.flutter.background.refresh</string>
</array>
- The following background modes will also need to be added to
ios/Runner/Info.plist
(or check the boxes in XCode):
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>fetch</string>
<string>processing</string>
</array>
The timer uses a sqflite database to send data to the background service. Note that the frontend and the service cannot directly communicate - data must be passed through shared preferences, a database, etc. Stored in the database is the list of timer intervals for the service to iterate through. The database is cleared before storing the next set of data on service startup.
This package ships with some copyright free audio assets located at lib/assets/audio
Set what audio to use for an interval by specifying the asset file name (no need to include .mp3
, leave blank for no audio):
IntervalType(
...
startSound: "long-bell",
halfwaySound: "short-halfway-beep",
countdownSound: "countdown-beep",
endSound: ""),
There are four configurable audio cues for intervals outlined below:
Parameter Name | Description |
---|---|
startSound |
Plays at the start of an interval |
halfwaySound |
Plays at the halfway point of an interval. |
countdownSound |
Plays at the 3, 2, and 1 seconds marks to signify the interval is ending. |
endSound |
Plays at the end of an interval. If intervals are back to back, the start sound of the next interval takes precedence over the end sound of the previous interval. |
This package currently uses the soundpool package to play audio. The package does not configure an audio session, leaving it up to the developer to set the audio session as they see fit. The example uses the audio_session package to configure the audio session so that audio from the service will play on both iOS and Android when the app is in the background. The session is also configured to not stop or duck audio from other apps.
- You can create a function to initialize an audio session with your desired settings. The following function sets the session configuration for the behavior in the example app described above:
Future<void> initializeAudioSession() async {
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration(
avAudioSessionCategory: AVAudioSessionCategory.playback,
avAudioSessionCategoryOptions:
AVAudioSessionCategoryOptions.mixWithOthers,
avAudioSessionMode: AVAudioSessionMode.defaultMode,
avAudioSessionRouteSharingPolicy:
AVAudioSessionRouteSharingPolicy.defaultPolicy,
avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none,
androidAudioAttributes: AndroidAudioAttributes(
contentType: AndroidAudioContentType.sonification,
flags: AndroidAudioFlags.audibilityEnforced,
usage: AndroidAudioUsage.notification,
),
androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
androidWillPauseWhenDucked: true,
));
}
Without managing the audio session, the timer background service may not play audio when the app is in the background.
The timer service will send back some data to the frontend:
Variable Name | Type | Description |
---|---|---|
paused |
Boolean | Whether the timer is currently paused. Passed to the service via shared preferences. |
status |
String | Name of the current interval. |
currentInterval |
Integer | Index of the current active interval, zero being the first and so on. |
currentMicroSeconds |
Integer | Time in microseconds that has currently passed for the active interval. |
currentMicroSeconds |
Integer | Full amount of time in microseconds for the active interval. |
volume |
double | The volume at which to play timer sound effects. Passed to the service via shared preferences. |
changeVolume |
boolean | Used to update the UI to show volume controls. Passed to the service via shared preferences. |
This data can then be used to determine what to show in the UI, for example showing the currentMicroSeconds:
build: (_, TimerState timerState) {
Text(timerState.currentMicroSeconds.toString())
}
On iOS, the background service will automatically be killed if sound has not played recently. To remediate this, the timer will play a blank audio file every second a sound effect does not play. On Android, this is not necessary to keep the service alive. However, the sound effects can have some delay on Android when the timer first starts. Playing blank audio eliminates this delay. Thus, the blank audio is fired on both iOS and Android for different reasons.