AVPlayer seekable time range information incorrect at playback start

Originator:defagos
Number:rdar://39168067 Date Originated:April 4 2018
Status:Closed Resolved:in iOS 12
Product:iOS Product Version:11.3
Classification:Bug Reproducible:Always
 
Area:
AVFoundation

Summary:

Since iOS 11.3, `AVPlayer` behavior at playback start has changed. The `seekableTimeRanges` property is not reliable anymore during the first seconds after playback has started. The actual duration for the "first few seconds" seems correlated to the media chunk size. For a stream with 10 second chunks, you namely have to wait about ~10 seconds until the information gets reliable. For a stream with 6 second chunks, the information gets reliable after ~6 seconds.

As a result, implementations relying on this seekable time range information are now confused. This is for example the case for the Safari media player, or for `AVPlayerViewController`, most notably when playing livestreams. This is of course also the case for custom `AVPlayer`-based implementations, which is quite annoying.

The steps below reproduce the issue with standard players. I also attached a sample project and a detailed analysis of this issue based on this sample (markdown file).

Steps to Reproduce:

Open the following URL with Safari on an iOS 11.3 device: http://tagesschau-lh.akamaihd.net/i/tagesschau_1@119231/master.m3u8?dw=0

This stream is a livestream with no ability to seek to an earlier location (at any time, the playlist only returns 3 chunks of 10 seconds each).

Expected Results:

The Safari media player displays "Live broadcast", hides the seek bar, and disables +/-15 seconds skip buttons (see attached screenshot). This is how the player behaves until iOS 11.3.
 
Actual Results:

The Safari media player never displays "Live broadcast". The slider is displayed but will never provide a way to seek to the past. The -15 seconds skip button and the "Forward to live" button are displayed but are useless (see attached screenshot). The player obviously incorrectly thinks that the stream supports DVR, whereas it actually doesn't.

Version/Build:

iOS 11.3 SDK. Issue is the same with iOS 11.4 beta 1.

Configuration:

Xcode 9.3, tested on an iPhone X running iOS 11.0 and on an iPhone 7 running iOS 11.3.

Comments

This bug has been fixed in iOS 12 beta 1.

Sample project

Available at: https://github.com/defagos/radars

Analysis

Sample project

The attached sample project simply uses an AVPlayerViewController and an associated AVPlayer instance to play various kinds of streams (on-demand, livestream with DVR support, livestream with no DVR support). It attaches a periodic time observer (triggered each second), simply logging the seekable and loaded time range properties.

Note that the discussion below is based on the livestream sample (no DVR support).

The playlist

The following master playlist is used: http://tagesschau-lh.akamaihd.net/i/tagesschau_1@119231/master.m3u8?dw=0

Akamai dw=0 parameter here provides a way to truncate the playlist to only 3 chunks, so that the livestream is a "pure" live broadcast with no possibility to seek to the past. Here is the m3u chunk list at some point in time and for some bitrate:

#EXTM3U #EXT-X-TARGETDURATION:10 #EXT-X-ALLOW-CACHE:YES #EXT-X-VERSION:3 #EXT-X-MEDIA-SEQUENCE:152282559 #EXTINF:10.000, http://tagesschau-lh.akamaihd.net/i/tagesschau_1@119231/segment152282559_184_av-p.ts?sd=10&dw=0&rebase=on #EXTINF:10.000, http://tagesschau-lh.akamaihd.net/i/tagesschau_1@119231/segment152282560_184_av-p.ts?sd=10&dw=0&rebase=on #EXTINF:10.000, http://tagesschau-lh.akamaihd.net/i/tagesschau_1@119231/segment152282561_184_av-p.ts?sd=10&dw=0&rebase=on

You can see that 3 chunks are available at any time, of 10 seconds each.

pre-iOS 11.3 behavior

2018-04-04 09:13:03.799984+0200 Seekable: (null); loaded: (null)) 2018-04-04 09:13:03.929831+0200 Seekable: (null); loaded: CMTimeRange: {{979200/720000 = 1.360}, {95040/720000 = 0.132}}) 2018-04-04 09:13:03.941903+0200 Seekable: (null); loaded: CMTimeRange: {{979200/720000 = 1.360}, {6100800/720000 = 8.473}}) 2018-04-04 09:13:04.579819+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {13420800/720000 = 18.640}}) 2018-04-04 09:13:05.579807+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {13392000/720000 = 18.600}}) 2018-04-04 09:13:06.579835+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {13427520/720000 = 18.649}}) 2018-04-04 09:13:07.578918+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {13427520/720000 = 18.649}}) 2018-04-04 09:13:08.580074+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {8092800/720000 = 11.240}}) 2018-04-04 09:13:09.579953+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {13392000/720000 = 18.600}}) 2018-04-04 09:13:10.579280+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {17050560/720000 = 23.681}}) 2018-04-04 09:13:11.580066+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {20583360/720000 = 28.588}}) 2018-04-04 09:13:12.580018+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {20583360/720000 = 28.588}}) 2018-04-04 09:13:13.579925+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {20583360/720000 = 28.588}}) 2018-04-04 09:13:14.580219+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {20583360/720000 = 28.588}}) 2018-04-04 09:13:15.580219+0200 Seekable: CMTimeRange: {{10000/1000 = 10.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{10000/1000 = 10.000}, {14362560/720000 = 19.948}}) 2018-04-04 09:13:16.580205+0200 Seekable: CMTimeRange: {{10000/1000 = 10.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{10000/1000 = 10.000}, {18601920/720000 = 25.836}}) 2018-04-04 09:13:17.580147+0200 Seekable: CMTimeRange: {{10000/1000 = 10.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{10000/1000 = 10.000}, {21428160/720000 = 29.761}}) 2018-04-04 09:13:18.580150+0200 Seekable: CMTimeRange: {{10000/1000 = 10.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{10000/1000 = 10.000}, {21428160/720000 = 29.761}}) 2018-04-04 09:13:19.580108+0200 Seekable: CMTimeRange: {{10000/1000 = 10.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{10000/1000 = 10.000}, {21428160/720000 = 29.761}}) 2018-04-04 09:13:20.579957+0200 Seekable: CMTimeRange: {{10000/1000 = 10.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{10000/1000 = 10.000}, {21428160/720000 = 29.761}}) 2018-04-04 09:13:21.580159+0200 Seekable: CMTimeRange: {{10000/1000 = 10.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{10000/1000 = 10.000}, {21428160/720000 = 29.761}}) 2018-04-04 09:13:22.580112+0200 Seekable: CMTimeRange: {{10000/1000 = 10.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{10000/1000 = 10.000}, {21428160/720000 = 29.761}}) 2018-04-04 09:13:23.580057+0200 Seekable: CMTimeRange: {{10000/1000 = 10.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{10000/1000 = 10.000}, {21428160/720000 = 29.761}})

Playback starts at 09:13:04, the seekable time range has a length of 0 seconds, which is correct since the stream is not seekable.

~10 seconds later, at 09:13:15, we see that one additional chunk has been retrieved. The seekable time range length correctly remains 0.

iOS 11.3 behavior

2018-04-04 07:42:03.701610+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: (null)) 2018-04-04 07:42:03.752842+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: (null)) 2018-04-04 07:42:03.875330+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: (null)) 2018-04-04 07:42:03.875624+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: (null)) 2018-04-04 07:42:04.508130+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {13420800/720000 = 18.640}}) 2018-04-04 07:42:05.508533+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {13392000/720000 = 18.600}}) 2018-04-04 07:42:06.508475+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {13435200/720000 = 18.660}}) 2018-04-04 07:42:07.508978+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {10080000/720000 = 14.000}}) 2018-04-04 07:42:08.508546+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {17042880/720000 = 23.671}}) 2018-04-04 07:42:09.509076+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {20575680/720000 = 28.577}}) 2018-04-04 07:42:10.508779+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {20575680/720000 = 28.577}}) 2018-04-04 07:42:11.509149+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {20575680/720000 = 28.577}}) 2018-04-04 07:42:12.510158+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {20575680/720000 = 28.577}}) 2018-04-04 07:42:13.510552+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {20575680/720000 = 28.577}}) 2018-04-04 07:42:14.511767+0200 Seekable: CMTimeRange: {{0/1000 = 0.000}, {30000/1000 = 30.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {20575680/720000 = 28.577}}) 2018-04-04 07:42:15.513701+0200 Seekable: CMTimeRange: {{10000/1000 = 10.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{979200/720000 = 1.360}, {20575680/720000 = 28.577}}) 2018-04-04 07:42:16.509327+0200 Seekable: CMTimeRange: {{10000/1000 = 10.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{10000/1000 = 10.000}, {21420480/720000 = 29.751}}) 2018-04-04 07:42:17.508842+0200 Seekable: CMTimeRange: {{10000/1000 = 10.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{10000/1000 = 10.000}, {21420480/720000 = 29.751}}) 2018-04-04 07:42:18.509690+0200 Seekable: CMTimeRange: {{10000/1000 = 10.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{10000/1000 = 10.000}, {21420480/720000 = 29.751}}) 2018-04-04 07:42:19.509894+0200 Seekable: CMTimeRange: {{10000/1000 = 10.000}, {0/1000 = 0.000}}; loaded: CMTimeRange: {{10000/1000 = 10.000}, {21420480/720000 = 29.751}})

Playback starts at 07:42:04, at which point a loaded time range becomes available. Right from the start, though, even when no chunk has been loaded, the player determines a seekable time range of 30 seconds (= 3 chunks, since the stream as a 10 seconds chunk size), probably based on the information from the m3u. This is incorrect.

~10 seconds later, at 07:42:15, the seekable time range length drops to 0 seconds, which is what is expected when playing a livestream with no ability to seek. This value then remains stable and correct.


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!