mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Replace cancelled HLS preload parts
Issue: #5011 PiperOrigin-RevId: 343277357
This commit is contained in:
parent
31166d41c4
commit
39f8c77568
4 changed files with 75 additions and 12 deletions
|
|
@ -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;
|
||||||
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;
|
||||||
endOfStream = false;
|
endOfStream = false;
|
||||||
playlistUrl = null;
|
playlistUrl = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -222,6 +221,44 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
this.isTimestampMaster = isTimestampMaster;
|
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.
|
* Returns the next chunk to load.
|
||||||
*
|
*
|
||||||
|
|
@ -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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
|
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
/** 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;
|
||||||
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;
|
||||||
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
|
||||||
|
|
||||||
@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;
|
||||||
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;
|
||||||
/* 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;
|
||||||
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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue