Commit 6b9e1824 by tonihei Committed by Oliver Woodman

Extend updateSelectedTrack method with additional information.

This provides the list of currently buffered media chunks and iterators over
the potential next chunks to the track selection. Having these two parameters
enables more advanced decision logic based on this data.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=210551812
parent a429f481
......@@ -130,6 +130,9 @@
([#4681](https://github.com/google/ExoPlayer/issues/4681).
* Fix handling of postrolls with multiple ads
([#4710](https://github.com/google/ExoPlayer/issues/4710).
* Provide additional information for adaptive track selection.
`TrackSelection.updateSelectedTrack` has two new parameters for the current
queue of media chunks and iterators for information about upcoming chunks.
### 2.8.4 ###
......
......@@ -20,6 +20,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Util;
......@@ -328,9 +329,14 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
}
@Override
public void updateSelectedTrack(long playbackPositionUs, long bufferedDurationUs,
long availableDurationUs) {
public void updateSelectedTrack(
long playbackPositionUs,
long bufferedDurationUs,
long availableDurationUs,
List<? extends MediaChunk> queue,
MediaChunkIterator[] mediaChunkIterators) {
long nowMs = clock.elapsedRealtime();
// Stash the current selection, then make a new one.
int currentSelectedIndex = selectedIndex;
selectedIndex = determineIdealSelectedIndex(nowMs);
......
......@@ -20,15 +20,17 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import java.util.List;
/**
* A track selection consisting of a static subset of selected tracks belonging to a
* {@link TrackGroup}, and a possibly varying individual selected track from the subset.
* <p>
* Tracks belonging to the subset are exposed in decreasing bandwidth order. The individual selected
* track may change as a result of calling {@link #updateSelectedTrack(long, long, long)}.
* A track selection consisting of a static subset of selected tracks belonging to a {@link
* TrackGroup}, and a possibly varying individual selected track from the subset.
*
* <p>Tracks belonging to the subset are exposed in decreasing bandwidth order. The individual
* selected track may change as a result of calling {@link #updateSelectedTrack(long, long, long,
* List, MediaChunkIterator[])}.
*/
public interface TrackSelection {
......@@ -148,25 +150,47 @@ public interface TrackSelection {
void onPlaybackSpeed(float speed);
/**
* Updates the selected track.
* <p>
* This method may only be called when the selection is enabled.
* @deprecated Use and implement {@link #updateSelectedTrack(long, long, long, float, List,
* MediaChunkIterator[])} instead.
*/
@Deprecated
default void updateSelectedTrack(
long playbackPositionUs, long bufferedDurationUs, long availableDurationUs) {
throw new UnsupportedOperationException();
}
/**
* Updates the selected track for sources that load media in discrete {@link MediaChunk}s.
*
* <p>This method may only be called when the selection is enabled.
*
* @param playbackPositionUs The current playback position in microseconds. If playback of the
* period to which this track selection belongs has not yet started, the value will be the
* starting position in the period minus the duration of any media in previous periods still
* to be played.
* @param bufferedDurationUs The duration of media currently buffered from the current playback
* position, in microseconds. Note that the next load position can be calculated as
* {@code (playbackPositionUs + bufferedDurationUs)}.
* position, in microseconds. Note that the next load position can be calculated as {@code
* (playbackPositionUs + bufferedDurationUs)}.
* @param availableDurationUs The duration of media available for buffering from the current
* playback position, in microseconds, or {@link C#TIME_UNSET} if media can be buffered
* to the end of the current period. Note that if not set to {@link C#TIME_UNSET}, the
* position up to which media is available for buffering can be calculated as
* {@code (playbackPositionUs + availableDurationUs)}.
*/
void updateSelectedTrack(long playbackPositionUs, long bufferedDurationUs,
long availableDurationUs);
* playback position, in microseconds, or {@link C#TIME_UNSET} if media can be buffered to the
* end of the current period. Note that if not set to {@link C#TIME_UNSET}, the position up to
* which media is available for buffering can be calculated as {@code (playbackPositionUs +
* availableDurationUs)}.
* @param queue The queue of already buffered {@link MediaChunk}s. Must not be modified.
* @param mediaChunkIterators An array of {@link MediaChunkIterator}s providing information about
* the sequence of upcoming media chunks for each track in the selection. All iterators start
* from the media chunk which will be loaded next if the respective track is selected. Note
* that this information may not be available for all tracks, and so some iterators may be
* empty.
*/
default void updateSelectedTrack(
long playbackPositionUs,
long bufferedDurationUs,
long availableDurationUs,
List<? extends MediaChunk> queue,
MediaChunkIterator[] mediaChunkIterators) {
updateSelectedTrack(playbackPositionUs, bufferedDurationUs, availableDurationUs);
}
/**
* May be called periodically by sources that load media in discrete {@link MediaChunk}s and
......@@ -190,12 +214,13 @@ public interface TrackSelection {
/**
* Attempts to blacklist the track at the specified index in the selection, making it ineligible
* for selection by calls to {@link #updateSelectedTrack(long, long, long)} for the specified
* period of time. Blacklisting will fail if all other tracks are currently blacklisted. If
* blacklisting the currently selected track, note that it will remain selected until the next
* call to {@link #updateSelectedTrack(long, long, long)}.
* <p>
* This method may only be called when the selection is enabled.
* for selection by calls to {@link #updateSelectedTrack(long, long, long, List,
* MediaChunkIterator[])} for the specified period of time. Blacklisting will fail if all other
* tracks are currently blacklisted. If blacklisting the currently selected track, note that it
* will remain selected until the next call to {@link #updateSelectedTrack(long, long, long, List,
* MediaChunkIterator[])}.
*
* <p>This method may only be called when the selection is enabled.
*
* @param index The index of the track in the selection.
* @param blacklistDurationMs The duration of time for which the track should be blacklisted, in
......@@ -203,5 +228,4 @@ public interface TrackSelection {
* @return Whether blacklisting was successful.
*/
boolean blacklist(int index, long blacklistDurationMs);
}
......@@ -28,6 +28,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.testutil.FakeClock;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
......@@ -36,6 +37,7 @@ import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
......@@ -47,6 +49,11 @@ import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public final class AdaptiveTrackSelectionTest {
private static final MediaChunkIterator[] THREE_EMPTY_MEDIA_CHUNK_ITERATORS =
new MediaChunkIterator[] {
MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY
};
@Mock private BandwidthMeter mockBandwidthMeter;
private FakeClock fakeClock;
......@@ -70,7 +77,9 @@ public final class AdaptiveTrackSelectionTest {
adaptiveTrackSelection.updateSelectedTrack(
/* playbackPositionUs= */ 0,
/* bufferedDurationUs= */ 0,
/* availableDurationUs= */ C.TIME_UNSET);
/* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ Collections.emptyList(),
/* mediaChunkIterators= */ new MediaChunkIterator[] {MediaChunkIterator.EMPTY});
verify(initialBandwidthMeter, atLeastOnce()).getBitrateEstimate();
verifyZeroInteractions(injectedBandwidthMeter);
......@@ -121,7 +130,9 @@ public final class AdaptiveTrackSelectionTest {
adaptiveTrackSelection.updateSelectedTrack(
/* playbackPositionUs= */ 0,
/* bufferedDurationUs= */ 9_999_000,
/* availableDurationUs= */ C.TIME_UNSET);
/* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ Collections.emptyList(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS);
// When bandwidth estimation is updated to 2000L, we can switch up to use a higher bitrate
// format. However, since we only buffered 9_999_000 us, which is smaller than
......@@ -147,7 +158,9 @@ public final class AdaptiveTrackSelectionTest {
adaptiveTrackSelection.updateSelectedTrack(
/* playbackPositionUs= */ 0,
/* bufferedDurationUs= */ 10_000_000,
/* availableDurationUs= */ C.TIME_UNSET);
/* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ Collections.emptyList(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS);
// When bandwidth estimation is updated to 2000L, we can switch up to use a higher bitrate
// format. When we have buffered enough (10_000_000 us, which is equal to
......@@ -173,7 +186,9 @@ public final class AdaptiveTrackSelectionTest {
adaptiveTrackSelection.updateSelectedTrack(
/* playbackPositionUs= */ 0,
/* bufferedDurationUs= */ 25_000_000,
/* availableDurationUs= */ C.TIME_UNSET);
/* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ Collections.emptyList(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS);
// When bandwidth estimation is updated to 500L, we should switch down to use a lower bitrate
// format. However, since we have enough buffer at higher quality (25_000_000 us, which is equal
......@@ -199,7 +214,9 @@ public final class AdaptiveTrackSelectionTest {
adaptiveTrackSelection.updateSelectedTrack(
/* playbackPositionUs= */ 0,
/* bufferedDurationUs= */ 24_999_000,
/* availableDurationUs= */ C.TIME_UNSET);
/* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ Collections.emptyList(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS);
// When bandwidth estimation is updated to 500L, we should switch down to use a lower bitrate
// format. When we don't have enough buffer at higher quality (24_999_000 us is smaller than
......
......@@ -264,7 +264,37 @@ public class DefaultDashChunkSource implements DashChunkSource {
return;
}
trackSelection.updateSelectedTrack(playbackPositionUs, bufferedDurationUs, timeToLiveEdgeUs);
long nowUnixTimeUs = getNowUnixTimeUs();
MediaChunk previous = queue.isEmpty() ? null : queue.get(queue.size() - 1);
MediaChunkIterator[] chunkIterators = new MediaChunkIterator[trackSelection.length()];
for (int i = 0; i < chunkIterators.length; i++) {
RepresentationHolder representationHolder = representationHolders[i];
if (representationHolder.segmentIndex == null) {
chunkIterators[i] = MediaChunkIterator.EMPTY;
} else {
long firstAvailableSegmentNum =
representationHolder.getFirstAvailableSegmentNum(manifest, periodIndex, nowUnixTimeUs);
long lastAvailableSegmentNum =
representationHolder.getLastAvailableSegmentNum(manifest, periodIndex, nowUnixTimeUs);
long segmentNum =
getSegmentNum(
representationHolder,
previous,
loadPositionUs,
firstAvailableSegmentNum,
lastAvailableSegmentNum);
if (segmentNum < firstAvailableSegmentNum) {
chunkIterators[i] = MediaChunkIterator.EMPTY;
} else {
chunkIterators[i] =
new RepresentationSegmentIterator(
representationHolder, segmentNum, lastAvailableSegmentNum);
}
}
}
trackSelection.updateSelectedTrack(
playbackPositionUs, bufferedDurationUs, timeToLiveEdgeUs, queue, chunkIterators);
RepresentationHolder representationHolder =
representationHolders[trackSelection.getSelectedIndex()];
......@@ -288,48 +318,31 @@ public class DefaultDashChunkSource implements DashChunkSource {
}
}
int availableSegmentCount = representationHolder.getSegmentCount();
if (availableSegmentCount == 0) {
if (representationHolder.getSegmentCount() == 0) {
// The index doesn't define any segments.
out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1);
return;
}
long firstAvailableSegmentNum = representationHolder.getFirstSegmentNum();
long lastAvailableSegmentNum;
if (availableSegmentCount == DashSegmentIndex.INDEX_UNBOUNDED) {
// The index is itself unbounded. We need to use the current time to calculate the range of
// available segments.
long liveEdgeTimeUs = getNowUnixTimeUs() - C.msToUs(manifest.availabilityStartTimeMs);
long periodStartUs = C.msToUs(manifest.getPeriod(periodIndex).startMs);
long liveEdgeTimeInPeriodUs = liveEdgeTimeUs - periodStartUs;
if (manifest.timeShiftBufferDepthMs != C.TIME_UNSET) {
long bufferDepthUs = C.msToUs(manifest.timeShiftBufferDepthMs);
firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum,
representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs - bufferDepthUs));
}
// getSegmentNum(liveEdgeTimeInPeriodUs) will not be completed yet, so subtract one to get the
// index of the last completed segment.
lastAvailableSegmentNum = representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs) - 1;
} else {
lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1;
}
long firstAvailableSegmentNum =
representationHolder.getFirstAvailableSegmentNum(manifest, periodIndex, nowUnixTimeUs);
long lastAvailableSegmentNum =
representationHolder.getLastAvailableSegmentNum(manifest, periodIndex, nowUnixTimeUs);
updateLiveEdgeTimeUs(representationHolder, lastAvailableSegmentNum);
long segmentNum;
if (queue.isEmpty()) {
segmentNum = Util.constrainValue(representationHolder.getSegmentNum(loadPositionUs),
firstAvailableSegmentNum, lastAvailableSegmentNum);
} else {
segmentNum = queue.get(queue.size() - 1).getNextChunkIndex();
if (segmentNum < firstAvailableSegmentNum) {
// This is before the first chunk in the current manifest.
fatalError = new BehindLiveWindowException();
return;
}
long segmentNum =
getSegmentNum(
representationHolder,
previous,
loadPositionUs,
firstAvailableSegmentNum,
lastAvailableSegmentNum);
if (segmentNum < firstAvailableSegmentNum) {
// This is before the first chunk in the current manifest.
fatalError = new BehindLiveWindowException();
return;
}
if (segmentNum > lastAvailableSegmentNum
|| (missingLastSegment && segmentNum >= lastAvailableSegmentNum)) {
// This is beyond the last chunk in the current manifest.
......@@ -409,6 +422,20 @@ public class DefaultDashChunkSource implements DashChunkSource {
// Internal methods.
private long getSegmentNum(
RepresentationHolder representationHolder,
@Nullable MediaChunk previousChunk,
long loadPositionUs,
long firstAvailableSegmentNum,
long lastAvailableSegmentNum) {
return previousChunk != null
? previousChunk.getNextChunkIndex()
: Util.constrainValue(
representationHolder.getSegmentNum(loadPositionUs),
firstAvailableSegmentNum,
lastAvailableSegmentNum);
}
private ArrayList<Representation> getRepresentations() {
List<AdaptationSet> manifestAdapationSets = manifest.getPeriod(periodIndex).adaptationSets;
ArrayList<Representation> representations = new ArrayList<>();
......@@ -683,6 +710,38 @@ public class DefaultDashChunkSource implements DashChunkSource {
return segmentIndex.getSegmentUrl(segmentNum - segmentNumShift);
}
public long getFirstAvailableSegmentNum(
DashManifest manifest, int periodIndex, long nowUnixTimeUs) {
if (getSegmentCount() == DashSegmentIndex.INDEX_UNBOUNDED
&& manifest.timeShiftBufferDepthMs != C.TIME_UNSET) {
// The index is itself unbounded. We need to use the current time to calculate the range of
// available segments.
long liveEdgeTimeUs = nowUnixTimeUs - C.msToUs(manifest.availabilityStartTimeMs);
long periodStartUs = C.msToUs(manifest.getPeriod(periodIndex).startMs);
long liveEdgeTimeInPeriodUs = liveEdgeTimeUs - periodStartUs;
long bufferDepthUs = C.msToUs(manifest.timeShiftBufferDepthMs);
return Math.max(
getFirstSegmentNum(), getSegmentNum(liveEdgeTimeInPeriodUs - bufferDepthUs));
}
return getFirstSegmentNum();
}
public long getLastAvailableSegmentNum(
DashManifest manifest, int periodIndex, long nowUnixTimeUs) {
int availableSegmentCount = getSegmentCount();
if (availableSegmentCount == DashSegmentIndex.INDEX_UNBOUNDED) {
// The index is itself unbounded. We need to use the current time to calculate the range of
// available segments.
long liveEdgeTimeUs = nowUnixTimeUs - C.msToUs(manifest.availabilityStartTimeMs);
long periodStartUs = C.msToUs(manifest.getPeriod(periodIndex).startMs);
long liveEdgeTimeInPeriodUs = liveEdgeTimeUs - periodStartUs;
// getSegmentNum(liveEdgeTimeInPeriodUs) will not be completed yet, so subtract one to get
// the index of the last completed segment.
return getSegmentNum(liveEdgeTimeInPeriodUs) - 1;
}
return getFirstSegmentNum() + availableSegmentCount - 1;
}
private static boolean mimeTypeIsWebm(String mimeType) {
return mimeType.startsWith(MimeTypes.VIDEO_WEBM) || mimeType.startsWith(MimeTypes.AUDIO_WEBM)
|| mimeType.startsWith(MimeTypes.APPLICATION_WEBM);
......
......@@ -25,6 +25,7 @@ import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator;
import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.DataChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
......@@ -250,7 +251,9 @@ import java.util.List;
}
// Select the variant.
trackSelection.updateSelectedTrack(playbackPositionUs, bufferedDurationUs, timeToLiveEdgeUs);
MediaChunkIterator[] mediaChunkIterators = createMediaChunkIterators(previous, loadPositionUs);
trackSelection.updateSelectedTrack(
playbackPositionUs, bufferedDurationUs, timeToLiveEdgeUs, queue, mediaChunkIterators);
int selectedVariantIndex = trackSelection.getSelectedIndexInTrackGroup();
boolean switchingVariant = oldVariantIndex != selectedVariantIndex;
......@@ -268,42 +271,25 @@ import java.util.List;
updateLiveEdgeTimeUs(mediaPlaylist);
// Select the chunk.
long chunkMediaSequence;
long startOfPlaylistInPeriodUs =
mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs();
if (previous == null || switchingVariant) {
long endOfPlaylistInPeriodUs = startOfPlaylistInPeriodUs + mediaPlaylist.durationUs;
long targetPositionInPeriodUs =
(previous == null || independentSegments) ? loadPositionUs : previous.startTimeUs;
if (!mediaPlaylist.hasEndTag && targetPositionInPeriodUs >= endOfPlaylistInPeriodUs) {
// If the playlist is too old to contain the chunk, we need to refresh it.
chunkMediaSequence = mediaPlaylist.mediaSequence + mediaPlaylist.segments.size();
long chunkMediaSequence =
getChunkMediaSequence(
previous, switchingVariant, mediaPlaylist, startOfPlaylistInPeriodUs, loadPositionUs);
if (chunkMediaSequence < mediaPlaylist.mediaSequence) {
if (previous != null && switchingVariant) {
// We try getting the next chunk without adapting in case that's the reason for falling
// behind the live window.
selectedVariantIndex = oldVariantIndex;
selectedUrl = variants[selectedVariantIndex];
mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
startOfPlaylistInPeriodUs =
mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs();
chunkMediaSequence = previous.getNextChunkIndex();
} else {
long targetPositionInPlaylistUs = targetPositionInPeriodUs - startOfPlaylistInPeriodUs;
chunkMediaSequence =
Util.binarySearchFloor(
mediaPlaylist.segments,
/* value= */ targetPositionInPlaylistUs,
/* inclusive= */ true,
/* stayInBounds= */ !playlistTracker.isLive() || previous == null)
+ mediaPlaylist.mediaSequence;
if (chunkMediaSequence < mediaPlaylist.mediaSequence && previous != null) {
// We try getting the next chunk without adapting in case that's the reason for falling
// behind the live window.
selectedVariantIndex = oldVariantIndex;
selectedUrl = variants[selectedVariantIndex];
mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
startOfPlaylistInPeriodUs =
mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs();
chunkMediaSequence = previous.getNextChunkIndex();
}
fatalError = new BehindLiveWindowException();
return;
}
} else {
chunkMediaSequence = previous.getNextChunkIndex();
}
if (chunkMediaSequence < mediaPlaylist.mediaSequence) {
fatalError = new BehindLiveWindowException();
return;
}
int chunkIndex = (int) (chunkMediaSequence - mediaPlaylist.mediaSequence);
......@@ -433,8 +419,70 @@ import java.util.List;
|| trackSelection.blacklist(trackSelectionIndex, blacklistDurationMs);
}
/**
* Returns list of {@link MediaChunkIterator}s for upcoming media chunks.
*
* @param previous The previous media chunk. May be null.
* @param loadPositionUs The position at which the iterators will start.
* @return Array of {@link MediaChunkIterator}s for each track.
*/
public MediaChunkIterator[] createMediaChunkIterators(
@Nullable HlsMediaChunk previous, long loadPositionUs) {
int oldVariantIndex =
previous == null ? C.INDEX_UNSET : trackGroup.indexOf(previous.trackFormat);
MediaChunkIterator[] chunkIterators = new MediaChunkIterator[trackSelection.length()];
for (int i = 0; i < chunkIterators.length; i++) {
int variantIndex = trackSelection.getIndexInTrackGroup(i);
HlsUrl variantUrl = variants[variantIndex];
if (!playlistTracker.isSnapshotValid(variantUrl)) {
chunkIterators[i] = MediaChunkIterator.EMPTY;
continue;
}
HlsMediaPlaylist playlist = playlistTracker.getPlaylistSnapshot(variantUrl);
long startOfPlaylistInPeriodUs =
playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
boolean switchingVariant = variantIndex != oldVariantIndex;
long chunkMediaSequence =
getChunkMediaSequence(
previous, switchingVariant, playlist, startOfPlaylistInPeriodUs, loadPositionUs);
if (chunkMediaSequence < playlist.mediaSequence) {
chunkIterators[i] = MediaChunkIterator.EMPTY;
continue;
}
int chunkIndex = (int) (chunkMediaSequence - playlist.mediaSequence);
chunkIterators[i] =
new HlsMediaPlaylistSegmentIterator(playlist, startOfPlaylistInPeriodUs, chunkIndex);
}
return chunkIterators;
}
// Private methods.
private long getChunkMediaSequence(
@Nullable HlsMediaChunk previous,
boolean switchingVariant,
HlsMediaPlaylist mediaPlaylist,
long startOfPlaylistInPeriodUs,
long loadPositionUs) {
if (previous == null || switchingVariant) {
long endOfPlaylistInPeriodUs = startOfPlaylistInPeriodUs + mediaPlaylist.durationUs;
long targetPositionInPeriodUs =
(previous == null || independentSegments) ? loadPositionUs : previous.startTimeUs;
if (!mediaPlaylist.hasEndTag && targetPositionInPeriodUs >= endOfPlaylistInPeriodUs) {
// If the playlist is too old to contain the chunk, we need to refresh it.
return mediaPlaylist.mediaSequence + mediaPlaylist.segments.size();
}
long targetPositionInPlaylistUs = targetPositionInPeriodUs - startOfPlaylistInPeriodUs;
return Util.binarySearchFloor(
mediaPlaylist.segments,
/* value= */ targetPositionInPlaylistUs,
/* inclusive= */ true,
/* stayInBounds= */ !playlistTracker.isLive() || previous == null)
+ mediaPlaylist.mediaSequence;
}
return previous.getNextChunkIndex();
}
private long resolveTimeToLiveEdgeUs(long playbackPositionUs) {
final boolean resolveTimeToLiveEdgePossible = liveEdgeInPeriodTimeUs != C.TIME_UNSET;
return resolveTimeToLiveEdgePossible
......@@ -498,8 +546,12 @@ import java.util.List;
}
@Override
public void updateSelectedTrack(long playbackPositionUs, long bufferedDurationUs,
long availableDurationUs) {
public void updateSelectedTrack(
long playbackPositionUs,
long bufferedDurationUs,
long availableDurationUs,
List<? extends MediaChunk> queue,
MediaChunkIterator[] mediaChunkIterators) {
long nowMs = SystemClock.elapsedRealtime();
if (!isBlacklisted(selectedIndex, nowMs)) {
return;
......
......@@ -33,6 +33,7 @@ import com.google.android.exoplayer2.source.SequenceableLoader;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
import com.google.android.exoplayer2.trackselection.TrackSelection;
......@@ -324,8 +325,16 @@ import java.util.List;
boolean primarySampleQueueDirty = false;
if (!seenFirstTrackSelection) {
long bufferedDurationUs = positionUs < 0 ? -positionUs : 0;
primaryTrackSelection.updateSelectedTrack(positionUs, bufferedDurationUs, C.TIME_UNSET);
int chunkIndex = chunkSource.getTrackGroup().indexOf(getLastMediaChunk().trackFormat);
HlsMediaChunk lastMediaChunk = getLastMediaChunk();
MediaChunkIterator[] mediaChunkIterators =
chunkSource.createMediaChunkIterators(lastMediaChunk, positionUs);
primaryTrackSelection.updateSelectedTrack(
positionUs,
bufferedDurationUs,
C.TIME_UNSET,
readOnlyMediaChunks,
mediaChunkIterators);
int chunkIndex = chunkSource.getTrackGroup().indexOf(lastMediaChunk.trackFormat);
if (primaryTrackSelection.getSelectedIndexInTrackGroup() != chunkIndex) {
// This is the first selection and the chunk loaded during preparation does not match
// the initially selected format.
......
......@@ -213,7 +213,14 @@ public class DefaultSsChunkSource implements SsChunkSource {
long bufferedDurationUs = loadPositionUs - playbackPositionUs;
long timeToLiveEdgeUs = resolveTimeToLiveEdgeUs(playbackPositionUs);
trackSelection.updateSelectedTrack(playbackPositionUs, bufferedDurationUs, timeToLiveEdgeUs);
MediaChunkIterator[] chunkIterators = new MediaChunkIterator[trackSelection.length()];
for (int i = 0; i < chunkIterators.length; i++) {
int trackIndex = trackSelection.getIndexInTrackGroup(i);
chunkIterators[i] = new StreamElementIterator(streamElement, trackIndex, chunkIndex);
}
trackSelection.updateSelectedTrack(
playbackPositionUs, bufferedDurationUs, timeToLiveEdgeUs, queue, chunkIterators);
long chunkStartTimeUs = streamElement.getStartTimeUs(chunkIndex);
long chunkEndTimeUs = chunkStartTimeUs + streamElement.getChunkDurationUs(chunkIndex);
......
......@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.ChunkHolder;
import com.google.android.exoplayer2.source.chunk.ChunkSource;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.source.chunk.SingleSampleMediaChunk;
import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData.Segment;
import com.google.android.exoplayer2.trackselection.TrackSelection;
......@@ -109,11 +110,17 @@ public final class FakeChunkSource implements ChunkSource {
List<? extends MediaChunk> queue,
ChunkHolder out) {
long bufferedDurationUs = loadPositionUs - playbackPositionUs;
trackSelection.updateSelectedTrack(playbackPositionUs, bufferedDurationUs, C.TIME_UNSET);
int chunkIndex =
queue.isEmpty()
? dataSet.getChunkIndexByPosition(playbackPositionUs)
: (int) queue.get(queue.size() - 1).getNextChunkIndex();
MediaChunkIterator[] chunkIterators = new MediaChunkIterator[trackSelection.length()];
for (int i = 0; i < chunkIterators.length; i++) {
int trackGroupIndex = trackSelection.getIndexInTrackGroup(i);
chunkIterators[i] = new FakeAdaptiveDataSet.Iterator(dataSet, trackGroupIndex, chunkIndex);
}
trackSelection.updateSelectedTrack(
playbackPositionUs, bufferedDurationUs, C.TIME_UNSET, queue, chunkIterators);
if (chunkIndex >= dataSet.getChunkCount()) {
out.endOfStream = true;
} else {
......
......@@ -21,6 +21,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import java.util.List;
......@@ -119,7 +120,11 @@ public final class FakeTrackSelection implements TrackSelection {
@Override
public void updateSelectedTrack(
long playbackPositionUs, long bufferedDurationUs, long availableDurationUs) {
long playbackPositionUs,
long bufferedDurationUs,
long availableDurationUs,
List<? extends MediaChunk> queue,
MediaChunkIterator[] mediaChunkIterators) {
assertThat(isEnabled).isTrue();
}
......
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