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
This commit is contained in:
tonihei 2018-01-26 07:32:32 -08:00 committed by Oliver Woodman
parent ea21f72c62
commit 46c4ca7ddb
12 changed files with 234 additions and 185 deletions

View File

@ -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<Void> {
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);
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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<Void> {
/**
* 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;
}

View File

@ -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 <T> The type of the id used to identify prepared child sources.
*/
public abstract class CompositeMediaSource<T> implements MediaSource {
private final HashMap<T, MediaSource> 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.
*
* <p>{@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.
*
* <p>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();
}
}

View File

@ -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<Integer> {
private final MediaSource[] mediaSources;
private final Timeline[] timelines;
private final Object[] manifests;
private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
private final boolean[] duplicateFlags;
private final boolean isAtomic;
private final ShuffleOrder shuffleOrder;
@ -84,36 +83,21 @@ 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();
}
}
}
@ -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;
}

View File

@ -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<MediaSourceHolder>
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<MediaSource> 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<MediaSourceHolder> {
/** Data class to hold playlist media sources together with meta data needed to process them. */
/* package */ static final class MediaSourceHolder implements Comparable<MediaSourceHolder> {
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<CustomType> {
/** Message used to post actions from app thread to playback thread. */
private static final class MessageData<T> {
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;

View File

@ -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.
* <p>
* Note: To loop a {@link MediaSource} indefinitely, it is usually better to use
* {@link ExoPlayer#setRepeatMode(int)}.
*
* <p>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<Void> {
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 {

View File

@ -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.
* <p>
* The {@link Timeline}s of the sources being merged must have the same number of periods.
*
* <p>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<Integer> {
/**
* 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;
}

View File

@ -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<MediaPeriodId> {
/** 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<DeferredMediaPeriod>());
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) {