From 46c4ca7ddbb0ff80a5ea64a56f035037a345d0bb Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 26 Jan 2018 07:32:32 -0800 Subject: [PATCH] Add CompositeMediaSource to handle common tasks for composite media sources. This removes some boiler-plate code for compostite sources and will also simplify resuing media source in the future (because this class can keep track of child listeners). Issue:#3498 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=183387123 --- .../exoplayer2/ext/ima/ImaAdsMediaSource.java | 29 ++--- .../source/ClippingMediaSourceTest.java | 5 +- .../source/ConcatenatingMediaSourceTest.java | 9 +- .../source/LoopingMediaSourceTest.java | 5 +- .../source/MergingMediaSourceTest.java | 6 +- .../source/ClippingMediaSource.java | 18 ++- .../source/CompositeMediaSource.java | 113 ++++++++++++++++++ .../source/ConcatenatingMediaSource.java | 43 ++----- .../DynamicConcatenatingMediaSource.java | 48 +++----- .../exoplayer2/source/LoopingMediaSource.java | 42 +++---- .../exoplayer2/source/MergingMediaSource.java | 33 ++--- .../exoplayer2/source/ads/AdsMediaSource.java | 68 ++++------- 12 files changed, 234 insertions(+), 185 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java index cd646daf42..981e8352e0 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java @@ -20,12 +20,12 @@ import android.support.annotation.Nullable; import android.view.ViewGroup; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.source.CompositeMediaSource; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; -import java.io.IOException; /** * A {@link MediaSource} that inserts ads linearly with a provided content media source. @@ -33,9 +33,10 @@ import java.io.IOException; * @deprecated Use com.google.android.exoplayer2.source.ads.AdsMediaSource with ImaAdsLoader. */ @Deprecated -public final class ImaAdsMediaSource implements MediaSource { +public final class ImaAdsMediaSource extends CompositeMediaSource { private final AdsMediaSource adsMediaSource; + private Listener listener; /** * Constructs a new source that inserts ads linearly with the content specified by @@ -74,20 +75,10 @@ public final class ImaAdsMediaSource implements MediaSource { } @Override - public void prepareSource(final ExoPlayer player, boolean isTopLevelSource, - final Listener listener) { - adsMediaSource.prepareSource(player, false, new Listener() { - @Override - public void onSourceInfoRefreshed(MediaSource source, Timeline timeline, - @Nullable Object manifest) { - listener.onSourceInfoRefreshed(ImaAdsMediaSource.this, timeline, manifest); - } - }); - } - - @Override - public void maybeThrowSourceInfoRefreshError() throws IOException { - adsMediaSource.maybeThrowSourceInfoRefreshError(); + public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + super.prepareSource(player, isTopLevelSource, listener); + this.listener = listener; + prepareChildSource(/* id= */ null, adsMediaSource); } @Override @@ -101,8 +92,8 @@ public final class ImaAdsMediaSource implements MediaSource { } @Override - public void releaseSource() { - adsMediaSource.releaseSource(); + protected void onChildSourceInfoRefreshed( + Void id, MediaSource mediaSource, Timeline timeline, @Nullable Object manifest) { + listener.onSourceInfoRefreshed(this, timeline, manifest); } - } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java index a389c62942..4d5b94e88a 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java @@ -150,7 +150,10 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { ClippingMediaSource mediaSource = new ClippingMediaSource(fakeMediaSource, startMs, endMs); MediaSourceTestRunner testRunner = new MediaSourceTestRunner(mediaSource, null); try { - return testRunner.prepareSource(); + Timeline clippedTimeline = testRunner.prepareSource(); + testRunner.releaseSource(); + fakeMediaSource.assertReleased(); + return clippedTimeline; } finally { testRunner.release(); } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java index ca75d7455e..14f6563cda 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java @@ -243,7 +243,7 @@ public final class ConcatenatingMediaSourceTest extends TestCase { */ private static Timeline getConcatenatedTimeline(boolean isRepeatOneAtomic, Timeline... timelines) throws IOException { - MediaSource[] mediaSources = new MediaSource[timelines.length]; + FakeMediaSource[] mediaSources = new FakeMediaSource[timelines.length]; for (int i = 0; i < timelines.length; i++) { mediaSources[i] = new FakeMediaSource(timelines[i], null); } @@ -251,7 +251,12 @@ public final class ConcatenatingMediaSourceTest extends TestCase { new FakeShuffleOrder(mediaSources.length), mediaSources); MediaSourceTestRunner testRunner = new MediaSourceTestRunner(mediaSource, null); try { - return testRunner.prepareSource(); + Timeline timeline = testRunner.prepareSource(); + testRunner.releaseSource(); + for (int i = 0; i < mediaSources.length; i++) { + mediaSources[i].assertReleased(); + } + return timeline; } finally { testRunner.release(); } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java index 7648af195c..68a9e7676c 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java @@ -115,7 +115,10 @@ public class LoopingMediaSourceTest extends TestCase { LoopingMediaSource mediaSource = new LoopingMediaSource(fakeMediaSource, loopCount); MediaSourceTestRunner testRunner = new MediaSourceTestRunner(mediaSource, null); try { - return testRunner.prepareSource(); + Timeline loopingTimeline = testRunner.prepareSource(); + testRunner.releaseSource(); + fakeMediaSource.assertReleased(); + return loopingTimeline; } finally { testRunner.release(); } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java index 463ed984b0..6c048db09d 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java @@ -64,7 +64,7 @@ public class MergingMediaSourceTest extends TestCase { * forwards the first of the wrapped timelines. */ private static void testMergingMediaSourcePrepare(Timeline... timelines) throws IOException { - MediaSource[] mediaSources = new MediaSource[timelines.length]; + FakeMediaSource[] mediaSources = new FakeMediaSource[timelines.length]; for (int i = 0; i < timelines.length; i++) { mediaSources[i] = new FakeMediaSource(timelines[i], null); } @@ -74,6 +74,10 @@ public class MergingMediaSourceTest extends TestCase { Timeline timeline = testRunner.prepareSource(); // The merged timeline should always be the one from the first child. assertThat(timeline).isEqualTo(timelines[0]); + testRunner.releaseSource(); + for (int i = 0; i < mediaSources.length; i++) { + mediaSources[i].assertReleased(); + } } finally { testRunner.release(); } 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 42b1cfd1cf..e8d20c1c61 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 @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.source; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Timeline; @@ -31,7 +32,7 @@ import java.util.ArrayList; * positions. The wrapped source must consist of a single period that starts at the beginning of the * corresponding window. */ -public final class ClippingMediaSource implements MediaSource, MediaSource.Listener { +public final class ClippingMediaSource extends CompositeMediaSource { /** * Thrown when a {@link ClippingMediaSource} cannot clip its wrapped source. @@ -131,9 +132,10 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste @Override public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + super.prepareSource(player, isTopLevelSource, listener); Assertions.checkState(sourceListener == null, MEDIA_SOURCE_REUSED_ERROR_MESSAGE); sourceListener = listener; - mediaSource.prepareSource(player, false, this); + prepareChildSource(/* id= */ null, mediaSource); } @Override @@ -141,7 +143,7 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste if (clippingError != null) { throw clippingError; } - mediaSource.maybeThrowSourceInfoRefreshError(); + super.maybeThrowSourceInfoRefreshError(); } @Override @@ -160,14 +162,8 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste } @Override - public void releaseSource() { - mediaSource.releaseSource(); - } - - // MediaSource.Listener implementation. - - @Override - public void onSourceInfoRefreshed(MediaSource source, Timeline timeline, Object manifest) { + protected void onChildSourceInfoRefreshed( + Void id, MediaSource mediaSource, Timeline timeline, @Nullable Object manifest) { if (clippingError != null) { return; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java new file mode 100644 index 0000000000..6472fe3c2f --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.source; + +import android.support.annotation.CallSuper; +import android.support.annotation.Nullable; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.util.Assertions; +import java.io.IOException; +import java.util.HashMap; + +/** + * Composite {@link MediaSource} consisting of multiple child sources. + * + * @param The type of the id used to identify prepared child sources. + */ +public abstract class CompositeMediaSource implements MediaSource { + + private final HashMap childSources; + private ExoPlayer player; + + /** Create composite media source without child sources. */ + protected CompositeMediaSource() { + childSources = new HashMap<>(); + } + + @Override + @CallSuper + public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + this.player = player; + } + + @Override + @CallSuper + public void maybeThrowSourceInfoRefreshError() throws IOException { + for (MediaSource childSource : childSources.values()) { + childSource.maybeThrowSourceInfoRefreshError(); + } + } + + @Override + @CallSuper + public void releaseSource() { + for (MediaSource childSource : childSources.values()) { + childSource.releaseSource(); + } + childSources.clear(); + player = null; + } + + /** + * Called when the source info of a child source has been refreshed. + * + * @param id The unique id used to prepare the child source. + * @param mediaSource The child source whose source info has been refreshed. + * @param timeline The timeline of the child source. + * @param manifest The manifest of the child source. + */ + protected abstract void onChildSourceInfoRefreshed( + @Nullable T id, MediaSource mediaSource, Timeline timeline, @Nullable Object manifest); + + /** + * Prepares a child source. + * + *

{@link #onChildSourceInfoRefreshed(Object, MediaSource, Timeline, Object)} will be called + * when the child source updates its timeline and/or manifest with the same {@code id} passed to + * this method. + * + *

Any child sources that aren't explicitly released with {@link #releaseChildSource(Object)} + * will be released in {@link #releaseSource()}. + * + * @param id A unique id to identify the child source preparation. Null is allowed as an id. + * @param mediaSource The child {@link MediaSource}. + */ + protected void prepareChildSource(@Nullable final T id, final MediaSource mediaSource) { + Assertions.checkArgument(!childSources.containsKey(id)); + childSources.put(id, mediaSource); + mediaSource.prepareSource( + player, + /* isTopLevelSource= */ false, + new Listener() { + @Override + public void onSourceInfoRefreshed( + MediaSource source, Timeline timeline, @Nullable Object manifest) { + onChildSourceInfoRefreshed(id, mediaSource, timeline, manifest); + } + }); + } + + /** + * Releases a child source. + * + * @param id The unique id used to prepare the child source. + */ + protected void releaseChildSource(@Nullable T id) { + MediaSource removedChild = childSources.remove(id); + removedChild.releaseSource(); + } +} 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 2b089cfdbe..4cf0156aad 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 @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Player; @@ -23,7 +24,6 @@ import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; -import java.io.IOException; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; @@ -32,13 +32,12 @@ import java.util.Map; * Concatenates multiple {@link MediaSource}s. It is valid for the same {@link MediaSource} instance * to be present more than once in the concatenation. */ -public final class ConcatenatingMediaSource implements MediaSource { +public final class ConcatenatingMediaSource extends CompositeMediaSource { private final MediaSource[] mediaSources; private final Timeline[] timelines; private final Object[] manifests; private final Map sourceIndexByMediaPeriod; - private final boolean[] duplicateFlags; private final boolean isAtomic; private final ShuffleOrder shuffleOrder; @@ -84,40 +83,25 @@ public final class ConcatenatingMediaSource implements MediaSource { timelines = new Timeline[mediaSources.length]; manifests = new Object[mediaSources.length]; sourceIndexByMediaPeriod = new HashMap<>(); - duplicateFlags = buildDuplicateFlags(mediaSources); } @Override public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + super.prepareSource(player, isTopLevelSource, listener); Assertions.checkState(this.listener == null, MEDIA_SOURCE_REUSED_ERROR_MESSAGE); this.listener = listener; + boolean[] duplicateFlags = buildDuplicateFlags(mediaSources); if (mediaSources.length == 0) { listener.onSourceInfoRefreshed(this, Timeline.EMPTY, null); } else { for (int i = 0; i < mediaSources.length; i++) { if (!duplicateFlags[i]) { - final int index = i; - mediaSources[i].prepareSource(player, false, new Listener() { - @Override - public void onSourceInfoRefreshed(MediaSource source, Timeline timeline, - Object manifest) { - handleSourceInfoRefreshed(index, timeline, manifest); - } - }); + prepareChildSource(i, mediaSources[i]); } } } } - @Override - public void maybeThrowSourceInfoRefreshError() throws IOException { - for (int i = 0; i < mediaSources.length; i++) { - if (!duplicateFlags[i]) { - mediaSources[i].maybeThrowSourceInfoRefreshError(); - } - } - } - @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { int sourceIndex = timeline.getChildIndexByPeriodIndex(id.periodIndex); @@ -136,22 +120,17 @@ public final class ConcatenatingMediaSource implements MediaSource { } @Override - public void releaseSource() { - for (int i = 0; i < mediaSources.length; i++) { - if (!duplicateFlags[i]) { - mediaSources[i].releaseSource(); - } - } - } - - private void handleSourceInfoRefreshed(int sourceFirstIndex, Timeline sourceTimeline, - Object sourceManifest) { + protected void onChildSourceInfoRefreshed( + Integer sourceFirstIndex, + MediaSource mediaSource, + Timeline sourceTimeline, + @Nullable Object sourceManifest) { // Set the timeline and manifest. timelines[sourceFirstIndex] = sourceTimeline; manifests[sourceFirstIndex] = sourceManifest; // Also set the timeline and manifest for any duplicate entries of the same source. for (int i = sourceFirstIndex + 1; i < mediaSources.length; i++) { - if (mediaSources[i] == mediaSources[sourceFirstIndex]) { + if (mediaSources[i] == mediaSource) { timelines[i] = sourceTimeline; manifests[i] = sourceManifest; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java index 5dd4f004fa..d02478f8d5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java @@ -25,11 +25,11 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.PlayerMessage; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource.MediaSourceHolder; import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; -import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -41,7 +41,8 @@ import java.util.Map; * Concatenates multiple {@link MediaSource}s. The list of {@link MediaSource}s can be modified * during playback. Access to this class is thread-safe. */ -public final class DynamicConcatenatingMediaSource implements MediaSource, PlayerMessage.Target { +public final class DynamicConcatenatingMediaSource extends CompositeMediaSource + implements PlayerMessage.Target { private static final int MSG_ADD = 0; private static final int MSG_ADD_MULTIPLE = 1; @@ -331,6 +332,7 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe @Override public synchronized void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + super.prepareSource(player, isTopLevelSource, listener); Assertions.checkState(this.listener == null, MEDIA_SOURCE_REUSED_ERROR_MESSAGE); this.player = player; this.listener = listener; @@ -341,13 +343,6 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe maybeNotifyListener(null); } - @Override - public void maybeThrowSourceInfoRefreshError() throws IOException { - for (int i = 0; i < mediaSourceHolders.size(); i++) { - mediaSourceHolders.get(i).mediaSource.maybeThrowSourceInfoRefreshError(); - } - } - @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { int mediaSourceHolderIndex = findMediaSourceHolderByPeriodIndex(id.periodIndex); @@ -378,10 +373,12 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe } @Override - public void releaseSource() { - for (int i = 0; i < mediaSourceHolders.size(); i++) { - mediaSourceHolders.get(i).mediaSource.releaseSource(); - } + protected void onChildSourceInfoRefreshed( + MediaSourceHolder mediaSourceHolder, + MediaSource mediaSource, + Timeline timeline, + @Nullable Object manifest) { + updateMediaSourceInternal(mediaSourceHolder, timeline); } @Override @@ -459,12 +456,7 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe } correctOffsets(newIndex, newTimeline.getWindowCount(), newTimeline.getPeriodCount()); mediaSourceHolders.add(newIndex, newMediaSourceHolder); - newMediaSourceHolder.mediaSource.prepareSource(player, false, new Listener() { - @Override - public void onSourceInfoRefreshed(MediaSource source, Timeline newTimeline, Object manifest) { - updateMediaSourceInternal(newMediaSourceHolder, newTimeline); - } - }); + prepareChildSource(newMediaSourceHolder, newMediaSourceHolder.mediaSource); } private void addMediaSourcesInternal(int index, Collection mediaSources) { @@ -505,7 +497,7 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe mediaSourceHolders.remove(index); Timeline oldTimeline = holder.timeline; correctOffsets(index, -oldTimeline.getWindowCount(), -oldTimeline.getPeriodCount()); - holder.mediaSource.releaseSource(); + releaseChildSource(holder); } private void moveMediaSourceInternal(int currentIndex, int newIndex) { @@ -545,10 +537,8 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe return index; } - /** - * Data class to hold playlist media sources together with meta data needed to process them. - */ - private static final class MediaSourceHolder implements Comparable { + /** Data class to hold playlist media sources together with meta data needed to process them. */ + /* package */ static final class MediaSourceHolder implements Comparable { public final MediaSource mediaSource; public final Object uid; @@ -593,16 +583,14 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe } - /** - * Message used to post actions from app thread to playback thread. - */ - private static final class MessageData { + /** Message used to post actions from app thread to playback thread. */ + private static final class MessageData { public final int index; - public final CustomType customData; + public final T customData; public final @Nullable EventDispatcher actionOnCompletion; - public MessageData(int index, CustomType customData, @Nullable Runnable actionOnCompletion) { + public MessageData(int index, T customData, @Nullable Runnable actionOnCompletion) { this.index = index; this.actionOnCompletion = actionOnCompletion != null ? new EventDispatcher(actionOnCompletion) : null; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java index 5cf110c9ac..f92814cadc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Player; @@ -22,21 +23,20 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.ShuffleOrder.UnshuffledShuffleOrder; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Assertions; -import java.io.IOException; /** * Loops a {@link MediaSource} a specified number of times. - *

- * Note: To loop a {@link MediaSource} indefinitely, it is usually better to use - * {@link ExoPlayer#setRepeatMode(int)}. + * + *

Note: To loop a {@link MediaSource} indefinitely, it is usually better to use {@link + * ExoPlayer#setRepeatMode(int)}. */ -public final class LoopingMediaSource implements MediaSource { +public final class LoopingMediaSource extends CompositeMediaSource { private final MediaSource childSource; private final int loopCount; private int childPeriodCount; - private boolean wasPrepareSourceCalled; + private Listener listener; /** * Loops the provided source indefinitely. Note that it is usually better to use @@ -62,22 +62,10 @@ public final class LoopingMediaSource implements MediaSource { @Override public void prepareSource(ExoPlayer player, boolean isTopLevelSource, final Listener listener) { - Assertions.checkState(!wasPrepareSourceCalled, MEDIA_SOURCE_REUSED_ERROR_MESSAGE); - wasPrepareSourceCalled = true; - childSource.prepareSource(player, false, new Listener() { - @Override - public void onSourceInfoRefreshed(MediaSource source, Timeline timeline, Object manifest) { - childPeriodCount = timeline.getPeriodCount(); - Timeline loopingTimeline = loopCount != Integer.MAX_VALUE - ? new LoopingTimeline(timeline, loopCount) : new InfinitelyLoopingTimeline(timeline); - listener.onSourceInfoRefreshed(LoopingMediaSource.this, loopingTimeline, manifest); - } - }); - } - - @Override - public void maybeThrowSourceInfoRefreshError() throws IOException { - childSource.maybeThrowSourceInfoRefreshError(); + super.prepareSource(player, isTopLevelSource, listener); + Assertions.checkState(this.listener == null, MEDIA_SOURCE_REUSED_ERROR_MESSAGE); + this.listener = listener; + prepareChildSource(/* id= */ null, childSource); } @Override @@ -94,8 +82,14 @@ public final class LoopingMediaSource implements MediaSource { } @Override - public void releaseSource() { - childSource.releaseSource(); + protected void onChildSourceInfoRefreshed( + Void id, MediaSource mediaSource, Timeline timeline, @Nullable Object manifest) { + childPeriodCount = timeline.getPeriodCount(); + Timeline loopingTimeline = + loopCount != Integer.MAX_VALUE + ? new LoopingTimeline(timeline, loopCount) + : new InfinitelyLoopingTimeline(timeline); + listener.onSourceInfoRefreshed(this, loopingTimeline, manifest); } private static final class LoopingTimeline extends AbstractConcatenatedTimeline { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index accf82a68c..64fe35eb22 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.source; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.upstream.Allocator; @@ -28,10 +29,10 @@ import java.util.Arrays; /** * Merges multiple {@link MediaSource}s. - *

- * The {@link Timeline}s of the sources being merged must have the same number of periods. + * + *

The {@link Timeline}s of the sources being merged must have the same number of periods. */ -public final class MergingMediaSource implements MediaSource { +public final class MergingMediaSource extends CompositeMediaSource { /** * Thrown when a {@link MergingMediaSource} cannot merge its sources. @@ -98,16 +99,11 @@ public final class MergingMediaSource implements MediaSource { @Override public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + super.prepareSource(player, isTopLevelSource, listener); Assertions.checkState(this.listener == null, MEDIA_SOURCE_REUSED_ERROR_MESSAGE); this.listener = listener; for (int i = 0; i < mediaSources.length; i++) { - final int sourceIndex = i; - mediaSources[sourceIndex].prepareSource(player, false, new Listener() { - @Override - public void onSourceInfoRefreshed(MediaSource source, Timeline timeline, Object manifest) { - handleSourceInfoRefreshed(sourceIndex, timeline, manifest); - } - }); + prepareChildSource(i, mediaSources[i]); } } @@ -116,9 +112,7 @@ public final class MergingMediaSource implements MediaSource { if (mergeError != null) { throw mergeError; } - for (MediaSource mediaSource : mediaSources) { - mediaSource.maybeThrowSourceInfoRefreshError(); - } + super.maybeThrowSourceInfoRefreshError(); } @Override @@ -139,21 +133,16 @@ public final class MergingMediaSource implements MediaSource { } @Override - public void releaseSource() { - for (MediaSource mediaSource : mediaSources) { - mediaSource.releaseSource(); - } - } - - private void handleSourceInfoRefreshed(int sourceIndex, Timeline timeline, Object manifest) { + protected void onChildSourceInfoRefreshed( + Integer id, MediaSource mediaSource, Timeline timeline, @Nullable Object manifest) { if (mergeError == null) { mergeError = checkTimelineMerges(timeline); } if (mergeError != null) { return; } - pendingTimelineSources.remove(mediaSources[sourceIndex]); - if (sourceIndex == 0) { + pendingTimelineSources.remove(mediaSource); + if (mediaSource == mediaSources[0]) { primaryTimeline = timeline; primaryManifest = manifest; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index dbff387d82..bf76bbf9ab 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -24,10 +24,12 @@ import android.view.ViewGroup; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.source.CompositeMediaSource; import com.google.android.exoplayer2.source.DeferredMediaPeriod; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; @@ -39,10 +41,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -/** - * A {@link MediaSource} that inserts ads linearly with a provided content media source. - */ -public final class AdsMediaSource implements MediaSource { +/** A {@link MediaSource} that inserts ads linearly with a provided content media source. */ +public final class AdsMediaSource extends CompositeMediaSource { /** Factory for creating {@link MediaSource}s to play ad media. */ public interface MediaSourceFactory { @@ -107,7 +107,6 @@ public final class AdsMediaSource implements MediaSource { private final Timeline.Period period; private Handler playerHandler; - private ExoPlayer player; private volatile boolean released; // Accessed on the player thread. @@ -203,17 +202,12 @@ public final class AdsMediaSource implements MediaSource { @Override public void prepareSource(final ExoPlayer player, boolean isTopLevelSource, Listener listener) { + super.prepareSource(player, isTopLevelSource, listener); Assertions.checkArgument(isTopLevelSource); Assertions.checkState(this.listener == null, MEDIA_SOURCE_REUSED_ERROR_MESSAGE); this.listener = listener; - this.player = player; playerHandler = new Handler(); - contentMediaSource.prepareSource(player, false, new Listener() { - @Override - public void onSourceInfoRefreshed(MediaSource source, Timeline timeline, Object manifest) { - AdsMediaSource.this.onContentSourceInfoRefreshed(timeline, manifest); - } - }); + prepareChildSource(new MediaPeriodId(/* periodIndex= */ 0), contentMediaSource); mainHandler.post(new Runnable() { @Override public void run() { @@ -222,26 +216,14 @@ public final class AdsMediaSource implements MediaSource { }); } - @Override - public void maybeThrowSourceInfoRefreshError() throws IOException { - contentMediaSource.maybeThrowSourceInfoRefreshError(); - for (MediaSource[] mediaSources : adGroupMediaSources) { - for (MediaSource mediaSource : mediaSources) { - if (mediaSource != null) { - mediaSource.maybeThrowSourceInfoRefreshError(); - } - } - } - } - @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { if (adPlaybackState.adGroupCount > 0 && id.isAd()) { - final int adGroupIndex = id.adGroupIndex; - final int adIndexInAdGroup = id.adIndexInAdGroup; + int adGroupIndex = id.adGroupIndex; + int adIndexInAdGroup = id.adIndexInAdGroup; if (adGroupMediaSources[adGroupIndex].length <= adIndexInAdGroup) { Uri adUri = adPlaybackState.adUris[id.adGroupIndex][id.adIndexInAdGroup]; - final MediaSource adMediaSource = + MediaSource adMediaSource = adMediaSourceFactory.createMediaSource(adUri, eventHandler, eventListener); int oldAdCount = adGroupMediaSources[id.adGroupIndex].length; if (adIndexInAdGroup >= oldAdCount) { @@ -253,13 +235,7 @@ public final class AdsMediaSource implements MediaSource { } adGroupMediaSources[adGroupIndex][adIndexInAdGroup] = adMediaSource; deferredMediaPeriodByAdMediaSource.put(adMediaSource, new ArrayList()); - adMediaSource.prepareSource(player, false, new MediaSource.Listener() { - @Override - public void onSourceInfoRefreshed(MediaSource source, Timeline timeline, - @Nullable Object manifest) { - onAdSourceInfoRefreshed(adMediaSource, adGroupIndex, adIndexInAdGroup, timeline); - } - }); + prepareChildSource(id, adMediaSource); } MediaSource mediaSource = adGroupMediaSources[adGroupIndex][adIndexInAdGroup]; DeferredMediaPeriod deferredMediaPeriod = @@ -287,15 +263,8 @@ public final class AdsMediaSource implements MediaSource { @Override public void releaseSource() { + super.releaseSource(); released = true; - contentMediaSource.releaseSource(); - for (MediaSource[] mediaSources : adGroupMediaSources) { - for (MediaSource mediaSource : mediaSources) { - if (mediaSource != null) { - mediaSource.releaseSource(); - } - } - } mainHandler.post(new Runnable() { @Override public void run() { @@ -304,6 +273,21 @@ public final class AdsMediaSource implements MediaSource { }); } + @Override + protected void onChildSourceInfoRefreshed( + MediaPeriodId mediaPeriodId, + MediaSource mediaSource, + Timeline timeline, + @Nullable Object manifest) { + if (mediaPeriodId.isAd()) { + int adGroupIndex = mediaPeriodId.adGroupIndex; + int adIndexInAdGroup = mediaPeriodId.adIndexInAdGroup; + onAdSourceInfoRefreshed(mediaSource, adGroupIndex, adIndexInAdGroup, timeline); + } else { + onContentSourceInfoRefreshed(timeline, manifest); + } + } + // Internal methods. private void onAdPlaybackState(AdPlaybackState adPlaybackState) {