Support duplicate entries in ConcatenatingMediaSource

People will inevitably try and do it, and it's pretty
easy to handle properly, so why not...

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=132047019
This commit is contained in:
olly 2016-09-02 03:39:00 -07:00 committed by Oliver Woodman
parent d48bf94145
commit bd76ec8b13
2 changed files with 66 additions and 27 deletions

View File

@ -43,7 +43,6 @@ import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.drm.FrameworkMediaDrm; import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager; import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
@ -364,7 +363,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi
} }
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl, HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl,
buildHttpDataSourceFactory(false)); buildHttpDataSourceFactory(false));
return new StreamingDrmSessionManager<FrameworkMediaCrypto>(uuid, return new StreamingDrmSessionManager<>(uuid,
FrameworkMediaDrm.newInstance(uuid), drmCallback, null, mainHandler, eventLogger); FrameworkMediaDrm.newInstance(uuid), drmCallback, null, mainHandler, eventLogger);
} }
@ -404,7 +403,9 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi
/** /**
* Build a HttpDataSource factory. * Build a HttpDataSource factory.
* @param useBandwidthMeter *
* @param useBandwidthMeter Whether to set {@link #BANDWIDTH_METER} as a listener to the new
* DataSource factory.
*/ */
private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) { private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) {
return new DefaultHttpDataSourceFactory(userAgent, useBandwidthMeter ? BANDWIDTH_METER : null); return new DefaultHttpDataSourceFactory(userAgent, useBandwidthMeter ? BANDWIDTH_METER : null);

View File

@ -23,10 +23,12 @@ import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map; import java.util.Map;
/** /**
* Concatenates multiple {@link MediaSource}s. * 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 implements MediaSource {
@ -34,47 +36,45 @@ public final class ConcatenatingMediaSource implements MediaSource {
private final Timeline[] timelines; private final Timeline[] timelines;
private final Object[] manifests; private final Object[] manifests;
private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod; private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
private final boolean[] duplicateFlags;
private Listener listener;
private ConcatenatedTimeline timeline; private ConcatenatedTimeline timeline;
/** /**
* @param mediaSources The {@link MediaSource}s to concatenate. * @param mediaSources The {@link MediaSource}s to concatenate. It is valid for the same
* {@link MediaSource} instance to be present more than once in the array.
*/ */
public ConcatenatingMediaSource(MediaSource... mediaSources) { public ConcatenatingMediaSource(MediaSource... mediaSources) {
this.mediaSources = mediaSources; this.mediaSources = mediaSources;
timelines = new Timeline[mediaSources.length]; timelines = new Timeline[mediaSources.length];
manifests = new Object[mediaSources.length]; manifests = new Object[mediaSources.length];
sourceIndexByMediaPeriod = new HashMap<>(); sourceIndexByMediaPeriod = new HashMap<>();
duplicateFlags = buildDuplicateFlags(mediaSources);
} }
@Override @Override
public void prepareSource(final Listener listener) { public void prepareSource(Listener listener) {
this.listener = listener;
for (int i = 0; i < mediaSources.length; i++) { for (int i = 0; i < mediaSources.length; i++) {
final int index = i; if (!duplicateFlags[i]) {
mediaSources[i].prepareSource(new Listener() { final int index = i;
mediaSources[i].prepareSource(new Listener() {
@Override @Override
public void onSourceInfoRefreshed(Timeline sourceTimeline, Object manifest) { public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
timelines[index] = sourceTimeline; handleSourceInfoRefreshed(index, timeline, manifest);
manifests[index] = manifest;
for (Timeline timeline : timelines) {
if (timeline == null) {
// Don't invoke the listener until all sources have timelines.
return;
}
} }
timeline = new ConcatenatedTimeline(timelines.clone()); });
listener.onSourceInfoRefreshed(timeline, manifests.clone()); }
}
});
} }
} }
@Override @Override
public void maybeThrowSourceInfoRefreshError() throws IOException { public void maybeThrowSourceInfoRefreshError() throws IOException {
for (MediaSource mediaSource : mediaSources) { for (int i = 0; i < mediaSources.length; i++) {
mediaSource.maybeThrowSourceInfoRefreshError(); if (!duplicateFlags[i]) {
mediaSources[i].maybeThrowSourceInfoRefreshError();
}
} }
} }
@ -98,11 +98,49 @@ public final class ConcatenatingMediaSource implements MediaSource {
@Override @Override
public void releaseSource() { public void releaseSource() {
for (MediaSource mediaSource : mediaSources) { for (int i = 0; i < mediaSources.length; i++) {
mediaSource.releaseSource(); if (!duplicateFlags[i]) {
mediaSources[i].releaseSource();
}
} }
} }
private void handleSourceInfoRefreshed(int sourceFirstIndex, Timeline sourceTimeline,
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]) {
timelines[i] = sourceTimeline;
manifests[i] = sourceManifest;
}
}
for (Timeline timeline : timelines) {
if (timeline == null) {
// Don't invoke the listener until all sources have timelines.
return;
}
}
timeline = new ConcatenatedTimeline(timelines.clone());
listener.onSourceInfoRefreshed(timeline, manifests.clone());
}
private static boolean[] buildDuplicateFlags(MediaSource[] mediaSources) {
boolean[] duplicateFlags = new boolean[mediaSources.length];
IdentityHashMap<MediaSource, Void> sources = new IdentityHashMap<>(mediaSources.length);
for (int i = 0; i < mediaSources.length; i++) {
MediaSource source = mediaSources[i];
if (!sources.containsKey(source)) {
sources.put(source, null);
} else {
duplicateFlags[i] = true;
}
}
return duplicateFlags;
}
/** /**
* A {@link Timeline} that is the concatenation of one or more {@link Timeline}s. * A {@link Timeline} that is the concatenation of one or more {@link Timeline}s.
*/ */