Fix bug in MaskingMediaSource caused by removed periods.

If MaskingMediaSource masks a multi-window media source, it may be that a period
is removed while we are using an initial unprepared masking MediaPeriod. That
means it's not guaranteed that a timeline update still contains our
unpreparedMaskingMediaPeriod and we should ignore timeline updates where the
period is no longer present because the it will be removed anyway.

PiperOrigin-RevId: 302383787
This commit is contained in:
tonihei 2020-03-23 08:46:48 +00:00 committed by Oliver Woodman
parent af00d91f79
commit d38c6c84a6
2 changed files with 43 additions and 1 deletions

View File

@ -234,7 +234,15 @@ public final class MaskingMediaSource extends CompositeMediaSource<Void> {
@RequiresNonNull("unpreparedMaskingMediaPeriod")
private void setPreparePositionOverrideToUnpreparedMaskingPeriod(long preparePositionOverrideUs) {
MaskingMediaPeriod maskingPeriod = unpreparedMaskingMediaPeriod;
long periodDurationUs = timeline.getPeriodByUid(maskingPeriod.id.periodUid, period).durationUs;
int maskingPeriodIndex = timeline.getIndexOfPeriod(maskingPeriod.id.periodUid);
if (maskingPeriodIndex == C.INDEX_UNSET) {
// The new timeline doesn't contain this period anymore. This can happen if the media source
// has multiple periods and removed the first period with a timeline update. Ignore the
// update, as the non-existing period will be released anyway as soon as the player receives
// this new timeline.
return;
}
long periodDurationUs = timeline.getPeriod(maskingPeriodIndex, period).durationUs;
if (periodDurationUs != C.TIME_UNSET) {
// Ensure the overridden position doesn't exceed the period duration.
if (preparePositionOverrideUs >= periodDurationUs) {

View File

@ -4276,6 +4276,40 @@ public final class ExoPlayerTest {
assertArrayEquals(new long[] {5_000, 5_000}, currentPlaybackPositions);
}
@Test
public void
timelineUpdateInMultiWindowMediaSource_removingPeriod_withUnpreparedMaskingMediaPeriod_doesNotThrow()
throws Exception {
TimelineWindowDefinition window1 =
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1);
TimelineWindowDefinition window2 =
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 2);
FakeMediaSource mediaSource = new FakeMediaSource(/* timeline= */ null);
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_BUFFERING)
// Do something and wait so that the player can create its unprepared MaskingMediaPeriod
.seek(/* positionMs= */ 0)
.waitForSeekProcessed()
// Let the player assign the unprepared period to window1.
.executeRunnable(() -> mediaSource.setNewSourceInfo(new FakeTimeline(window1, window2)))
.waitForTimelineChanged()
// Remove window1 and assume the update is handled without throwing.
.executeRunnable(() -> mediaSource.setNewSourceInfo(new FakeTimeline(window2)))
.waitForTimelineChanged()
.stop()
.build();
new ExoPlayerTestRunner.Builder()
.setMediaSources(mediaSource)
.setActionSchedule(actionSchedule)
.build(context)
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
// Assertion is to not throw while running the action schedule above.
}
@Test
public void setPlayWhenReady_keepsCurrentPosition() throws Exception {
AtomicLong positionAfterSetPlayWhenReady = new AtomicLong(C.TIME_UNSET);