From 6b82d1c2bdf7464845d7f31250f87313154ced50 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 5 Apr 2018 07:21:18 -0700 Subject: [PATCH] Add setters to MediaSource factories for custom window tag. This field (formerly "id") is almost impossible to use so far. Having setters in the factories allows to specify custom tags for all media sources. Also added a ExoPlayer.getCurrentTag() method to retrieve the tag of the currently playing media source in a convenient way. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=191738754 --- RELEASENOTES.md | 2 + .../exoplayer2/ext/cast/CastPlayer.java | 8 ++ .../exoplayer2/ext/cast/CastTimeline.java | 18 +++- .../android/exoplayer2/ExoPlayerImpl.java | 8 ++ .../com/google/android/exoplayer2/Player.java | 6 ++ .../android/exoplayer2/SimpleExoPlayer.java | 5 + .../google/android/exoplayer2/Timeline.java | 96 ++++++++++--------- .../source/AbstractConcatenatedTimeline.java | 9 +- .../source/ClippingMediaSource.java | 6 +- .../source/ConcatenatingMediaSource.java | 4 +- .../source/ExtractorMediaSource.java | 30 +++++- .../exoplayer2/source/ForwardingTimeline.java | 6 +- .../source/SinglePeriodTimeline.java | 91 ++++++++++++++---- .../source/SingleSampleMediaSource.java | 30 +++++- .../source/ads/SinglePeriodAdTimeline.java | 6 +- .../android/exoplayer2/TimelineTest.java | 4 +- .../source/ClippingMediaSourceTest.java | 29 ++++-- .../source/ConcatenatingMediaSourceTest.java | 40 ++++---- .../source/LoopingMediaSourceTest.java | 6 +- .../source/SinglePeriodTimelineTest.java | 55 ++++++++++- .../source/dash/DashMediaSource.java | 95 +++++++++++++----- .../exoplayer2/source/hls/HlsMediaSource.java | 33 ++++++- .../source/smoothstreaming/SsMediaSource.java | 81 ++++++++++++---- .../exoplayer2/testutil/FakeTimeline.java | 20 ++-- .../exoplayer2/testutil/StubExoPlayer.java | 6 ++ .../exoplayer2/testutil/TimelineAsserts.java | 14 +-- 26 files changed, 517 insertions(+), 191 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d95288bb4d..f56a511f6a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -27,6 +27,8 @@ * Added callbacks to `MediaSourceEventListener` to get notified when media periods are created, released and being read from. * Support live stream clipping with `ClippingMediaSource`. + * Allow setting custom tags for all media sources in their factories. The tag + of the current window can be retrieved with `ExoPlayer.getCurrentTag`. * Audio: * Factor out `AudioTrack` position tracking from `DefaultAudioSink`. * Fix an issue where the playback position would pause just after playback diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index 50c883c3f6..7e0753ba10 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -481,6 +481,14 @@ public final class CastPlayer implements Player { : currentTimeline.getPreviousWindowIndex(getCurrentWindowIndex(), repeatMode, false); } + @Override + public @Nullable Object getCurrentTag() { + int windowIndex = getCurrentWindowIndex(); + return windowIndex > currentTimeline.getWindowCount() + ? null + : currentTimeline.getWindow(windowIndex, window, /* setTag= */ true).tag; + } + // TODO: Fill the cast timeline information with ProgressListener's duration updates. // See [Internal: b/65152553]. @Override diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTimeline.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTimeline.java index a0be844439..24d815bae2 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTimeline.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTimeline.java @@ -73,12 +73,22 @@ import java.util.Map; } @Override - public Window getWindow(int windowIndex, Window window, boolean setIds, - long defaultPositionProjectionUs) { + public Window getWindow( + int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) { long durationUs = durationsUs[windowIndex]; boolean isDynamic = durationUs == C.TIME_UNSET; - return window.set(ids[windowIndex], C.TIME_UNSET, C.TIME_UNSET, !isDynamic, isDynamic, - defaultPositionsUs[windowIndex], durationUs, windowIndex, windowIndex, 0); + Object tag = setTag ? ids[windowIndex] : null; + return window.set( + tag, + /* presentationStartTimeMs= */ C.TIME_UNSET, + /* windowStartTimeMs= */ C.TIME_UNSET, + /* isSeekable= */ !isDynamic, + isDynamic, + defaultPositionsUs[windowIndex], + durationUs, + /* firstPeriodIndex= */ windowIndex, + /* lastPeriodIndex= */ windowIndex, + /* positionInFirstPeriodUs= */ 0); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index ae55870baf..e33b7358a4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -311,6 +311,14 @@ import java.util.concurrent.CopyOnWriteArraySet; internalPlayer.setSeekParameters(seekParameters); } + @Override + public @Nullable Object getCurrentTag() { + int windowIndex = getCurrentWindowIndex(); + return windowIndex > playbackInfo.timeline.getWindowCount() + ? null + : playbackInfo.timeline.getWindow(windowIndex, window, /* setTag= */ true).tag; + } + @Override public void stop() { stop(/* reset= */ false); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 443ff8a2ea..85872339a3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -655,6 +655,12 @@ public interface Player { */ int getPreviousWindowIndex(); + /** + * Returns the tag of the currently playing window in the timeline. May be null if no tag is set + * or the timeline is not yet available. + */ + @Nullable Object getCurrentTag(); + /** * Returns the duration of the current window in milliseconds, or {@link C#TIME_UNSET} if the * duration is not known. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 419e082dea..e6979b4a60 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -664,6 +664,11 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player player.setSeekParameters(seekParameters); } + @Override + public @Nullable Object getCurrentTag() { + return player.getCurrentTag(); + } + @Override public void stop() { player.stop(); 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 50a3e66880..884bdc0f4d 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 @@ -118,10 +118,8 @@ public abstract class Timeline { */ public static final class Window { - /** - * An identifier for the window. Not necessarily unique. - */ - public Object id; + /** A custom tag for the window. Not necessarily unique. */ + public Object tag; /** * The start time of the presentation to which this window belongs in milliseconds since the @@ -174,13 +172,19 @@ public abstract class Timeline { */ public long positionInFirstPeriodUs; - /** - * Sets the data held by this window. - */ - public Window set(Object id, long presentationStartTimeMs, long windowStartTimeMs, - boolean isSeekable, boolean isDynamic, long defaultPositionUs, long durationUs, - int firstPeriodIndex, int lastPeriodIndex, long positionInFirstPeriodUs) { - this.id = id; + /** Sets the data held by this window. */ + public Window set( + Object tag, + long presentationStartTimeMs, + long windowStartTimeMs, + boolean isSeekable, + boolean isDynamic, + long defaultPositionUs, + long durationUs, + int firstPeriodIndex, + int lastPeriodIndex, + long positionInFirstPeriodUs) { + this.tag = tag; this.presentationStartTimeMs = presentationStartTimeMs; this.windowStartTimeMs = windowStartTimeMs; this.isSeekable = isSeekable; @@ -486,38 +490,36 @@ public abstract class Timeline { } - /** - * An empty timeline. - */ - public static final Timeline EMPTY = new Timeline() { + /** An empty timeline. */ + public static final Timeline EMPTY = + new Timeline() { - @Override - public int getWindowCount() { - return 0; - } + @Override + public int getWindowCount() { + return 0; + } - @Override - public Window getWindow(int windowIndex, Window window, boolean setIds, - long defaultPositionProjectionUs) { - throw new IndexOutOfBoundsException(); - } + @Override + public Window getWindow( + int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) { + throw new IndexOutOfBoundsException(); + } - @Override - public int getPeriodCount() { - return 0; - } + @Override + public int getPeriodCount() { + return 0; + } - @Override - public Period getPeriod(int periodIndex, Period period, boolean setIds) { - throw new IndexOutOfBoundsException(); - } + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + throw new IndexOutOfBoundsException(); + } - @Override - public int getIndexOfPeriod(Object uid) { - return C.INDEX_UNSET; - } - - }; + @Override + public int getIndexOfPeriod(Object uid) { + return C.INDEX_UNSET; + } + }; /** * Returns whether the timeline is empty. @@ -607,7 +609,7 @@ public abstract class Timeline { /** * Populates a {@link Window} with data for the window at the specified index. Does not populate - * {@link Window#id}. + * {@link Window#tag}. * * @param windowIndex The index of the window. * @param window The {@link Window} to populate. Must not be null. @@ -622,12 +624,12 @@ public abstract class Timeline { * * @param windowIndex The index of the window. * @param window The {@link Window} to populate. Must not be null. - * @param setIds Whether {@link Window#id} should be populated. If false, the field will be set to - * null. The caller should pass false for efficiency reasons unless the field is required. + * @param setTag Whether {@link Window#tag} should be populated. If false, the field will be set + * to null. The caller should pass false for efficiency reasons unless the field is required. * @return The populated {@link Window}, for convenience. */ - public final Window getWindow(int windowIndex, Window window, boolean setIds) { - return getWindow(windowIndex, window, setIds, 0); + public final Window getWindow(int windowIndex, Window window, boolean setTag) { + return getWindow(windowIndex, window, setTag, 0); } /** @@ -635,14 +637,14 @@ public abstract class Timeline { * * @param windowIndex The index of the window. * @param window The {@link Window} to populate. Must not be null. - * @param setIds Whether {@link Window#id} should be populated. If false, the field will be set to - * null. The caller should pass false for efficiency reasons unless the field is required. + * @param setTag Whether {@link Window#tag} should be populated. If false, the field will be set + * to null. The caller should pass false for efficiency reasons unless the field is required. * @param defaultPositionProjectionUs A duration into the future that the populated window's * default start position should be projected. * @return The populated {@link Window}, for convenience. */ - public abstract Window getWindow(int windowIndex, Window window, boolean setIds, - long defaultPositionProjectionUs); + public abstract Window getWindow( + int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs); /** * Returns the number of periods in the timeline. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java b/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java index 696a6f6fad..8663b4c05c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java @@ -155,13 +155,14 @@ import com.google.android.exoplayer2.Timeline; } @Override - public final Window getWindow(int windowIndex, Window window, boolean setIds, - long defaultPositionProjectionUs) { + public final Window getWindow( + int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) { int childIndex = getChildIndexByWindowIndex(windowIndex); int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); int firstPeriodIndexInChild = getFirstPeriodIndexByChildIndex(childIndex); - getTimelineByChildIndex(childIndex).getWindow(windowIndex - firstWindowIndexInChild, window, - setIds, defaultPositionProjectionUs); + getTimelineByChildIndex(childIndex) + .getWindow( + windowIndex - firstWindowIndexInChild, window, setTag, defaultPositionProjectionUs); window.firstPeriodIndex += firstPeriodIndexInChild; window.lastPeriodIndex += firstPeriodIndexInChild; return window; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index a3f1b10d30..f633dd8f15 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -363,10 +363,10 @@ public final class ClippingMediaSource extends CompositeMediaSource { } @Override - public Window getWindow(int windowIndex, Window window, boolean setIds, - long defaultPositionProjectionUs) { + public Window getWindow( + int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) { timeline.getWindow( - /* windowIndex= */ 0, window, setIds, /* defaultPositionProjectionUs= */ 0); + /* windowIndex= */ 0, window, setTag, /* defaultPositionProjectionUs= */ 0); window.positionInFirstPeriodUs += startUs; window.durationUs = durationUs; window.isDynamic = isDynamic; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 7e44895ac7..3e39139918 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -865,9 +865,9 @@ public class ConcatenatingMediaSource extends CompositeMediaSourceasList(childSources[4], childSources[5], childSources[6])); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3); - TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 555, 666, 777, 333); + TimelineAsserts.assertWindowTags(timeline, 222, 444, 111, 555, 666, 777, 333); // Move sources. mediaSource.moveMediaSource(2, 3); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 5, 1, 6, 7, 3); - TimelineAsserts.assertWindowIds(timeline, 222, 444, 555, 111, 666, 777, 333); + TimelineAsserts.assertWindowTags(timeline, 222, 444, 555, 111, 666, 777, 333); mediaSource.moveMediaSource(3, 2); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3); - TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 555, 666, 777, 333); + TimelineAsserts.assertWindowTags(timeline, 222, 444, 111, 555, 666, 777, 333); mediaSource.moveMediaSource(0, 6); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 4, 1, 5, 6, 7, 3, 2); - TimelineAsserts.assertWindowIds(timeline, 444, 111, 555, 666, 777, 333, 222); + TimelineAsserts.assertWindowTags(timeline, 444, 111, 555, 666, 777, 333, 222); mediaSource.moveMediaSource(6, 0); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3); - TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 555, 666, 777, 333); + TimelineAsserts.assertWindowTags(timeline, 222, 444, 111, 555, 666, 777, 333); // Remove in the middle. mediaSource.removeMediaSource(3); @@ -130,7 +130,7 @@ public final class ConcatenatingMediaSourceTest { mediaSource.removeMediaSource(1); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 2, 1, 3); - TimelineAsserts.assertWindowIds(timeline, 222, 111, 333); + TimelineAsserts.assertWindowTags(timeline, 222, 111, 333); for (int i = 3; i <= 6; i++) { childSources[i].assertReleased(); } @@ -169,14 +169,14 @@ public final class ConcatenatingMediaSourceTest { mediaSource.removeMediaSource(0); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 1, 3); - TimelineAsserts.assertWindowIds(timeline, 111, 333); + TimelineAsserts.assertWindowTags(timeline, 111, 333); childSources[1].assertReleased(); // Remove at back of queue. mediaSource.removeMediaSource(1); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 1); - TimelineAsserts.assertWindowIds(timeline, 111); + TimelineAsserts.assertWindowTags(timeline, 111); childSources[2].assertReleased(); // Remove last source. @@ -200,7 +200,7 @@ public final class ConcatenatingMediaSourceTest { Timeline timeline = testRunner.prepareSource(); TimelineAsserts.assertPeriodCounts(timeline, 3, 4, 2); - TimelineAsserts.assertWindowIds(timeline, 333, 444, 222); + TimelineAsserts.assertWindowTags(timeline, 333, 444, 222); TimelineAsserts.assertNextWindowIndices( timeline, Player.REPEAT_MODE_OFF, false, 1, 2, C.INDEX_UNSET); TimelineAsserts.assertPreviousWindowIndices( @@ -241,7 +241,7 @@ public final class ConcatenatingMediaSourceTest { // placeholder information for lazy sources. Timeline timeline = testRunner.prepareSource(); TimelineAsserts.assertPeriodCounts(timeline, 1, 1); - TimelineAsserts.assertWindowIds(timeline, 111, null); + TimelineAsserts.assertWindowTags(timeline, 111, null); TimelineAsserts.assertWindowIsDynamic(timeline, false, true); // Trigger source info refresh for lazy source and check that the timeline now contains all @@ -255,7 +255,7 @@ public final class ConcatenatingMediaSourceTest { }); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 1, 9); - TimelineAsserts.assertWindowIds(timeline, 111, 999); + TimelineAsserts.assertWindowTags(timeline, 111, 999); TimelineAsserts.assertWindowIsDynamic(timeline, false, false); testRunner.assertPrepareAndReleaseAllPeriods(); testRunner.assertCompletedManifestLoads(0, 1); @@ -272,7 +272,7 @@ public final class ConcatenatingMediaSourceTest { mediaSource.removeMediaSource(2); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 2, 9); - TimelineAsserts.assertWindowIds(timeline, null, 111, 222, 999); + TimelineAsserts.assertWindowTags(timeline, null, 111, 222, 999); TimelineAsserts.assertWindowIsDynamic(timeline, true, false, false, false); // Create a period from an unprepared lazy media source and assert Callback.onPrepared is not @@ -300,7 +300,7 @@ public final class ConcatenatingMediaSourceTest { }); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 8, 1, 2, 9); - TimelineAsserts.assertWindowIds(timeline, 888, 111, 222, 999); + TimelineAsserts.assertWindowTags(timeline, 888, 111, 222, 999); TimelineAsserts.assertWindowIsDynamic(timeline, false, false, false, false); assertThat(preparedCondition.getCount()).isEqualTo(0); @@ -346,7 +346,7 @@ public final class ConcatenatingMediaSourceTest { testRunner.assertTimelineChangeBlocking(); mediaSource.addMediaSource(6, mediaSources[2]); timeline = testRunner.assertTimelineChangeBlocking(); - TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); + TimelineAsserts.assertWindowTags(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3); TimelineAsserts.assertPreviousWindowIndices( timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET, 0, 1); @@ -685,7 +685,7 @@ public final class ConcatenatingMediaSourceTest { testRunner = new MediaSourceTestRunner(mediaSource, null); mediaSource.addMediaSources(Arrays.asList(createMediaSources(3))); Timeline timeline = testRunner.prepareSource(); - TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); + TimelineAsserts.assertWindowTags(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3); TimelineAsserts.assertPreviousWindowIndices( timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false, C.INDEX_UNSET, 0, 1); @@ -736,7 +736,7 @@ public final class ConcatenatingMediaSourceTest { nestedSource2.addMediaSource(childSources[3]); Timeline timeline = testRunner.assertTimelineChangeBlocking(); - TimelineAsserts.assertWindowIds(timeline, 111, 222, 333, 444); + TimelineAsserts.assertWindowTags(timeline, 111, 222, 333, 444); TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3, 4); TimelineAsserts.assertPreviousWindowIndices( timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false, C.INDEX_UNSET, 0, 1, 2); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java index 6aa710aff4..d639bc168a 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java @@ -50,7 +50,7 @@ public class LoopingMediaSourceTest { @Test public void testSingleLoop() throws IOException { Timeline timeline = getLoopingTimeline(multiWindowTimeline, 1); - TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); + TimelineAsserts.assertWindowTags(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1); for (boolean shuffled : new boolean[] {false, true}) { TimelineAsserts.assertPreviousWindowIndices( @@ -69,7 +69,7 @@ public class LoopingMediaSourceTest { @Test public void testMultiLoop() throws IOException { Timeline timeline = getLoopingTimeline(multiWindowTimeline, 3); - TimelineAsserts.assertWindowIds(timeline, 111, 222, 333, 111, 222, 333, 111, 222, 333); + TimelineAsserts.assertWindowTags(timeline, 111, 222, 333, 111, 222, 333, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1, 1, 1, 1, 1, 1); for (boolean shuffled : new boolean[] {false, true}) { TimelineAsserts.assertPreviousWindowIndices( @@ -90,7 +90,7 @@ public class LoopingMediaSourceTest { @Test public void testInfiniteLoop() throws IOException { Timeline timeline = getLoopingTimeline(multiWindowTimeline, Integer.MAX_VALUE); - TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); + TimelineAsserts.assertWindowTags(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1); for (boolean shuffled : new boolean[] {false, true}) { TimelineAsserts.assertPreviousWindowIndices( diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java index 2627052cc5..2587b78d99 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java @@ -56,8 +56,15 @@ public final class SinglePeriodTimelineTest { @Test public void testGetPeriodPositionDynamicWindowKnownDuration() { long windowDurationUs = 1000; - SinglePeriodTimeline timeline = new SinglePeriodTimeline(windowDurationUs, windowDurationUs, 0, - 0, false, true); + SinglePeriodTimeline timeline = + new SinglePeriodTimeline( + windowDurationUs, + windowDurationUs, + /* windowPositionInPeriodUs= */ 0, + /* windowDefaultStartPositionUs= */ 0, + /* isSeekable= */ false, + /* isDynamic= */ true, + /* tag= */ null); // Should return null with a positive position projection beyond window duration. Pair position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, windowDurationUs + 1); @@ -72,4 +79,48 @@ public final class SinglePeriodTimelineTest { assertThat(position.second).isEqualTo(0); } + @Test + public void setNullTag_returnsNullTag_butUsesDefaultUid() { + SinglePeriodTimeline timeline = + new SinglePeriodTimeline( + /* durationUs= */ C.TIME_UNSET, + /* isSeekable= */ false, + /* isDynamic= */ false, + /* tag= */ null); + + assertThat(timeline.getWindow(/* windowIndex= */ 0, window, /* setTag= */ false).tag).isNull(); + assertThat(timeline.getWindow(/* windowIndex= */ 0, window, /* setTag= */ true).tag).isNull(); + assertThat(timeline.getPeriod(/* periodIndex= */ 0, period, /* setIds= */ false).id).isNull(); + assertThat(timeline.getPeriod(/* periodIndex= */ 0, period, /* setIds= */ true).id).isNull(); + assertThat(timeline.getPeriod(/* periodIndex= */ 0, period, /* setIds= */ false).uid).isNull(); + assertThat(timeline.getPeriod(/* periodIndex= */ 0, period, /* setIds= */ true).uid) + .isNotNull(); + } + + @Test + public void setTag_isUsedForWindowTag() { + Object tag = new Object(); + SinglePeriodTimeline timeline = + new SinglePeriodTimeline( + /* durationUs= */ C.TIME_UNSET, /* isSeekable= */ false, /* isDynamic= */ false, tag); + + assertThat(timeline.getWindow(/* windowIndex= */ 0, window, /* setTag= */ false).tag).isNull(); + assertThat(timeline.getWindow(/* windowIndex= */ 0, window, /* setTag= */ true).tag) + .isEqualTo(tag); + } + + @Test + public void getIndexOfPeriod_returnsPeriod() { + SinglePeriodTimeline timeline = + new SinglePeriodTimeline( + /* durationUs= */ C.TIME_UNSET, + /* isSeekable= */ false, + /* isDynamic= */ false, + /* tag= */ null); + Object uid = timeline.getPeriod(/* periodIndex= */ 0, period, /* setIds= */ true).uid; + + assertThat(timeline.getIndexOfPeriod(uid)).isEqualTo(0); + assertThat(timeline.getIndexOfPeriod(/* uid= */ null)).isEqualTo(C.INDEX_UNSET); + assertThat(timeline.getIndexOfPeriod(/* uid= */ new Object())).isEqualTo(C.INDEX_UNSET); + } } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index b6371c2219..7b854e9d29 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -77,6 +77,7 @@ public final class DashMediaSource extends BaseMediaSource { private int minLoadableRetryCount; private long livePresentationDelayMs; private boolean isCreateCalled; + private @Nullable Object tag; /** * Creates a new factory for {@link DashMediaSource}s. @@ -97,6 +98,21 @@ public final class DashMediaSource extends BaseMediaSource { compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory(); } + /** + * Sets a tag for the media source which will be published in the {@link + * com.google.android.exoplayer2.Timeline} of the source as {@link + * com.google.android.exoplayer2.Timeline.Window#tag}. + * + * @param tag A tag for the media source. + * @return This factory, for convenience. + * @throws IllegalStateException If one of the {@code create} methods has already been called. + */ + public Factory setTag(Object tag) { + Assertions.checkState(!isCreateCalled); + this.tag = tag; + return this; + } + /** * Sets the minimum number of times to retry if a loading error occurs. The default value is * {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT}. @@ -175,13 +191,14 @@ public final class DashMediaSource extends BaseMediaSource { isCreateCalled = true; return new DashMediaSource( manifest, - null, - null, - null, + /* manifestUri= */ null, + /* manifestDataSourceFactory= */ null, + /* manifestParser= */ null, chunkSourceFactory, compositeSequenceableLoaderFactory, minLoadableRetryCount, - livePresentationDelayMs); + livePresentationDelayMs, + tag); } /** @@ -213,14 +230,15 @@ public final class DashMediaSource extends BaseMediaSource { manifestParser = new DashManifestParser(); } return new DashMediaSource( - null, + /* manifest= */ null, Assertions.checkNotNull(manifestUri), manifestDataSourceFactory, manifestParser, chunkSourceFactory, compositeSequenceableLoaderFactory, minLoadableRetryCount, - livePresentationDelayMs); + livePresentationDelayMs, + tag); } /** @@ -290,6 +308,7 @@ public final class DashMediaSource extends BaseMediaSource { private final Runnable simulateManifestRefreshRunnable; private final PlayerEmsgCallback playerEmsgCallback; private final LoaderErrorThrower manifestLoadErrorThrower; + private final @Nullable Object tag; private DataSource dataSource; private Loader loader; @@ -349,13 +368,14 @@ public final class DashMediaSource extends BaseMediaSource { MediaSourceEventListener eventListener) { this( manifest, - null, - null, - null, + /* manifestUri= */ null, + /* manifestDataSourceFactory= */ null, + /* manifestParser= */ null, chunkSourceFactory, new DefaultCompositeSequenceableLoaderFactory(), minLoadableRetryCount, - DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS); + DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS, + /* tag= */ null); if (eventHandler != null && eventListener != null) { addEventListener(eventHandler, eventListener); } @@ -444,14 +464,15 @@ public final class DashMediaSource extends BaseMediaSource { Handler eventHandler, MediaSourceEventListener eventListener) { this( - null, + /* manifest= */ null, manifestUri, manifestDataSourceFactory, manifestParser, chunkSourceFactory, new DefaultCompositeSequenceableLoaderFactory(), minLoadableRetryCount, - livePresentationDelayMs); + livePresentationDelayMs, + /* tag= */ null); if (eventHandler != null && eventListener != null) { addEventListener(eventHandler, eventListener); } @@ -465,7 +486,8 @@ public final class DashMediaSource extends BaseMediaSource { DashChunkSource.Factory chunkSourceFactory, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, int minLoadableRetryCount, - long livePresentationDelayMs) { + long livePresentationDelayMs, + @Nullable Object tag) { this.initialManifestUri = manifestUri; this.manifest = manifest; this.manifestUri = manifestUri; @@ -475,6 +497,7 @@ public final class DashMediaSource extends BaseMediaSource { this.minLoadableRetryCount = minLoadableRetryCount; this.livePresentationDelayMs = livePresentationDelayMs; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; + this.tag = tag; sideloadedManifest = manifest != null; manifestEventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null); manifestUriLock = new Object(); @@ -862,9 +885,16 @@ public final class DashMediaSource extends BaseMediaSource { } long windowStartTimeMs = manifest.availabilityStartTimeMs + manifest.getPeriod(0).startMs + C.usToMs(currentStartTimeUs); - DashTimeline timeline = new DashTimeline(manifest.availabilityStartTimeMs, windowStartTimeMs, - firstPeriodId, currentStartTimeUs, windowDurationUs, windowDefaultStartPositionUs, - manifest); + DashTimeline timeline = + new DashTimeline( + manifest.availabilityStartTimeMs, + windowStartTimeMs, + firstPeriodId, + currentStartTimeUs, + windowDurationUs, + windowDefaultStartPositionUs, + manifest, + tag); refreshSourceInfo(timeline, manifest); if (!sideloadedManifest) { @@ -993,10 +1023,17 @@ public final class DashMediaSource extends BaseMediaSource { private final long windowDurationUs; private final long windowDefaultStartPositionUs; private final DashManifest manifest; + private final @Nullable Object windowTag; - public DashTimeline(long presentationStartTimeMs, long windowStartTimeMs, int firstPeriodId, - long offsetInFirstPeriodUs, long windowDurationUs, long windowDefaultStartPositionUs, - DashManifest manifest) { + public DashTimeline( + long presentationStartTimeMs, + long windowStartTimeMs, + int firstPeriodId, + long offsetInFirstPeriodUs, + long windowDurationUs, + long windowDefaultStartPositionUs, + DashManifest manifest, + @Nullable Object windowTag) { this.presentationStartTimeMs = presentationStartTimeMs; this.windowStartTimeMs = windowStartTimeMs; this.firstPeriodId = firstPeriodId; @@ -1004,6 +1041,7 @@ public final class DashMediaSource extends BaseMediaSource { this.windowDurationUs = windowDurationUs; this.windowDefaultStartPositionUs = windowDefaultStartPositionUs; this.manifest = manifest; + this.windowTag = windowTag; } @Override @@ -1028,14 +1066,23 @@ public final class DashMediaSource extends BaseMediaSource { } @Override - public Window getWindow(int windowIndex, Window window, boolean setIdentifier, - long defaultPositionProjectionUs) { + public Window getWindow( + int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) { Assertions.checkIndex(windowIndex, 0, 1); long windowDefaultStartPositionUs = getAdjustedWindowDefaultStartPositionUs( defaultPositionProjectionUs); - return window.set(null, presentationStartTimeMs, windowStartTimeMs, true /* isSeekable */, - manifest.dynamic, windowDefaultStartPositionUs, windowDurationUs, 0, - manifest.getPeriodCount() - 1, offsetInFirstPeriodUs); + Object tag = setTag ? windowTag : null; + return window.set( + tag, + presentationStartTimeMs, + windowStartTimeMs, + /* isSeekable= */ true, + manifest.dynamic, + windowDefaultStartPositionUs, + windowDurationUs, + /* firstPeriodIndex= */ 0, + manifest.getPeriodCount() - 1, + offsetInFirstPeriodUs); } @Override diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 37d37575c2..01bb36f6ce 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -62,6 +62,7 @@ public final class HlsMediaSource extends BaseMediaSource private int minLoadableRetryCount; private boolean allowChunklessPreparation; private boolean isCreateCalled; + private @Nullable Object tag; /** * Creates a new factory for {@link HlsMediaSource}s. @@ -87,6 +88,21 @@ public final class HlsMediaSource extends BaseMediaSource compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory(); } + /** + * Sets a tag for the media source which will be published in the {@link + * com.google.android.exoplayer2.Timeline} of the source as {@link + * com.google.android.exoplayer2.Timeline.Window#tag}. + * + * @param tag A tag for the media source. + * @return This factory, for convenience. + * @throws IllegalStateException If one of the {@code create} methods has already been called. + */ + public Factory setTag(Object tag) { + Assertions.checkState(!isCreateCalled); + this.tag = tag; + return this; + } + /** * Sets the factory for {@link Extractor}s for the segments. The default value is {@link * HlsExtractorFactory#DEFAULT}. @@ -181,7 +197,8 @@ public final class HlsMediaSource extends BaseMediaSource compositeSequenceableLoaderFactory, minLoadableRetryCount, playlistParser, - allowChunklessPreparation); + allowChunklessPreparation, + tag); } /** @@ -218,6 +235,7 @@ public final class HlsMediaSource extends BaseMediaSource private final int minLoadableRetryCount; private final ParsingLoadable.Parser playlistParser; private final boolean allowChunklessPreparation; + private final @Nullable Object tag; private HlsPlaylistTracker playlistTracker; @@ -292,7 +310,8 @@ public final class HlsMediaSource extends BaseMediaSource new DefaultCompositeSequenceableLoaderFactory(), minLoadableRetryCount, playlistParser, - false); + /* allowChunklessPreparation= */ false, + /* tag= */ null); if (eventHandler != null && eventListener != null) { addEventListener(eventHandler, eventListener); } @@ -305,7 +324,8 @@ public final class HlsMediaSource extends BaseMediaSource CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, int minLoadableRetryCount, ParsingLoadable.Parser playlistParser, - boolean allowChunklessPreparation) { + boolean allowChunklessPreparation, + @Nullable Object tag) { this.manifestUri = manifestUri; this.dataSourceFactory = dataSourceFactory; this.extractorFactory = extractorFactory; @@ -313,6 +333,7 @@ public final class HlsMediaSource extends BaseMediaSource this.minLoadableRetryCount = minLoadableRetryCount; this.playlistParser = playlistParser; this.allowChunklessPreparation = allowChunklessPreparation; + this.tag = tag; } @Override @@ -388,7 +409,8 @@ public final class HlsMediaSource extends BaseMediaSource /* windowPositionInPeriodUs= */ offsetFromInitialStartTimeUs, windowDefaultStartPositionUs, /* isSeekable= */ true, - /* isDynamic= */ !playlist.hasEndTag); + /* isDynamic= */ !playlist.hasEndTag, + tag); } else /* not live */ { if (windowDefaultStartPositionUs == C.TIME_UNSET) { windowDefaultStartPositionUs = 0; @@ -402,7 +424,8 @@ public final class HlsMediaSource extends BaseMediaSource /* windowPositionInPeriodUs= */ 0, windowDefaultStartPositionUs, /* isSeekable= */ true, - /* isDynamic= */ false); + /* isDynamic= */ false, + tag); } refreshSourceInfo(timeline, new HlsManifest(playlistTracker.getMasterPlaylist(), playlist)); } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index d07cfd116b..72d1ba1efd 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -66,6 +66,7 @@ public final class SsMediaSource extends BaseMediaSource private int minLoadableRetryCount; private long livePresentationDelayMs; private boolean isCreateCalled; + private @Nullable Object tag; /** * Creates a new factory for {@link SsMediaSource}s. @@ -86,6 +87,20 @@ public final class SsMediaSource extends BaseMediaSource compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory(); } + /** + * Sets a tag for the media source which will be published in the {@link Timeline} of the source + * as {@link Timeline.Window#tag}. + * + * @param tag A tag for the media source. + * @return This factory, for convenience. + * @throws IllegalStateException If one of the {@code create} methods has already been called. + */ + public Factory setTag(Object tag) { + Assertions.checkState(!isCreateCalled); + this.tag = tag; + return this; + } + /** * Sets the minimum number of times to retry if a loading error occurs. The default value is * {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT}. @@ -161,13 +176,14 @@ public final class SsMediaSource extends BaseMediaSource isCreateCalled = true; return new SsMediaSource( manifest, - null, - null, - null, + /* manifestUri= */ null, + /* manifestDataSourceFactory= */ null, + /* manifestParser= */ null, chunkSourceFactory, compositeSequenceableLoaderFactory, minLoadableRetryCount, - livePresentationDelayMs); + livePresentationDelayMs, + tag); } /** @@ -199,14 +215,15 @@ public final class SsMediaSource extends BaseMediaSource manifestParser = new SsManifestParser(); } return new SsMediaSource( - null, + /* manifest= */ null, Assertions.checkNotNull(manifestUri), manifestDataSourceFactory, manifestParser, chunkSourceFactory, compositeSequenceableLoaderFactory, minLoadableRetryCount, - livePresentationDelayMs); + livePresentationDelayMs, + tag); } /** @@ -261,6 +278,7 @@ public final class SsMediaSource extends BaseMediaSource private final EventDispatcher manifestEventDispatcher; private final ParsingLoadable.Parser manifestParser; private final ArrayList mediaPeriods; + private final @Nullable Object tag; private DataSource manifestDataSource; private Loader manifestLoader; @@ -309,13 +327,14 @@ public final class SsMediaSource extends BaseMediaSource MediaSourceEventListener eventListener) { this( manifest, - null, - null, - null, + /* manifestUri= */ null, + /* manifestDataSourceFactory= */ null, + /* manifestParser= */ null, chunkSourceFactory, new DefaultCompositeSequenceableLoaderFactory(), minLoadableRetryCount, - DEFAULT_LIVE_PRESENTATION_DELAY_MS); + DEFAULT_LIVE_PRESENTATION_DELAY_MS, + /* tag= */ null); if (eventHandler != null && eventListener != null) { addEventListener(eventHandler, eventListener); } @@ -400,14 +419,15 @@ public final class SsMediaSource extends BaseMediaSource Handler eventHandler, MediaSourceEventListener eventListener) { this( - null, + /* manifest= */ null, manifestUri, manifestDataSourceFactory, manifestParser, chunkSourceFactory, new DefaultCompositeSequenceableLoaderFactory(), minLoadableRetryCount, - livePresentationDelayMs); + livePresentationDelayMs, + /* tag= */ null); if (eventHandler != null && eventListener != null) { addEventListener(eventHandler, eventListener); } @@ -421,7 +441,8 @@ public final class SsMediaSource extends BaseMediaSource SsChunkSource.Factory chunkSourceFactory, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, int minLoadableRetryCount, - long livePresentationDelayMs) { + long livePresentationDelayMs, + @Nullable Object tag) { Assertions.checkState(manifest == null || !manifest.isLive); this.manifest = manifest; this.manifestUri = manifestUri == null ? null : SsUtil.fixManifestUri(manifestUri); @@ -432,6 +453,7 @@ public final class SsMediaSource extends BaseMediaSource this.minLoadableRetryCount = minLoadableRetryCount; this.livePresentationDelayMs = livePresentationDelayMs; this.manifestEventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null); + this.tag = tag; sideloadedManifest = manifest != null; mediaPeriods = new ArrayList<>(); } @@ -555,8 +577,15 @@ public final class SsMediaSource extends BaseMediaSource Timeline timeline; if (startTimeUs == Long.MAX_VALUE) { long periodDurationUs = manifest.isLive ? C.TIME_UNSET : 0; - timeline = new SinglePeriodTimeline(periodDurationUs, 0, 0, 0, true /* isSeekable */, - manifest.isLive /* isDynamic */); + timeline = + new SinglePeriodTimeline( + periodDurationUs, + /* windowDurationUs= */ 0, + /* windowPositionInPeriodUs= */ 0, + /* windowDefaultStartPositionUs= */ 0, + /* isSeekable= */ true, + manifest.isLive, + tag); } else if (manifest.isLive) { if (manifest.dvrWindowLengthUs != C.TIME_UNSET && manifest.dvrWindowLengthUs > 0) { startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs); @@ -569,13 +598,27 @@ public final class SsMediaSource extends BaseMediaSource // it to the middle of the window. defaultStartPositionUs = Math.min(MIN_LIVE_DEFAULT_START_POSITION_US, durationUs / 2); } - timeline = new SinglePeriodTimeline(C.TIME_UNSET, durationUs, startTimeUs, - defaultStartPositionUs, true /* isSeekable */, true /* isDynamic */); + timeline = + new SinglePeriodTimeline( + /* periodDurationUs= */ C.TIME_UNSET, + durationUs, + startTimeUs, + defaultStartPositionUs, + /* isSeekable= */ true, + /* isDynamic= */ true, + tag); } else { long durationUs = manifest.durationUs != C.TIME_UNSET ? manifest.durationUs : endTimeUs - startTimeUs; - timeline = new SinglePeriodTimeline(startTimeUs + durationUs, durationUs, startTimeUs, 0, - true /* isSeekable */, false /* isDynamic */); + timeline = + new SinglePeriodTimeline( + startTimeUs + durationUs, + durationUs, + startTimeUs, + /* windowDefaultStartPositionUs= */ 0, + /* isSeekable= */ true, + /* isDynamic= */ false, + tag); } refreshSourceInfo(timeline, manifest); } 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 521c8ee52a..4e118366d7 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 @@ -164,13 +164,21 @@ public final class FakeTimeline extends Timeline { } @Override - public Window getWindow(int windowIndex, Window window, boolean setIds, - long defaultPositionProjectionUs) { + public Window getWindow( + int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) { TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex]; - Object id = setIds ? windowDefinition.id : null; - return window.set(id, C.TIME_UNSET, C.TIME_UNSET, windowDefinition.isSeekable, - windowDefinition.isDynamic, 0, windowDefinition.durationUs, periodOffsets[windowIndex], - periodOffsets[windowIndex + 1] - 1, 0); + Object tag = setTag ? windowDefinition.id : null; + return window.set( + tag, + /* presentationStartTimeMs= */ C.TIME_UNSET, + /* windowStartTimeMs= */ C.TIME_UNSET, + windowDefinition.isSeekable, + windowDefinition.isDynamic, + /* defaultPositionUs= */ 0, + windowDefinition.durationUs, + periodOffsets[windowIndex], + periodOffsets[windowIndex + 1] - 1, + /* positionInFirstPeriodUs= */ 0); } @Override diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index af8b10e6d3..8de92ddfa9 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.testutil; import android.os.Looper; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; @@ -142,6 +143,11 @@ public abstract class StubExoPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } + @Override + public @Nullable Object getCurrentTag() { + throw new UnsupportedOperationException(); + } + @Override public void stop() { throw new UnsupportedOperationException(); diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/TimelineAsserts.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/TimelineAsserts.java index 17045f749a..a0ca6af8a9 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/TimelineAsserts.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/TimelineAsserts.java @@ -34,7 +34,7 @@ public final class TimelineAsserts { /** Assert that timeline is empty (i.e. has no windows or periods). */ public static void assertEmpty(Timeline timeline) { - assertWindowIds(timeline); + assertWindowTags(timeline); assertPeriodCounts(timeline); for (boolean shuffled : new boolean[] {false, true}) { assertThat(timeline.getFirstWindowIndex(shuffled)).isEqualTo(C.INDEX_UNSET); @@ -43,18 +43,18 @@ public final class TimelineAsserts { } /** - * Asserts that window IDs are set correctly. + * Asserts that window tags are set correctly. * - * @param expectedWindowIds A list of expected window IDs. If an ID is unknown or not important + * @param expectedWindowTags A list of expected window tags. If a tag is unknown or not important * {@code null} can be passed to skip this window. */ - public static void assertWindowIds(Timeline timeline, Object... expectedWindowIds) { + public static void assertWindowTags(Timeline timeline, Object... expectedWindowTags) { Window window = new Window(); - assertThat(timeline.getWindowCount()).isEqualTo(expectedWindowIds.length); + assertThat(timeline.getWindowCount()).isEqualTo(expectedWindowTags.length); for (int i = 0; i < timeline.getWindowCount(); i++) { timeline.getWindow(i, window, true); - if (expectedWindowIds[i] != null) { - assertThat(window.id).isEqualTo(expectedWindowIds[i]); + if (expectedWindowTags[i] != null) { + assertThat(window.tag).isEqualTo(expectedWindowTags[i]); } } }