Which API doesn't behave as documented, and how does it misbehave?
Calling await _player.seek(Duration(seconds: 400), index:1) seeks to index 1, and starts playing from beginning, but displays position 400.
Minimal reproduction project
https://github.com/snaeji/just_audio/tree/issue-branch
https://github.com/snaeji/just_audio/blob/issue-branch/just_audio/example/lib/main.dart
To Reproduce (i.e. user steps, not code)
Steps to reproduce the behavior:
_init1() - _init2() functionThere is a//TODO: in the code so it should be easy to find
Expected behavior
The player should play from the correct position
Smartphone (please complete the following information):
Flutter SDK version
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 1.22.1, on Mac OS X 10.15.7 19H2, locale en-GB)
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[✓] Xcode - develop for iOS and macOS (Xcode 12.0.1)
[!] Android Studio (version 4.1)
✗ Flutter plugin not installed; this adds Flutter specific functionality.
✗ Dart plugin not installed; this adds Dart specific functionality.
[✓] IntelliJ IDEA Community Edition (version 2018.2.4)
[✓] VS Code (version 1.49.3)
[✓] Connected device (1 available)
! Error: Auglysingadeild4thgen is not connected. Xcode will continue when Auglysingadeild4thgen is connected. (code -13)
! Error: Living Room is not connected. Xcode will continue when Living Room is connected. (code -13)
! Error: Living Room is not connected. Xcode will continue when Living Room is connected. (code -13)
! Doctor found issues in 1 category.
Additional context
This may be closely related to issue https://github.com/ryanheise/just_audio/issues/206
Does this issue only occur with large playlists?
Unfortunately not. init3() loads the _smallPlaylist containing 10 songs, and we get the same results.
That's good to know. I was having problems on the simulator even with _smallPlaylist when I first tried your example, so I'll aim to get it working on a playlist of 2 songs.
Since this is likely a concurrency issue, you may have success avoiding the issue meanwhile by awaiting the seek before playing. In terms of a proper solution, my goal is to implement all of these methods so that they can be called at any time. E.g. so that you can call play while seeking or even while loading. That has been on my todo list for a while, but it is probably the highest priority thing to work on now.
Yeah these simulators can be hard to work on in regards to audio stuff.
Unfortunately with https://github.com/ryanheise/just_audio/issues/206 seeking never completes so play is never called if we await the seek function.
I have also tried some other approaches and they have come down to this. This was on a physical device with 2 song playlist.
Plays from position 0, but displays time 10:
_player.seek(Duration(seconds:10),index:1);
_player.play();
Plays from position 0, but displays time 10:
_player.seek(Duration(seconds:10),index:1);
await Future.delayed(const Duration(seconds: 1));
_player.play();
Never starts playing because _player.seek never completes
await _player.seek(Duration(seconds:10),index:1);
_player.play();
Plays from position 10 and displays time 10:
_player.seek(Duration.zero,index:1);
await Future.delayed(const Duration(seconds: 1));
_player.seek(Duration(seconds:10));
_player.play();
I have pinpointed why we don't seek to the position when we are seeking position and index at the same time.
Hope this could guide us in the right direction.
I added a else statement in UriAudioSource.m and it seems like we don't pass the if statement.
- (void)seek:(CMTime)position completionHandler:(void (^)(BOOL))completionHandler {
if (!completionHandler || (_playerItem.status == AVPlayerItemStatusReadyToPlay)) {
NSValue *seekableRange = _playerItem.seekableTimeRanges.lastObject;
if (seekableRange) {
CMTimeRange range = [seekableRange CMTimeRangeValue];
position = CMTimeAdd(position, range.start);
}
[_playerItem seekToTime:position toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:completionHandler];
}
else {
NSLog(@"THIS STATEMENT RUNS WHEN SEEKING INDEX AND POSITION");
}
}
At the top of AudioPlayer.m I have this TODO note which I have only partially fulfilled:
TODO: Apply Apple's guidance on seeking: https://developer.apple.com/library/archive/qa/qa1820/_index.html
This document will help to explain some of the reasoning behind that if condition, however rather than just throw away the seek request, what I planned to do was queue the request until it was ready to play.
It will take some care, though, since seeks are used for different purposes throughout just_audio. Sometimes they are used in response to a request from the app, but other times they are used to prepare an item for playback from the correct position when jumping about the playlist. Seek requests that have a nil completion handler should definitely not be queued, for starters.
Also, we don't want to queue every seek request that does have a completion handler. Basically, if the user seeks 4 times in quick succession, we can ignore all of the intermediate requests (and issue immediate results on the method channel), and jump straight to the final seek request. So there should be some sort of queue abstraction to represent these requests. This abstraction should be smart enough to know when to put the request into a queue, and when to interrupt what's currently in the queue. I will take a look at this tomorrow morning.
I've just committed some code to queue seek requests until the item is ready to play. I haven't fully tested all of your scenarios, but it did work for me on a small playlist of 2 items.
Thank you responding so quickly to this @ryanheise
I really admire that you're putting in all the work to follow all the guidelines and not taking any shortcuts. In the long term it really makes the difference to the quality of the plugins.
After testing the new implementation for an hour and haven't been able able to find any problems. I'll let you know if I find any bugs.
This fix is now included in release 0.5.5. Assuming things are now working for you, I'll close this issue.