mirror of
https://github.com/samsonjs/media.git
synced 2026-04-22 14:05:55 +00:00
PlayerEmsgHandler: Track stream max chunk times separately
Issue: #8408 PiperOrigin-RevId: 350786430
This commit is contained in:
parent
c1529c46d8
commit
a7379ee658
3 changed files with 77 additions and 90 deletions
|
|
@ -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,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. */
|
||||
|
|
|
|||
Loading…
Reference in a new issue