Skip to content

Commit

Permalink
Replace cancelled HLS preload parts
Browse files Browse the repository at this point in the history
Issue: #5011
PiperOrigin-RevId: 343277357
  • Loading branch information
marcbaechinger authored and ojw28 committed Nov 19, 2020
1 parent 31166d4 commit 39f8c77
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 12 deletions.
Expand Up @@ -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;
Expand All @@ -39,7 +40,6 @@
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;
Expand Down Expand Up @@ -84,7 +84,6 @@ public void clear() {
endOfStream = false;
playlistUrl = null;
}

}

/**
Expand Down Expand Up @@ -222,6 +221,44 @@ public void setIsTimestampMaster(boolean 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.
*
Expand Down Expand Up @@ -270,7 +307,6 @@ public void getNextChunk(
trackSelection.updateSelectedTrack(
playbackPositionUs, bufferedDurationUs, timeToLiveEdgeUs, queue, mediaChunkIterators);
int selectedTrackIndex = trackSelection.getSelectedIndexInTrackGroup();

boolean switchingTrack = oldTrackIndex != selectedTrackIndex;
Uri selectedPlaylistUrl = playlistUrls[selectedTrackIndex];
if (!playlistTracker.isSnapshotValid(selectedPlaylistUrl)) {
Expand All @@ -284,7 +320,7 @@ public void getNextChunk(
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);
Expand All @@ -306,7 +342,7 @@ public void getNextChunk(
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.
Expand Down Expand Up @@ -366,7 +402,6 @@ public void getNextChunk(
if (out.chunk != null) {
return;
}

out.chunk =
HlsMediaChunk.createInstance(
extractorFactory,
Expand Down Expand Up @@ -399,7 +434,7 @@ private static SegmentBaseHolder getNextSegmentHolder(

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()) {
Expand All @@ -417,6 +452,7 @@ private static SegmentBaseHolder getNextSegmentHolder(
return new SegmentBaseHolder(
mediaPlaylist.trailingParts.get(0), nextMediaSequence + 1, /* partIndex= */ 0);
}
// End of stream.
return null;
}

Expand All @@ -430,8 +466,7 @@ public void onChunkLoadCompleted(Chunk chunk) {
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()));
}
}

Expand Down Expand Up @@ -499,7 +534,7 @@ public MediaChunkIterator[] createMediaChunkIterators(
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;
Expand Down Expand Up @@ -704,13 +739,17 @@ private static Uri getFullEncryptionKeyUri(
public final HlsMediaPlaylist.SegmentBase segmentBase;
public final long mediaSequence;
public final int partIndex;
public final boolean isPreload;

/** Creates a new instance. */
public SegmentBaseHolder(
HlsMediaPlaylist.SegmentBase segmentBase, long mediaSequence, int partIndex) {
this.segmentBase = segmentBase;
this.mediaSequence = mediaSequence;
this.partIndex = partIndex;
this.isPreload =
segmentBase instanceof HlsMediaPlaylist.Part
&& ((HlsMediaPlaylist.Part) segmentBase).isPreload;
}
}

Expand Down
Expand Up @@ -169,6 +169,7 @@ public static HlsMediaChunk createInstance(
segmentEndTimeInPeriodUs,
segmentBaseHolder.mediaSequence,
segmentBaseHolder.partIndex,
segmentBaseHolder.isPreload,
discontinuitySequenceNumber,
mediaSegment.hasGapTag,
isMasterTimestampSource,
Expand Down Expand Up @@ -204,6 +205,9 @@ public static HlsMediaChunk createInstance(
/** 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;
Expand Down Expand Up @@ -247,6 +251,7 @@ private HlsMediaChunk(
long endTimeUs,
long chunkMediaSequence,
int partIndex,
boolean isPreload,
int discontinuitySequenceNumber,
boolean hasGapTag,
boolean isMasterTimestampSource,
Expand All @@ -267,6 +272,7 @@ private HlsMediaChunk(
chunkMediaSequence);
this.mediaSegmentEncrypted = mediaSegmentEncrypted;
this.partIndex = partIndex;
this.isPreload = isPreload;
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.initDataSpec = initDataSpec;
this.initDataSource = initDataSource;
Expand Down
Expand Up @@ -445,6 +445,9 @@ public void onContinueLoadingRequested(HlsSampleStreamWrapper sampleStreamWrappe

@Override
public void onPlaylistChanged() {
for (HlsSampleStreamWrapper streamWrapper : sampleStreamWrappers) {
streamWrapper.onPlaylistUpdated();
}
callback.onContinueLoadingRequested(this);
}

Expand Down
Expand Up @@ -504,6 +504,16 @@ public boolean seekToUs(long positionUs, boolean forceReset) {
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
Expand Down Expand Up @@ -672,8 +682,8 @@ public boolean continueLoading(long positionUs) {
/* 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) {
Expand Down Expand Up @@ -727,6 +737,11 @@ public void reevaluateBuffer(long positionUs) {
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);
Expand Down

0 comments on commit 39f8c77

Please sign in to comment.