Commit a7379ee6 by olly Committed by Oliver Woodman

PlayerEmsgHandler: Track stream max chunk times separately

Issue: #8408
PiperOrigin-RevId: 350786430
parent c1529c46
......@@ -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.
......
......@@ -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
......
......@@ -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,6 +118,36 @@ public final class PlayerEmsgHandler implements Handler.Callback {
removePreviouslyExpiredManifestPublishTimeValues();
}
/** Returns a {@link TrackOutput} that emsg messages could be written to. */
public PlayerTrackEmsgHandler newPlayerTrackEmsgHandler() {
return new PlayerTrackEmsgHandler(allocator);
}
/** Release this emsg handler. It should not be reused after this call. */
public void release() {
released = true;
handler.removeCallbacksAndMessages(null);
}
@Override
public boolean handleMessage(Message message) {
if (released) {
return true;
}
switch (message.what) {
case (EMSG_MANIFEST_EXPIRED):
ManifestExpiryEventInfo messageObj = (ManifestExpiryEventInfo) message.obj;
handleManifestExpiredMessage(
messageObj.eventTimeUs, messageObj.manifestPublishTimeMsInEmsg);
return true;
default:
// Do nothing.
}
return false;
}
// Internal methods.
/* package */ boolean maybeRefreshManifestBeforeLoadingNextChunk(long presentationPositionUs) {
if (!manifest.dynamic) {
return false;
......@@ -146,83 +173,27 @@ public final class PlayerEmsgHandler implements Handler.Callback {
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) {
/* package */ void onChunkLoadCompleted(Chunk chunk) {
chunkLoadedCompletedSinceLastManifestRefreshRequest = true;
}
/* package */ boolean onChunkLoadError(boolean isForwardSeek) {
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.
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;
}
/**
* 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);
}
/** Release this emsg handler. It should not be reused after this call. */
public void release() {
released = true;
handler.removeCallbacksAndMessages(null);
}
@Override
public boolean handleMessage(Message message) {
if (released) {
return true;
}
switch (message.what) {
case (EMSG_MANIFEST_EXPIRED):
ManifestExpiryEventInfo messageObj = (ManifestExpiryEventInfo) message.obj;
handleManifestExpiredMessage(
messageObj.eventTimeUs, messageObj.manifestPublishTimeMsInEmsg);
return true;
default:
// Do nothing.
}
return false;
}
// Internal methods.
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. */
......
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