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
This commit is contained in:
olly 2018-11-14 07:13:10 -08:00 committed by Oliver Woodman
parent 177f3883e9
commit 84fc24492f
5 changed files with 34 additions and 77 deletions

View file

@ -2,6 +2,8 @@
### 2.9.2 ###
* DASH: Fix detecting the end of live events
([#4780](https://github.com/google/ExoPlayer/issues/4780)).
* Include channel count in audio capabilities check
([#4690](https://github.com/google/ExoPlayer/issues/4690)).

View file

@ -139,9 +139,12 @@ public abstract class Timeline {
*/
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;
/**

View file

@ -376,7 +376,6 @@ public final class DashMediaSource extends BaseMediaSource {
private int staleManifestReloadAttempt;
private long expiredManifestPublishTimeUs;
private boolean dynamicMediaPresentationEnded;
private int firstPeriodId;
@ -679,7 +678,6 @@ public final class DashMediaSource extends BaseMediaSource {
elapsedRealtimeOffsetMs = 0;
staleManifestReloadAttempt = 0;
expiredManifestPublishTimeUs = C.TIME_UNSET;
dynamicMediaPresentationEnded = false;
firstPeriodId = 0;
periodsById.clear();
}
@ -691,10 +689,6 @@ public final class DashMediaSource extends BaseMediaSource {
startLoadingManifest();
}
/* package */ void onDashLiveMediaPresentationEndSignalEncountered() {
this.dynamicMediaPresentationEnded = true;
}
/* package */ void onDashManifestPublishTimeExpired(long expiredManifestPublishTimeUs) {
if (this.expiredManifestPublishTimeUs == C.TIME_UNSET
|| this.expiredManifestPublishTimeUs < expiredManifestPublishTimeUs) {
@ -734,9 +728,8 @@ public final class DashMediaSource extends BaseMediaSource {
// 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.
@ -745,8 +738,6 @@ public final class DashMediaSource extends BaseMediaSource {
"Loaded stale dynamic manifest: "
+ newManifest.publishTimeMs
+ ", "
+ dynamicMediaPresentationEnded
+ ", "
+ expiredManifestPublishTimeUs);
isManifestStale = true;
}
@ -763,7 +754,6 @@ public final class DashMediaSource extends BaseMediaSource {
staleManifestReloadAttempt = 0;
}
manifest = newManifest;
manifestLoadPending &= manifest.dynamic;
manifestLoadStartTimestampMs = elapsedRealtimeMs - loadDurationMs;
@ -1170,12 +1160,16 @@ public final class DashMediaSource extends BaseMediaSource {
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,
@ -1253,11 +1247,6 @@ public final class DashMediaSource extends BaseMediaSource {
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>> {

View file

@ -318,9 +318,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
}
}
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;
}
@ -343,17 +346,15 @@ public class DefaultDashChunkSource implements DashChunkSource {
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;

View file

@ -60,8 +60,7 @@ import java.util.TreeMap;
*/
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 {
@ -75,9 +74,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
* @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;
@ -88,7 +84,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
private DashManifest manifest;
private boolean dynamicMediaPresentationEnded;
private long expiredManifestPublishTimeUs;
private long lastLoadedChunkEndTimeUs;
private long lastLoadedChunkEndTimeBeforeRefreshUs;
@ -134,21 +129,15 @@ public final class PlayerEmsgHandler implements Handler.Callback {
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) {
@ -221,9 +210,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
return true;
}
switch (message.what) {
case (EMSG_MEDIA_PRESENTATION_ENDED):
handleMediaPresentationEndedMessageEncountered();
return true;
case (EMSG_MANIFEST_EXPIRED):
ManifestExpiryEventInfo messageObj = (ManifestExpiryEventInfo) message.obj;
handleManifestExpiredMessage(
@ -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) {
return manifestPublishTimeToExpiryTimeUs.ceilingEntry(publishTimeMs);
}
@ -273,10 +254,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
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
@ -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. */
public final class PlayerTrackEmsgHandler implements TrackOutput {
@ -413,16 +384,7 @@ public final class PlayerEmsgHandler implements Handler.Callback {
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(