Commit 5e6174fe by olly Committed by Oliver Woodman

DASH: Fix detection of end of live events

The remaining work is to split Window.isDynamic so that it's
possible to represent a window that wont be appended to, but
may still be removed from.

Issue: #4780

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=221439220
parent 76eb06d6
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
### dev-v2 (not yet released) ### ### dev-v2 (not yet released) ###
* DASH: Fix detecting the end of live events
([#4780](https://github.com/google/ExoPlayer/issues/4780)).
* Support for playing spherical videos on Daydream. * Support for playing spherical videos on Daydream.
* Improve decoder re-use between playbacks. TODO: Write and link a blog post * Improve decoder re-use between playbacks. TODO: Write and link a blog post
here ([#2826](https://github.com/google/ExoPlayer/issues/2826)). here ([#2826](https://github.com/google/ExoPlayer/issues/2826)).
......
...@@ -139,9 +139,12 @@ public abstract class Timeline { ...@@ -139,9 +139,12 @@ public abstract class Timeline {
*/ */
public boolean isSeekable; public boolean isSeekable;
/** // TODO: Split this to better describe which parts of the window might change. For example it
* Whether this window may change when the timeline is updated. // should be possible to individually determine whether the start and end positions of the
*/ // window may change relative to the underlying periods. For an example of where it's useful to
// know that the end position is fixed whilst the start position may still change, see:
// https://github.com/google/ExoPlayer/issues/4780.
/** Whether this window may change when the timeline is updated. */
public boolean isDynamic; public boolean isDynamic;
/** /**
......
...@@ -376,7 +376,6 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -376,7 +376,6 @@ public final class DashMediaSource extends BaseMediaSource {
private int staleManifestReloadAttempt; private int staleManifestReloadAttempt;
private long expiredManifestPublishTimeUs; private long expiredManifestPublishTimeUs;
private boolean dynamicMediaPresentationEnded;
private int firstPeriodId; private int firstPeriodId;
...@@ -679,7 +678,6 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -679,7 +678,6 @@ public final class DashMediaSource extends BaseMediaSource {
elapsedRealtimeOffsetMs = 0; elapsedRealtimeOffsetMs = 0;
staleManifestReloadAttempt = 0; staleManifestReloadAttempt = 0;
expiredManifestPublishTimeUs = C.TIME_UNSET; expiredManifestPublishTimeUs = C.TIME_UNSET;
dynamicMediaPresentationEnded = false;
firstPeriodId = 0; firstPeriodId = 0;
periodsById.clear(); periodsById.clear();
} }
...@@ -691,10 +689,6 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -691,10 +689,6 @@ public final class DashMediaSource extends BaseMediaSource {
startLoadingManifest(); startLoadingManifest();
} }
/* package */ void onDashLiveMediaPresentationEndSignalEncountered() {
this.dynamicMediaPresentationEnded = true;
}
/* package */ void onDashManifestPublishTimeExpired(long expiredManifestPublishTimeUs) { /* package */ void onDashManifestPublishTimeExpired(long expiredManifestPublishTimeUs) {
if (this.expiredManifestPublishTimeUs == C.TIME_UNSET if (this.expiredManifestPublishTimeUs == C.TIME_UNSET
|| this.expiredManifestPublishTimeUs < expiredManifestPublishTimeUs) { || this.expiredManifestPublishTimeUs < expiredManifestPublishTimeUs) {
...@@ -734,9 +728,8 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -734,9 +728,8 @@ public final class DashMediaSource extends BaseMediaSource {
// behind. // behind.
Log.w(TAG, "Loaded out of sync manifest"); Log.w(TAG, "Loaded out of sync manifest");
isManifestStale = true; isManifestStale = true;
} else if (dynamicMediaPresentationEnded } else if (expiredManifestPublishTimeUs != C.TIME_UNSET
|| (expiredManifestPublishTimeUs != C.TIME_UNSET && newManifest.publishTimeMs * 1000 <= expiredManifestPublishTimeUs) {
&& newManifest.publishTimeMs * 1000 <= expiredManifestPublishTimeUs)) {
// If we receive a dynamic manifest that's older than expected (i.e. its publish time has // If we receive a dynamic manifest that's older than expected (i.e. its publish time has
// expired, or it's dynamic and we know the presentation has ended), then this manifest is // expired, or it's dynamic and we know the presentation has ended), then this manifest is
// stale. // stale.
...@@ -745,8 +738,6 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -745,8 +738,6 @@ public final class DashMediaSource extends BaseMediaSource {
"Loaded stale dynamic manifest: " "Loaded stale dynamic manifest: "
+ newManifest.publishTimeMs + newManifest.publishTimeMs
+ ", " + ", "
+ dynamicMediaPresentationEnded
+ ", "
+ expiredManifestPublishTimeUs); + expiredManifestPublishTimeUs);
isManifestStale = true; isManifestStale = true;
} }
...@@ -763,7 +754,6 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -763,7 +754,6 @@ public final class DashMediaSource extends BaseMediaSource {
staleManifestReloadAttempt = 0; staleManifestReloadAttempt = 0;
} }
manifest = newManifest; manifest = newManifest;
manifestLoadPending &= manifest.dynamic; manifestLoadPending &= manifest.dynamic;
manifestLoadStartTimestampMs = elapsedRealtimeMs - loadDurationMs; manifestLoadStartTimestampMs = elapsedRealtimeMs - loadDurationMs;
...@@ -1170,12 +1160,16 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1170,12 +1160,16 @@ public final class DashMediaSource extends BaseMediaSource {
long windowDefaultStartPositionUs = getAdjustedWindowDefaultStartPositionUs( long windowDefaultStartPositionUs = getAdjustedWindowDefaultStartPositionUs(
defaultPositionProjectionUs); defaultPositionProjectionUs);
Object tag = setTag ? windowTag : null; Object tag = setTag ? windowTag : null;
boolean isDynamic =
manifest.dynamic
&& manifest.minUpdatePeriodMs != C.TIME_UNSET
&& manifest.durationMs == C.TIME_UNSET;
return window.set( return window.set(
tag, tag,
presentationStartTimeMs, presentationStartTimeMs,
windowStartTimeMs, windowStartTimeMs,
/* isSeekable= */ true, /* isSeekable= */ true,
manifest.dynamic, isDynamic,
windowDefaultStartPositionUs, windowDefaultStartPositionUs,
windowDurationUs, windowDurationUs,
/* firstPeriodIndex= */ 0, /* firstPeriodIndex= */ 0,
...@@ -1253,11 +1247,6 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1253,11 +1247,6 @@ public final class DashMediaSource extends BaseMediaSource {
public void onDashManifestPublishTimeExpired(long expiredManifestPublishTimeUs) { public void onDashManifestPublishTimeExpired(long expiredManifestPublishTimeUs) {
DashMediaSource.this.onDashManifestPublishTimeExpired(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>> {
......
...@@ -318,9 +318,12 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -318,9 +318,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
} }
} }
long periodDurationUs = representationHolder.periodDurationUs;
boolean periodEnded = periodDurationUs != C.TIME_UNSET;
if (representationHolder.getSegmentCount() == 0) { if (representationHolder.getSegmentCount() == 0) {
// The index doesn't define any segments. // The index doesn't define any segments.
out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1); out.endOfStream = periodEnded;
return; return;
} }
...@@ -343,17 +346,15 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -343,17 +346,15 @@ public class DefaultDashChunkSource implements DashChunkSource {
fatalError = new BehindLiveWindowException(); fatalError = new BehindLiveWindowException();
return; return;
} }
if (segmentNum > lastAvailableSegmentNum if (segmentNum > lastAvailableSegmentNum
|| (missingLastSegment && segmentNum >= lastAvailableSegmentNum)) { || (missingLastSegment && segmentNum >= lastAvailableSegmentNum)) {
// The segment is beyond the end of the period. We know the period will not be extended if the // The segment is beyond the end of the period.
// manifest is static, or if there's a period after this one. out.endOfStream = periodEnded;
out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1);
return; return;
} }
long periodDurationUs = representationHolder.periodDurationUs; if (periodEnded && representationHolder.getSegmentStartTimeUs(segmentNum) >= periodDurationUs) {
if (periodDurationUs != C.TIME_UNSET
&& representationHolder.getSegmentStartTimeUs(segmentNum) >= periodDurationUs) {
// The period duration clips the period to a position before the segment. // The period duration clips the period to a position before the segment.
out.endOfStream = true; out.endOfStream = true;
return; return;
......
...@@ -60,8 +60,7 @@ import java.util.TreeMap; ...@@ -60,8 +60,7 @@ import java.util.TreeMap;
*/ */
public final class PlayerEmsgHandler implements Handler.Callback { public final class PlayerEmsgHandler implements Handler.Callback {
private static final int EMSG_MEDIA_PRESENTATION_ENDED = 1; private static final int EMSG_MANIFEST_EXPIRED = 1;
private static final int EMSG_MANIFEST_EXPIRED = 2;
/** Callbacks for player emsg events encountered during DASH live stream. */ /** Callbacks for player emsg events encountered during DASH live stream. */
public interface PlayerEmsgCallback { public interface PlayerEmsgCallback {
...@@ -75,9 +74,6 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -75,9 +74,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
* @param expiredManifestPublishTimeUs The manifest publish time that has been expired. * @param expiredManifestPublishTimeUs The manifest publish time that has been expired.
*/ */
void onDashManifestPublishTimeExpired(long expiredManifestPublishTimeUs); void onDashManifestPublishTimeExpired(long expiredManifestPublishTimeUs);
/** Called when a media presentation end signal is encountered during live stream. * */
void onDashLiveMediaPresentationEndSignalEncountered();
} }
private final Allocator allocator; private final Allocator allocator;
...@@ -88,7 +84,6 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -88,7 +84,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
private DashManifest manifest; private DashManifest manifest;
private boolean dynamicMediaPresentationEnded;
private long expiredManifestPublishTimeUs; private long expiredManifestPublishTimeUs;
private long lastLoadedChunkEndTimeUs; private long lastLoadedChunkEndTimeUs;
private long lastLoadedChunkEndTimeBeforeRefreshUs; private long lastLoadedChunkEndTimeBeforeRefreshUs;
...@@ -134,21 +129,15 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -134,21 +129,15 @@ public final class PlayerEmsgHandler implements Handler.Callback {
return true; return true;
} }
boolean manifestRefreshNeeded = false; boolean manifestRefreshNeeded = false;
if (dynamicMediaPresentationEnded) { // Find the smallest publishTime (greater than or equal to the current manifest's publish time)
// The manifest we have is dynamic, but we know a non-dynamic one representing the final state // that has a corresponding expiry time.
// should be available. Map.Entry<Long, Long> expiredEntry = ceilingExpiryEntryForPublishTime(manifest.publishTimeMs);
manifestRefreshNeeded = true; if (expiredEntry != null) {
} else { long expiredPointUs = expiredEntry.getValue();
// Find the smallest publishTime (greater than or equal to the current manifest's publish if (expiredPointUs < presentationPositionUs) {
// time) that has a corresponding expiry time. expiredManifestPublishTimeUs = expiredEntry.getKey();
Map.Entry<Long, Long> expiredEntry = ceilingExpiryEntryForPublishTime(manifest.publishTimeMs); notifyManifestPublishTimeExpired();
if (expiredEntry != null) { manifestRefreshNeeded = true;
long expiredPointUs = expiredEntry.getValue();
if (expiredPointUs < presentationPositionUs) {
expiredManifestPublishTimeUs = expiredEntry.getKey();
notifyManifestPublishTimeExpired();
manifestRefreshNeeded = true;
}
} }
} }
if (manifestRefreshNeeded) { if (manifestRefreshNeeded) {
...@@ -221,9 +210,6 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -221,9 +210,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
return true; return true;
} }
switch (message.what) { switch (message.what) {
case (EMSG_MEDIA_PRESENTATION_ENDED):
handleMediaPresentationEndedMessageEncountered();
return true;
case (EMSG_MANIFEST_EXPIRED): case (EMSG_MANIFEST_EXPIRED):
ManifestExpiryEventInfo messageObj = (ManifestExpiryEventInfo) message.obj; ManifestExpiryEventInfo messageObj = (ManifestExpiryEventInfo) message.obj;
handleManifestExpiredMessage( handleManifestExpiredMessage(
...@@ -248,11 +234,6 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -248,11 +234,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
} }
} }
private void handleMediaPresentationEndedMessageEncountered() {
dynamicMediaPresentationEnded = true;
notifySourceMediaPresentationEnded();
}
private @Nullable Map.Entry<Long, Long> ceilingExpiryEntryForPublishTime(long publishTimeMs) { private @Nullable Map.Entry<Long, Long> ceilingExpiryEntryForPublishTime(long publishTimeMs) {
return manifestPublishTimeToExpiryTimeUs.ceilingEntry(publishTimeMs); return manifestPublishTimeToExpiryTimeUs.ceilingEntry(publishTimeMs);
} }
...@@ -273,10 +254,6 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -273,10 +254,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
playerEmsgCallback.onDashManifestPublishTimeExpired(expiredManifestPublishTimeUs); playerEmsgCallback.onDashManifestPublishTimeExpired(expiredManifestPublishTimeUs);
} }
private void notifySourceMediaPresentationEnded() {
playerEmsgCallback.onDashLiveMediaPresentationEndSignalEncountered();
}
/** Requests DASH media manifest to be refreshed if necessary. */ /** Requests DASH media manifest to be refreshed if necessary. */
private void maybeNotifyDashManifestRefreshNeeded() { private void maybeNotifyDashManifestRefreshNeeded() {
if (lastLoadedChunkEndTimeBeforeRefreshUs != C.TIME_UNSET if (lastLoadedChunkEndTimeBeforeRefreshUs != C.TIME_UNSET
...@@ -298,12 +275,6 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -298,12 +275,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
} }
} }
private static boolean isMessageSignalingMediaPresentationEnded(EventMessage eventMessage) {
// According to section 4.5.2.1 DASH-IF IOP, if both presentation time delta and event duration
// are zero, the media presentation is ended.
return eventMessage.presentationTimeUs == 0 && eventMessage.durationMs == 0;
}
/** Handles emsg messages for a specific track for the player. */ /** Handles emsg messages for a specific track for the player. */
public final class PlayerTrackEmsgHandler implements TrackOutput { public final class PlayerTrackEmsgHandler implements TrackOutput {
...@@ -413,16 +384,7 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -413,16 +384,7 @@ public final class PlayerEmsgHandler implements Handler.Callback {
if (manifestPublishTimeMsInEmsg == C.TIME_UNSET) { if (manifestPublishTimeMsInEmsg == C.TIME_UNSET) {
return; return;
} }
onManifestExpiredMessageEncountered(eventTimeUs, manifestPublishTimeMsInEmsg);
if (isMessageSignalingMediaPresentationEnded(eventMessage)) {
onMediaPresentationEndedMessageEncountered();
} else {
onManifestExpiredMessageEncountered(eventTimeUs, manifestPublishTimeMsInEmsg);
}
}
private void onMediaPresentationEndedMessageEncountered() {
handler.sendMessage(handler.obtainMessage(EMSG_MEDIA_PRESENTATION_ENDED));
} }
private void onManifestExpiredMessageEncountered( private void onManifestExpiredMessageEncountered(
......
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