I am using flutter audio_service (https://pub.dev/packages/audio_service) and just_audio (https://pub.dev/packages/just_audio) to play audio files in foreground and background.
I subclassed BackgroundAudioTask, added an instance of AudioPlayer, and override needed methods. For example I updated repeat mode:
@override
Future<void> onSetRepeatMode(AudioServiceRepeatMode repeatMode) async {
super.onSetRepeatMode(repeatMode);
switch (repeatMode)
{
case AudioServiceRepeatMode.all:
_audioPlayer.setLoopMode(LoopMode.all);
break;
case AudioServiceRepeatMode.none:
_audioPlayer.setLoopMode(LoopMode.off);
break;
case AudioServiceRepeatMode.one:
_audioPlayer.setLoopMode(LoopMode.one);
break;
case AudioServiceRepeatMode.group:
_audioPlayer.setLoopMode(LoopMode.all);
break;
}
}
@override
Future<void> onPlayFromMediaId(String mediaId) async {
await _audioPlayer.stop();
// Get queue index by mediaId.
_queueIndex = _queue.indexWhere((test) => test.id == mediaId);
// Set url source to _audioPlayer.
downloadAndPlay(_mediaItem);
}
I am trying to add a playlist and have a loop of audio files, but seems something is missing and after playing track #1, the player seeks to start of same #1 track and plays it again. Here is my code (part of downloadAndPlay) :
var list=List<AudioSource>();
for (int t=0;t<_queue.length;t++)
{
var mi=_queue[t];
var url = mi.extras['source'];
list.add(AudioSource.uri(Uri.parse(url)));
}
D("Loading list with ${list.length} items");
D("Seeking to index : $_queueIndex");
await _audioPlayer.load(ConcatenatingAudioSource(children: list),
initialIndex: _queueIndex, initialPosition: Duration.zero);
AudioService.updateQueue(_queue);
AudioService.setRepeatMode(AudioServiceRepeatMode.all);
_audioPlayer.play();
I added this to check if ProcessingState.completed fired, which means it reached end of track, but it doesn't fire:
playerEventSubscription = _audioPlayer.playbackEventStream.listen((event) {
D("audioPlayerTask: playbackEventStream : ${event.processingState}");
switch (event.processingState) {
case ProcessingState.ready:
_setState(state: AudioProcessingState.ready);
break;
case ProcessingState.buffering:
_setState(state: AudioProcessingState.buffering);
break;
case ProcessingState.completed:
_handlePlaybackCompleted();
break;
default:
break;
}
});
You never invoked _audioPlayer.setLoopMode()
so it won't loop.
You are also using audio_service but your audio code appears to be half inside and half outside audio_service. Specifically, your instance of _audioPlayer
lives outside audio_service, while your implementation of setRepeatMode
lives inside audio_service and will not have access to your _audioPlayer
instance which is outside audio_service.
Encapsulate all of your audio logic within audio_service so that your setRepeatMode
implementation is able to reference your _audioPlayer
instance and call _audioPlayer.setLoopMode()
on it. This is actually the very reason why we encapsulate (bundle the methods with the data that they need to operate on), but in this case there is another reason why we do it in audio_service, and that is that everything outside the audio_service "capsule" could be destroyed at any time. So if your app transitions into the background and your UI is destroyed, you don't want half of your audio logic to be destroyed. If you encapsulate it all within the audio_service, it will be able to survive the destruction of the UI and since it's self contained, will have everything it needs to be able to keep on playing audio in the background.
Also, the fact that your app may also want to play audio in the foreground does not mean that you need to break that encapsulation. This encapsulated audio code is perfectly capable of running with the UI and without the UI, and so you don't actually need to change your programming style to support the foreground case.
I didn't share enough, but _audioPlayer is within and extent of BackgroundAudioTask. And I override onSetRepeatMode there ( updated in code above). But still not working!
There is a block of code where you use the client API (e.g. AudioService.updateQueue) to send a message "from" the client "to" the background task. That same block of code (which is in the client side) references a variable called _audioPlayer. That audio player is therefore in the client side and not in the background task. You never called _audioPlayer.setLoopMode() on that player. If that block is actually in the background task, it doesn't look like it. It's using client APIs, and so the code wouldn't make sense in this interpretation either. You need to clearly separate the two layers.
I think I found the problem: AudioService.currentMediaItemStream is not firing when the track is finished and it starts playing the next track.
As I explained above, you seem to have two different _audioPlayer variables, one inside the background task and one outside, and you're mixing things up, loading one of them with a media source, but configuring the other one with loop mode. If this is not the case, you need to answer my previous comment and explain whether that block of code in question is inside or outside the background audio task, and if inside, why it is using client APIs that should only be used from outside.
No sir, I am not using two audio players, just one, which is inside a subclass of BackgroundAudioTask, exactly as you ordered :) But AudioService.currentMediaItemStream does not add anything when track goes to next automatically.