skip to Main Content

I am in the process of developing an application that streams audio from URLs retrieved from a backend service. The goal is for the application to play audio tracks seamlessly, one after the other, without user intervention.

Unfortunately, I have encountered a recurring error that is hindering this functionality. Whenever I attempt to set a new audio source with the following code:

await _audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(audioUrl)));

I receive the error message: "Bad state: Cannot fire new event. Controller is already firing an event."

To manage audio playback progression, I am currently utilizing the playbackEventStream with a listener that looks for the completion of the current track to trigger the next one, as shown below:

audioPlayer.playbackEventStream.listen((event) {
  if (event.processingState == ProcessingState.completed) {
    handleNextItem();
  }
});

Despite this, the error persists. I am seeking advice on how to resolve this issue so that audio playback can proceed smoothly from one track to the next. Any guidance or suggestions you could offer would be greatly appreciated

2

Answers


  1. Chosen as BEST ANSWER

    Many thanks to @Vladyslav Ulianytskyi for his valuable advice. In the process of updating my app according to his suggestions, I discovered an alternative solution to the bug I previously mentioned. By replacing the line Player.playbackEventStream.map(_transformEvent).pipe(playbackState), which was automatically piping events from one stream to another, and instead manually handling each event, the issue was resolved.

    Here's the updated code block within AudioPlayerHandler():

        _AudioPlayerHandler() {
          _Player.playbackEventStream.listen((event) {
            final playing = _Player.playing;
            playbackState.add(playbackState.value.copyWith(
              playing: playing,
              processingState: _getProcessingState(event.processingState),
            ));
        
            if (event.processingState == ProcessingState.completed) {
              developer.log('Speech audio playback completed. Moving to the next item.');
              handleNextItem(); 
            }
          });
        }
        
        AudioProcessingState _getProcessingState(ProcessingState state) {
          switch (state) {
            case ProcessingState.idle:
              return AudioProcessingState.idle;
            case ProcessingState.loading:
              return AudioProcessingState.loading;
            case ProcessingState.buffering:
              return AudioProcessingState.buffering;
            case ProcessingState.ready:
              return AudioProcessingState.ready;
            case ProcessingState.completed:
              return AudioProcessingState.completed;
            default:
              return AudioProcessingState.idle;
          }
        }
    
        _Player.playbackEventStream.listen((event) {
          final playing = _Player.playing;
          playbackState.add(playbackState.value.copyWith(
            playing: playing,
            processingState: _getProcessingState(event.processingState),
          ));
    
          if (event.processingState == ProcessingState.completed) {
            developer
                .log('Speech audio playback completed. Moving to the next item.');
            handleNextItem(); 
          }
        });
      }
    
      AudioProcessingState _getProcessingState(ProcessingState state) {
        switch (state) {
          case ProcessingState.idle:
            return AudioProcessingState.idle;
          case ProcessingState.loading:
            return AudioProcessingState.loading;
          case ProcessingState.buffering:
            return AudioProcessingState.buffering;
          case ProcessingState.ready:
            return AudioProcessingState.ready;
          case ProcessingState.completed:
            return AudioProcessingState.completed;
          default:
            return AudioProcessingState.idle;
        }
      }
    

  2. You can’t call _audioPlayer.setAudioSource(..) multiple times. You should do it once on player start. You could try this:

    class AppAudioHandler extends BaseAudioHandler {
      AppAudioHandler();
          AudioPlayer? _player;
          late ConcatenatingAudioSource _playlist;
    
          Future<void> initPlayer() async {
            try {
              if (_player != null) {
                await _player?.dispose();
              }
              _player = AudioPlayer();
            } catch (e) {
              print(e);
            }
          }
    
    
      Future<void> loadEmptyPlaylist() async {
        await initPlayer();
        _playlist = ConcatenatingAudioSource(
          children: [],
          useLazyPreparation: false,
        );
        await _player?.setAudioSource(_playlist);
      }
    ...
    

    Then after player initialization:

    await _audioHandler.loadEmptyPlaylist()
    

    when you want to add track url you just updating playlist(e.g. add some method updatePlaylist(..) to the AppAudioHandler):

    await playlist.add(AudioSource.uri(Uri.parse(audioUrl)));
    ...
    

    More info here: just_audio

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search