Allow DashMediaSource to take an optional DashManifest

Also allow custom DashManifestParser injection, to
support parsing of custom elements and attributes that
service providers may wish to include in their manifests
(e.g. #2058).

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=139813108
This commit is contained in:
olly 2016-11-21 11:51:59 -08:00 committed by Oliver Woodman
parent 90e0919d09
commit 81ce6bba7a
2 changed files with 167 additions and 34 deletions

View File

@ -33,6 +33,7 @@ import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -80,6 +81,7 @@ public final class DashMediaSource implements MediaSource {
private static final String TAG = "DashMediaSource"; private static final String TAG = "DashMediaSource";
private final boolean sideloadedManifest;
private final DataSource.Factory manifestDataSourceFactory; private final DataSource.Factory manifestDataSourceFactory;
private final DashChunkSource.Factory chunkSourceFactory; private final DashChunkSource.Factory chunkSourceFactory;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
@ -95,6 +97,7 @@ public final class DashMediaSource implements MediaSource {
private MediaSource.Listener sourceListener; private MediaSource.Listener sourceListener;
private DataSource dataSource; private DataSource dataSource;
private Loader loader; private Loader loader;
private LoaderErrorThrower loaderErrorThrower;
private Uri manifestUri; private Uri manifestUri;
private long manifestLoadStartTimestamp; private long manifestLoadStartTimestamp;
@ -105,6 +108,47 @@ public final class DashMediaSource implements MediaSource {
private int firstPeriodId; private int firstPeriodId;
/**
* Constructs an instance to play a given {@link DashManifest}, which must be static.
*
* @param manifest The manifest. {@link DashManifest#dynamic} must be false.
* @param chunkSourceFactory A factory for {@link DashChunkSource} instances.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public DashMediaSource(DashManifest manifest, DashChunkSource.Factory chunkSourceFactory,
Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) {
this(manifest, chunkSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, eventHandler,
eventListener);
}
/**
* Constructs an instance to play a given {@link DashManifest}, which must be static.
*
* @param manifest The manifest. {@link DashManifest#dynamic} must be false.
* @param chunkSourceFactory A factory for {@link DashChunkSource} instances.
* @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public DashMediaSource(DashManifest manifest, DashChunkSource.Factory chunkSourceFactory,
int minLoadableRetryCount, Handler eventHandler, AdaptiveMediaSourceEventListener
eventListener) {
this(manifest, null, null, null, chunkSourceFactory, minLoadableRetryCount,
DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS, eventHandler, eventListener);
}
/**
* Constructs an instance to play the manifest at a given {@link Uri}, which may be dynamic or
* static.
*
* @param manifestUri The manifest {@link Uri}.
* @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used
* to load (and refresh) the manifest.
* @param chunkSourceFactory A factory for {@link DashChunkSource} instances.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory,
DashChunkSource.Factory chunkSourceFactory, Handler eventHandler, DashChunkSource.Factory chunkSourceFactory, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) { AdaptiveMediaSourceEventListener eventListener) {
@ -113,32 +157,91 @@ public final class DashMediaSource implements MediaSource {
eventHandler, eventListener); eventHandler, eventListener);
} }
/**
* Constructs an instance to play the manifest at a given {@link Uri}, which may be dynamic or
* static.
*
* @param manifestUri The manifest {@link Uri}.
* @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used
* to load (and refresh) the manifest.
* @param chunkSourceFactory A factory for {@link DashChunkSource} instances.
* @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs.
* @param livePresentationDelayMs For live playbacks, the duration in milliseconds by which the
* default start position should precede the end of the live window. Use
* {@link #DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS} to use the value specified by
* the manifest, if present.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory,
DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount,
long livePresentationDelayMs, Handler eventHandler, long livePresentationDelayMs, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) { AdaptiveMediaSourceEventListener eventListener) {
this(manifestUri, manifestDataSourceFactory, new DashManifestParser(), chunkSourceFactory,
minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener);
}
/**
* Constructs an instance to play the manifest at a given {@link Uri}, which may be dynamic or
* static.
*
* @param manifestUri The manifest {@link Uri}.
* @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used
* to load (and refresh) the manifest.
* @param manifestParser A parser for loaded manifest data.
* @param chunkSourceFactory A factory for {@link DashChunkSource} instances.
* @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs.
* @param livePresentationDelayMs For live playbacks, the duration in milliseconds by which the
* default start position should precede the end of the live window. Use
* {@link #DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS} to use the value specified by
* the manifest, if present.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory,
DashManifestParser manifestParser, DashChunkSource.Factory chunkSourceFactory,
int minLoadableRetryCount, long livePresentationDelayMs, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) {
this(null, manifestUri, manifestDataSourceFactory, manifestParser, chunkSourceFactory,
minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener);
}
private DashMediaSource(DashManifest manifest, Uri manifestUri,
DataSource.Factory manifestDataSourceFactory, DashManifestParser manifestParser,
DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount,
long livePresentationDelayMs, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) {
this.manifest = manifest;
this.manifestUri = manifestUri; this.manifestUri = manifestUri;
this.manifestDataSourceFactory = manifestDataSourceFactory; this.manifestDataSourceFactory = manifestDataSourceFactory;
this.manifestParser = manifestParser;
this.chunkSourceFactory = chunkSourceFactory; this.chunkSourceFactory = chunkSourceFactory;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.livePresentationDelayMs = livePresentationDelayMs; this.livePresentationDelayMs = livePresentationDelayMs;
sideloadedManifest = manifest != null;
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
manifestParser = new DashManifestParser();
manifestCallback = new ManifestCallback();
manifestUriLock = new Object(); manifestUriLock = new Object();
periodsById = new SparseArray<>(); periodsById = new SparseArray<>();
refreshManifestRunnable = new Runnable() { if (sideloadedManifest) {
@Override Assertions.checkState(!manifest.dynamic);
public void run() { manifestCallback = null;
startLoadingManifest(); refreshManifestRunnable = null;
} simulateManifestRefreshRunnable = null;
}; } else {
simulateManifestRefreshRunnable = new Runnable() { manifestCallback = new ManifestCallback();
@Override refreshManifestRunnable = new Runnable() {
public void run() { @Override
processManifest(); public void run() {
} startLoadingManifest();
}; }
};
simulateManifestRefreshRunnable = new Runnable() {
@Override
public void run() {
processManifest(false);
}
};
}
} }
/** /**
@ -157,15 +260,21 @@ public final class DashMediaSource implements MediaSource {
@Override @Override
public void prepareSource(MediaSource.Listener listener) { public void prepareSource(MediaSource.Listener listener) {
sourceListener = listener; sourceListener = listener;
dataSource = manifestDataSourceFactory.createDataSource(); if (sideloadedManifest) {
loader = new Loader("Loader:DashMediaSource"); loaderErrorThrower = new LoaderErrorThrower.Dummy();
handler = new Handler(); processManifest(false);
startLoadingManifest(); } else {
dataSource = manifestDataSourceFactory.createDataSource();
loader = new Loader("Loader:DashMediaSource");
loaderErrorThrower = loader;
handler = new Handler();
startLoadingManifest();
}
} }
@Override @Override
public void maybeThrowSourceInfoRefreshError() throws IOException { public void maybeThrowSourceInfoRefreshError() throws IOException {
loader.maybeThrowError(); loaderErrorThrower.maybeThrowError();
} }
@Override @Override
@ -174,7 +283,7 @@ public final class DashMediaSource implements MediaSource {
manifest.getPeriod(periodIndex).startMs); manifest.getPeriod(periodIndex).startMs);
DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + periodIndex, manifest, DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + periodIndex, manifest,
periodIndex, chunkSourceFactory, minLoadableRetryCount, periodEventDispatcher, periodIndex, chunkSourceFactory, minLoadableRetryCount, periodEventDispatcher,
elapsedRealtimeOffsetMs, loader, allocator); elapsedRealtimeOffsetMs, loaderErrorThrower, allocator);
periodsById.put(mediaPeriod.id, mediaPeriod); periodsById.put(mediaPeriod.id, mediaPeriod);
return mediaPeriod; return mediaPeriod;
} }
@ -189,6 +298,7 @@ public final class DashMediaSource implements MediaSource {
@Override @Override
public void releaseSource() { public void releaseSource() {
dataSource = null; dataSource = null;
loaderErrorThrower = null;
if (loader != null) { if (loader != null) {
loader.release(); loader.release();
loader = null; loader = null;
@ -247,11 +357,11 @@ public final class DashMediaSource implements MediaSource {
if (manifest.utcTiming != null) { if (manifest.utcTiming != null) {
resolveUtcTimingElement(manifest.utcTiming); resolveUtcTimingElement(manifest.utcTiming);
} else { } else {
processManifestAndScheduleRefresh(); processManifest(true);
} }
} else { } else {
firstPeriodId += removedPeriodCount; firstPeriodId += removedPeriodCount;
processManifestAndScheduleRefresh(); processManifest(true);
} }
} }
@ -327,21 +437,16 @@ public final class DashMediaSource implements MediaSource {
private void onUtcTimestampResolved(long elapsedRealtimeOffsetMs) { private void onUtcTimestampResolved(long elapsedRealtimeOffsetMs) {
this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs; this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs;
processManifestAndScheduleRefresh(); processManifest(true);
} }
private void onUtcTimestampResolutionError(IOException error) { private void onUtcTimestampResolutionError(IOException error) {
Log.e(TAG, "Failed to resolve UtcTiming element.", error); Log.e(TAG, "Failed to resolve UtcTiming element.", error);
// Be optimistic and continue in the hope that the device clock is correct. // Be optimistic and continue in the hope that the device clock is correct.
processManifestAndScheduleRefresh(); processManifest(true);
} }
private void processManifestAndScheduleRefresh() { private void processManifest(boolean scheduleRefresh) {
processManifest();
scheduleManifestRefresh();
}
private void processManifest() {
// Update any periods. // Update any periods.
for (int i = 0; i < periodsById.size(); i++) { for (int i = 0; i < periodsById.size(); i++) {
int id = periodsById.keyAt(i); int id = periodsById.keyAt(i);
@ -351,9 +456,8 @@ public final class DashMediaSource implements MediaSource {
// This period has been removed from the manifest so it doesn't need to be updated. // This period has been removed from the manifest so it doesn't need to be updated.
} }
} }
// Remove any pending simulated updates.
handler.removeCallbacks(simulateManifestRefreshRunnable);
// Update the window. // Update the window.
boolean windowChangingImplicitly = false;
int lastPeriodIndex = manifest.getPeriodCount() - 1; int lastPeriodIndex = manifest.getPeriodCount() - 1;
PeriodSeekInfo firstPeriodSeekInfo = PeriodSeekInfo.createPeriodSeekInfo(manifest.getPeriod(0), PeriodSeekInfo firstPeriodSeekInfo = PeriodSeekInfo.createPeriodSeekInfo(manifest.getPeriod(0),
manifest.getPeriodDurationUs(0)); manifest.getPeriodDurationUs(0));
@ -384,8 +488,7 @@ public final class DashMediaSource implements MediaSource {
currentStartTimeUs = manifest.getPeriodDurationUs(0); currentStartTimeUs = manifest.getPeriodDurationUs(0);
} }
} }
// The window is changing implicitly. Post a simulated manifest refresh to update it. windowChangingImplicitly = true;
handler.postDelayed(simulateManifestRefreshRunnable, NOTIFY_MANIFEST_INTERVAL_MS);
} }
long windowDurationUs = currentEndTimeUs - currentStartTimeUs; long windowDurationUs = currentEndTimeUs - currentStartTimeUs;
for (int i = 0; i < manifest.getPeriodCount() - 1; i++) { for (int i = 0; i < manifest.getPeriodCount() - 1; i++) {
@ -414,6 +517,19 @@ public final class DashMediaSource implements MediaSource {
firstPeriodId, currentStartTimeUs, windowDurationUs, windowDefaultStartPositionUs, firstPeriodId, currentStartTimeUs, windowDurationUs, windowDefaultStartPositionUs,
manifest); manifest);
sourceListener.onSourceInfoRefreshed(timeline, manifest); sourceListener.onSourceInfoRefreshed(timeline, manifest);
if (!sideloadedManifest) {
// Remove any pending simulated refresh.
handler.removeCallbacks(simulateManifestRefreshRunnable);
// If the window is changing implicitly, post a simulated manifest refresh to update it.
if (windowChangingImplicitly) {
handler.postDelayed(simulateManifestRefreshRunnable, NOTIFY_MANIFEST_INTERVAL_MS);
}
// Schedule an explicit refresh if needed.
if (scheduleRefresh) {
scheduleManifestRefresh();
}
}
} }
private void scheduleManifestRefresh() { private void scheduleManifestRefresh() {

View File

@ -43,4 +43,21 @@ public interface LoaderErrorThrower {
*/ */
void maybeThrowError(int minRetryCount) throws IOException; void maybeThrowError(int minRetryCount) throws IOException;
/**
* A {@link LoaderErrorThrower} that never throws.
*/
final class Dummy implements LoaderErrorThrower {
@Override
public void maybeThrowError() throws IOException {
// Do nothing.
}
@Override
public void maybeThrowError(int minRetryCount) throws IOException {
// Do nothing.
}
}
} }