Search code examples
flutterstream-builderjust-audioflutter-streambuilderaudio-service

Changing AudioHandler.mediaItem without a Queue


I am working on my first Flutter app which implements basic stream functionality. Upon starting, the app loads a stream from source A. The app contains navigation menus to allow the user to play streams from additional sources B, C, D... etc. I've been able to update the item playing by overloading AudioHandler.playMediaItem(MediaItem mediaItem) to call AudioPlayer.setUrl(mediaItem.id):

class AudioPlayerHandler extends BaseAudioHandler {
  // Declare MediaItem used for streaming
  static const _initial_stream = MediaItem(
    id: someURL,
    title: 'Stream Title',
  );

  // Instantiate a just_audio AudioPlayer
  final _player = AudioPlayer();

  //Initialize audio handler
  AudioPlayerHandler() {
    _player.playbackEventStream.map(_transformEvent).pipe(playbackState);
    mediaItem.add(_initial_stream);
    _player.setAudioSource(AudioSource.uri(Uri.parse(_stream.id)));
  }

  @override
  Future<void> play() => _player.play();

  @override
  Future<void> pause() => _player.pause();

  @override
  Future<void> stop() => _player.stop();

  @override
  Future<void> playMediaItem(MediaItem mediaItem) => _player.setUrl(mediaItem.id);

Within the UI the new stream can be played by implementing the following within an onPressed() method:

var stream_b = MediaItem(
  id: someURL,
  title: 'Stream B',
);

audioHandler.playMediaItem(stream_b);
audioHandler.play();

I would like my UI elements to reflect this change, but currently the StreamBuilder still shows the initial stream even after the user selects a different stream. I have tried implementing AudioHandler.updateMediaItem() and AudioServiceBackground.setMediaItem(mediaItem):

  @override
  Future<void> playMediaItem(MediaItem mediaItem) async {
    _player.setUrl(mediaItem.id);
    updateMediaItem(mediaItem);
    AudioServiceBackground.setMediaItem(mediaItem);
  }

But neither work. AudioServiceBackground.setMediaItem() is deprecated, and displays a suggestion to use BaseAudioHandler.mediaItem instead. When attempting to set audioHandler.mediaItem = stream_b within my UI, I see an error that AudioHandler doesn't contain a method to set mediaItem, and when I try to implement my own within AudioHandler, I see an error that since mediaItem is a final field within BaseAudioClass, it cannot be changed with a setter.

Considering mediaItem is a final field within BaseAudioHandler, I suspect my approach at changing/updating the mediaItem is not correct. Can anyone suggest a way to set AudioHandler.mediaItem such that it can be used within a stream builder to display the item that is currently playing?

StreamBuilder<MediaItem?>(
 stream: audioHandler.mediaItem, // how to update mediaItem so this provides real-time information? 
 builder: (context, snapshot) {
   var mediaItem = snapshot.data;
   return Text(mediaItem?.title ?? '');
 },
),

Solution

  • When setting the AudioProcessingState to idle it removes the MediaItem from the queue. Just queue the song and then set AudioProcessingState.idle and after AudioProcessingState.ready.

    Something like this:

    playbackState.add(playbackState.value.copyWith(
            processingState:AudioProcessingState.idle));
    var _item2 = MediaItem(
            id: '2',
            album: "Out of Time",
            title: "REM - Losing My Religion"
    );
    mediaItem.add(_item2)
    playbackState.add(playbackState.value.copyWith(
            processingState:AudioProcessingState.ready));
    

    This might not be the best way to do it, but it does get the job done.