Skip to content

Commit

Permalink
Don't allow spliced-in preload chunks.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
tonihei authored and ojw28 committed Jun 6, 2021
1 parent 5fc6b2f commit 10c19af
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -352,70 +352,67 @@ public void getNextChunk(
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);
}

Expand All @@ -426,24 +423,36 @@ public void getNextChunk(
// 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,
Expand All @@ -453,7 +462,8 @@ public void getNextChunk(
timestampAdjusterProvider,
previous,
/* mediaSegmentKey= */ keyCache.get(mediaSegmentKeyUri),
/* initSegmentKey= */ keyCache.get(initSegmentKeyUri));
/* initSegmentKey= */ keyCache.get(initSegmentKeyUri),
shouldSpliceIn);
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
* @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,
Expand All @@ -91,7 +92,8 @@ public static HlsMediaChunk createInstance(
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 =
Expand Down Expand Up @@ -135,17 +137,12 @@ public static HlsMediaChunk createInstance(
@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
Expand All @@ -155,7 +152,6 @@ public static HlsMediaChunk createInstance(
} else {
id3Decoder = new Id3Decoder();
scratchId3Data = new ParsableByteArray(Id3Decoder.ID3_HEADER_LENGTH);
shouldSpliceIn = false;
}
return new HlsMediaChunk(
extractorFactory,
Expand Down Expand Up @@ -186,6 +182,41 @@ public static HlsMediaChunk createInstance(
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";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,7 @@ public boolean continueLoading(long positionUs) {
? lastMediaChunk.endTimeUs
: max(lastSeekPositionUs, lastMediaChunk.startTimeUs);
}
nextChunkHolder.clear();
chunkSource.getNextChunk(
positionUs,
loadPositionUs,
Expand All @@ -718,7 +719,6 @@ public boolean continueLoading(long positionUs) {
boolean endOfStream = nextChunkHolder.endOfStream;
@Nullable Chunk loadable = nextChunkHolder.chunk;
@Nullable Uri playlistUrlToLoad = nextChunkHolder.playlistUrl;
nextChunkHolder.clear();

if (endOfStream) {
pendingResetPositionUs = C.TIME_UNSET;
Expand Down

0 comments on commit 10c19af

Please sign in to comment.