PlayerEmsgHandler: Track stream max chunk times separately

Issue: #8408
PiperOrigin-RevId: 350786430
This commit is contained in:
olly 2021-01-08 18:11:20 +00:00 committed by Oliver Woodman
parent c1529c46d8
commit a7379ee658
3 changed files with 77 additions and 90 deletions

View file

@ -64,6 +64,9 @@
* Support low-latency DASH playback (`availabilityTimeOffset` and
`ServiceDescription` tags)
([#4904](https://github.com/google/ExoPlayer/issues/4904)).
* Improve logic for determining whether to refresh the manifest when a
chunk load error occurs in a live streams that contains EMSG data
([#8408](https://github.com/google/ExoPlayer/issues/8408)).
* HLS:
* Support playlist delta updates, blocking playlist reloads and rendition
reports.

View file

@ -429,8 +429,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
if (!cancelable) {
return false;
}
if (playerTrackEmsgHandler != null
&& playerTrackEmsgHandler.maybeRefreshManifestOnLoadingError(chunk)) {
if (playerTrackEmsgHandler != null && playerTrackEmsgHandler.onChunkLoadError(chunk)) {
return true;
}
// Workaround for missing segment at the end of the period

View file

@ -85,8 +85,7 @@ public final class PlayerEmsgHandler implements Handler.Callback {
private DashManifest manifest;
private long expiredManifestPublishTimeUs;
private long lastLoadedChunkEndTimeUs;
private long lastLoadedChunkEndTimeBeforeRefreshUs;
private boolean chunkLoadedCompletedSinceLastManifestRefreshRequest;
private boolean isWaitingForManifestRefresh;
private boolean released;
@ -105,8 +104,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
manifestPublishTimeToExpiryTimeUs = new TreeMap<>();
handler = Util.createHandlerForCurrentLooper(/* callback= */ this);
decoder = new EventMessageDecoder();
lastLoadedChunkEndTimeUs = C.TIME_UNSET;
lastLoadedChunkEndTimeBeforeRefreshUs = C.TIME_UNSET;
}
/**
@ -121,78 +118,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
removePreviouslyExpiredManifestPublishTimeValues();
}
/* package */ boolean maybeRefreshManifestBeforeLoadingNextChunk(long presentationPositionUs) {
if (!manifest.dynamic) {
return false;
}
if (isWaitingForManifestRefresh) {
return true;
}
boolean manifestRefreshNeeded = false;
// 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) {
maybeNotifyDashManifestRefreshNeeded();
}
return manifestRefreshNeeded;
}
/**
* For live streaming with emsg event stream, forward seeking can seek pass the emsg messages that
* signals end-of-stream or Manifest expiry, which results in load error. In this case, we should
* notify the Dash media source to refresh its manifest.
*
* @param chunk The chunk whose load encountered the error.
* @return True if manifest refresh has been requested, false otherwise.
*/
/* package */ boolean maybeRefreshManifestOnLoadingError(Chunk chunk) {
if (!manifest.dynamic) {
return false;
}
if (isWaitingForManifestRefresh) {
return true;
}
boolean isAfterForwardSeek =
lastLoadedChunkEndTimeUs != C.TIME_UNSET && lastLoadedChunkEndTimeUs < chunk.startTimeUs;
if (isAfterForwardSeek) {
// if we are after a forward seek, and the playback is dynamic with embedded emsg stream,
// there's a chance that we have seek over the emsg messages, in which case we should ask
// media source for a refresh.
maybeNotifyDashManifestRefreshNeeded();
return true;
}
return false;
}
/**
* Called when the a new chunk in the current media stream has been loaded.
*
* @param chunk The chunk whose load has been completed.
*/
/* package */ void onChunkLoadCompleted(Chunk chunk) {
if (lastLoadedChunkEndTimeUs != C.TIME_UNSET || chunk.endTimeUs > lastLoadedChunkEndTimeUs) {
lastLoadedChunkEndTimeUs = chunk.endTimeUs;
}
}
/**
* Returns whether an event with given schemeIdUri and value is a DASH emsg event targeting the
* player.
*/
public static boolean isPlayerEmsgEvent(String schemeIdUri, String value) {
return "urn:mpeg:dash:event:2012".equals(schemeIdUri)
&& ("1".equals(value) || "2".equals(value) || "3".equals(value));
}
/** Returns a {@link TrackOutput} that emsg messages could be written to. */
public PlayerTrackEmsgHandler newPlayerTrackEmsgHandler() {
return new PlayerTrackEmsgHandler(allocator);
@ -223,6 +148,52 @@ public final class PlayerEmsgHandler implements Handler.Callback {
// Internal methods.
/* package */ boolean maybeRefreshManifestBeforeLoadingNextChunk(long presentationPositionUs) {
if (!manifest.dynamic) {
return false;
}
if (isWaitingForManifestRefresh) {
return true;
}
boolean manifestRefreshNeeded = false;
// 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) {
maybeNotifyDashManifestRefreshNeeded();
}
return manifestRefreshNeeded;
}
/* package */ void onChunkLoadCompleted(Chunk chunk) {
chunkLoadedCompletedSinceLastManifestRefreshRequest = true;
}
/* package */ boolean onChunkLoadError(boolean isForwardSeek) {
if (!manifest.dynamic) {
return false;
}
if (isWaitingForManifestRefresh) {
return true;
}
if (isForwardSeek) {
// If a forward seek has occurred, there's a chance that the seek has skipped EMSGs signalling
// end-of-stream or manifest expiration. We must assume that the manifest might need to be
// refreshed.
maybeNotifyDashManifestRefreshNeeded();
return true;
}
return false;
}
private void handleManifestExpiredMessage(long eventTimeUs, long manifestPublishTimeMsInEmsg) {
Long previousExpiryTimeUs = manifestPublishTimeToExpiryTimeUs.get(manifestPublishTimeMsInEmsg);
if (previousExpiryTimeUs == null) {
@ -256,13 +227,12 @@ public final class PlayerEmsgHandler implements Handler.Callback {
/** Requests DASH media manifest to be refreshed if necessary. */
private void maybeNotifyDashManifestRefreshNeeded() {
if (lastLoadedChunkEndTimeBeforeRefreshUs != C.TIME_UNSET
&& lastLoadedChunkEndTimeBeforeRefreshUs == lastLoadedChunkEndTimeUs) {
// Already requested manifest refresh.
if (!chunkLoadedCompletedSinceLastManifestRefreshRequest) {
// Don't request a refresh unless some progress has been made.
return;
}
isWaitingForManifestRefresh = true;
lastLoadedChunkEndTimeBeforeRefreshUs = lastLoadedChunkEndTimeUs;
chunkLoadedCompletedSinceLastManifestRefreshRequest = false;
playerEmsgCallback.onDashManifestRefreshRequested();
}
@ -275,6 +245,15 @@ public final class PlayerEmsgHandler implements Handler.Callback {
}
}
/**
* Returns whether an event with given schemeIdUri and value is a DASH emsg event targeting the
* player.
*/
private static boolean isPlayerEmsgEvent(String schemeIdUri, String value) {
return "urn:mpeg:dash:event:2012".equals(schemeIdUri)
&& ("1".equals(value) || "2".equals(value) || "3".equals(value));
}
/** Handles emsg messages for a specific track for the player. */
public final class PlayerTrackEmsgHandler implements TrackOutput {
@ -282,10 +261,13 @@ public final class PlayerEmsgHandler implements Handler.Callback {
private final FormatHolder formatHolder;
private final MetadataInputBuffer buffer;
private long maxLoadedChunkEndTimeUs;
/* package */ PlayerTrackEmsgHandler(Allocator allocator) {
this.sampleQueue = SampleQueue.createWithoutDrm(allocator);
formatHolder = new FormatHolder();
buffer = new MetadataInputBuffer();
maxLoadedChunkEndTimeUs = C.TIME_UNSET;
}
@Override
@ -325,24 +307,27 @@ public final class PlayerEmsgHandler implements Handler.Callback {
}
/**
* Called when the a new chunk in the current media stream has been loaded.
* Called when a chunk load has been completed.
*
* @param chunk The chunk whose load has been completed.
*/
public void onChunkLoadCompleted(Chunk chunk) {
if (maxLoadedChunkEndTimeUs == C.TIME_UNSET || chunk.endTimeUs > maxLoadedChunkEndTimeUs) {
maxLoadedChunkEndTimeUs = chunk.endTimeUs;
}
PlayerEmsgHandler.this.onChunkLoadCompleted(chunk);
}
/**
* For live streaming with emsg event stream, forward seeking can seek pass the emsg messages
* that signals end-of-stream or Manifest expiry, which results in load error. In this case, we
* should notify the Dash media source to refresh its manifest.
* Called when a chunk load has encountered an error.
*
* @param chunk The chunk whose load encountered the error.
* @return True if manifest refresh has been requested, false otherwise.
* @param chunk The chunk whose load encountered an error.
* @return Whether a manifest refresh has been requested.
*/
public boolean maybeRefreshManifestOnLoadingError(Chunk chunk) {
return PlayerEmsgHandler.this.maybeRefreshManifestOnLoadingError(chunk);
public boolean onChunkLoadError(Chunk chunk) {
boolean isAfterForwardSeek =
maxLoadedChunkEndTimeUs != C.TIME_UNSET && maxLoadedChunkEndTimeUs < chunk.startTimeUs;
return PlayerEmsgHandler.this.onChunkLoadError(isAfterForwardSeek);
}
/** Release this track emsg handler. It should not be reused after this call. */