Repository navigation
Start position of 0 always resolves to default position for live playbacks #7975
Description
[REQUIRED] Searched documentation and issues
Since the problem only appeared after updating from 2.11.8 to 2.12.0 I searched the recent issues for "seek" and "startPosition" but didn't find anything that seemed related.
[REQUIRED] Question
In our TV app we offer users the possibility to watch the current program from the beginning, i. e. instead of simple live stream with a short live window we have a stream with a fixed start but which grows in length every time a new segment is available on the streaming server.
Since the default position in those streams seems to be right at the live edge we have always passed the start position (simpleExoPlayer.seekTo(0)) explicitly before prepare()ing the player. Up until and including 2.11.8 this has worked without any problems.
Since updating to 2.12.0 a few days ago this "initial seek position" seems to be ignored and the player starts at the default position, at the live edge. I saw that the prepare(mediaSource, false, true) which we were using so far is now deprecated. But replacing it with
simpleExoPlayer.setMediaSource(buildMediaSource(), startPositionInMs)
simpleExoPlayer.prepare()
does not resolve the problem. Even here, it seems that the startPositionInMs is ignored.
Unfortunately, I cannot provide a public test stream to demonstrate the problem, but this is the HLS master playlist:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=1963735,PROGRAM-ID=1,CODECS="avc1.4d4028,mp4a.40.2",RESOLUTION=960x540
04(start=1600850100_stop=1600864500).m3u8
#EXT-X-STREAM-INF:BANDWIDTH=4636146,PROGRAM-ID=1,CODECS="avc1.4d4028,mp4a.40.2",RESOLUTION=1280x720
01(start=1600850100_stop=1600864500).m3u8
#EXT-X-STREAM-INF:BANDWIDTH=3055720,PROGRAM-ID=1,CODECS="avc1.4d4028,mp4a.40.2",RESOLUTION=1280x720
02(start=1600850100_stop=1600864500).m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2340502,PROGRAM-ID=1,CODECS="avc1.4d4028,mp4a.40.2",RESOLUTION=1280x720
03(start=1600850100_stop=1600864500).m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1146343,PROGRAM-ID=1,CODECS="avc1.4d401e,mp4a.40.2",RESOLUTION=640x360
05(start=1600850100_stop=1600864500).m3u8
#EXT-X-STREAM-INF:BANDWIDTH=686561,PROGRAM-ID=1,CODECS="avc1.4d401e,mp4a.40.2",RESOLUTION=480x270
06(start=1600850100_stop=1600864500).m3u8
#EXT-X-STREAM-INF:BANDWIDTH=185753,PROGRAM-ID=1,CODECS="avc1.4d400d,mp4a.40.2",RESOLUTION=320x180
07(start=1600850100_stop=1600864500).m3u8
and this is the start of one of the variant playlists:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-PLAYLIST-TYPE:EVENT
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:400212523
#EXT-X-PROGRAM-DATE-TIME:2020-09-23T08:35:03Z
#EXTINF:4, no desc
20200814T123515-01-400212523.ts
#EXT-X-PROGRAM-DATE-TIME:2020-09-23T08:35:07Z
#EXTINF:4, no desc
20200814T123515-01-400212524.ts
#EXT-X-PROGRAM-DATE-TIME:2020-09-23T08:35:11Z
#EXTINF:4, no desc
20200814T123515-01-400212525.ts
#EXT-X-PROGRAM-DATE-TIME:2020-09-23T08:35:15Z
#EXTINF:4, no desc
20200814T123515-01-400212526.ts
#EXT-X-PROGRAM-DATE-TIME:2020-09-23T08:35:19Z
#EXTINF:4, no desc
20200814T123515-01-400212527.ts
#EXT-X-PROGRAM-DATE-TIME:2020-09-23T08:35:23Z
#EXTINF:4, no desc
20200814T123515-01-400212528.ts
#EXT-X-PROGRAM-DATE-TIME:2020-09-23T08:35:27Z
#EXTINF:4, no desc
20200814T123515-01-400212529.ts
#EXT-X-PROGRAM-DATE-TIME:2020-09-23T08:35:31Z
#EXTINF:4, no desc
20200814T123515-01-400212530.ts
...
As I said, the variant playlists grow for three more lines every four seconds, when the next segment becomes available.
It may well be a configuration issue with our streams (this time, for once, the newly configured DASH streams work better for the same scenario, but we cannot migrate all streams from HLS to DASH at the moment) - but since it still worked with 2.11.8 I thought there might be a way to get it to work with 2.12.0.
Activity
stevemayhew commentedon Jul 6, 2021
@AquilesCanta I've seen this too with our 2.12.3 upgrade.
Since the MediaItem API did not exist with an initial start position we wait until the first timeline update occurs with a non-empty timeline to issue the seek. What I see is the position is overwritten with the default window start for live after the track selection. It is not reproduced 100%, and if you enable chunk less prepare, even less frequent, so almost certainly it is a timing issue.
I'm looking into the issue, I'll send you some logs to and tag this issue number.
stevemayhew commentedon Jul 7, 2021
This is easy to reproduce with current
dev-v2...Modify the demo player to force an initial seek:
You can see with some debug prints that ExoPlayer issues the seek internally when the first source refresh occurs, but then wipes it out when the
MSG_PERIOD_PREPAREDis processed.For live you probably want to wait till after the prepare completes to process the
pendingInitialSeekPositionHere's the logs that show this:
Initial seek is processed:
Prepare starts, pending seek already processed and renderPosition is 0.
Prepare completes and sets position to default live start position, overwriting the initial requested seek.
stevemayhew commentedon Jul 7, 2021
@tonihei this is caused by the fix for BLWE issue #8675
On further testing with the code from my comment above, I found only seek to 0 is ignored. Here is why:
The 0 value for the initial seek is what fails (all the test cases use non-zero). The
Window#defaultPositionUsis initialized to 0 by default and theWindowin theMaskingMediaPeriodwas set to 0 explicitly so even though logically the if should be true it is false sowindowStartPositionUsremains the default (live offset) and the seek is ignored.See my comment here: bc9fb86#r53184789
I didn't attempt a pull request for this, as the fix will probably alter lots of assumptions the ad insertion logic has that I'm not aware of, comments on the
Window#defaultPositionUsseem to implyFor now, @DavidMihola a simple work around is to seek to anything but 0, not ideal but you miss the first frame or so at worst. Note this is only an issue for live.
FWIW,
seekToDefaultPosition()to recover from BLWE is not an great solution for a very large live window, you want to start at the oldest content.marcbaechinger commentedon Jul 8, 2021
Thanks @stevemayhew for the excellent analysis. I can repro and see the difference when using
seekTo(0)vs.seekTo(1).I agree is that the expression
windowPreparePositionUs != oldWindowDefaultPositionUsalways is0 != 0with an initial seek to 0.oldWindowDefaultPositionUshas thedefaultPositionUsof thePlaceholderTimelinewhenhasRealTimeline == falsewhich is always 0.windowPreparePositionUsis derived from the what is passed toMaskingMediaSource.createPeriod(..., startPositionUs)which is 0 in all cases except when there is an initialSeek that is not equal 0. If I'm not mistaken this value in 'startPositionUs' is the defaultdefaultPositionUs(not a typo, there is double default :)) or the initial seek position.This boils down to your conclusion that the
defaultPositionUsof thenewTimelineis used when a seek to 0 is performed. This is an issue for all media sources that have adefaultStartPositionthat is not 0, which, like you say, are probably live sources only.Please note, that the long comment some lines further up, that explains the three possible cases actually state exactly that:
Am I correct that your live stream has a live window with indefinite availability? So the assumption in the comment about seeking to zero being unlikely is not correct?
stevemayhew commentedon Jul 8, 2021
@marcbaechinger exactly... I've started on a test case in
ExoPlayerTestto prove this, great to have this framework now since code inExoPlayerImplInternalis pretty tough to test.My thought from there was to look at enforcing these states for
defaultPositionUsSome logic (
ClippingMediaSource) checks for this already, others (notablyMaskingMediaSource) do not. I get the general idea of ad insertion and timelines, but you all are in the thick of it more ;-)Let me know if the test case would be welcome with a pull request, otherwise I have plenty to work on ;-) The seek to 1 is dirty, but it "works"
marcbaechinger commentedon Jul 8, 2021
Ok, if you have other things to do, give me some time to look into this on our end. Not saying that a pull with unit test is not welcome. :)
I need to wrap my head around this some more. I agree that defaulting to 0 is problematic/scary but wanted to see if there are other ways to recognize that case (looking at hasRealTimeline or similar). I'm not fully understanding yet what all the implications would be either. It also seemed to work for DASH live with a seekTo(0) what I wanted to confirm/understand.
stevemayhew commentedon Jul 8, 2021
No worries.. I'll definitely finish up the unit test that demonstrates the bug (first step to fixing it ;-) ) this way whatever solution we decide on has an way to verify it really fixes the issue without having to find a live stream to test.
The use of C.TIME_UNSET (in fact, the origin of
defaultPositionUs) comes from adding theClippingMediaSourceissue #1988 to allow setting playing "clipped" time ranges on single videoLive start offset uses this same
defaultPositionUsinWindow, here again C.TIME_UNSET is used to indicate the default start is "invalid". See the this commit 856c2f8stevemayhew commentedon Jul 21, 2021
@marcbaechinger I added to the pull request #9174 with a couple of choices for unit and functional (EPII level) tests.
My gut feels it is best pushed to the unit test level and re-work the way the logic in the
MaskingMediaPeriodandMaskingMediaSourceusepreparePositionOverrideUsandpreparePositionUsto keep the initial seek/prepare start position until the timeline updates.The logic in EPII looks like it already has a few too many complicated use cases. Is there a spec around for how the playlist transition from one to the next MediaSource is supposed to propagate position?
2 remaining items
tonihei commentedon Sep 29, 2021
Generalized this bug to also track the problem described in #9386, which has the same root cause.
The root cause (as identified above) is that
MaskingMediaSource/MaskingMediaPerioduse the value of0for both the literal0and to identify the default position. We attempt to disambiguate by make informed guesses here, but explicitly seeking to0in a live stream or enabling a track selection after seeking to0will revert back to using the default position of the live stream.There is a known workaround to use
seek(1)instead.The potential fix will need to introduce
C.TIME_UNSETtoMediaSource.createPeriodandMediaPeriod.preparemethods to clearly identify the default position and adapting implementations and callers of the methods to handle this case correctly.#9386 and #9174 contain unit tests we can use once we submit a fix for this problem.
stevemayhew commentedon Sep 29, 2021
Exactly, the seekTo(1) is fine for us. I agree with your suggestion for the fix, looked more involved then I wanted to attempt in a pull request
Add a HLS playlist factory which creates media playlists which start …
Use correct period-window offset for initial prepare position.
Fixes issue #9347 - jump to live on seek to 0
Fixes issue #9347 - jump to live on seek to 0
Fixes issue #9347 - jump to live on seek to 0
Fixes issue #9347 - jump to live on seek to 0