Don't allow spliced-in preload chunks.
Preload chunks may still need to be discarded. However, we don't currently support discarding spliced-in chunks. Thus, we need to avoid loadng a preload chunk that needs to be spliced-in. Issue: #8937 #minor-release PiperOrigin-RevId: 374851661
This commit is contained in:
parent
5fc6b2ff9d
commit
10c19afa35
@ -352,70 +352,67 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
return;
|
||||
}
|
||||
@Nullable
|
||||
HlsMediaPlaylist mediaPlaylist =
|
||||
HlsMediaPlaylist playlist =
|
||||
playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */ true);
|
||||
// playlistTracker snapshot is valid (checked by if() above), so mediaPlaylist must be non-null.
|
||||
checkNotNull(mediaPlaylist);
|
||||
independentSegments = mediaPlaylist.hasIndependentSegments;
|
||||
// playlistTracker snapshot is valid (checked by if() above), so playlist must be non-null.
|
||||
checkNotNull(playlist);
|
||||
independentSegments = playlist.hasIndependentSegments;
|
||||
|
||||
updateLiveEdgeTimeUs(mediaPlaylist);
|
||||
updateLiveEdgeTimeUs(playlist);
|
||||
|
||||
// Select the chunk.
|
||||
long startOfPlaylistInPeriodUs =
|
||||
mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs();
|
||||
long startOfPlaylistInPeriodUs = playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
|
||||
Pair<Long, Integer> nextMediaSequenceAndPartIndex =
|
||||
getNextMediaSequenceAndPartIndex(
|
||||
previous, switchingTrack, mediaPlaylist, startOfPlaylistInPeriodUs, loadPositionUs);
|
||||
previous, switchingTrack, playlist, startOfPlaylistInPeriodUs, loadPositionUs);
|
||||
long chunkMediaSequence = nextMediaSequenceAndPartIndex.first;
|
||||
int partIndex = nextMediaSequenceAndPartIndex.second;
|
||||
if (chunkMediaSequence < mediaPlaylist.mediaSequence && previous != null && switchingTrack) {
|
||||
if (chunkMediaSequence < playlist.mediaSequence && previous != null && switchingTrack) {
|
||||
// We try getting the next chunk without adapting in case that's the reason for falling
|
||||
// behind the live window.
|
||||
selectedTrackIndex = oldTrackIndex;
|
||||
selectedPlaylistUrl = playlistUrls[selectedTrackIndex];
|
||||
mediaPlaylist =
|
||||
playlist =
|
||||
playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */ true);
|
||||
// playlistTracker snapshot is valid (checked by if() above), so mediaPlaylist must be
|
||||
// non-null.
|
||||
checkNotNull(mediaPlaylist);
|
||||
startOfPlaylistInPeriodUs =
|
||||
mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs();
|
||||
// playlistTracker snapshot is valid (checked by if() above), so playlist must be non-null.
|
||||
checkNotNull(playlist);
|
||||
startOfPlaylistInPeriodUs = playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
|
||||
// Get the next segment/part without switching tracks.
|
||||
Pair<Long, Integer> nextMediaSequenceAndPartIndexWithoutAdapting =
|
||||
getNextMediaSequenceAndPartIndex(
|
||||
previous,
|
||||
/* switchingTrack= */ false,
|
||||
mediaPlaylist,
|
||||
playlist,
|
||||
startOfPlaylistInPeriodUs,
|
||||
loadPositionUs);
|
||||
chunkMediaSequence = nextMediaSequenceAndPartIndexWithoutAdapting.first;
|
||||
partIndex = nextMediaSequenceAndPartIndexWithoutAdapting.second;
|
||||
}
|
||||
|
||||
if (chunkMediaSequence < mediaPlaylist.mediaSequence) {
|
||||
if (chunkMediaSequence < playlist.mediaSequence) {
|
||||
fatalError = new BehindLiveWindowException();
|
||||
return;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
SegmentBaseHolder segmentBaseHolder =
|
||||
getNextSegmentHolder(mediaPlaylist, chunkMediaSequence, partIndex);
|
||||
getNextSegmentHolder(playlist, chunkMediaSequence, partIndex);
|
||||
if (segmentBaseHolder == null) {
|
||||
if (!mediaPlaylist.hasEndTag) {
|
||||
if (!playlist.hasEndTag) {
|
||||
// Reload the playlist in case of a live stream.
|
||||
out.playlistUrl = selectedPlaylistUrl;
|
||||
seenExpectedPlaylistError &= selectedPlaylistUrl.equals(expectedPlaylistUrl);
|
||||
expectedPlaylistUrl = selectedPlaylistUrl;
|
||||
return;
|
||||
} else if (allowEndOfStream || mediaPlaylist.segments.isEmpty()) {
|
||||
} else if (allowEndOfStream || playlist.segments.isEmpty()) {
|
||||
out.endOfStream = true;
|
||||
return;
|
||||
}
|
||||
// Use the last segment available in case of a VOD stream.
|
||||
segmentBaseHolder =
|
||||
new SegmentBaseHolder(
|
||||
Iterables.getLast(mediaPlaylist.segments),
|
||||
mediaPlaylist.mediaSequence + mediaPlaylist.segments.size() - 1,
|
||||
Iterables.getLast(playlist.segments),
|
||||
playlist.mediaSequence + playlist.segments.size() - 1,
|
||||
/* partIndex= */ C.INDEX_UNSET);
|
||||
}
|
||||
|
||||
@ -426,24 +423,36 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
// Check if the media segment or its initialization segment are fully encrypted.
|
||||
@Nullable
|
||||
Uri initSegmentKeyUri =
|
||||
getFullEncryptionKeyUri(mediaPlaylist, segmentBaseHolder.segmentBase.initializationSegment);
|
||||
getFullEncryptionKeyUri(playlist, segmentBaseHolder.segmentBase.initializationSegment);
|
||||
out.chunk = maybeCreateEncryptionChunkFor(initSegmentKeyUri, selectedTrackIndex);
|
||||
if (out.chunk != null) {
|
||||
return;
|
||||
}
|
||||
@Nullable
|
||||
Uri mediaSegmentKeyUri = getFullEncryptionKeyUri(mediaPlaylist, segmentBaseHolder.segmentBase);
|
||||
Uri mediaSegmentKeyUri = getFullEncryptionKeyUri(playlist, segmentBaseHolder.segmentBase);
|
||||
out.chunk = maybeCreateEncryptionChunkFor(mediaSegmentKeyUri, selectedTrackIndex);
|
||||
if (out.chunk != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean shouldSpliceIn =
|
||||
HlsMediaChunk.shouldSpliceIn(
|
||||
previous, selectedPlaylistUrl, playlist, segmentBaseHolder, startOfPlaylistInPeriodUs);
|
||||
if (shouldSpliceIn && segmentBaseHolder.isPreload) {
|
||||
// We don't support discarding spliced-in segments [internal: b/159904763], but preload
|
||||
// parts may need to be discarded if they are removed before becoming permanently published.
|
||||
// Hence, don't allow this combination and instead wait with loading the next part until it
|
||||
// becomes fully available (or the track selection selects another track).
|
||||
return;
|
||||
}
|
||||
|
||||
out.chunk =
|
||||
HlsMediaChunk.createInstance(
|
||||
extractorFactory,
|
||||
mediaDataSource,
|
||||
playlistFormats[selectedTrackIndex],
|
||||
startOfPlaylistInPeriodUs,
|
||||
mediaPlaylist,
|
||||
playlist,
|
||||
segmentBaseHolder,
|
||||
selectedPlaylistUrl,
|
||||
muxedCaptionFormats,
|
||||
@ -453,7 +462,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
timestampAdjusterProvider,
|
||||
previous,
|
||||
/* mediaSegmentKey= */ keyCache.get(mediaSegmentKeyUri),
|
||||
/* initSegmentKey= */ keyCache.get(initSegmentKeyUri));
|
||||
/* initSegmentKey= */ keyCache.get(initSegmentKeyUri),
|
||||
shouldSpliceIn);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -75,6 +75,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
* @param mediaSegmentKey The media segment decryption key, if fully encrypted. Null otherwise.
|
||||
* @param initSegmentKey The initialization segment decryption key, if fully encrypted. Null
|
||||
* otherwise.
|
||||
* @param shouldSpliceIn Whether samples for this chunk should be spliced into existing samples.
|
||||
*/
|
||||
public static HlsMediaChunk createInstance(
|
||||
HlsExtractorFactory extractorFactory,
|
||||
@ -91,7 +92,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
TimestampAdjusterProvider timestampAdjusterProvider,
|
||||
@Nullable HlsMediaChunk previousChunk,
|
||||
@Nullable byte[] mediaSegmentKey,
|
||||
@Nullable byte[] initSegmentKey) {
|
||||
@Nullable byte[] initSegmentKey,
|
||||
boolean shouldSpliceIn) {
|
||||
// Media segment.
|
||||
HlsMediaPlaylist.SegmentBase mediaSegment = segmentBaseHolder.segmentBase;
|
||||
DataSpec dataSpec =
|
||||
@ -135,17 +137,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
@Nullable HlsMediaChunkExtractor previousExtractor = null;
|
||||
Id3Decoder id3Decoder;
|
||||
ParsableByteArray scratchId3Data;
|
||||
boolean shouldSpliceIn;
|
||||
|
||||
if (previousChunk != null) {
|
||||
boolean isFollowingChunk =
|
||||
playlistUrl.equals(previousChunk.playlistUrl) && previousChunk.loadCompleted;
|
||||
id3Decoder = previousChunk.id3Decoder;
|
||||
scratchId3Data = previousChunk.scratchId3Data;
|
||||
boolean isIndependent = isIndependent(segmentBaseHolder, mediaPlaylist);
|
||||
boolean canContinueWithoutSplice =
|
||||
isFollowingChunk
|
||||
|| (isIndependent && segmentStartTimeInPeriodUs >= previousChunk.endTimeUs);
|
||||
shouldSpliceIn = !canContinueWithoutSplice;
|
||||
previousExtractor =
|
||||
isFollowingChunk
|
||||
&& !previousChunk.extractorInvalidated
|
||||
@ -155,7 +152,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
} else {
|
||||
id3Decoder = new Id3Decoder();
|
||||
scratchId3Data = new ParsableByteArray(Id3Decoder.ID3_HEADER_LENGTH);
|
||||
shouldSpliceIn = false;
|
||||
}
|
||||
return new HlsMediaChunk(
|
||||
extractorFactory,
|
||||
@ -186,6 +182,41 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
shouldSpliceIn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether samples of a new HLS media chunk should be spliced into existing samples.
|
||||
*
|
||||
* @param previousChunk The previous existing media chunk, or null if the new chunk is the first
|
||||
* in the queue.
|
||||
* @param playlistUrl The URL of the playlist from which the new chunk will be obtained.
|
||||
* @param mediaPlaylist The {@link HlsMediaPlaylist} containing the new chunk.
|
||||
* @param segmentBaseHolder The {@link HlsChunkSource.SegmentBaseHolder} with information about
|
||||
* the new chunk.
|
||||
* @param startOfPlaylistInPeriodUs The start time of the playlist in the period, in microseconds.
|
||||
* @return Whether samples of the new chunk should be spliced into existing samples.
|
||||
*/
|
||||
public static boolean shouldSpliceIn(
|
||||
@Nullable HlsMediaChunk previousChunk,
|
||||
Uri playlistUrl,
|
||||
HlsMediaPlaylist mediaPlaylist,
|
||||
HlsChunkSource.SegmentBaseHolder segmentBaseHolder,
|
||||
long startOfPlaylistInPeriodUs) {
|
||||
if (previousChunk == null) {
|
||||
// First chunk doesn't require splicing.
|
||||
return false;
|
||||
}
|
||||
if (playlistUrl.equals(previousChunk.playlistUrl) && previousChunk.loadCompleted) {
|
||||
// Continuing with the next chunk in the same playlist after fully loading the previous chunk
|
||||
// (i.e. the load wasn't cancelled or failed) is always possible.
|
||||
return false;
|
||||
}
|
||||
// Changing playlists or continuing after a chunk cancellation/failure requires independent,
|
||||
// non-overlapping segments to avoid the splice.
|
||||
long segmentStartTimeInPeriodUs =
|
||||
startOfPlaylistInPeriodUs + segmentBaseHolder.segmentBase.relativeStartTimeUs;
|
||||
return !isIndependent(segmentBaseHolder, mediaPlaylist)
|
||||
|| segmentStartTimeInPeriodUs < previousChunk.endTimeUs;
|
||||
}
|
||||
|
||||
public static final String PRIV_TIMESTAMP_FRAME_OWNER =
|
||||
"com.apple.streaming.transportStreamTimestamp";
|
||||
|
||||
|
@ -709,6 +709,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
? lastMediaChunk.endTimeUs
|
||||
: max(lastSeekPositionUs, lastMediaChunk.startTimeUs);
|
||||
}
|
||||
nextChunkHolder.clear();
|
||||
chunkSource.getNextChunk(
|
||||
positionUs,
|
||||
loadPositionUs,
|
||||
@ -718,7 +719,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
boolean endOfStream = nextChunkHolder.endOfStream;
|
||||
@Nullable Chunk loadable = nextChunkHolder.chunk;
|
||||
@Nullable Uri playlistUrlToLoad = nextChunkHolder.playlistUrl;
|
||||
nextChunkHolder.clear();
|
||||
|
||||
if (endOfStream) {
|
||||
pendingResetPositionUs = C.TIME_UNSET;
|
||||
|
Loading…
x
Reference in New Issue
Block a user