Commit 39f8c775 by bachinger Committed by Oliver Woodman

Replace cancelled HLS preload parts

Issue: #5011
PiperOrigin-RevId: 343277357
parent 31166d41
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.source.hls; package com.google.android.exoplayer2.source.hls;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static java.lang.Math.max; import static java.lang.Math.max;
import android.net.Uri; import android.net.Uri;
...@@ -39,7 +40,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelection; ...@@ -39,7 +40,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener; 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.TimestampAdjuster;
import com.google.android.exoplayer2.util.UriUtil; import com.google.android.exoplayer2.util.UriUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -84,7 +84,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -84,7 +84,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
endOfStream = false; endOfStream = false;
playlistUrl = null; playlistUrl = null;
} }
} }
/** /**
...@@ -223,6 +222,44 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -223,6 +222,44 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
/** /**
* 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. * Returns the next chunk to load.
* *
* <p>If a chunk is available then {@link HlsChunkHolder#chunk} is set. If the end of the stream * <p>If a chunk is available then {@link HlsChunkHolder#chunk} is set. If the end of the stream
...@@ -270,7 +307,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -270,7 +307,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
trackSelection.updateSelectedTrack( trackSelection.updateSelectedTrack(
playbackPositionUs, bufferedDurationUs, timeToLiveEdgeUs, queue, mediaChunkIterators); playbackPositionUs, bufferedDurationUs, timeToLiveEdgeUs, queue, mediaChunkIterators);
int selectedTrackIndex = trackSelection.getSelectedIndexInTrackGroup(); int selectedTrackIndex = trackSelection.getSelectedIndexInTrackGroup();
boolean switchingTrack = oldTrackIndex != selectedTrackIndex; boolean switchingTrack = oldTrackIndex != selectedTrackIndex;
Uri selectedPlaylistUrl = playlistUrls[selectedTrackIndex]; Uri selectedPlaylistUrl = playlistUrls[selectedTrackIndex];
if (!playlistTracker.isSnapshotValid(selectedPlaylistUrl)) { if (!playlistTracker.isSnapshotValid(selectedPlaylistUrl)) {
...@@ -284,7 +320,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -284,7 +320,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
HlsMediaPlaylist mediaPlaylist = HlsMediaPlaylist mediaPlaylist =
playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */ true); playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */ true);
// playlistTracker snapshot is valid (checked by if() above), so mediaPlaylist must be non-null. // playlistTracker snapshot is valid (checked by if() above), so mediaPlaylist must be non-null.
Assertions.checkNotNull(mediaPlaylist); checkNotNull(mediaPlaylist);
independentSegments = mediaPlaylist.hasIndependentSegments; independentSegments = mediaPlaylist.hasIndependentSegments;
updateLiveEdgeTimeUs(mediaPlaylist); updateLiveEdgeTimeUs(mediaPlaylist);
...@@ -306,7 +342,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -306,7 +342,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */ true); playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */ true);
// playlistTracker snapshot is valid (checked by if() above), so mediaPlaylist must be // playlistTracker snapshot is valid (checked by if() above), so mediaPlaylist must be
// non-null. // non-null.
Assertions.checkNotNull(mediaPlaylist); checkNotNull(mediaPlaylist);
startOfPlaylistInPeriodUs = startOfPlaylistInPeriodUs =
mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs(); mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs();
// Get the next segment/part without switching tracks. // Get the next segment/part without switching tracks.
...@@ -366,7 +402,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -366,7 +402,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (out.chunk != null) { if (out.chunk != null) {
return; return;
} }
out.chunk = out.chunk =
HlsMediaChunk.createInstance( HlsMediaChunk.createInstance(
extractorFactory, extractorFactory,
...@@ -399,7 +434,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -399,7 +434,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
Segment mediaSegment = mediaPlaylist.segments.get(segmentIndexInPlaylist); Segment mediaSegment = mediaPlaylist.segments.get(segmentIndexInPlaylist);
if (nextPartIndex == C.INDEX_UNSET) { 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()) { if (nextPartIndex < mediaSegment.parts.size()) {
...@@ -417,6 +452,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -417,6 +452,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return new SegmentBaseHolder( return new SegmentBaseHolder(
mediaPlaylist.trailingParts.get(0), nextMediaSequence + 1, /* partIndex= */ 0); mediaPlaylist.trailingParts.get(0), nextMediaSequence + 1, /* partIndex= */ 0);
} }
// End of stream.
return null; return null;
} }
...@@ -430,8 +466,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -430,8 +466,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (chunk instanceof EncryptionKeyChunk) { if (chunk instanceof EncryptionKeyChunk) {
EncryptionKeyChunk encryptionKeyChunk = (EncryptionKeyChunk) chunk; EncryptionKeyChunk encryptionKeyChunk = (EncryptionKeyChunk) chunk;
scratchSpace = encryptionKeyChunk.getDataHolder(); scratchSpace = encryptionKeyChunk.getDataHolder();
keyCache.put( keyCache.put(encryptionKeyChunk.dataSpec.uri, checkNotNull(encryptionKeyChunk.getResult()));
encryptionKeyChunk.dataSpec.uri, Assertions.checkNotNull(encryptionKeyChunk.getResult()));
} }
} }
...@@ -499,7 +534,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -499,7 +534,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
HlsMediaPlaylist playlist = HlsMediaPlaylist playlist =
playlistTracker.getPlaylistSnapshot(playlistUrl, /* isForPlayback= */ false); playlistTracker.getPlaylistSnapshot(playlistUrl, /* isForPlayback= */ false);
// Playlist snapshot is valid (checked by if() above) so playlist must be non-null. // Playlist snapshot is valid (checked by if() above) so playlist must be non-null.
Assertions.checkNotNull(playlist); checkNotNull(playlist);
long startOfPlaylistInPeriodUs = long startOfPlaylistInPeriodUs =
playlist.startTimeUs - playlistTracker.getInitialStartTimeUs(); playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
boolean switchingTrack = trackIndex != oldTrackIndex; boolean switchingTrack = trackIndex != oldTrackIndex;
...@@ -704,6 +739,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -704,6 +739,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public final HlsMediaPlaylist.SegmentBase segmentBase; public final HlsMediaPlaylist.SegmentBase segmentBase;
public final long mediaSequence; public final long mediaSequence;
public final int partIndex; public final int partIndex;
public final boolean isPreload;
/** Creates a new instance. */ /** Creates a new instance. */
public SegmentBaseHolder( public SegmentBaseHolder(
...@@ -711,6 +747,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -711,6 +747,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.segmentBase = segmentBase; this.segmentBase = segmentBase;
this.mediaSequence = mediaSequence; this.mediaSequence = mediaSequence;
this.partIndex = partIndex; this.partIndex = partIndex;
this.isPreload =
segmentBase instanceof HlsMediaPlaylist.Part
&& ((HlsMediaPlaylist.Part) segmentBase).isPreload;
} }
} }
......
...@@ -169,6 +169,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -169,6 +169,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
segmentEndTimeInPeriodUs, segmentEndTimeInPeriodUs,
segmentBaseHolder.mediaSequence, segmentBaseHolder.mediaSequence,
segmentBaseHolder.partIndex, segmentBaseHolder.partIndex,
segmentBaseHolder.isPreload,
discontinuitySequenceNumber, discontinuitySequenceNumber,
mediaSegment.hasGapTag, mediaSegment.hasGapTag,
isMasterTimestampSource, isMasterTimestampSource,
...@@ -204,6 +205,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -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 */ /** The part index or {@link C#INDEX_UNSET} if the chunk is a full segment */
public final int partIndex; public final int partIndex;
/** Whether this chunk is a preload chunk. */
public final boolean isPreload;
@Nullable private final DataSource initDataSource; @Nullable private final DataSource initDataSource;
@Nullable private final DataSpec initDataSpec; @Nullable private final DataSpec initDataSpec;
@Nullable private final HlsMediaChunkExtractor previousExtractor; @Nullable private final HlsMediaChunkExtractor previousExtractor;
...@@ -247,6 +251,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -247,6 +251,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
long endTimeUs, long endTimeUs,
long chunkMediaSequence, long chunkMediaSequence,
int partIndex, int partIndex,
boolean isPreload,
int discontinuitySequenceNumber, int discontinuitySequenceNumber,
boolean hasGapTag, boolean hasGapTag,
boolean isMasterTimestampSource, boolean isMasterTimestampSource,
...@@ -267,6 +272,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -267,6 +272,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
chunkMediaSequence); chunkMediaSequence);
this.mediaSegmentEncrypted = mediaSegmentEncrypted; this.mediaSegmentEncrypted = mediaSegmentEncrypted;
this.partIndex = partIndex; this.partIndex = partIndex;
this.isPreload = isPreload;
this.discontinuitySequenceNumber = discontinuitySequenceNumber; this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.initDataSpec = initDataSpec; this.initDataSpec = initDataSpec;
this.initDataSource = initDataSource; this.initDataSource = initDataSource;
......
...@@ -445,6 +445,9 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -445,6 +445,9 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
@Override @Override
public void onPlaylistChanged() { public void onPlaylistChanged() {
for (HlsSampleStreamWrapper streamWrapper : sampleStreamWrappers) {
streamWrapper.onPlaylistUpdated();
}
callback.onContinueLoadingRequested(this); callback.onContinueLoadingRequested(this);
} }
......
...@@ -504,6 +504,16 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -504,6 +504,16 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return true; 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() { public void release() {
if (prepared) { if (prepared) {
// Discard as much as we can synchronously. We only do this if we're prepared, since otherwise // 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; ...@@ -672,8 +682,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/* allowEndOfStream= */ prepared || !chunkQueue.isEmpty(), /* allowEndOfStream= */ prepared || !chunkQueue.isEmpty(),
nextChunkHolder); nextChunkHolder);
boolean endOfStream = nextChunkHolder.endOfStream; boolean endOfStream = nextChunkHolder.endOfStream;
Chunk loadable = nextChunkHolder.chunk; @Nullable Chunk loadable = nextChunkHolder.chunk;
Uri playlistUrlToLoad = nextChunkHolder.playlistUrl; @Nullable Uri playlistUrlToLoad = nextChunkHolder.playlistUrl;
nextChunkHolder.clear(); nextChunkHolder.clear();
if (endOfStream) { if (endOfStream) {
...@@ -727,6 +737,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -727,6 +737,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return; return;
} }
if (!readOnlyMediaChunks.isEmpty()
&& chunkSource.isMediaChunkRemoved(Iterables.getLast(readOnlyMediaChunks))) {
discardUpstream(mediaChunks.size() - 1);
}
int preferredQueueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks); int preferredQueueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks);
if (preferredQueueSize < mediaChunks.size()) { if (preferredQueueSize < mediaChunks.size()) {
discardUpstream(preferredQueueSize); discardUpstream(preferredQueueSize);
......
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