Reputation: 49
in my Flutter app I can play a list of radio stations, but if while the audio is playing, I put the app in the background and lock the screen, sometimes immediately or after 30 seconds, the audio is interrupted and the control center is set with a "Not playing" label.
main.dart
import 'package:audio_service/audio_service.dart';
import 'package:radioplayer/const.dart';
import 'package:radioplayer/services/audio_handler.dart';
Future<void> main() async {
audioHandler = await AudioService.init(
builder: () => AudioPlayerHandler(),
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.moeasy.radioplayer.channel.audio',
androidNotificationChannelName: 'Radio Format',
androidNotificationOngoing: true,
preloadArtwork: true,
),
);
}
audio_handler.dart
import 'package:audio_service/audio_service.dart';
import 'package:audio_session/audio_session.dart';
import 'package:just_audio/just_audio.dart';
import 'package:radioplayer/api/stations_api.dart';
import 'package:radioplayer/const.dart';
int? currentStationIndexOnLockScreen;
class AudioPlayerHandler extends BaseAudioHandler {
AudioPlayerHandler() {
_init();
}
Future<void> _init() async {
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.music());
playbackState.add(playbackState.value.copyWith(
processingState: AudioProcessingState.idle,
));
await Future.delayed(const Duration(seconds: 2));
audioPlayer.playbackEventStream.listen(_transformEvent);
}
_addMediaItem(MediaItem item) {
mediaItem.add(item);
}
@override
Future<void> playMediaItem(MediaItem mediaItem) async {
playbackState.add(playbackState.value.copyWith(
playing: true,
controls: [
MediaControl.skipToPrevious,
MediaControl.pause,
MediaControl.stop,
MediaControl.skipToNext
],
processingState: AudioProcessingState.ready,
));
_addMediaItem(mediaItem);
await audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(mediaItem.id)));
}
@override
Future<void> play() async {
playbackState.add(playbackState.value.copyWith(
playing: true,
controls: [
MediaControl.skipToPrevious,
MediaControl.pause,
MediaControl.stop,
MediaControl.skipToNext
],
processingState: AudioProcessingState.ready,
));
audioPlayer.play();
}
@override
Future<void> pause() async {
playbackState.add(playbackState.value.copyWith(
playing: false,
controls: [
MediaControl.skipToPrevious,
MediaControl.play,
MediaControl.stop,
MediaControl.skipToNext
],
processingState: AudioProcessingState.ready,
));
audioPlayer.pause();
}
@override
Future<void> stop() async {
audioPlayer.stop();
playbackState.add(playbackState.value.copyWith(
processingState: AudioProcessingState.idle,
));
}
@override
Future<void> skipToPrevious() async {
if (currentStationIndexOnLockScreen != 0) {
currentStationIndexOnLockScreen = currentStationIndexOnLockScreen! - 1;
} else {
currentStationIndexOnLockScreen = allStations.length - 1;
}
MediaItem mediaItem = MediaItem(
id: allStations[currentStationIndexOnLockScreen!].id,
title: allStations[currentStationIndexOnLockScreen!].title,
artist: allStations[currentStationIndexOnLockScreen!].artist,
artUri: allStations[currentStationIndexOnLockScreen!].artUri,
);
_addMediaItem(mediaItem);
audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(mediaItem.id)));
}
@override
Future<void> skipToNext() async {
if (currentStationIndexOnLockScreen! < allStations.length - 1) {
currentStationIndexOnLockScreen = currentStationIndexOnLockScreen! + 1;
} else {
currentStationIndexOnLockScreen = 0;
}
MediaItem mediaItem = MediaItem(
id: allStations[currentStationIndexOnLockScreen!].id,
title: allStations[currentStationIndexOnLockScreen!].title,
artist: allStations[currentStationIndexOnLockScreen!].artist,
artUri: allStations[currentStationIndexOnLockScreen!].artUri,
);
_addMediaItem(mediaItem);
audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(mediaItem.id)));
}
_transformEvent(PlaybackEvent event) {
final playing = audioPlayer.playing;
playbackState.add(playbackState.value.copyWith(
controls: [
MediaControl.skipToPrevious,
if (playing) MediaControl.pause else MediaControl.play,
MediaControl.stop,
MediaControl.skipToNext,
],
systemActions: const {
MediaAction.seek,
MediaAction.seekForward,
MediaAction.seekBackward,
},
androidCompactActionIndices: const [0, 1, 3],
processingState: const {
ProcessingState.idle: AudioProcessingState.idle,
ProcessingState.loading: AudioProcessingState.loading,
ProcessingState.buffering: AudioProcessingState.buffering,
ProcessingState.ready: AudioProcessingState.ready,
ProcessingState.completed: AudioProcessingState.completed,
}[audioPlayer.processingState]!,
playing: playing,
updatePosition: audioPlayer.position,
bufferedPosition: audioPlayer.bufferedPosition,
speed: audioPlayer.speed,
queueIndex: event.currentIndex,
));
}
}
Anyone know how to help me please?
UPDATE:
I've edited the _init() method in audio_handler.dart with this:
Future<void> _init() async {
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration(
avAudioSessionCategory: AVAudioSessionCategory.playback,
avAudioSessionCategoryOptions:
AVAudioSessionCategoryOptions.defaultToSpeaker,
avAudioSessionMode: AVAudioSessionMode.defaultMode,
avAudioSessionRouteSharingPolicy:
AVAudioSessionRouteSharingPolicy.defaultPolicy,
avAudioSessionSetActiveOptions:
AVAudioSessionSetActiveOptions.notifyOthersOnDeactivation,
androidAudioAttributes: AndroidAudioAttributes(
contentType: AndroidAudioContentType.music,
flags: AndroidAudioFlags.audibilityEnforced,
usage: AndroidAudioUsage.media,
),
androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
androidWillPauseWhenDucked: true,
));
playbackState.add(playbackState.value.copyWith(
processingState: AudioProcessingState.idle,
));
await Future.delayed(const Duration(seconds: 2));
audioPlayer.playbackEventStream.listen(_transformEvent);
}
Now work perfectly in background, with bluetooth device and airplay but now I see this error despite the app working great. I've tried changing the configuration to session but that's the only way it works fine. Does anyone know if this error / incompatibility below could affect the time of publishing on the appstore?
This is the error:
[as_client] AVAudioSession_iOS.mm:2194 Error: category option 'defaultToSpeaker' is only applicable with category 'playAndRecord'
[as_client] AVAudioSession_iOS.mm:2370 Failed to set category, error: -50
[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(-50, Impossibile completare l'operazione. (Errore OSStatus -50)., null, null)
#0 StandardMethodCodec.decodeEnvelope
package:flutter/…/services/message_codecs.dart:653
#1 MethodChannel._invokeMethod
package:flutter/…/services/platform_channel.dart:296
<asynchronous suspension>
#2 AudioSession.configure
package:audio_session/src/core.dart:201
<asynchronous suspension>
#3 AudioPlayerHandler._init
package:radioplayer/services/audio_handler.dart:16
<asynchronous suspension>
Upvotes: 3
Views: 547