-
Notifications
You must be signed in to change notification settings - Fork 6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TrackSelection does not jump to live with HlsMediaPeriod #9386
Conversation
Use CasesMost origin vendors support live playback with a rolling-buffer in support of Cloud DVR. In these cases the live window represents only the part of what is available for playback with a simple For example, a SOCU (Start Over / Catch UP) operation converts the sliding live playlist into a live EVENT playlist, assuming the program is in progress. The operations and how they are implemented are: Initiate SOCU for in-progress LiveHere live playback is restarted playback (prepare) with URL indicating start-time / end-time (end time in this case is in the future). The steps in the player are simple: ExoPlayer player = ...
liveEventSource = // code converts current live URL to SOCU
player.setMediaSource(liveEventSource, 0);
player.prepare(); Restart on BLWEThis (obviously) can happen prior to the user switching to SOCU they scrub or trick-play to the start of the live window, position is very possibly negative at the time of the error report. It is disruptive in this case to jump to live edge. Instead, in the error handler, logically like to do: player.seekTo(0);
player.prepare(); During SOCU this is obviously never possible, even if the Current FlowThe SOCU Prepare Flow Sequence Diagram shows how the player flow works for the SOCU use case above. The "Create Loading Period" works perfectly, picking up the requested prepare position into the Looking at the "First Source Info Refresh" issue #7975 comes into play, the masked media period ( Next, HLS library completes the "prepare" operation and reports The "Subsequent Source Info Refresh" flow may occur before or after the "Prepare Completes" flow (definitely possible for non-chunkless prepare). Updating the 'prepare' position might be warranted in these cases. But, of The block of code in @Override
public long selectTracks(
...
if (preparePositionOverrideUs != C.TIME_UNSET && positionUs == preparePositionUs) {
positionUs = preparePositionOverrideUs;
preparePositionOverrideUs = C.TIME_UNSET;
} Unless there is a real use case for this (no test case fail with the code removed) then removing it is the most strait forward solution. Otherwise resetting I owe unit test cases for this request once we decide on the correct path forward @tonihei and @ojw28 what do you think. |
This is a fix for issue google#9347 Once the MaskingMediaPeriod completes `prepare()` (when the masked HlsMediaPeriod reports the first real Timeline with a duration and window start position) and the `preparePositionUs` is used by the player in the `onPrepared()` callback to set the render position it should not use it again. The bug is a track selection causes a jump to live if you are at position 0 in the timeline, even after playback was started from the live point (the prepare position override).
Test case fails without the change to `MaskingMediaPeriod` so it's `selectTracks` method does not force a seek to the override start position.
b7a7d94
to
9f9621d
Compare
Test cases added. See inline comments |
if (preparePositionOverrideUs != C.TIME_UNSET && positionUs == preparePositionUs) { | ||
positionUs = preparePositionOverrideUs; | ||
preparePositionOverrideUs = C.TIME_UNSET; | ||
} | ||
return castNonNull(mediaPeriod) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The position change to start at the default position has already happened when the onPrepare()
callback is processed by ExoPlayerImplInternal
. Removing this causes no existing test cases to fail.
if (unpreparedMaskingMediaPeriod != null) { | ||
// Reset override in case the duration changed and we need to update our override. | ||
setPreparePositionOverrideToUnpreparedMaskingPeriod( | ||
unpreparedMaskingMediaPeriod.getPreparePositionOverrideUs()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems remotely possible, perhaps if the timeline is growing when it is a newly started live playback where it has not reached its target window size. @marcbaechinger what do you think? My new test case still passes if we remove this change, however I'd suggest if we leave it that it is covered by a unit test case (right now it is not).
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT); | ||
FakeTrackSelector trackSelector = new FakeTrackSelector(); | ||
FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO); | ||
FakeRenderer audioRenderer = new FakeRenderer(C.TRACK_TYPE_AUDIO); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Start with a MediaSource with both audio and video
new MediaItem.Builder().setUri(Uri.EMPTY).build(); | ||
final long windowDefaultStartPositionUs = 5 * C.MICROS_PER_SECOND; | ||
final AtomicLong expectedPosition = new AtomicLong(C.TIME_UNSET); | ||
final int lastSeekPosition = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Window start override is 5 seconds, the seekTo 0 is what does not get honored.
.playUntilPosition(0, windowDefaultStartPositionUs) | ||
.pause() | ||
.seekAndWait(lastSeekPosition) // paused at this position, should not change from here | ||
.executeRunnable(() -> { | ||
DefaultTrackSelector.Parameters parameters = trackSelector.getParameters(); | ||
trackSelector.setParameters(parameters.buildUpon() | ||
.setMaxAudioBitrate(0) // Cause track selection change | ||
.build() | ||
); | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Playback starts, reaches the default window start and is paused there. Issue a track selection that changes parameters so as to invalidate the current selection.
expectedPosition.set(player.getCurrentPosition()); | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point, after track selection position should still be 0, but the removed code would have reset it to the default position (causing this test to fail)
@ojw28 this code is in our source tree now, please have someone take a look at the change. It fixes the bug and includes a test case that demonstrates the bug. |
MaskingMediaSource needs to resolve the prepare position set for a MaskingPeriod while the source was still unprepared to the first actual prepare position. It currently assumes that the period-window offset and the default position is zero. This assumption is correct when a PlaceholderTimeline is used, but it may not be true if the real timeline is already known (e.g. when re-preparing a live stream after a playback error). Fix this by using the known timeline at the time of the preparation. Also: - Update a test that should have caught this to use lazy re-preparation. - Change the demo app code to use the recommended way to restart playback after a BehindLiveWindowException. Issue: #8675 PiperOrigin-RevId: 361604191
GitHub is not allowing a convert to Draft for this pull request, but it has an issue that an error recovery on playback that attempts to reset to the default playback position for live will not work (as it looks like only the |
This is a fix for issue #9347
Once the MaskingMediaPeriod completes
prepare()
(when the masked HlsMediaPeriod reports thefirst real Timeline with a duration and window start position) and the
preparePositionUs
is usedby the player in the
onPrepared()
callback to set the render position it should not use it again.The bug is a track selection causes a jump to live if you are at position 0
in the timeline, even after playback was started from the live point (the prepare position override).