ryanheise / just_audio

Audio Player
1.03k stars 655 forks source link

Rewind radio broadcasts #595

Open sigjak opened 2 years ago

sigjak commented 2 years ago

Is your feature request related to a problem? Please describe. Sometimes I would like to be able to rewind when listening to streaming radio broadcasts.

Describe the solution you'd like I would like to suggest something that may already be available or possible but has escaped me. If so, I apologize beforehand. It would be nice to have method in just_audio which would make it possible to to go backwards in a continuous stream such as a radio station broadcast stream and start listening some time before ‘now’. Something similar to what player.seek() does for fixed duration streams. maybe player.rewind(). Would this be feasible?

All the best Sig

Describe alternatives you've considered No solution has come to mind

Additional context Add any other context or screenshots about the feature request here.

ryanheise commented 2 years ago

I agree it would be useful to not only seek back in a livestream, but also seek forward to return to the tip of the livestream (the latter has already been requested in a previous feature request).

I have experimented with this a bit in the past but didn't have any success on iOS. If iOS programmers would like to have a go, I welcome pull requests.

In terms of what the API should look like, I think it should go through the standard seek method but maybe there needs to be an additional API to allow you to query what is the current seekable range.

swiftymf commented 2 years ago

I think this depends on the type of url/stream that's being used. I am using an HLS stream for one of my live radio broadcasts and am able to rewind some amount using AudioPlayer.position. I set the button to rewind 10 seconds each time is tapped. I know with my HLS stream I am getting about 120 seconds of buffer(?) and I think that's what allows me to rewind.

There is another issue around caching that could possibly help solve this? I'm trying to learn more about how that works here and/or using flutter_cache_manager to try to set something up for my project.

ryanheise commented 2 years ago

There are also some buffering options that can be passed to the constructor of AudioPlayer but on iOS I don't see any option for configuring the backwards buffer. For these sorts of things, I would typically search on how native iOS programmers solve this type of use case and then try to replicate the equivalent in this plugin.

swiftymf commented 2 years ago

@ryanheise Would caching be a potential solution here? I'm still very new to this, but trying to learn through reading through this package and repo.

One idea I had was if we started downloading the radio stream with dio (or maybe just_audio caching?), once we have downloaded at least a few seconds we then set the audio source in just_audio to that file location where the download is and start playing it from that file. Requesting byte range (I think I'm understanding that correctly) wouldn't be needed since we are just getting whatever is given through the stream, but it would allow us to seek backwards since we are caching the file and then seek forward up until whatever is available and downloaded at that moment in time.

Then we add in some file management so we aren't saving tons of radio show audio, but that's if any of this could even work.

swiftymf commented 2 years ago

I set this up to try out what I mentioned.

The audio continues to download, but it seems like just_audio doesn't know about anything downloaded after play started.

@override
  Future<void> playMediaItem(MediaItem mediaItem) async {
    await _audioPlayer.dispose();
    _setupAudioPlayer();
    updateMediaItem(mediaItem);

    var dir = await getApplicationDocumentsDirectory();
    var fullPath = dir.path + '/Audios/audio.mp3';

    // Delete previous file if it exists
    final file = File(fullPath);
    if (file.existsSync()) {
      file.delete();
    }

    // start downloading the audio
    // Allow to download for 10 seconds before playing audio
    await _downloadRadio(Uri.parse(mediaItem.id));

    try {
      await _audioPlayer.setAsset(fullPath)
      _audioPlayer.play();

    } catch (e) {
      Fimber.d('_audioPlayer.play() error: $e');
    }
  }

 _downloadRadio(Uri uri) async {
    final dio = Dio();
    var dir = await getApplicationDocumentsDirectory();
    var fullPath = dir.path + '/Audios/audio.mp3';
    dio.downloadUri(uri, fullPath, onReceiveProgress: (received, total) {
      log('dio progress: $received, $total');
    });
    // allow download for 10 seconds
    await futureWait(milliseconds: 10000);
  }
ryanheise commented 2 years ago

I'm not sure if the file idea will work. I would still suggesting to search how native iOS developers have solved this problem in the past and then try to recreate that.