Replace cancelled HLS preload parts

Issue: #5011
PiperOrigin-RevId: 343277357
This commit is contained in:
bachinger 2020-11-19 14:20:55 +00:00 committed by Oliver Woodman
parent 31166d41c4
commit 39f8c77568
4 changed files with 75 additions and 12 deletions

View file

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.hls;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static java.lang.Math.max;
import android.net.Uri;
@ -39,7 +40,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.UriUtil;
import com.google.android.exoplayer2.util.Util;
@ -84,7 +84,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
endOfStream = false;
playlistUrl = null;
}
}
/**
@ -222,6 +221,44 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.isTimestampMaster = isTimestampMaster;
}
/**
* Checks whether the previous media chunk is a preload chunk that has been removed in the current
* playlist.
*
* @param previous The previous media chunk.
* @return True if the previous media chunk has been removed in the current playlist.
*/
public boolean isMediaChunkRemoved(HlsMediaChunk previous) {
if (!previous.isPreload) {
return false;
}
Uri playlistUrl = playlistUrls[trackGroup.indexOf(previous.trackFormat)];
HlsMediaPlaylist mediaPlaylist =
checkNotNull(playlistTracker.getPlaylistSnapshot(playlistUrl, /* isForPlayback= */ false));
int segmentIndexInPlaylist = (int) (previous.chunkIndex - mediaPlaylist.mediaSequence);
if (segmentIndexInPlaylist < 0) {
// The segment of the previous chunk is not in the current playlist anymore.
return false;
}
List<HlsMediaPlaylist.Part> partsInCurrentPlaylist =
segmentIndexInPlaylist < mediaPlaylist.segments.size()
? mediaPlaylist.segments.get(segmentIndexInPlaylist).parts
: mediaPlaylist.trailingParts;
if (previous.partIndex >= partsInCurrentPlaylist.size()) {
// In case the part hinted in the previous playlist has been wrongly assigned to the then full
// but not yet terminated segment, we discard it regardless whether the URI is different or
// not. While this is theoretically possible and unspecified, it appears to be an edge case
// which we can avoid with a small inefficiency of discarding in vain. We could allow this
// here but, if the chunk is not discarded, it could create unpredictable problems later,
// because the media sequence in previous.chunkIndex does not match to the actual media
// sequence in the new playlist.
return true;
}
HlsMediaPlaylist.Part publishedPart = partsInCurrentPlaylist.get(previous.partIndex);
Uri publishedUri = Uri.parse(UriUtil.resolve(mediaPlaylist.baseUri, publishedPart.url));
return !Util.areEqual(publishedUri, previous.dataSpec.uri);
}
/**
* Returns the next chunk to load.
*
@ -270,7 +307,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
trackSelection.updateSelectedTrack(
playbackPositionUs, bufferedDurationUs, timeToLiveEdgeUs, queue, mediaChunkIterators);
int selectedTrackIndex = trackSelection.getSelectedIndexInTrackGroup();
boolean switchingTrack = oldTrackIndex != selectedTrackIndex;
Uri selectedPlaylistUrl = playlistUrls[selectedTrackIndex];
if (!playlistTracker.isSnapshotValid(selectedPlaylistUrl)) {
@ -284,7 +320,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
HlsMediaPlaylist mediaPlaylist =
playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */ true);
// playlistTracker snapshot is valid (checked by if() above), so mediaPlaylist must be non-null.
Assertions.checkNotNull(mediaPlaylist);
checkNotNull(mediaPlaylist);
independentSegments = mediaPlaylist.hasIndependentSegments;
updateLiveEdgeTimeUs(mediaPlaylist);
@ -306,7 +342,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */ true);
// playlistTracker snapshot is valid (checked by if() above), so mediaPlaylist must be
// non-null.
Assertions.checkNotNull(mediaPlaylist);
checkNotNull(mediaPlaylist);
startOfPlaylistInPeriodUs =
mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs();
// Get the next segment/part without switching tracks.
@ -366,7 +402,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (out.chunk != null) {
return;
}
out.chunk =
HlsMediaChunk.createInstance(
extractorFactory,
@ -399,7 +434,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
Segment mediaSegment = mediaPlaylist.segments.get(segmentIndexInPlaylist);
if (nextPartIndex == C.INDEX_UNSET) {
return new SegmentBaseHolder(mediaSegment, nextMediaSequence, nextPartIndex);
return new SegmentBaseHolder(mediaSegment, nextMediaSequence, /* partIndex= */ C.INDEX_UNSET);
}
if (nextPartIndex < mediaSegment.parts.size()) {
@ -417,6 +452,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return new SegmentBaseHolder(
mediaPlaylist.trailingParts.get(0), nextMediaSequence + 1, /* partIndex= */ 0);
}
// End of stream.
return null;
}
@ -430,8 +466,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (chunk instanceof EncryptionKeyChunk) {
EncryptionKeyChunk encryptionKeyChunk = (EncryptionKeyChunk) chunk;
scratchSpace = encryptionKeyChunk.getDataHolder();
keyCache.put(
encryptionKeyChunk.dataSpec.uri, Assertions.checkNotNull(encryptionKeyChunk.getResult()));
keyCache.put(encryptionKeyChunk.dataSpec.uri, checkNotNull(encryptionKeyChunk.getResult()));
}
}
@ -499,7 +534,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
HlsMediaPlaylist playlist =
playlistTracker.getPlaylistSnapshot(playlistUrl, /* isForPlayback= */ false);
// Playlist snapshot is valid (checked by if() above) so playlist must be non-null.
Assertions.checkNotNull(playlist);
checkNotNull(playlist);
long startOfPlaylistInPeriodUs =
playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
boolean switchingTrack = trackIndex != oldTrackIndex;
@ -704,6 +739,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public final HlsMediaPlaylist.SegmentBase segmentBase;
public final long mediaSequence;
public final int partIndex;
public final boolean isPreload;
/** Creates a new instance. */
public SegmentBaseHolder(
@ -711,6 +747,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.segmentBase = segmentBase;
this.mediaSequence = mediaSequence;
this.partIndex = partIndex;
this.isPreload =
segmentBase instanceof HlsMediaPlaylist.Part
&& ((HlsMediaPlaylist.Part) segmentBase).isPreload;
}
}

View file

@ -169,6 +169,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
segmentEndTimeInPeriodUs,
segmentBaseHolder.mediaSequence,
segmentBaseHolder.partIndex,
segmentBaseHolder.isPreload,
discontinuitySequenceNumber,
mediaSegment.hasGapTag,
isMasterTimestampSource,
@ -204,6 +205,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** The part index or {@link C#INDEX_UNSET} if the chunk is a full segment */
public final int partIndex;
/** Whether this chunk is a preload chunk. */
public final boolean isPreload;
@Nullable private final DataSource initDataSource;
@Nullable private final DataSpec initDataSpec;
@Nullable private final HlsMediaChunkExtractor previousExtractor;
@ -247,6 +251,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
long endTimeUs,
long chunkMediaSequence,
int partIndex,
boolean isPreload,
int discontinuitySequenceNumber,
boolean hasGapTag,
boolean isMasterTimestampSource,
@ -267,6 +272,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
chunkMediaSequence);
this.mediaSegmentEncrypted = mediaSegmentEncrypted;
this.partIndex = partIndex;
this.isPreload = isPreload;
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.initDataSpec = initDataSpec;
this.initDataSource = initDataSource;

View file

@ -445,6 +445,9 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
@Override
public void onPlaylistChanged() {
for (HlsSampleStreamWrapper streamWrapper : sampleStreamWrappers) {
streamWrapper.onPlaylistUpdated();
}
callback.onContinueLoadingRequested(this);
}

View file

@ -504,6 +504,16 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return true;
}
/** Called when the playlist is updated. */
public void onPlaylistUpdated() {
if (!loadingFinished
&& loader.isLoading()
&& !mediaChunks.isEmpty()
&& chunkSource.isMediaChunkRemoved(Iterables.getLast(mediaChunks))) {
loader.cancelLoading();
}
}
public void release() {
if (prepared) {
// Discard as much as we can synchronously. We only do this if we're prepared, since otherwise
@ -672,8 +682,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/* allowEndOfStream= */ prepared || !chunkQueue.isEmpty(),
nextChunkHolder);
boolean endOfStream = nextChunkHolder.endOfStream;
Chunk loadable = nextChunkHolder.chunk;
Uri playlistUrlToLoad = nextChunkHolder.playlistUrl;
@Nullable Chunk loadable = nextChunkHolder.chunk;
@Nullable Uri playlistUrlToLoad = nextChunkHolder.playlistUrl;
nextChunkHolder.clear();
if (endOfStream) {
@ -727,6 +737,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return;
}
if (!readOnlyMediaChunks.isEmpty()
&& chunkSource.isMediaChunkRemoved(Iterables.getLast(readOnlyMediaChunks))) {
discardUpstream(mediaChunks.size() - 1);
}
int preferredQueueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks);
if (preferredQueueSize < mediaChunks.size()) {
discardUpstream(preferredQueueSize);