I am trying to create an music app with audio_service and just_audio packages. Everything works but when I pull down the notification on my real iPhone device it stops and destroys the notification. That also happens if I lock the phone and then unlock it or if I just simply put the app in the background and then come back to it, it just seems to destroy the instance of the player (no sound is playing and notification is destroyed). Then I cannot play any other song until I reset the app. I think the problem is with how I configured the AudioServiceHandler class. Here is the AudioServiceHandler class code:
import 'dart:async';
import 'package:audio_session/audio_session.dart';
import '../../exports.dart';
class AudioServiceHandler extends BaseAudioHandler with SeekHandler {
// AudioPlayer instance
final AudioPlayer audioPlayer = AudioPlayer();
// Create audio source from media item
LockCachingAudioSource createAudioSource(MediaItem item) {
return LockCachingAudioSource(
Uri.parse(
"${Constants().urlb}/play_song/${item.id.split(".")[2]}"),
);
}
// Listen for changes in the current song index and update the media item
void listenForCurrentSongIndexChanges() {
audioPlayer.currentIndexStream.listen((index) {
final playlist = queue.value;
if (index == null || playlist.isEmpty) return;
mediaItem.add(playlist[index]);
});
}
/// Computes the effective queue index taking shuffle mode into account.
int? getQueueIndex(
int? currentIndex, bool shuffleModeEnabled, List<int>? shuffleIndices) {
final effectiveIndices = audioPlayer.effectiveIndices ?? [];
final shuffleIndicesInv = List.filled(effectiveIndices.length, 0);
for (var i = 0; i < effectiveIndices.length; i++) {
shuffleIndicesInv[effectiveIndices[i]] = i;
}
return (shuffleModeEnabled &&
((currentIndex ?? 0) < shuffleIndicesInv.length))
? shuffleIndicesInv[currentIndex ?? 0]
: currentIndex;
}
// Broadcast the current playback state based on the received PlaybackEvent
void broadcastState(PlaybackEvent event) {
audioPlayer.playbackEventStream.listen((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,
},
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]!,
repeatMode: const {
LoopMode.off: AudioServiceRepeatMode.none,
LoopMode.one: AudioServiceRepeatMode.one,
LoopMode.all: AudioServiceRepeatMode.all,
}[audioPlayer.loopMode]!,
shuffleMode: (audioPlayer.shuffleModeEnabled)
? AudioServiceShuffleMode.all
: AudioServiceShuffleMode.none,
playing: playing,
updatePosition: audioPlayer.position,
bufferedPosition: audioPlayer.bufferedPosition,
speed: audioPlayer.speed,
queueIndex: event.currentIndex,
));
});
}
// Function to initialize the songs and set up the audio player
Future<void> initSongs({required List<MediaItem> songs}) async {
final session = await AudioSession.instance;
await session.configure(AudioSessionConfiguration.music());
// Listen for playback events and broadcast the state
audioPlayer.playbackEventStream.listen(broadcastState);
// Create a list of audio sources from the provided songs
final audioSource = ConcatenatingAudioSource(
children: songs.map(createAudioSource).toList(),
);
// Set the audio source of the audio player to the concatenation of the audio sources
audioPlayer.setAudioSource(audioSource);
// Add the songs to the queue
queue.add(songs);
// Listen for changes in the current song index
listenForCurrentSongIndexChanges();
// Listen for processing state changes and skip to the next song when completed
audioPlayer.processingStateStream.listen((state) async {
if (state == ProcessingState.completed) {
if (queue.value.length - 1 == audioPlayer.currentIndex) {
await skipToQueueItem(0);
pause();
} else {
skipToNext();
}
}
});
}
// Play function to start playback
@override
Future<void> play() async => audioPlayer.play();
// Pause function to pause playback
@override
Future<void> pause() async => audioPlayer.pause();
// Seek function to change the playback position
@override
Future<void> seek(Duration position) async => audioPlayer.seek(position);
// Skip to a specific item in the queue and start playback
@override
Future<void> skipToQueueItem(int index) async {
await audioPlayer.seek(Duration.zero, index: index);
play();
}
// Skip to the next item in the queue
@override
Future<void> skipToNext() async => audioPlayer.seekToNext();
// Skip to the previous item in the queue
@override
Future<void> skipToPrevious() async => audioPlayer.seekToPrevious();
// Set repeat mode
@override
Future<void> setRepeatMode(AudioServiceRepeatMode repeatMode) async {
switch (repeatMode) {
case AudioServiceRepeatMode.none:
audioPlayer.setLoopMode(LoopMode.off);
break;
case AudioServiceRepeatMode.one:
audioPlayer.setLoopMode(LoopMode.one);
break;
case AudioServiceRepeatMode.group:
case AudioServiceRepeatMode.all:
audioPlayer.setLoopMode(LoopMode.all);
break;
}
}
// Set the shuffle mode
@override
Future<void> setShuffleMode(AudioServiceShuffleMode shuffleMode) async {}
// Position stream
Stream<Duration> get positionStream {
return audioPlayer.positionStream;
}
// Buffer Stream
Stream<Duration> get bufferStream {
return audioPlayer.bufferedPositionStream;
}
// Duration Stream
Stream<Duration?> get durationStream {
return audioPlayer.durationStream;
}
// Loop Mode Stream
Stream<LoopMode> get loopModeStream {
return audioPlayer.loopModeStream;
}
}
I have tried to replicate the error inside official audio_service example but it does not even allow background play. When it is played and put to background it automatically stops. Also looked at other examples and think that maybe I am missing inside the AudioServiceHandler
this:
AudioServiceHandler() {
// Some code for instancing it?
}
However I was unable to figure out what to put inside. I have tried just the broadcasting function, that did not work, tried to remove it from initSongs
while keeping it inside the AudioServiceHandler function
no progress. I have tried putting listeners and removing them from initSongs
but still nothing. Also tried putting the whole songInit with pre given data, but then the song would not even start playing.
The problem was with main.dart which I did not show. I used real_volume package with audio_service. The problem was me using its volume listener. When I commented that line, everything is working as expected. Audio player notification works as expected.