mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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:
parent
177f3883e9
commit
84fc24492f
5 changed files with 34 additions and 77 deletions
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
### 2.9.2 ###
|
### 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
|
* Include channel count in audio capabilities check
|
||||||
([#4690](https://github.com/google/ExoPlayer/issues/4690)).
|
([#4690](https://github.com/google/ExoPlayer/issues/4690)).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
// 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 {
|
||||||
"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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
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;
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
* @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 {
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
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(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue