just_audio icon indicating copy to clipboard operation
just_audio copied to clipboard

Seek position of Flac audio actually leads to an earlier position

Open YukunXia opened this issue 2 years ago • 10 comments

Which API doesn't behave as documented, and how does it misbehave? player.seek.

The example uses seekBar widget, and dragging or clicking some position on the bar should move the progress of the audio playing to the corresponding position. However, for some files, eg. flac files longer than ~3min, the resultant position is earlier than the real position for 5~30s. So finally, the thumb of the seekBar has reached the end, but the audio is still on.

Minimal reproduction project Provide a link here using one of two options: https://github.com/YukunXia/just_audio/tree/bug_flac_duration

To Reproduce (i.e. user steps, not code) Steps to reproduce the behavior: 1, Go to just_audio/example 2, flutter run -d macos 4, Click the load audio button 5, Click the play button 6. Click the seekBar at a position very close to the end, eg. 10 sec before the end of the duration. 7. Wait for the thumb of the seekBar to reach the end

Error messages

No error message

Expected behavior

The thumb of the seekBar has reached the end, but the audio is still on. For example, in the screenshot below, the audio is longer than the duration shown in the track for 30sec. However, if the song is played without being seeked, this behavior won't happen, and the remaining time will be 00:00 rather than 00:30.

Screenshots

image

Desktop (please complete the following information):

  • macOS
  • Version: either the one in the repo or ^0.8.0

Flutter SDK version

flutter doctor                   
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel beta, 2.3.0-24.1.pre, on macOS 11.1 20C69 darwin-x64, locale en-US)
[!] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    ✗ cmdline-tools component is missing
      Run `path/to/sdkmanager --install "cmdline-tools;latest"`
      See https://developer.android.com/studio/command-line for more details.
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/macos#android-setup for more
      details.
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 4.2)
[✓] VS Code (version 1.57.1)
[✓] Connected device (2 available)

Additional context

This issue happens on different flac files at the length of ~4min, but I'd expect this will happen on flac files with different scales of duration. The sample.flac provided on my branch is the sample2 on this site https://filesamples.com/formats/flac.

By the way, don't be misled by the remaining time duration in the screenshot. That remaining time actually reduced to 0 once, but finally became positive again.

YukunXia avatar Jul 05 '21 07:07 YukunXia

Possibly related to #333 .

ryanheise avatar Jul 05 '21 08:07 ryanheise

Possibly related to #333 .

Thanks for redirecting me to that issue. Reading that, I expect, if I attach a properly distributed seek table to flac files, the seek operation will be much more accurate. So I did some search on this, and found I can add seek points to flac files through terminal commands, like metaflac --add-seekpoint=0.1s *.flac. 0.1s is the time interval.

After doing that, I can see the sample flac file becomes larger. However, the mentioned problem still persists.

Besides, the official flac page https://xiph.org/flac/format.html mentions that

without a seek table, but the delay can be unpredictable since the bitrate may vary widely within a stream.

So, I'm not sure if a constant bitrate would work for flac files, as discussed in #333.

YukunXia avatar Jul 05 '21 20:07 YukunXia

#333 is actually related to ExoPlayer behaviour for Android, and not applicable to macOS. (Looking at a ExoPlayer further, it should be able to seek both types of FLAC files.)

It was worth a try adding a seek table to the FLAC file but given that didn't solve the problem, I would be searching for iOS/macOS-specific issues with FLAC files in case this is a known limitation of FLAC on those platforms.

ryanheise avatar Jul 06 '21 01:07 ryanheise

#333 is actually related to ExoPlayer behaviour for Android, and not applicable to macOS. (Looking at a ExoPlayer further, it should be able to seek both types of FLAC files.)

It was worth a try adding a seek table to the FLAC file but given that didn't solve the problem, I would be searching for iOS/macOS-specific issues with FLAC files in case this is a known limitation of FLAC on those platforms.

You are right! Looking at your code, I think you're using AVFoundation as the player backend. And it has an issue with its default setting to seek a long duration of FLAC files: https://developer.apple.com/forums/thread/665417. Someone mentioned setting "AVURLAssetPreferPreciseDurationAndTimingKey = true" is the correct way to fix the problem, but I'm not sure how to do that in your package.

YukunXia avatar Jul 06 '21 06:07 YukunXia

I will need to add an option for this on iOS/macOS, and for #333 I need to add a similar option for Android. I just need to decide on the API, whether I add this as a parameter of AudioPlayer or a parameter of AudioSource.

ryanheise avatar Jul 06 '21 08:07 ryanheise

Note for later. Documentation link:

https://developer.apple.com/documentation/avfoundation/avurlassetpreferprecisedurationandtimingkey?language=objc

ryanheise avatar May 28 '22 17:05 ryanheise

It looks like you initialise the asset with that flag set to true, something like this:

NSDictionary* options = @{AVURLAssetPreferPreciseDurationAndTimingKey:@YES};
AVAsset* asset = [AVURLAsset URLAssetWithURL:inputVideoUrl options:options]

It'd probably make more sense to define this for each source then. Personally, I need this for downloaded items only, so it would be easier for me this way.

jmshrv avatar May 15 '23 13:05 jmshrv

Definitely should be some options here, but still some of the options are available on the audio source and others on the seek operation. See: AVURLAssetPreferPreciseDurationAndTimingKey and AVPlayer's seekToTime:toleranceBefore:toleranceAfter:.

Although I'm not actually quite sure on the interaction between these two options, so if you have any experience with that, that could also help inform me to make the correct API design choice.

For reference:

https://developer.apple.com/documentation/avfoundation/avurlassetpreferprecisedurationandtimingkey?language=objc https://developer.apple.com/documentation/avfoundation/avplayer/1387741-seektotime?language=objc

ryanheise avatar May 15 '23 14:05 ryanheise

It looks like seeking already has a tolerence of zero, and I'm not sure where AVAssets are loaded. Are they abstracted away somewhere?

jmshrv avatar May 22 '23 13:05 jmshrv

It looks like seeking already has a tolerence of zero

That can change. The question is really what the new API will shape up to be, and to inform that, how the above two options interact.

I'm not sure where AVAssets are loaded.

I currently use AVPlayerItem initWithURL rather than initWithAsset in just_audio/darwin/Classes/UriAudioSource.m.

ryanheise avatar May 22 '23 13:05 ryanheise

Any updates on this? I've encountered a fun issue where everything I encode now is subject to this problem on iOS (by way of Finamp) and I have been unable to find a way around this.

Interestingly, I've noticed two related quirks:

  1. An identical problem with seeking occurs if I open one of the problematic FLAC files in iOS Safari. Not sure whether this means Safari has the same problem or that it's a bug with AVFoundation's FLAC decoder
  2. When playing multiple affected files in sequence using ConcatenatingAudioSource (again, via Finamp), transitions that should be gapless have gaps or omissions (even without seeking, strangely); additionally, if a song runs past its expected length due to a bad seek, a brief silence is sometimes inserted at the point where it should have ended

If it would be of any help I can try to draw up some non-copyrighted files that cause the same problems for me.

ray-kast avatar Feb 14 '24 03:02 ray-kast

In a recent release I added some options for precise seeking on both Android and iOS in de7a461c95217048ca614b5103d93c09cb0c69df and 93abeba0d891319d2163ed7f798f43974e3167b3.

You can experiment with the options parameter to ProgressiveAudioSource to see if it helps in any way.

ryanheise avatar Feb 14 '24 03:02 ryanheise

You can experiment with the options parameter to ProgressiveAudioSource to see if it helps in any way.

Replacing AudioSource.uri with ProgressiveAudioSource to get at the options parameter does seem to fix the issue. Having some issues with ConcatenatingAudioSource but I can't tell if that's just issues with my debug build. Are there plans to expose the options parameter to the other streams? I saw in the diff for 93abeba that the Objective-C code passes options to the dash and hls sources as well.

ray-kast avatar Feb 14 '24 05:02 ray-kast

You could make a separate feature request for it, although it would be necessary to think about what specific options you want exposed from the underlying native APIs for iOS/Android. I actually did not see any for HLS on Android. And on iOS, you might want to hack the Objective C to hard code your desired option to be on for HLS to confirm whether that in fact works for what you're trying to do, before I consider supporting such an option.

ryanheise avatar Feb 14 '24 07:02 ryanheise

@ryanheise The only option I changed was preferPreciseDurationAndTiming for iOS. I did not encounter any playback issues on Android so I left those parameters alone. Because the code I patched called AudioSource.uri I'm unsure of whether the original code made use of dash or hls streams at that point, and if it did I'm not sure how to maintain that behavior while setting options. I'll do some more testing to see if it requires both calling .uri() and passing options.

ray-kast avatar Feb 14 '24 10:02 ray-kast