ryanheise / audio_service

Flutter plugin to play audio in the background while the screen is off.
795 stars 474 forks source link

Demonstrating how to reset MediaItem duration in the example app #543

Open suragch opened 3 years ago

suragch commented 3 years ago

To which pages does your suggestion apply?

I'm considering the example app with its comment docs as documentation since it is quite useful for learning how to use audio_service and combine it with an audio player.

The section in the FAQs is helpful since this does in fact appear to be a frequently asked question (#5, #126, #229, #279, #310, #384). However, without a full example, it's still difficult to understand.

Quote the sentences(s) from the documentation to be improved (if any)

The MediaLibrary in the example app hardcodes the duration:

class MediaLibrary {
  final _items = <MediaItem>[
    MediaItem(
      ...
      duration: Duration(milliseconds: 5739820),

This is in contrast to the advice found in the FAQs, which shows how to update the media item after the duration is known:

modifiedMediaItem = mediaItem.copyWith(duration: duration);

Describe your suggestion

I realize that audio_service doesn't itself play audio and however one chooses to use an audio player is an implementation detail. However, given (1) the example app already shows one implementation with just_audio and (2) needing to update the audio duration after it's known from the audio player is a common problem, I'd like to suggest that we use the example app to demonstrate passing in a list of media items with unknown duration. Then update the media item and queue with the new duration once it is known.

ryanheise commented 3 years ago

I agree this is worth doing.

However, I'll have to get around to https://github.com/ryanheise/just_audio/issues/141 first.

suragch commented 3 years ago

I added a Stack Overflow Q&A for this topic:

It seems to be working but if anyone has time to give me a code review, that would be helpful. I was kind of grasping in the dark.

ryanheise commented 3 years ago

Your StackOverflow answer is the correct idea.

I was hoping to implement https://github.com/ryanheise/just_audio/issues/141 first, since the demo example contains a playlist and ideally I could display the duration next to all media items in a list.

Currently, just_audio has to start decoding audio in order to discover its duration, and because the playlist is loaded lazily, we won't know the duration of the second item in the playlist until we reach it.

In ExoPlayer, it is in some cases possible to read the duration without decoding the audio, but that would unfortunately not be the case in the audio_service example, so perhaps you're right, I should not need to wait for that just_audio feature to be implemented before adding an example of updating the media item duration in audio_service.

I'll see what I can do.

suragch commented 3 years ago

Thanks for your reply and checking on that code. Since it seems to be working and there is at least a link to an example here, I guess there isn't a big rush to add it to the repo example, but I imagine people will still find it useful.

gOzaru commented 2 years ago

I just made a question in stackoverflow.com regarding the duration issue. Can @ryanheise spare time to answer it? Retrieve the same data from database after updating MediaItem Thank you.

ryanheise commented 2 years ago

@gOzaru You might find #585 helpful, although it is based on an older version of audio_service.

There is also the source code of just_audio_background which implements this for the new version of audio_service.

ryanheise commented 2 years ago

I just read your S/O question although I was not clear on what you were asking:

My question is: Can the data included inside media item have the same duration value after being downloaded from Cloud Firestore? I meant, do we need to add Duration value again in the same song??? Or the Duration value is already stored within the song?

I suspect this more of a Dart question, in that you want to understand how copyWith works (it's a fairly standard pattern in Dart). #585 demonstrates its use, the only difference with the latest version of audio_service is that you should now broadcast the new MediaItem instance (the one created by copyWith) using mediaItem.add(....) rather than AudioServiceBackground.setMediaItem(...).

I could be wrong, but I believe there was already a question on S/O about this, so you might try searching the past questions, too.

gOzaru commented 2 years ago

@ryanheise, I asked it because I found out that after using #585 example, the progress bar is not displaying after I clicked the Shuffle button. You can view the image below, that it is missing in the process. [Progress bar started at zero] No matter what I did, unless I defined the duration value and assigned it to Media Item before uploading the playlist into AudioHandler.

SS - Audio Service progress bar is error

ryanheise commented 2 years ago

Your S/O question has the following code which suggested you may not have understood how copyWith works:

    songInfo.copyWith(duration: durSong);

But you may want to update your S/O question. The reason for the progress bar starting at zero is not just about updating the duration property of MediaItem but also updating the position in the playback state. Your S/O question doesn't mention anything about position, so if that is your question, you may want to edit it on S/O and clarify these details.

gOzaru commented 2 years ago

I am so sorry about my S/O question. I should have added more explanation about that. Sorry, I am new in asking question of S/O. But, I found a new way in reading Duration value and intended to apply it to downloaded songs from backend, and this will be applied only once for all if it is a new user. So, my use case is different.

Anyway, if I have to update playbackState too, then would you mind show me in which part I have to add? And what are the codes for that? Since I checked the audio_service example file, and it doesn't have any added duration like #585. All of the examples have their own duration stated inside the MediaItem.

Thank you very much for the time and effort to answer this, @ryanheise

ryanheise commented 2 years ago

This is difficult to answer because the latest example already demonstrates how to set the position, and the documentation also describes how it is supposed to be used. And if that's not actually working for you, it means that you may be doing something wrong, but since I have never seen what you are currently doing with position, it is impossible to guess what you might be doing wrong.

585 is still a useful example, but instead of using AudioServiceBackground.setState(), you now use playbackState.add() in the latest version of audio_service.

gOzaru commented 2 years ago

Okay, if that is what you said; I had tried to add the #518 solution inside example_playlist.dart. I erased all of duration of items inside MediaItem. It did read duration of song, but when I tap on Shuffle/Repeat button, the progress bar returns to zero again, as shown in the image below. SS - Audio Service progress bar is error after added more codes

    _player.durationStream.listen((duration) {
      var index = _player.currentIndex;
      if (index != null && duration != null) {
        if (_player.shuffleModeEnabled == true) {
          index = _player.shuffleIndices![index];
        }
        final newQueue = queue.value;
        final oldMediaItem = newQueue[index];
        final newMediaItem = oldMediaItem.copyWith(duration: duration);
        newQueue[index] = newMediaItem;
        mediaItem.add(newMediaItem);
        playbackState.add(playbackState.value.copyWith(
          updatePosition: _player.position,
          bufferedPosition: _player.bufferedPosition,
        ));
      }
    });

This is why I suggested to use the alternative way in S/O post above. I hope there is solution for this since 8 months ago. But it turns out, there is not a correct example until now. I hope you can help us figure out, where we went wrong. Thank you for helping us out, @ryanheise

gOzaru commented 2 years ago

I think I know where I went wrong. I should not add this code below.

        if (_player.shuffleModeEnabled == true) {
          index = _player.shuffleIndices![index];
        }

And the whole code inside duration is :

    _player.durationStream.listen((duration) {
      var index = _player.currentIndex;
      if (index != null && duration != null) {
        final newQueue = queue.value;
        final oldMediaItem = newQueue[index];
        final newMediaItem = oldMediaItem.copyWith(duration: duration);
        newQueue[index] = newMediaItem;
        mediaItem.add(newMediaItem);
      }
    });

We should add also these codes in function setShuffleMode

  @override
  Future<void> setShuffleMode(AudioServiceShuffleMode shuffleMode) async {
    final enabled = shuffleMode == AudioServiceShuffleMode.all;
    if (enabled) {
      await _player.shuffle();
    }
    playbackState.add(playbackState.value.copyWith(
      shuffleMode: shuffleMode,
      updatePosition: _player.position, // <-- This line
      bufferedPosition: _player.bufferedPosition, // <-- This line
    ));
    await _player.setShuffleModeEnabled(enabled);
  }

You need to put this inside init() function. Problem is now solved.

gOzaru commented 2 years ago

Weird. The progress bar works very well if first song is playing and Shuffle button is pressed. But, when the player switches to second song, and I press Shuffle button twice, so off and on, the progress bar returns to zero again. Really, I am very confused with this logic. It should have not been reset. This happens to third and the next song as well. I think I am gonna stick to alternative way.

gOzaru commented 2 years ago

I think this is the correct answer. We need to add function to listen song's duration automatically in function setShuffleMode, and put them in condition if shuffleMode is equal to AudioServiceShuffleMode.all or not, like the codes below:

@override
  Future<void> setShuffleMode(AudioServiceShuffleMode shuffleMode) async {
    final enabled = shuffleMode == AudioServiceShuffleMode.all;
    if (enabled) {
      await _player.shuffle();
      await _player.setShuffleModeEnabled(true);
      _player.durationStream.listen((duration) {
        var index = _player.currentIndex;
        if (index != null && duration != null) {
          final newQueue = queue.value;
          final oldMediaItem = newQueue[index];
          final newMediaItem = oldMediaItem.copyWith(duration: duration);
          newQueue[index] = newMediaItem;
          mediaItem.add(newMediaItem);
          playbackState.add(playbackState.value.copyWith(
            shuffleMode: shuffleMode,
            updatePosition: _player.position,
            bufferedPosition: _player.bufferedPosition,
          ));
        }
      });
    } else {
      shuffleMode = AudioServiceShuffleMode.none;
      await _player.setShuffleModeEnabled(false);
      _player.durationStream.listen((duration) {
        var index = _player.currentIndex;
        if (index != null && duration != null) {
          final newQueue = queue.value;
          final oldMediaItem = newQueue[index];
          final newMediaItem = oldMediaItem.copyWith(duration: duration);
          newQueue[index] = newMediaItem;
          mediaItem.add(newMediaItem);
          playbackState.add(playbackState.value.copyWith(
            shuffleMode: shuffleMode,
            updatePosition: _player.position,
            bufferedPosition: _player.bufferedPosition,
          ));
        }
      });
    }
  }

Only by then, we can return the value of position in progress bar automatically if we are about to toggle Shuffle on and off in all songs. This really works. Tested 7 times in different song. You can use example_playlist.dart in audio_service github example, and remove all Duration data inside MediaItem of each song. After that, add codes above and replace the original.

Oh yeah, don't forget to add these codes in init() function:

    _player.durationStream.listen((duration) {
      var index = _player.currentIndex;
      if (index != null && duration != null) {
        final newQueue = queue.value;
        final oldMediaItem = newQueue[index];
        final newMediaItem = oldMediaItem.copyWith(duration: duration);
        newQueue[index] = newMediaItem;
        mediaItem.add(newMediaItem);
      }
    });

Give it a try using example_playlist.dart.

gOzaru commented 2 years ago

I think you can close this @suragch. Now you can update your tutorial based on my answer above.

Thank you @ryanheise, author of Audio Service.
I think you are the second biggest contributor after Jonatas Law [getx]. I will let you know if my projects have reached the net revenue target.

ryanheise commented 2 years ago

Congratulations @gOzaru on arriving at a solution. I stepped away for dinner and when I came back I found you had made a lot of progress.

I think we should still keep this issue open, though, until I add an example to the repo. Looking at your example above, I think it is more code than we should have, ideally, so I may in the process need to either find a simpler way, or make changes to just_audio to make a simpler way possible.

gOzaru commented 2 years ago

The method above also works for function setRepeatMode(AudioServiceRepeatMode repeatMode). If we don't provide the duration for each song [MediaItem], tapping on Repeat button will break the progress bar duration value down to zero. So, we need to add it inside the function. We just need to replace: shuffleMode: shuffleMode with repeatMode: repeatMode.

Yeah, @ryanheise. It should be left open.

mainul-hossain commented 2 years ago

Yes @gOzaru it works.

_player.durationStream.listen((duration) {
      var index = _player.currentIndex;
      if (index != null && duration != null) {
        final newMediaItem = queue.value[index].copyWith(duration: duration);
        queue.value[index] = newMediaItem;
        mediaItem.add(newMediaItem);
      }
    });