Commit 6749623c by hoangtc Committed by Oliver Woodman

Handle DASH `emsg' events targeting player.

For live streaming, there are several types of DASH `emsg' events that directly
target the player. These events can signal whether the manifest is expired, or
the live streaming has ended, and should be handle directly within the player.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=182034591
parent 6bed2ffc
...@@ -32,7 +32,13 @@ ...@@ -32,7 +32,13 @@
seeking to the closest sync points before, either side or after specified seek seeking to the closest sync points before, either side or after specified seek
positions. positions.
* Note: `SeekParameters` are not currently supported when playing HLS streams. * Note: `SeekParameters` are not currently supported when playing HLS streams.
* DASH: Support DASH manifest EventStream elements. * DRM: Optimistically attempt playback of DRM protected content that does not
declare scheme specific init data
([#3630](https://github.com/google/ExoPlayer/issues/3630)).
* DASH:
* Support in-band Emsg events targeting player with scheme id
"urn:mpeg:dash:event:2012" and scheme value of either "1", "2" or "3".
* Support DASH manifest EventStream elements.
* HLS: Add opt-in support for chunkless preparation in HLS. This allows an * HLS: Add opt-in support for chunkless preparation in HLS. This allows an
HLS source to finish preparation without downloading any chunks, which can HLS source to finish preparation without downloading any chunks, which can
significantly reduce initial buffering time significantly reduce initial buffering time
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.source.chunk; package com.google.android.exoplayer2.source.chunk;
import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
...@@ -41,6 +42,17 @@ import java.util.List; ...@@ -41,6 +42,17 @@ import java.util.List;
public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, SequenceableLoader, public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, SequenceableLoader,
Loader.Callback<Chunk>, Loader.ReleaseCallback { Loader.Callback<Chunk>, Loader.ReleaseCallback {
/** A callback to be notified when a sample stream has finished being released. */
public interface ReleaseCallback<T extends ChunkSource> {
/**
* Called when the {@link ChunkSampleStream} has finished being released.
*
* @param chunkSampleStream The released sample stream.
*/
void onSampleStreamReleased(ChunkSampleStream<T> chunkSampleStream);
}
private static final String TAG = "ChunkSampleStream"; private static final String TAG = "ChunkSampleStream";
public final int primaryTrackType; public final int primaryTrackType;
...@@ -61,6 +73,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -61,6 +73,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
private final BaseMediaChunkOutput mediaChunkOutput; private final BaseMediaChunkOutput mediaChunkOutput;
private Format primaryDownstreamTrackFormat; private Format primaryDownstreamTrackFormat;
private ReleaseCallback<T> releaseCallback;
private long pendingResetPositionUs; private long pendingResetPositionUs;
/* package */ long lastSeekPositionUs; /* package */ long lastSeekPositionUs;
/* package */ boolean loadingFinished; /* package */ boolean loadingFinished;
...@@ -247,10 +260,26 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -247,10 +260,26 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
/** /**
* Releases the stream. * Releases the stream.
* <p> *
* This method should be called when the stream is no longer required. * <p>This method should be called when the stream is no longer required. Either this method or
* {@link #release(ReleaseCallback)} can be used to release this stream.
*/ */
public void release() { public void release() {
release(null);
}
/**
* Releases the stream.
*
* <p>This method should be called when the stream is no longer required. Either this method or
* {@link #release()} can be used to release this stream.
*
* @param callback A callback to be called when the release ends. Will be called synchronously
* from this method if no load is in progress, or asynchronously once the load has been
* canceled otherwise.
*/
public void release(@Nullable ReleaseCallback<T> callback) {
this.releaseCallback = callback;
boolean releasedSynchronously = loader.release(this); boolean releasedSynchronously = loader.release(this);
if (!releasedSynchronously) { if (!releasedSynchronously) {
// Discard as much as we can synchronously. // Discard as much as we can synchronously.
...@@ -267,6 +296,9 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -267,6 +296,9 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) { for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.reset(); embeddedSampleQueue.reset();
} }
if (releaseCallback != null) {
releaseCallback.onSampleStreamReleased(this);
}
} }
// SampleStream implementation. // SampleStream implementation.
......
...@@ -16,7 +16,9 @@ ...@@ -16,7 +16,9 @@
package com.google.android.exoplayer2.source.dash; package com.google.android.exoplayer2.source.dash;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.source.chunk.ChunkSource; import com.google.android.exoplayer2.source.chunk.ChunkSource;
import com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerTrackEmsgHandler;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
...@@ -53,7 +55,8 @@ public interface DashChunkSource extends ChunkSource { ...@@ -53,7 +55,8 @@ public interface DashChunkSource extends ChunkSource {
int type, int type,
long elapsedRealtimeOffsetMs, long elapsedRealtimeOffsetMs,
boolean enableEventMessageTrack, boolean enableEventMessageTrack,
boolean enableCea608Track); boolean enableCea608Track,
@Nullable PlayerTrackEmsgHandler playerEmsgHandler);
} }
/** /**
......
/*
* 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.dash;
import java.io.IOException;
/** Thrown when a live playback's manifest is expired and a new manifest could not be loaded. */
public final class DashManifestExpiredException extends IOException {}
...@@ -31,6 +31,8 @@ import com.google.android.exoplayer2.source.TrackGroup; ...@@ -31,6 +31,8 @@ import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.chunk.ChunkSampleStream; import com.google.android.exoplayer2.source.chunk.ChunkSampleStream;
import com.google.android.exoplayer2.source.chunk.ChunkSampleStream.EmbeddedSampleStream; import com.google.android.exoplayer2.source.chunk.ChunkSampleStream.EmbeddedSampleStream;
import com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerEmsgCallback;
import com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerTrackEmsgHandler;
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.source.dash.manifest.Descriptor; import com.google.android.exoplayer2.source.dash.manifest.Descriptor;
...@@ -47,14 +49,15 @@ import java.lang.annotation.RetentionPolicy; ...@@ -47,14 +49,15 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/** /** A DASH {@link MediaPeriod}. */
* A DASH {@link MediaPeriod}. /* package */ final class DashMediaPeriod
*/ implements MediaPeriod,
/* package */ final class DashMediaPeriod implements MediaPeriod, SequenceableLoader.Callback<ChunkSampleStream<DashChunkSource>>,
SequenceableLoader.Callback<ChunkSampleStream<DashChunkSource>> { ChunkSampleStream.ReleaseCallback<DashChunkSource> {
/* package */ final int id; /* package */ final int id;
private final DashChunkSource.Factory chunkSourceFactory; private final DashChunkSource.Factory chunkSourceFactory;
...@@ -66,6 +69,9 @@ import java.util.Map; ...@@ -66,6 +69,9 @@ import java.util.Map;
private final TrackGroupArray trackGroups; private final TrackGroupArray trackGroups;
private final TrackGroupInfo[] trackGroupInfos; private final TrackGroupInfo[] trackGroupInfos;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private final PlayerEmsgHandler playerEmsgHandler;
private final IdentityHashMap<ChunkSampleStream<DashChunkSource>, PlayerTrackEmsgHandler>
trackEmsgHandlerBySampleStream;
private Callback callback; private Callback callback;
private ChunkSampleStream<DashChunkSource>[] sampleStreams; private ChunkSampleStream<DashChunkSource>[] sampleStreams;
...@@ -75,11 +81,18 @@ import java.util.Map; ...@@ -75,11 +81,18 @@ import java.util.Map;
private int periodIndex; private int periodIndex;
private List<EventStream> eventStreams; private List<EventStream> eventStreams;
public DashMediaPeriod(int id, DashManifest manifest, int periodIndex, public DashMediaPeriod(
DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, int id,
EventDispatcher eventDispatcher, long elapsedRealtimeOffset, DashManifest manifest,
LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator, int periodIndex,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory) { DashChunkSource.Factory chunkSourceFactory,
int minLoadableRetryCount,
EventDispatcher eventDispatcher,
long elapsedRealtimeOffset,
LoaderErrorThrower manifestLoaderErrorThrower,
Allocator allocator,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
PlayerEmsgCallback playerEmsgCallback) {
this.id = id; this.id = id;
this.manifest = manifest; this.manifest = manifest;
this.periodIndex = periodIndex; this.periodIndex = periodIndex;
...@@ -90,8 +103,10 @@ import java.util.Map; ...@@ -90,8 +103,10 @@ import java.util.Map;
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.allocator = allocator; this.allocator = allocator;
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
playerEmsgHandler = new PlayerEmsgHandler(manifest, playerEmsgCallback, allocator);
sampleStreams = newSampleStreamArray(0); sampleStreams = newSampleStreamArray(0);
eventSampleStreams = new EventSampleStream[0]; eventSampleStreams = new EventSampleStream[0];
trackEmsgHandlerBySampleStream = new IdentityHashMap<>();
compositeSequenceableLoader = compositeSequenceableLoader =
compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams); compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams);
Period period = manifest.getPeriod(periodIndex); Period period = manifest.getPeriod(periodIndex);
...@@ -111,14 +126,14 @@ import java.util.Map; ...@@ -111,14 +126,14 @@ import java.util.Map;
public void updateManifest(DashManifest manifest, int periodIndex) { public void updateManifest(DashManifest manifest, int periodIndex) {
this.manifest = manifest; this.manifest = manifest;
this.periodIndex = periodIndex; this.periodIndex = periodIndex;
Period period = manifest.getPeriod(periodIndex); playerEmsgHandler.updateManifest(manifest);
if (sampleStreams != null) { if (sampleStreams != null) {
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) { for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
sampleStream.getChunkSource().updateManifest(manifest, periodIndex); sampleStream.getChunkSource().updateManifest(manifest, periodIndex);
} }
callback.onContinueLoadingRequested(this); callback.onContinueLoadingRequested(this);
} }
eventStreams = period.eventStreams; eventStreams = manifest.getPeriod(periodIndex).eventStreams;
for (EventSampleStream eventSampleStream : eventSampleStreams) { for (EventSampleStream eventSampleStream : eventSampleStreams) {
for (EventStream eventStream : eventStreams) { for (EventStream eventStream : eventStreams) {
if (eventStream.id().equals(eventSampleStream.eventStreamId())) { if (eventStream.id().equals(eventSampleStream.eventStreamId())) {
...@@ -130,11 +145,24 @@ import java.util.Map; ...@@ -130,11 +145,24 @@ import java.util.Map;
} }
public void release() { public void release() {
playerEmsgHandler.release();
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) { for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
sampleStream.release(); sampleStream.release(this);
}
}
// ChunkSampleStream.ReleaseCallback implementation.
@Override
public void onSampleStreamReleased(ChunkSampleStream<DashChunkSource> stream) {
PlayerTrackEmsgHandler trackEmsgHandler = trackEmsgHandlerBySampleStream.remove(stream);
if (trackEmsgHandler != null) {
trackEmsgHandler.release();
} }
} }
// MediaPeriod implementation.
@Override @Override
public void prepare(Callback callback, long positionUs) { public void prepare(Callback callback, long positionUs) {
this.callback = callback; this.callback = callback;
...@@ -181,7 +209,7 @@ import java.util.Map; ...@@ -181,7 +209,7 @@ import java.util.Map;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ChunkSampleStream<DashChunkSource> stream = (ChunkSampleStream<DashChunkSource>) streams[i]; ChunkSampleStream<DashChunkSource> stream = (ChunkSampleStream<DashChunkSource>) streams[i];
if (selections[i] == null || !mayRetainStreamFlags[i]) { if (selections[i] == null || !mayRetainStreamFlags[i]) {
stream.release(); stream.release(this);
streams[i] = null; streams[i] = null;
} else { } else {
int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup()); int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup());
...@@ -501,10 +529,22 @@ import java.util.Map; ...@@ -501,10 +529,22 @@ import java.util.Map;
embeddedTrackFormats = Arrays.copyOf(embeddedTrackFormats, embeddedTrackCount); embeddedTrackFormats = Arrays.copyOf(embeddedTrackFormats, embeddedTrackCount);
embeddedTrackTypes = Arrays.copyOf(embeddedTrackTypes, embeddedTrackCount); embeddedTrackTypes = Arrays.copyOf(embeddedTrackTypes, embeddedTrackCount);
} }
DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource( PlayerTrackEmsgHandler trackPlayerEmsgHandler =
manifestLoaderErrorThrower, manifest, periodIndex, trackGroupInfo.adaptationSetIndices, manifest.dynamic && enableEventMessageTrack
selection, trackGroupInfo.trackType, elapsedRealtimeOffset, enableEventMessageTrack, ? playerEmsgHandler.newPlayerTrackEmsgHandler()
enableCea608Track); : null;
DashChunkSource chunkSource =
chunkSourceFactory.createDashChunkSource(
manifestLoaderErrorThrower,
manifest,
periodIndex,
trackGroupInfo.adaptationSetIndices,
selection,
trackGroupInfo.trackType,
elapsedRealtimeOffset,
enableEventMessageTrack,
enableCea608Track,
trackPlayerEmsgHandler);
ChunkSampleStream<DashChunkSource> stream = ChunkSampleStream<DashChunkSource> stream =
new ChunkSampleStream<>( new ChunkSampleStream<>(
trackGroupInfo.trackType, trackGroupInfo.trackType,
...@@ -516,6 +556,7 @@ import java.util.Map; ...@@ -516,6 +556,7 @@ import java.util.Map;
positionUs, positionUs,
minLoadableRetryCount, minLoadableRetryCount,
eventDispatcher); eventDispatcher);
trackEmsgHandlerBySampleStream.put(stream, trackPlayerEmsgHandler);
return stream; return stream;
} }
...@@ -581,9 +622,8 @@ import java.util.Map; ...@@ -581,9 +622,8 @@ import java.util.Map;
private static final int CATEGORY_PRIMARY = 0; private static final int CATEGORY_PRIMARY = 0;
/** /**
* A track group whose samples are embedded within one of the primary streams. * A track group whose samples are embedded within one of the primary streams. For example: an
* For example: an EMSG track has its sample embedded in `emsg' atoms in one of the primary * EMSG track has its sample embedded in emsg atoms in one of the primary streams.
* streams.
*/ */
private static final int CATEGORY_EMBEDDED = 1; private static final int CATEGORY_EMBEDDED = 1;
......
...@@ -35,6 +35,7 @@ import com.google.android.exoplayer2.source.MediaSourceEventListener; ...@@ -35,6 +35,7 @@ import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.source.SequenceableLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerEmsgCallback;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement; import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement;
...@@ -56,9 +57,7 @@ import java.util.TimeZone; ...@@ -56,9 +57,7 @@ import java.util.TimeZone;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /** A DASH {@link MediaSource}. */
* A DASH {@link MediaSource}.
*/
public final class DashMediaSource implements MediaSource { public final class DashMediaSource implements MediaSource {
static { static {
...@@ -280,6 +279,7 @@ public final class DashMediaSource implements MediaSource { ...@@ -280,6 +279,7 @@ public final class DashMediaSource implements MediaSource {
private final SparseArray<DashMediaPeriod> periodsById; private final SparseArray<DashMediaPeriod> periodsById;
private final Runnable refreshManifestRunnable; private final Runnable refreshManifestRunnable;
private final Runnable simulateManifestRefreshRunnable; private final Runnable simulateManifestRefreshRunnable;
private final PlayerEmsgCallback playerEmsgCallback;
private Listener sourceListener; private Listener sourceListener;
private DataSource dataSource; private DataSource dataSource;
...@@ -291,7 +291,11 @@ public final class DashMediaSource implements MediaSource { ...@@ -291,7 +291,11 @@ public final class DashMediaSource implements MediaSource {
private long manifestLoadEndTimestamp; private long manifestLoadEndTimestamp;
private DashManifest manifest; private DashManifest manifest;
private Handler handler; private Handler handler;
private boolean pendingManifestLoading;
private long elapsedRealtimeOffsetMs; private long elapsedRealtimeOffsetMs;
private long expiredManifestPublishTimeUs;
private boolean dynamicMediaPresentationEnded;
private int staleManifestReloadAttempt;
private int firstPeriodId; private int firstPeriodId;
...@@ -446,6 +450,8 @@ public final class DashMediaSource implements MediaSource { ...@@ -446,6 +450,8 @@ public final class DashMediaSource implements MediaSource {
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
manifestUriLock = new Object(); manifestUriLock = new Object();
periodsById = new SparseArray<>(); periodsById = new SparseArray<>();
playerEmsgCallback = new DefaultPlayerEmsgCallback();
expiredManifestPublishTimeUs = C.TIME_UNSET;
if (sideloadedManifest) { if (sideloadedManifest) {
Assertions.checkState(!manifest.dynamic); Assertions.checkState(!manifest.dynamic);
manifestCallback = null; manifestCallback = null;
...@@ -507,9 +513,19 @@ public final class DashMediaSource implements MediaSource { ...@@ -507,9 +513,19 @@ public final class DashMediaSource implements MediaSource {
int periodIndex = periodId.periodIndex; int periodIndex = periodId.periodIndex;
EventDispatcher periodEventDispatcher = eventDispatcher.copyWithMediaTimeOffsetMs( EventDispatcher periodEventDispatcher = eventDispatcher.copyWithMediaTimeOffsetMs(
manifest.getPeriod(periodIndex).startMs); manifest.getPeriod(periodIndex).startMs);
DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + periodIndex, manifest, DashMediaPeriod mediaPeriod =
periodIndex, chunkSourceFactory, minLoadableRetryCount, periodEventDispatcher, new DashMediaPeriod(
elapsedRealtimeOffsetMs, loaderErrorThrower, allocator, compositeSequenceableLoaderFactory); firstPeriodId + periodIndex,
manifest,
periodIndex,
chunkSourceFactory,
minLoadableRetryCount,
periodEventDispatcher,
elapsedRealtimeOffsetMs,
loaderErrorThrower,
allocator,
compositeSequenceableLoaderFactory,
playerEmsgCallback);
periodsById.put(mediaPeriod.id, mediaPeriod); periodsById.put(mediaPeriod.id, mediaPeriod);
return mediaPeriod; return mediaPeriod;
} }
...@@ -523,6 +539,7 @@ public final class DashMediaSource implements MediaSource { ...@@ -523,6 +539,7 @@ public final class DashMediaSource implements MediaSource {
@Override @Override
public void releaseSource() { public void releaseSource() {
pendingManifestLoading = false;
dataSource = null; dataSource = null;
loaderErrorThrower = null; loaderErrorThrower = null;
if (loader != null) { if (loader != null) {
...@@ -540,6 +557,24 @@ public final class DashMediaSource implements MediaSource { ...@@ -540,6 +557,24 @@ public final class DashMediaSource implements MediaSource {
periodsById.clear(); periodsById.clear();
} }
// PlayerEmsgCallback callbacks.
/* package */ void onDashManifestRefreshRequested() {
handler.removeCallbacks(simulateManifestRefreshRunnable);
startLoadingManifest();
}
/* package */ void onDashLiveMediaPresentationEndSignalEncountered() {
this.dynamicMediaPresentationEnded = true;
}
/* package */ void onDashManifestPublishTimeExpired(long expiredManifestPublishTimeUs) {
if (this.expiredManifestPublishTimeUs == C.TIME_UNSET
|| this.expiredManifestPublishTimeUs < expiredManifestPublishTimeUs) {
this.expiredManifestPublishTimeUs = expiredManifestPublishTimeUs;
}
}
// Loadable callbacks. // Loadable callbacks.
/* package */ void onManifestLoadCompleted(ParsingLoadable<DashManifest> loadable, /* package */ void onManifestLoadCompleted(ParsingLoadable<DashManifest> loadable,
...@@ -566,9 +601,16 @@ public final class DashMediaSource implements MediaSource { ...@@ -566,9 +601,16 @@ public final class DashMediaSource implements MediaSource {
return; return;
} }
if (maybeReloadStaleDynamicManifest(newManifest)) {
return;
}
manifest = newManifest; manifest = newManifest;
manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs; manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs;
manifestLoadEndTimestamp = elapsedRealtimeMs; manifestLoadEndTimestamp = elapsedRealtimeMs;
staleManifestReloadAttempt = 0;
if (!manifest.dynamic) {
pendingManifestLoading = false;
}
if (manifest.location != null) { if (manifest.location != null) {
synchronized (manifestUriLock) { synchronized (manifestUriLock) {
// This condition checks that replaceManifestUri wasn't called between the start and end of // This condition checks that replaceManifestUri wasn't called between the start and end of
...@@ -622,11 +664,41 @@ public final class DashMediaSource implements MediaSource { ...@@ -622,11 +664,41 @@ public final class DashMediaSource implements MediaSource {
// Internal methods. // Internal methods.
/**
* Reloads a stale dynamic manifest to get a more recent version if possible.
*
* @return True if the reload is scheduled. False if we have already retried too many times.
*/
private boolean maybeReloadStaleDynamicManifest(DashManifest manifest) {
if (!isManifestStale(manifest)) {
return false;
}
String warning =
"Loaded a stale dynamic manifest "
+ manifest.publishTimeMs
+ " "
+ dynamicMediaPresentationEnded
+ " "
+ expiredManifestPublishTimeUs;
Log.w(TAG, warning);
if (staleManifestReloadAttempt++ < minLoadableRetryCount) {
startLoadingManifest();
return true;
}
return false;
}
private void startLoadingManifest() { private void startLoadingManifest() {
handler.removeCallbacks(refreshManifestRunnable);
if (loader.isLoading()) {
pendingManifestLoading = true;
return;
}
Uri manifestUri; Uri manifestUri;
synchronized (manifestUriLock) { synchronized (manifestUriLock) {
manifestUri = this.manifestUri; manifestUri = this.manifestUri;
} }
pendingManifestLoading = false;
startLoading(new ParsingLoadable<>(dataSource, manifestUri, C.DATA_TYPE_MANIFEST, startLoading(new ParsingLoadable<>(dataSource, manifestUri, C.DATA_TYPE_MANIFEST,
manifestParser), manifestCallback, minLoadableRetryCount); manifestParser), manifestCallback, minLoadableRetryCount);
} }
...@@ -753,13 +825,21 @@ public final class DashMediaSource implements MediaSource { ...@@ -753,13 +825,21 @@ public final class DashMediaSource implements MediaSource {
if (windowChangingImplicitly) { if (windowChangingImplicitly) {
handler.postDelayed(simulateManifestRefreshRunnable, NOTIFY_MANIFEST_INTERVAL_MS); handler.postDelayed(simulateManifestRefreshRunnable, NOTIFY_MANIFEST_INTERVAL_MS);
} }
if (pendingManifestLoading) {
startLoadingManifest();
} else if (scheduleRefresh) {
// Schedule an explicit refresh if needed. // Schedule an explicit refresh if needed.
if (scheduleRefresh) {
scheduleManifestRefresh(); scheduleManifestRefresh();
} }
} }
} }
private boolean isManifestStale(DashManifest manifest) {
return manifest.dynamic
&& (dynamicMediaPresentationEnded
|| manifest.publishTimeMs <= expiredManifestPublishTimeUs);
}
private void scheduleManifestRefresh() { private void scheduleManifestRefresh() {
if (!manifest.dynamic) { if (!manifest.dynamic) {
return; return;
...@@ -948,6 +1028,24 @@ public final class DashMediaSource implements MediaSource { ...@@ -948,6 +1028,24 @@ public final class DashMediaSource implements MediaSource {
} }
private final class DefaultPlayerEmsgCallback implements PlayerEmsgCallback {
@Override
public void onDashManifestRefreshRequested() {
DashMediaSource.this.onDashManifestRefreshRequested();
}
@Override
public void onDashManifestPublishTimeExpired(long expiredManifestPublishTimeUs) {
DashMediaSource.this.onDashManifestPublishTimeExpired(expiredManifestPublishTimeUs);
}
@Override
public void onDashLiveMediaPresentationEndSignalEncountered() {
DashMediaSource.this.onDashLiveMediaPresentationEndSignalEncountered();
}
}
private final class ManifestCallback implements Loader.Callback<ParsingLoadable<DashManifest>> { private final class ManifestCallback implements Loader.Callback<ParsingLoadable<DashManifest>> {
@Override @Override
...@@ -1039,5 +1137,4 @@ public final class DashMediaSource implements MediaSource { ...@@ -1039,5 +1137,4 @@ public final class DashMediaSource implements MediaSource {
} }
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment