Skip to content

Commit

Permalink
DASH: Fix detection of end of live events
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ojw28 committed Nov 14, 2018
1 parent 76eb06d commit 5e6174f
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 77 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### 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.
* Improve decoder re-use between playbacks. TODO: Write and link a blog post
here ([#2826](https://github.com/google/ExoPlayer/issues/2826)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,12 @@ public static final class Window {
*/
public boolean isSeekable;

/**
* Whether this window may change when the timeline is updated.
*/
// TODO: Split this to better describe which parts of the window might change. For example it
// 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;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,6 @@ public int[] getSupportedTypes() {

private int staleManifestReloadAttempt;
private long expiredManifestPublishTimeUs;
private boolean dynamicMediaPresentationEnded;

private int firstPeriodId;

Expand Down Expand Up @@ -679,7 +678,6 @@ public void releaseSourceInternal() {
elapsedRealtimeOffsetMs = 0;
staleManifestReloadAttempt = 0;
expiredManifestPublishTimeUs = C.TIME_UNSET;
dynamicMediaPresentationEnded = false;
firstPeriodId = 0;
periodsById.clear();
}
Expand All @@ -691,10 +689,6 @@ public void releaseSourceInternal() {
startLoadingManifest();
}

/* package */ void onDashLiveMediaPresentationEndSignalEncountered() {
this.dynamicMediaPresentationEnded = true;
}

/* package */ void onDashManifestPublishTimeExpired(long expiredManifestPublishTimeUs) {
if (this.expiredManifestPublishTimeUs == C.TIME_UNSET
|| this.expiredManifestPublishTimeUs < expiredManifestPublishTimeUs) {
Expand Down Expand Up @@ -734,9 +728,8 @@ public void releaseSourceInternal() {
// behind.
Log.w(TAG, "Loaded out of sync manifest");
isManifestStale = true;
} else if (dynamicMediaPresentationEnded
|| (expiredManifestPublishTimeUs != C.TIME_UNSET
&& newManifest.publishTimeMs * 1000 <= expiredManifestPublishTimeUs)) {
} else if (expiredManifestPublishTimeUs != C.TIME_UNSET
&& newManifest.publishTimeMs * 1000 <= expiredManifestPublishTimeUs) {
// 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
// stale.
Expand All @@ -745,8 +738,6 @@ public void releaseSourceInternal() {
"Loaded stale dynamic manifest: "
+ newManifest.publishTimeMs
+ ", "
+ dynamicMediaPresentationEnded
+ ", "
+ expiredManifestPublishTimeUs);
isManifestStale = true;
}
Expand All @@ -763,7 +754,6 @@ public void releaseSourceInternal() {
staleManifestReloadAttempt = 0;
}


manifest = newManifest;
manifestLoadPending &= manifest.dynamic;
manifestLoadStartTimestampMs = elapsedRealtimeMs - loadDurationMs;
Expand Down Expand Up @@ -1170,12 +1160,16 @@ public Window getWindow(
long windowDefaultStartPositionUs = getAdjustedWindowDefaultStartPositionUs(
defaultPositionProjectionUs);
Object tag = setTag ? windowTag : null;
boolean isDynamic =
manifest.dynamic
&& manifest.minUpdatePeriodMs != C.TIME_UNSET
&& manifest.durationMs == C.TIME_UNSET;
return window.set(
tag,
presentationStartTimeMs,
windowStartTimeMs,
/* isSeekable= */ true,
manifest.dynamic,
isDynamic,
windowDefaultStartPositionUs,
windowDurationUs,
/* firstPeriodIndex= */ 0,
Expand Down Expand Up @@ -1253,11 +1247,6 @@ public void onDashManifestRefreshRequested() {
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>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,12 @@ public void getNextChunk(
}
}

long periodDurationUs = representationHolder.periodDurationUs;
boolean periodEnded = periodDurationUs != C.TIME_UNSET;

if (representationHolder.getSegmentCount() == 0) {
// The index doesn't define any segments.
out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1);
out.endOfStream = periodEnded;
return;
}

Expand All @@ -343,17 +346,15 @@ public void getNextChunk(
fatalError = new BehindLiveWindowException();
return;
}

if (segmentNum > lastAvailableSegmentNum
|| (missingLastSegment && segmentNum >= lastAvailableSegmentNum)) {
// The segment is beyond the end of the period. We know the period will not be extended if the
// manifest is static, or if there's a period after this one.
out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1);
// The segment is beyond the end of the period.
out.endOfStream = periodEnded;
return;
}

long periodDurationUs = representationHolder.periodDurationUs;
if (periodDurationUs != C.TIME_UNSET
&& representationHolder.getSegmentStartTimeUs(segmentNum) >= periodDurationUs) {
if (periodEnded && representationHolder.getSegmentStartTimeUs(segmentNum) >= periodDurationUs) {
// The period duration clips the period to a position before the segment.
out.endOfStream = true;
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@
*/
public final class PlayerEmsgHandler implements Handler.Callback {

private static final int EMSG_MEDIA_PRESENTATION_ENDED = 1;
private static final int EMSG_MANIFEST_EXPIRED = 2;
private static final int EMSG_MANIFEST_EXPIRED = 1;

/** Callbacks for player emsg events encountered during DASH live stream. */
public interface PlayerEmsgCallback {
Expand All @@ -75,9 +74,6 @@ public interface PlayerEmsgCallback {
* @param expiredManifestPublishTimeUs The manifest publish time that has been expired.
*/
void onDashManifestPublishTimeExpired(long expiredManifestPublishTimeUs);

/** Called when a media presentation end signal is encountered during live stream. * */
void onDashLiveMediaPresentationEndSignalEncountered();
}

private final Allocator allocator;
Expand All @@ -88,7 +84,6 @@ public interface PlayerEmsgCallback {

private DashManifest manifest;

private boolean dynamicMediaPresentationEnded;
private long expiredManifestPublishTimeUs;
private long lastLoadedChunkEndTimeUs;
private long lastLoadedChunkEndTimeBeforeRefreshUs;
Expand Down Expand Up @@ -134,21 +129,15 @@ public void updateManifest(DashManifest newManifest) {
return true;
}
boolean manifestRefreshNeeded = false;
if (dynamicMediaPresentationEnded) {
// The manifest we have is dynamic, but we know a non-dynamic one representing the final state
// should be available.
manifestRefreshNeeded = true;
} else {
// Find the smallest publishTime (greater than or equal to the current manifest's publish
// time) that has a corresponding expiry time.
Map.Entry<Long, Long> expiredEntry = ceilingExpiryEntryForPublishTime(manifest.publishTimeMs);
if (expiredEntry != null) {
long expiredPointUs = expiredEntry.getValue();
if (expiredPointUs < presentationPositionUs) {
expiredManifestPublishTimeUs = expiredEntry.getKey();
notifyManifestPublishTimeExpired();
manifestRefreshNeeded = true;
}
// Find the smallest publishTime (greater than or equal to the current manifest's publish time)
// that has a corresponding expiry time.
Map.Entry<Long, Long> expiredEntry = ceilingExpiryEntryForPublishTime(manifest.publishTimeMs);
if (expiredEntry != null) {
long expiredPointUs = expiredEntry.getValue();
if (expiredPointUs < presentationPositionUs) {
expiredManifestPublishTimeUs = expiredEntry.getKey();
notifyManifestPublishTimeExpired();
manifestRefreshNeeded = true;
}
}
if (manifestRefreshNeeded) {
Expand Down Expand Up @@ -221,9 +210,6 @@ public boolean handleMessage(Message message) {
return true;
}
switch (message.what) {
case (EMSG_MEDIA_PRESENTATION_ENDED):
handleMediaPresentationEndedMessageEncountered();
return true;
case (EMSG_MANIFEST_EXPIRED):
ManifestExpiryEventInfo messageObj = (ManifestExpiryEventInfo) message.obj;
handleManifestExpiredMessage(
Expand All @@ -248,11 +234,6 @@ private void handleManifestExpiredMessage(long eventTimeUs, long manifestPublish
}
}

private void handleMediaPresentationEndedMessageEncountered() {
dynamicMediaPresentationEnded = true;
notifySourceMediaPresentationEnded();
}

private @Nullable Map.Entry<Long, Long> ceilingExpiryEntryForPublishTime(long publishTimeMs) {
return manifestPublishTimeToExpiryTimeUs.ceilingEntry(publishTimeMs);
}
Expand All @@ -273,10 +254,6 @@ private void notifyManifestPublishTimeExpired() {
playerEmsgCallback.onDashManifestPublishTimeExpired(expiredManifestPublishTimeUs);
}

private void notifySourceMediaPresentationEnded() {
playerEmsgCallback.onDashLiveMediaPresentationEndSignalEncountered();
}

/** Requests DASH media manifest to be refreshed if necessary. */
private void maybeNotifyDashManifestRefreshNeeded() {
if (lastLoadedChunkEndTimeBeforeRefreshUs != C.TIME_UNSET
Expand All @@ -298,12 +275,6 @@ private static long getManifestPublishTimeMsInEmsg(EventMessage eventMessage) {
}
}

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. */
public final class PlayerTrackEmsgHandler implements TrackOutput {

Expand Down Expand Up @@ -413,16 +384,7 @@ private void parsePlayerEmsgEvent(long eventTimeUs, EventMessage eventMessage) {
if (manifestPublishTimeMsInEmsg == C.TIME_UNSET) {
return;
}

if (isMessageSignalingMediaPresentationEnded(eventMessage)) {
onMediaPresentationEndedMessageEncountered();
} else {
onManifestExpiredMessageEncountered(eventTimeUs, manifestPublishTimeMsInEmsg);
}
}

private void onMediaPresentationEndedMessageEncountered() {
handler.sendMessage(handler.obtainMessage(EMSG_MEDIA_PRESENTATION_ENDED));
onManifestExpiredMessageEncountered(eventTimeUs, manifestPublishTimeMsInEmsg);
}

private void onManifestExpiredMessageEncountered(
Expand Down

0 comments on commit 5e6174f

Please sign in to comment.