0

I'm working on an app for podcasts and, one of the features of the app is that it saves the position where a user leaves a podcast to play another or leaves the app entirely. I couldn't find a way to play a saved podcast from its lastPlayed timestamp without first playing said podcast using audioHandler.play() and the seeking to the position where the user left off using audioHandler.seek(lastPlayed). Using this method of playing, I get this error: and the podcast plays from the beginning instead of where it should. Any suggestions?

W/System.err(27454): com.google.android.exoplayer2.IllegalSeekPositionException W/System.err(27454): at com.google.android.exoplayer2.ExoPlayerImpl.seekTo(ExoPlayerImpl.java:611) W/System.err(27454): at com.google.android.exoplayer2.SimpleExoPlayer.seekTo(SimpleExoPlayer.java:1527) W/System.err(27454): at com.ryanheise.just_audio.AudioPlayer.seek(AudioPlayer.java:953) W/System.err(27454): at com.ryanheise.just_audio.AudioPlayer.onMethodCall(AudioPlayer.java:455) W/System.err(27454): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:233) W/System.err(27454): at io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart(DartMessenger.java:85) W/System.err(27454): at io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage(FlutterJNI.java:818) W/System.err(27454): at android.os.MessageQueue.nativePollOnce(Native Method) W/System.err(27454): at android.os.MessageQueue.next(MessageQueue.java:335) W/System.err(27454): at android.os.Looper.loop(Looper.java:206) W/System.err(27454): at android.app.ActivityThread.main(ActivityThread.java:8512) W/System.err(27454): at java.lang.reflect.Method.invoke(Native Method) W/System.err(27454): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602) W/System.err(27454): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130) E/flutter (27454): [ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: PlatformException(Illegal state: null, null, null, null)

1 Answers1

0

You can achieve this in two ways:

  1. Wait for the seek to complete before calling play. Or,
  2. Preload the audio at a particular position using the initialPosition parameter of just_audio's setAudioSource.

I would try to handle the restoration of the player's saved position transparently, inside of your audio handler. That way, the client of your audio handler won't need to know how that is handled and simply calls audioHandler.play() when it wants to play, and the audio handler will internally know which position to start playing at.

The way you could achieve this is to restore the player's state from the constructor of your audio handler. So for example, your audio handler's initialisation code could include:

player = AudioPlayer();
await player.setAudioSource(source,
    initialPosition: savedPosition);

You're getting an IllegalSeekPositionException because the ExoPlayer library on Android thinks you're seeking to a position that is not within the timeline of that file. You might want to inspect the value of the seek position compared to the duration and ensure that it's less.

Ryan Heise
  • 2,273
  • 5
  • 14
  • Hi Ryan, I'm trying to implement this functionality using audioHandler. I'm trying to use addQueueItems to set the initial playlist and start from an initial index and initial position using setAudioSource as described. But since there is no optional parameters initialPosition and initialIndex in addQueueItems how can I pass these arguments to audioHandler? – Michał Klepaczko Jul 25 '22 at 09:05
  • The audio handler is only intended to handle the typical interactions from an input device. So for example, pressing a track in Android Auto, or doing similar in your Flutter UI. There is no input device and UI design I have ever seen where the user can, in a single action, choose a track and a position in one action. Rather, you implement your audio handler to automatically resume at the desired position by its own internal implementation. – Ryan Heise Jul 25 '22 at 12:33
  • (cont) The way the user interacts with your app will not allow them to specify that parameter, rather the audio handler should know to do this automatically and pass the appropriate parameters internally to its internal player. But if you really want to do it the way you describe (which is not recommended), then use a custom action. – Ryan Heise Jul 25 '22 at 12:33
  • Thanks for your quick reply. I just need to resume to a position and chapter from DB. Since it is for audiobooks and lazy loading is not yet implemented I'm currently loading each chapter seperatly rather than a whole queue. So I load the right track and then set the right position. Is there a controler or stream that informs that the track is loaded and ready to play? _audioHandler.playbackState. seems to provide many states and I can't figure out the right moment to set position. If done before track is loaded it resumes to position 0. – Michał Klepaczko Jul 26 '22 at 14:27
  • Listen to processing state changes from "loading" to "ready". The processing state is from just_audio but you're probably also mapping it onto a corresponding state in audio_service. In any case, this is not related to the current question on this S/O page. – Ryan Heise Jul 26 '22 at 15:08