Flutter just_audio loop mode is not looping

1,413

Solution 1

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.

Solution 2

From inside your BackgroundAudioTask subclass you should override the onSetRepeatMode method.

import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.dart';

class AudioPlayerTask extends BackgroundAudioTask {
  AudioPlayer _player = new AudioPlayer();

  ...

  @override
  Future<void> onSetRepeatMode(AudioServiceRepeatMode repeatMode) async {
    switch (repeatMode) {
      case AudioServiceRepeatMode.none:
        await _player.setLoopMode(LoopMode.off);
        break;
      case AudioServiceRepeatMode.one:
        await _player.setLoopMode(LoopMode.one);
        break;
      case AudioServiceRepeatMode.all:
      case AudioServiceRepeatMode.group:
        await _player.setLoopMode(LoopMode.all);
        break;
    }
  }
}

This particular implementation is using just_audio as the audio player. Note that AudioPlayerTask is the only class that the just_audio AudioPlayer appears in. It shouldn't be in any other class of your app. Everywhere else you'll use audio_service's AudioService class.

For example, when a user presses a button, you could run the following line of code:

AudioService.setRepeatMode(AudioServiceRepeatMode.none);

This will notify the system and also let the BackgroundAudioTask run onSetRepeatMode so that the internal AudioPlayer class can do its thing.

Solution 3

The way I did it was to simply put in the onStart method:

await _player.setLoopMode(LoopMode.all);

This of course isn't changeable by the user.

Share:
1,413
AVEbrahimi
Author by

AVEbrahimi

Untying an impossibly tangled knot is actually possible. For every problem, there is a solution. And I am just one who is interested in untying and solving, that's it :)

Updated on November 28, 2022

Comments

  • AVEbrahimi
    AVEbrahimi over 1 year

    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;
      }
    });
    
  • AVEbrahimi
    AVEbrahimi over 3 years
    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!
  • Ryan Heise
    Ryan Heise over 3 years
    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.
  • AVEbrahimi
    AVEbrahimi over 3 years
    I think I found the problem: AudioService.currentMediaItemStream is not firing when the track is finished and it starts playing the next track.
  • Ryan Heise
    Ryan Heise over 3 years
    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.
  • AVEbrahimi
    AVEbrahimi over 3 years
    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.
  • Ryan Heise
    Ryan Heise over 3 years
    If that code is inside the background task rather than the client, then why are you using the client APIs? I asked you this question already, but the question still stands because you haven't answered it yet. Either way, you are mixing up code which should be in the client and code which should be in the background task.
  • JavaRunner
    JavaRunner about 3 years
    Thanks for the simple example!