diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java index b07a9792a1..7c416c544f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -176,6 +176,12 @@ public abstract class Timeline { */ public boolean isLive; + /** + * Whether the information in this window is used as a placeholder for as long as the media + * hasn't provided the real information. + */ + public boolean isPlaceholder; + /** The index of the first period that belongs to this window. */ public int firstPeriodIndex; @@ -238,6 +244,7 @@ public abstract class Timeline { this.firstPeriodIndex = firstPeriodIndex; this.lastPeriodIndex = lastPeriodIndex; this.positionInFirstPeriodUs = positionInFirstPeriodUs; + this.isPlaceholder = false; return this; } @@ -319,6 +326,7 @@ public abstract class Timeline { && isSeekable == that.isSeekable && isDynamic == that.isDynamic && isLive == that.isLive + && isPlaceholder == that.isPlaceholder && defaultPositionUs == that.defaultPositionUs && durationUs == that.durationUs && firstPeriodIndex == that.firstPeriodIndex @@ -340,6 +348,7 @@ public abstract class Timeline { result = 31 * result + (isSeekable ? 1 : 0); result = 31 * result + (isDynamic ? 1 : 0); result = 31 * result + (isLive ? 1 : 0); + result = 31 * result + (isPlaceholder ? 1 : 0); result = 31 * result + (int) (defaultPositionUs ^ (defaultPositionUs >>> 32)); result = 31 * result + (int) (durationUs ^ (durationUs >>> 32)); result = 31 * result + firstPeriodIndex; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java index aff40d891d..1049d266f9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java @@ -331,7 +331,7 @@ public final class MaskingMediaSource extends CompositeMediaSource { @Override public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) { - return window.set( + window.set( Window.SINGLE_WINDOW_UID, tag, /* manifest= */ null, @@ -347,6 +347,8 @@ public final class MaskingMediaSource extends CompositeMediaSource { /* firstPeriodIndex= */ 0, /* lastPeriodIndex= */ 0, /* positionInFirstPeriodUs= */ 0); + window.isPlaceholder = true; + return window; } @Override diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 8e42309e89..ced29798db 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -3486,22 +3486,8 @@ public final class ExoPlayerTest { MediaSource mediaSource2 = new FakeMediaSource(timeline2); Timeline expectedDummyTimeline = new FakeTimeline( - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 1, - /* isSeekable= */ false, - /* isDynamic= */ true, - /* isLive= */ false, - /* durationUs= */ C.TIME_UNSET, - AdPlaybackState.NONE), - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 2, - /* isSeekable= */ false, - /* isDynamic= */ true, - /* isLive= */ false, - /* durationUs= */ C.TIME_UNSET, - AdPlaybackState.NONE)); + TimelineWindowDefinition.createDummy(/* tag= */ 1), + TimelineWindowDefinition.createDummy(/* tag= */ 2)); ActionSchedule actionSchedule = new ActionSchedule.Builder("testMoveMediaItem") .waitForTimelineChanged( @@ -3573,30 +3559,9 @@ public final class ExoPlayerTest { Timeline expectedDummyTimeline = new FakeTimeline( - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 1, - /* isSeekable= */ false, - /* isDynamic= */ true, - /* isLive= */ false, - /* durationUs= */ C.TIME_UNSET, - AdPlaybackState.NONE), - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 2, - /* isSeekable= */ false, - /* isDynamic= */ true, - /* isLive= */ false, - /* durationUs= */ C.TIME_UNSET, - AdPlaybackState.NONE), - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 3, - /* isSeekable= */ false, - /* isDynamic= */ true, - /* isLive= */ false, - /* durationUs= */ C.TIME_UNSET, - AdPlaybackState.NONE)); + TimelineWindowDefinition.createDummy(/* tag= */ 1), + TimelineWindowDefinition.createDummy(/* tag= */ 2), + TimelineWindowDefinition.createDummy(/* tag= */ 3)); Timeline expectedRealTimeline = new FakeTimeline(firstWindowDefinition, secondWindowDefinition, thirdWindowDefinition); Timeline expectedRealTimelineAfterRemove = @@ -3654,30 +3619,9 @@ public final class ExoPlayerTest { Timeline expectedDummyTimeline = new FakeTimeline( - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 1, - /* isSeekable= */ false, - /* isDynamic= */ true, - /* isLive= */ false, - /* durationUs= */ C.TIME_UNSET, - AdPlaybackState.NONE), - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 2, - /* isSeekable= */ false, - /* isDynamic= */ true, - /* isLive= */ false, - /* durationUs= */ C.TIME_UNSET, - AdPlaybackState.NONE), - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 3, - /* isSeekable= */ false, - /* isDynamic= */ true, - /* isLive= */ false, - /* durationUs= */ C.TIME_UNSET, - AdPlaybackState.NONE)); + TimelineWindowDefinition.createDummy(/* tag= */ 1), + TimelineWindowDefinition.createDummy(/* tag= */ 2), + TimelineWindowDefinition.createDummy(/* tag= */ 3)); Timeline expectedRealTimeline = new FakeTimeline(firstWindowDefinition, secondWindowDefinition, thirdWindowDefinition); Timeline expectedRealTimelineAfterRemove = new FakeTimeline(firstWindowDefinition); @@ -3819,22 +3763,8 @@ public final class ExoPlayerTest { Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE /* source update after prepare */); Timeline expectedSecondDummyTimeline = new FakeTimeline( - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 0, - /* isSeekable= */ false, - /* isDynamic= */ true, - /* isLive= */ false, - /* durationUs= */ C.TIME_UNSET, - AdPlaybackState.NONE), - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 0, - /* isSeekable= */ false, - /* isDynamic= */ true, - /* isLive= */ false, - /* durationUs= */ C.TIME_UNSET, - AdPlaybackState.NONE)); + TimelineWindowDefinition.createDummy(/* tag= */ 0), + TimelineWindowDefinition.createDummy(/* tag= */ 0)); Timeline expectedSecondRealTimeline = new FakeTimeline( new TimelineWindowDefinition( diff --git a/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java b/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java index 6bc70e3188..a7c0df2572 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java @@ -94,6 +94,10 @@ public class TimelineTest { otherWindow.isLive = true; assertThat(window).isNotEqualTo(otherWindow); + otherWindow = new Timeline.Window(); + otherWindow.isPlaceholder = true; + assertThat(window).isNotEqualTo(otherWindow); + otherWindow = new Timeline.Window(); otherWindow.defaultPositionUs = C.TIME_UNSET; assertThat(window).isNotEqualTo(otherWindow); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java index 6b738ec075..ce58aa5ca8 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java @@ -42,12 +42,31 @@ public final class FakeTimeline extends Timeline { public final boolean isSeekable; public final boolean isDynamic; public final boolean isLive; + public final boolean isPlaceholder; public final long durationUs; public final AdPlaybackState adPlaybackState; /** - * Creates a seekable, non-dynamic window definition with a duration of - * {@link #DEFAULT_WINDOW_DURATION_US}. + * Creates a window definition that corresponds to a dummy placeholder timeline using the given + * tag. + * + * @param tag The tag to use in the timeline. + */ + public static TimelineWindowDefinition createDummy(Object tag) { + return new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ tag, + /* isSeekable= */ false, + /* isDynamic= */ true, + /* isLive= */ false, + /* isPlaceholder= */ true, + /* durationUs= */ C.TIME_UNSET, + AdPlaybackState.NONE); + } + + /** + * Creates a seekable, non-dynamic window definition with a duration of {@link + * #DEFAULT_WINDOW_DURATION_US}. * * @param periodCount The number of periods in the window. Each period get an equal slice of the * total window duration. @@ -107,6 +126,7 @@ public final class FakeTimeline extends Timeline { isSeekable, isDynamic, /* isLive= */ isDynamic, + /* isPlaceholder= */ false, durationUs, adPlaybackState); } @@ -120,6 +140,7 @@ public final class FakeTimeline extends Timeline { * @param isSeekable Whether the window is seekable. * @param isDynamic Whether the window is dynamic. * @param isLive Whether the window is live. + * @param isPlaceholder Whether the window is a placeholder. * @param durationUs The duration of the window in microseconds. * @param adPlaybackState The ad playback state. */ @@ -129,6 +150,7 @@ public final class FakeTimeline extends Timeline { boolean isSeekable, boolean isDynamic, boolean isLive, + boolean isPlaceholder, long durationUs, AdPlaybackState adPlaybackState) { this.periodCount = periodCount; @@ -136,10 +158,10 @@ public final class FakeTimeline extends Timeline { this.isSeekable = isSeekable; this.isDynamic = isDynamic; this.isLive = isLive; + this.isPlaceholder = isPlaceholder; this.durationUs = durationUs; this.adPlaybackState = adPlaybackState; } - } private static final long AD_DURATION_US = 10 * C.MICROS_PER_SECOND; @@ -222,7 +244,7 @@ public final class FakeTimeline extends Timeline { @Override public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) { TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex]; - return window.set( + window.set( /* uid= */ windowDefinition.id, /* tag= */ windowDefinition.id, manifests[windowIndex], @@ -237,6 +259,8 @@ public final class FakeTimeline extends Timeline { periodOffsets[windowIndex], periodOffsets[windowIndex + 1] - 1, /* positionInFirstPeriodUs= */ 0); + window.isPlaceholder = windowDefinition.isPlaceholder; + return window; } @Override