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:
parent
ea21f72c62
commit
46c4ca7ddb
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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,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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user