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 @@ ...@@ -64,6 +64,9 @@
* Support low-latency DASH playback (`availabilityTimeOffset` and * Support low-latency DASH playback (`availabilityTimeOffset` and
`ServiceDescription` tags) `ServiceDescription` tags)
([#4904](https://github.com/google/ExoPlayer/issues/4904)). ([#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: * HLS:
* Support playlist delta updates, blocking playlist reloads and rendition * Support playlist delta updates, blocking playlist reloads and rendition
reports. reports.
......
...@@ -429,8 +429,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -429,8 +429,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
if (!cancelable) { if (!cancelable) {
return false; return false;
} }
if (playerTrackEmsgHandler != null if (playerTrackEmsgHandler != null && playerTrackEmsgHandler.onChunkLoadError(chunk)) {
&& playerTrackEmsgHandler.maybeRefreshManifestOnLoadingError(chunk)) {
return true; return true;
} }
// Workaround for missing segment at the end of the period // Workaround for missing segment at the end of the period
......
...@@ -85,8 +85,7 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -85,8 +85,7 @@ public final class PlayerEmsgHandler implements Handler.Callback {
private DashManifest manifest; private DashManifest manifest;
private long expiredManifestPublishTimeUs; private long expiredManifestPublishTimeUs;
private long lastLoadedChunkEndTimeUs; private boolean chunkLoadedCompletedSinceLastManifestRefreshRequest;
private long lastLoadedChunkEndTimeBeforeRefreshUs;
private boolean isWaitingForManifestRefresh; private boolean isWaitingForManifestRefresh;
private boolean released; private boolean released;
...@@ -105,8 +104,6 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -105,8 +104,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
manifestPublishTimeToExpiryTimeUs = new TreeMap<>(); manifestPublishTimeToExpiryTimeUs = new TreeMap<>();
handler = Util.createHandlerForCurrentLooper(/* callback= */ this); handler = Util.createHandlerForCurrentLooper(/* callback= */ this);
decoder = new EventMessageDecoder(); decoder = new EventMessageDecoder();
lastLoadedChunkEndTimeUs = C.TIME_UNSET;
lastLoadedChunkEndTimeBeforeRefreshUs = C.TIME_UNSET;
} }
/** /**
...@@ -121,6 +118,36 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -121,6 +118,36 @@ public final class PlayerEmsgHandler implements Handler.Callback {
removePreviouslyExpiredManifestPublishTimeValues(); 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) { /* package */ boolean maybeRefreshManifestBeforeLoadingNextChunk(long presentationPositionUs) {
if (!manifest.dynamic) { if (!manifest.dynamic) {
return false; return false;
...@@ -146,83 +173,27 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -146,83 +173,27 @@ public final class PlayerEmsgHandler implements Handler.Callback {
return manifestRefreshNeeded; return manifestRefreshNeeded;
} }
/** /* package */ void onChunkLoadCompleted(Chunk chunk) {
* For live streaming with emsg event stream, forward seeking can seek pass the emsg messages that chunkLoadedCompletedSinceLastManifestRefreshRequest = true;
* 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.
* /* package */ boolean onChunkLoadError(boolean isForwardSeek) {
* @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) { if (!manifest.dynamic) {
return false; return false;
} }
if (isWaitingForManifestRefresh) { if (isWaitingForManifestRefresh) {
return true; return true;
} }
boolean isAfterForwardSeek = if (isForwardSeek) {
lastLoadedChunkEndTimeUs != C.TIME_UNSET && lastLoadedChunkEndTimeUs < chunk.startTimeUs; // If a forward seek has occurred, there's a chance that the seek has skipped EMSGs signalling
if (isAfterForwardSeek) { // end-of-stream or manifest expiration. We must assume that the manifest might need to be
// if we are after a forward seek, and the playback is dynamic with embedded emsg stream, // refreshed.
// there's a chance that we have seek over the emsg messages, in which case we should ask
// media source for a refresh.
maybeNotifyDashManifestRefreshNeeded(); maybeNotifyDashManifestRefreshNeeded();
return true; return true;
} }
return false; 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) { private void handleManifestExpiredMessage(long eventTimeUs, long manifestPublishTimeMsInEmsg) {
Long previousExpiryTimeUs = manifestPublishTimeToExpiryTimeUs.get(manifestPublishTimeMsInEmsg); Long previousExpiryTimeUs = manifestPublishTimeToExpiryTimeUs.get(manifestPublishTimeMsInEmsg);
if (previousExpiryTimeUs == null) { if (previousExpiryTimeUs == null) {
...@@ -256,13 +227,12 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -256,13 +227,12 @@ public final class PlayerEmsgHandler implements Handler.Callback {
/** 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 (!chunkLoadedCompletedSinceLastManifestRefreshRequest) {
&& lastLoadedChunkEndTimeBeforeRefreshUs == lastLoadedChunkEndTimeUs) { // Don't request a refresh unless some progress has been made.
// Already requested manifest refresh.
return; return;
} }
isWaitingForManifestRefresh = true; isWaitingForManifestRefresh = true;
lastLoadedChunkEndTimeBeforeRefreshUs = lastLoadedChunkEndTimeUs; chunkLoadedCompletedSinceLastManifestRefreshRequest = false;
playerEmsgCallback.onDashManifestRefreshRequested(); playerEmsgCallback.onDashManifestRefreshRequested();
} }
...@@ -275,6 +245,15 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -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. */ /** Handles emsg messages for a specific track for the player. */
public final class PlayerTrackEmsgHandler implements TrackOutput { public final class PlayerTrackEmsgHandler implements TrackOutput {
...@@ -282,10 +261,13 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -282,10 +261,13 @@ public final class PlayerEmsgHandler implements Handler.Callback {
private final FormatHolder formatHolder; private final FormatHolder formatHolder;
private final MetadataInputBuffer buffer; private final MetadataInputBuffer buffer;
private long maxLoadedChunkEndTimeUs;
/* package */ PlayerTrackEmsgHandler(Allocator allocator) { /* package */ PlayerTrackEmsgHandler(Allocator allocator) {
this.sampleQueue = SampleQueue.createWithoutDrm(allocator); this.sampleQueue = SampleQueue.createWithoutDrm(allocator);
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
buffer = new MetadataInputBuffer(); buffer = new MetadataInputBuffer();
maxLoadedChunkEndTimeUs = C.TIME_UNSET;
} }
@Override @Override
...@@ -325,24 +307,27 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -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. * @param chunk The chunk whose load has been completed.
*/ */
public void onChunkLoadCompleted(Chunk chunk) { public void onChunkLoadCompleted(Chunk chunk) {
if (maxLoadedChunkEndTimeUs == C.TIME_UNSET || chunk.endTimeUs > maxLoadedChunkEndTimeUs) {
maxLoadedChunkEndTimeUs = chunk.endTimeUs;
}
PlayerEmsgHandler.this.onChunkLoadCompleted(chunk); PlayerEmsgHandler.this.onChunkLoadCompleted(chunk);
} }
/** /**
* For live streaming with emsg event stream, forward seeking can seek pass the emsg messages * Called when a chunk load has encountered an error.
* 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. * @param chunk The chunk whose load encountered an error.
* @return True if manifest refresh has been requested, false otherwise. * @return Whether a manifest refresh has been requested.
*/ */
public boolean maybeRefreshManifestOnLoadingError(Chunk chunk) { public boolean onChunkLoadError(Chunk chunk) {
return PlayerEmsgHandler.this.maybeRefreshManifestOnLoadingError(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. */ /** 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