Wege
Wege

Reputation: 133

Can we retrieve the same updated media item with added Duration in Dart [Flutter] which is derived from Cloud Firestore?

I have problem with [audio_service][1] of Dart's package inside Flutter. It has trouble in reading the duration of current MediaItem. As written in its official documentation [here][2], we are asked to create a new MediaItem to store the copied duration. If we don't do it, then we won't be able to maintain the progress bar when [audio_service][1] is playing the song. I meant, if it has no duration value of the song, it will break the progress bar if user tap on Pause button or Stop button.

Now, I thought another alternative in order to keep duration data. I used [flutter_ffmpeg][3], and one of its package to read duration in String. Then use these codes to convert String into Duration:

Future<MediaItem> addDuration(File myAudio) async {
    late final MediaItem songInfo;
    String duration = "";
    await readAudio(myAudio).then((value) => duration = value);
    durSong = parseDuration(duration);
    songInfo.copyWith(duration: durSong);
    log("Duration is $durSong");
    log("The current duration for ${songInfo.title} is ${songInfo.duration}");
    return songInfo;
  }

Future<String> readAudio(File audio) async {
    final FlutterFFprobe durReader = FlutterFFprobe();
    String duration = "";
    await durReader.getMediaInformation(audio.path).then((value) => duration = value.getMediaProperties()!["duration"]);
    return duration;
  }

Duration parseDuration(String s) {
    int hours = 0;
    int minutes = 0;
    int micros;
    List<String> parts = s.split(':');
    if (parts.length > 2) {
      hours = int.parse(parts[parts.length - 3]);
    }
    if (parts.length > 1) {
      minutes = int.parse(parts[parts.length - 2]);
    }
    micros = (double.parse(parts[parts.length - 1]) * 1000000).round();
    return Duration(hours: hours, minutes: minutes, microseconds: micros);
  }

My use case is I created server app and client app. Server app is used to process, retrieve the duration value using plugin flutter_ffmpeg and store it in MediaItem and then upload it to Cloud Firestore and Storage. Client app is used to retrieve data from Cloud Firestore and Storage.

I updated the media item according to [official documentation][2]. I intended to use the updated songs so I don't need to add duration again to current media item.

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?

[1]: https://pub.dev/packages/audio_service
[2]: https://github.com/ryanheise/audio_service/wiki/FAQ#how-do-i-update-an-existing-mediaitem
[3]: https://pub.dev/packages/flutter_ffmpeg

Upvotes: 1

Views: 572

Answers (2)

Wege
Wege

Reputation: 133

I found out where I went wrong. Actually, we just need to add these code inside 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);
      }
    });
I should also add these inside function setShuffleMode()

@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,
         ));
       }
     });
   }
 }

I don't need to read the duration value using library flutter_ffmpeg, I just need to add that code inside init() function and replace the original function setShuffleMode. I think I have to erase my below comment for alternative way which is so much harder than this answer. Well, thank you anyway.

Upvotes: 0

Wege
Wege

Reputation: 133

Okay, I think I know the answer. Duration type of data in Dart cannot be uploaded into Firestore and must be converted into Timestamp format. So, the only way for me to store the Duration data is to keep the String value of the return value in this function readAudio(File audio).

Future<String> readAudio(File audio) async {
    final FlutterFFprobe durReader = FlutterFFprobe();
    String duration = "";
    await durReader.getMediaInformation(audio.path).then((value) => duration = value.getMediaProperties()!["duration"]);
    return duration; //this data
}

And when I downloaded all songs data from Cloud Firestore and Storage, I should have set and convert the Duration value in String type into Duration type using function parseDuration(String s).

Duration parseDuration(String s) {
    int hours = 0;
    int minutes = 0;
    int micros;
    List<String> parts = s.split(':');
    if (parts.length > 2) {
      hours = int.parse(parts[parts.length - 3]);
    }
    if (parts.length > 1) {
      minutes = int.parse(parts[parts.length - 2]);
    }
    micros = (double.parse(parts[parts.length - 1]) * 1000000).round();
    return Duration(hours: hours, minutes: minutes, microseconds: micros);
}
After I get the Duration value, I will add it inside the class which will define the MediaItem for songs collection I prepare later. This is the only way I can think of to add Duration value inside MediaItem. Since the example file from audio_service package as shown in example_playlist.dart [inside github repository], doesn't have an example if the song has no Duration value.

I hope any experts would provide another solution in getting duration value and stop it from missing the position of progress bar, if Shuffle button/Repeat button is tapped by User. I would be really grateful for any tips and tricks.
Thank you very much.

Anyway, people who helped me in this stackoverflow.com will be considered as contributors and after my apps reach USD1.000.000, I will donate 10% of them to all the contributors. This is also valid for authors of dart packages which I used inside my projects, any tutorial websites and videos on YouTube. Their name will be list inside the apps. People who made free contributions like that should be rewarded and if it is very useful, I will gladly reward them.

My first app will be launched on first May, 2022.
I will not tell the app's name to protect the unique name in Google Play Store and Apple App Store.
My principle in working is that you help me out, I will reward you for 5 years non-stop after it reaches minimum revenue.

Upvotes: 0

Related Questions