mirror of
https://github.com/androidx/media.git
synced 2025-05-10 09:12:16 +08:00
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:
parent
90e0919d09
commit
81ce6bba7a
@ -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() {
|
||||||
|
@ -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.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user