mirror of
https://github.com/androidx/media.git
synced 2025-05-11 01:31:40 +08:00
Optimize in-buffer seeking for HLS
Also move to using an array to hold the SampleQueues, as we've moved to doing in ExtractorMediaPeriod. Issue: #551 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=161972990
This commit is contained in:
parent
a2ffcec200
commit
bf5495f2f5
@ -320,18 +320,18 @@ import java.util.Arrays;
|
||||
positionUs = seekMap.isSeekable() ? positionUs : 0;
|
||||
lastSeekPositionUs = positionUs;
|
||||
notifyDiscontinuity = false;
|
||||
// If we're not pending a reset, see if we can seek within the sample queues.
|
||||
boolean seekInsideBuffer = !isPendingReset() && seekInsideBufferUs(positionUs);
|
||||
// If we failed to seek within the sample queues, we need to restart.
|
||||
if (!seekInsideBuffer) {
|
||||
pendingResetPositionUs = positionUs;
|
||||
loadingFinished = false;
|
||||
if (loader.isLoading()) {
|
||||
loader.cancelLoading();
|
||||
} else {
|
||||
for (SampleQueue sampleQueue : sampleQueues) {
|
||||
sampleQueue.reset();
|
||||
}
|
||||
// If we're not pending a reset, see if we can seek within the buffer.
|
||||
if (!isPendingReset() && seekInsideBufferUs(positionUs)) {
|
||||
return positionUs;
|
||||
}
|
||||
// We were unable to seek within the buffer, so need to reset.
|
||||
pendingResetPositionUs = positionUs;
|
||||
loadingFinished = false;
|
||||
if (loader.isLoading()) {
|
||||
loader.cancelLoading();
|
||||
} else {
|
||||
for (SampleQueue sampleQueue : sampleQueues) {
|
||||
sampleQueue.reset();
|
||||
}
|
||||
}
|
||||
return positionUs;
|
||||
|
@ -33,6 +33,7 @@ import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
@ -53,9 +54,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||
private final Handler continueLoadingHandler;
|
||||
|
||||
private Callback callback;
|
||||
private long preparePositionUs;
|
||||
private int pendingPrepareCount;
|
||||
private boolean seenFirstTrackSelection;
|
||||
private TrackGroupArray trackGroups;
|
||||
private HlsSampleStreamWrapper[] sampleStreamWrappers;
|
||||
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
|
||||
@ -71,15 +70,15 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||
streamWrapperIndices = new IdentityHashMap<>();
|
||||
timestampAdjusterProvider = new TimestampAdjusterProvider();
|
||||
continueLoadingHandler = new Handler();
|
||||
sampleStreamWrappers = new HlsSampleStreamWrapper[0];
|
||||
enabledSampleStreamWrappers = new HlsSampleStreamWrapper[0];
|
||||
}
|
||||
|
||||
public void release() {
|
||||
playlistTracker.removeListener(this);
|
||||
continueLoadingHandler.removeCallbacksAndMessages(null);
|
||||
if (sampleStreamWrappers != null) {
|
||||
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
|
||||
sampleStreamWrapper.release();
|
||||
}
|
||||
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
|
||||
sampleStreamWrapper.release();
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,16 +86,13 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||
public void prepare(Callback callback, long positionUs) {
|
||||
this.callback = callback;
|
||||
playlistTracker.addListener(this);
|
||||
preparePositionUs = positionUs;
|
||||
buildAndPrepareSampleStreamWrappers(positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowPrepareError() throws IOException {
|
||||
if (sampleStreamWrappers != null) {
|
||||
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
|
||||
sampleStreamWrapper.maybeThrowPrepareError();
|
||||
}
|
||||
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
|
||||
sampleStreamWrapper.maybeThrowPrepareError();
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,23 +121,24 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||
}
|
||||
}
|
||||
}
|
||||
// We'll always need to seek if this is a first selection to a position other than the prepare
|
||||
// position.
|
||||
boolean seekRequired = !seenFirstTrackSelection && positionUs != preparePositionUs;
|
||||
|
||||
boolean forceReset = false;
|
||||
streamWrapperIndices.clear();
|
||||
// Select tracks for each child, copying the resulting streams back into a new streams array.
|
||||
SampleStream[] newStreams = new SampleStream[selections.length];
|
||||
SampleStream[] childStreams = new SampleStream[selections.length];
|
||||
TrackSelection[] childSelections = new TrackSelection[selections.length];
|
||||
ArrayList<HlsSampleStreamWrapper> enabledSampleStreamWrapperList = new ArrayList<>(
|
||||
sampleStreamWrappers.length);
|
||||
int newEnabledSampleStreamWrapperCount = 0;
|
||||
HlsSampleStreamWrapper[] newEnabledSampleStreamWrappers =
|
||||
new HlsSampleStreamWrapper[sampleStreamWrappers.length];
|
||||
for (int i = 0; i < sampleStreamWrappers.length; i++) {
|
||||
for (int j = 0; j < selections.length; j++) {
|
||||
childStreams[j] = streamChildIndices[j] == i ? streams[j] : null;
|
||||
childSelections[j] = selectionChildIndices[j] == i ? selections[j] : null;
|
||||
}
|
||||
seekRequired |= sampleStreamWrappers[i].selectTracks(childSelections, mayRetainStreamFlags,
|
||||
childStreams, streamResetFlags, positionUs, seenFirstTrackSelection, seekRequired);
|
||||
HlsSampleStreamWrapper sampleStreamWrapper = sampleStreamWrappers[i];
|
||||
boolean wasReset = sampleStreamWrapper.selectTracks(childSelections, mayRetainStreamFlags,
|
||||
childStreams, streamResetFlags, positionUs, forceReset);
|
||||
boolean wrapperEnabled = false;
|
||||
for (int j = 0; j < selections.length; j++) {
|
||||
if (selectionChildIndices[j] == i) {
|
||||
@ -156,37 +153,29 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||
}
|
||||
}
|
||||
if (wrapperEnabled) {
|
||||
enabledSampleStreamWrapperList.add(sampleStreamWrappers[i]);
|
||||
newEnabledSampleStreamWrappers[newEnabledSampleStreamWrapperCount] = sampleStreamWrapper;
|
||||
if (newEnabledSampleStreamWrapperCount++ == 0) {
|
||||
// The first enabled wrapper is responsible for initializing timestamp adjusters. This
|
||||
// way, if enabled, variants are responsible. Else audio renditions. Else text renditions.
|
||||
sampleStreamWrapper.setIsTimestampMaster(true);
|
||||
if (wasReset || enabledSampleStreamWrappers.length == 0
|
||||
|| sampleStreamWrapper != enabledSampleStreamWrappers[0]) {
|
||||
// The wrapper responsible for initializing the timestamp adjusters was reset or
|
||||
// changed. We need to reset the timestamp adjuster provider and all other wrappers.
|
||||
timestampAdjusterProvider.reset();
|
||||
forceReset = true;
|
||||
}
|
||||
} else {
|
||||
sampleStreamWrapper.setIsTimestampMaster(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copy the new streams back into the streams array.
|
||||
System.arraycopy(newStreams, 0, streams, 0, newStreams.length);
|
||||
// Update the local state.
|
||||
enabledSampleStreamWrappers = new HlsSampleStreamWrapper[enabledSampleStreamWrapperList.size()];
|
||||
enabledSampleStreamWrapperList.toArray(enabledSampleStreamWrappers);
|
||||
|
||||
// The first enabled sample stream wrapper is responsible for intializing the timestamp
|
||||
// adjuster. This way, if present, variants are responsible. Otherwise, audio renditions are.
|
||||
// If only subtitles are present, then text renditions are used for timestamp adjustment
|
||||
// initialization.
|
||||
if (enabledSampleStreamWrappers.length > 0) {
|
||||
enabledSampleStreamWrappers[0].setIsTimestampMaster(true);
|
||||
for (int i = 1; i < enabledSampleStreamWrappers.length; i++) {
|
||||
enabledSampleStreamWrappers[i].setIsTimestampMaster(false);
|
||||
}
|
||||
}
|
||||
|
||||
enabledSampleStreamWrappers = Arrays.copyOf(newEnabledSampleStreamWrappers,
|
||||
newEnabledSampleStreamWrapperCount);
|
||||
sequenceableLoader = new CompositeSequenceableLoader(enabledSampleStreamWrappers);
|
||||
if (seekRequired) {
|
||||
seekToUs(positionUs);
|
||||
// We'll need to reset renderers consuming from all streams due to the seek.
|
||||
for (int i = 0; i < selections.length; i++) {
|
||||
if (streams[i] != null) {
|
||||
streamResetFlags[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
seenFirstTrackSelection = true;
|
||||
return positionUs;
|
||||
}
|
||||
|
||||
@ -226,9 +215,16 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||
|
||||
@Override
|
||||
public long seekToUs(long positionUs) {
|
||||
timestampAdjusterProvider.reset();
|
||||
for (HlsSampleStreamWrapper sampleStreamWrapper : enabledSampleStreamWrappers) {
|
||||
sampleStreamWrapper.seekTo(positionUs);
|
||||
if (enabledSampleStreamWrappers.length > 0) {
|
||||
// We need to reset all wrappers if the one responsible for initializing timestamp adjusters
|
||||
// is reset. Else each wrapper can decide whether to reset independently.
|
||||
boolean forceReset = enabledSampleStreamWrappers[0].seekToUs(positionUs, false);
|
||||
for (int i = 1; i < enabledSampleStreamWrappers.length; i++) {
|
||||
enabledSampleStreamWrappers[i].seekToUs(positionUs, forceReset);
|
||||
}
|
||||
if (forceReset) {
|
||||
timestampAdjusterProvider.reset();
|
||||
}
|
||||
}
|
||||
return positionUs;
|
||||
}
|
||||
@ -348,6 +344,9 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||
sampleStreamWrapper.prepareSingleTrack(url.format);
|
||||
sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper;
|
||||
}
|
||||
|
||||
// All wrappers are enabled during preparation.
|
||||
enabledSampleStreamWrappers = sampleStreamWrappers;
|
||||
}
|
||||
|
||||
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] variants,
|
||||
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.source.hls;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import android.util.SparseArray;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.FormatHolder;
|
||||
@ -40,6 +39,7 @@ import com.google.android.exoplayer2.upstream.Loader;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
@ -81,11 +81,12 @@ import java.util.LinkedList;
|
||||
private final Loader loader;
|
||||
private final EventDispatcher eventDispatcher;
|
||||
private final HlsChunkSource.HlsChunkHolder nextChunkHolder;
|
||||
private final SparseArray<SampleQueue> sampleQueues;
|
||||
private final LinkedList<HlsMediaChunk> mediaChunks;
|
||||
private final Runnable maybeFinishPrepareRunnable;
|
||||
private final Handler handler;
|
||||
|
||||
private SampleQueue[] sampleQueues;
|
||||
private int[] sampleQueueTrackIds;
|
||||
private boolean sampleQueuesBuilt;
|
||||
private boolean prepared;
|
||||
private int enabledTrackCount;
|
||||
@ -97,12 +98,14 @@ import java.util.LinkedList;
|
||||
// Indexed by track (as exposed by this source).
|
||||
private TrackGroupArray trackGroups;
|
||||
private int primaryTrackGroupIndex;
|
||||
// Indexed by group.
|
||||
private boolean[] groupEnabledStates;
|
||||
private boolean haveAudioVideoTrackGroups;
|
||||
// Indexed by track group.
|
||||
private boolean[] trackGroupEnabledStates;
|
||||
private boolean[] trackGroupIsAudioVideoFlags;
|
||||
|
||||
private long lastSeekPositionUs;
|
||||
private long pendingResetPositionUs;
|
||||
|
||||
private boolean seenFirstTrackSelection;
|
||||
private boolean loadingFinished;
|
||||
|
||||
/**
|
||||
@ -128,7 +131,8 @@ import java.util.LinkedList;
|
||||
this.eventDispatcher = eventDispatcher;
|
||||
loader = new Loader("Loader:HlsSampleStreamWrapper");
|
||||
nextChunkHolder = new HlsChunkSource.HlsChunkHolder();
|
||||
sampleQueues = new SparseArray<>();
|
||||
sampleQueueTrackIds = new int[0];
|
||||
sampleQueues = new SampleQueue[0];
|
||||
mediaChunks = new LinkedList<>();
|
||||
maybeFinishPrepareRunnable = new Runnable() {
|
||||
@Override
|
||||
@ -177,16 +181,13 @@ import java.util.LinkedList;
|
||||
* @param streamResetFlags Will be updated to indicate new sample streams, and sample streams that
|
||||
* have been retained but with the requirement that the consuming renderer be reset.
|
||||
* @param positionUs The current playback position in microseconds.
|
||||
* @param seenFirstTrackSelection Whether we've already had the first track selection, meaning
|
||||
* this is a subsequent selection.
|
||||
* @param seekRequired Whether the parent {@link HlsMediaPeriod} is already guaranteed to perform
|
||||
* a seek as part of the track selection
|
||||
* @param forceReset If true then a reset is forced (i.e. a seek will be performed with in-buffer
|
||||
* seeking disabled).
|
||||
* @return Whether this wrapper requires the parent {@link HlsMediaPeriod} to perform a seek as
|
||||
* part of the track selection.
|
||||
*/
|
||||
public boolean selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
|
||||
SampleStream[] streams, boolean[] streamResetFlags, long positionUs,
|
||||
boolean seenFirstTrackSelection, boolean seekRequired) {
|
||||
SampleStream[] streams, boolean[] streamResetFlags, long positionUs, boolean forceReset) {
|
||||
Assertions.checkState(prepared);
|
||||
int oldEnabledTrackCount = enabledTrackCount;
|
||||
// Deselect old tracks.
|
||||
@ -197,24 +198,27 @@ import java.util.LinkedList;
|
||||
streams[i] = null;
|
||||
}
|
||||
}
|
||||
// We'll always need to seek if we're making a selection having previously disabled all tracks.
|
||||
seekRequired |= seenFirstTrackSelection && oldEnabledTrackCount == 0;
|
||||
// We'll always need to seek if we're being forced to reset, or if this is a first selection to
|
||||
// a position other than the one we started preparing with, or if we're making a selection
|
||||
// having previously disabled all tracks.
|
||||
boolean seekRequired = forceReset
|
||||
|| (seenFirstTrackSelection ? oldEnabledTrackCount == 0 : positionUs != lastSeekPositionUs);
|
||||
// Select new tracks.
|
||||
TrackSelection primaryTrackSelection = null;
|
||||
for (int i = 0; i < selections.length; i++) {
|
||||
if (streams[i] == null && selections[i] != null) {
|
||||
TrackSelection selection = selections[i];
|
||||
int group = trackGroups.indexOf(selection.getTrackGroup());
|
||||
setTrackGroupEnabledState(group, true);
|
||||
if (group == primaryTrackGroupIndex) {
|
||||
int trackGroupIndex = trackGroups.indexOf(selection.getTrackGroup());
|
||||
setTrackGroupEnabledState(trackGroupIndex, true);
|
||||
if (trackGroupIndex == primaryTrackGroupIndex) {
|
||||
primaryTrackSelection = selection;
|
||||
chunkSource.selectTracks(selection);
|
||||
}
|
||||
streams[i] = new HlsSampleStream(this, group);
|
||||
streams[i] = new HlsSampleStream(this, trackGroupIndex);
|
||||
streamResetFlags[i] = true;
|
||||
// If there's still a chance of avoiding a seek, try and seek within the sample queue.
|
||||
if (!seekRequired) {
|
||||
SampleQueue sampleQueue = sampleQueues.valueAt(group);
|
||||
SampleQueue sampleQueue = sampleQueues[trackGroupIndex];
|
||||
sampleQueue.rewind();
|
||||
// A seek can be avoided if we're able to advance to the current playback position in the
|
||||
// sample queue, or if we haven't read anything from the queue since the previous seek
|
||||
@ -230,57 +234,77 @@ import java.util.LinkedList;
|
||||
chunkSource.reset();
|
||||
downstreamTrackFormat = null;
|
||||
mediaChunks.clear();
|
||||
int sampleQueueCount = sampleQueues.size();
|
||||
if (loader.isLoading()) {
|
||||
// Discard as much as we can synchronously.
|
||||
for (int i = 0; i < sampleQueueCount; i++) {
|
||||
sampleQueues.valueAt(i).discardToEnd();
|
||||
for (SampleQueue sampleQueue : sampleQueues) {
|
||||
sampleQueue.discardToEnd();
|
||||
}
|
||||
loader.cancelLoading();
|
||||
} else {
|
||||
for (int i = 0; i < sampleQueueCount; i++) {
|
||||
sampleQueues.valueAt(i).reset();
|
||||
for (SampleQueue sampleQueue : sampleQueues) {
|
||||
sampleQueue.reset();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// If this is the first selection and the chunk loaded during preparation does not match the
|
||||
// selection, we call seekTo to discard it. Note that if seekRequired is true then the wrapping
|
||||
// HlsMediaPeriod will call seekTo regardless, and so we do not need to perform the selection
|
||||
// check here.
|
||||
if (!seekRequired && !seenFirstTrackSelection && primaryTrackSelection != null
|
||||
&& !mediaChunks.isEmpty()) {
|
||||
primaryTrackSelection.updateSelectedTrack(0);
|
||||
int chunkIndex = chunkSource.getTrackGroup().indexOf(mediaChunks.getLast().trackFormat);
|
||||
if (primaryTrackSelection.getSelectedIndexInTrackGroup() != chunkIndex) {
|
||||
// The loaded preparation chunk does not match the selection, so discard it.
|
||||
seekTo(positionUs);
|
||||
} else {
|
||||
if (!forceReset && !seenFirstTrackSelection && primaryTrackSelection != null
|
||||
&& !mediaChunks.isEmpty()) {
|
||||
primaryTrackSelection.updateSelectedTrack(0);
|
||||
int chunkIndex = chunkSource.getTrackGroup().indexOf(mediaChunks.getLast().trackFormat);
|
||||
if (primaryTrackSelection.getSelectedIndexInTrackGroup() != chunkIndex) {
|
||||
// This is the first selection and the chunk loaded during preparation does not match the
|
||||
// selection. We need to reset to discard it.
|
||||
forceReset = true;
|
||||
seekRequired = true;
|
||||
}
|
||||
}
|
||||
if (seekRequired) {
|
||||
seekToUs(positionUs, forceReset);
|
||||
// We'll need to reset renderers consuming from all streams due to the seek.
|
||||
for (int i = 0; i < streams.length; i++) {
|
||||
if (streams[i] != null) {
|
||||
streamResetFlags[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seenFirstTrackSelection = true;
|
||||
return seekRequired;
|
||||
}
|
||||
|
||||
public void discardBuffer(long positionUs) {
|
||||
int sampleQueueCount = sampleQueues.size();
|
||||
int sampleQueueCount = sampleQueues.length;
|
||||
for (int i = 0; i < sampleQueueCount; i++) {
|
||||
sampleQueues.valueAt(i).discardTo(positionUs, false, groupEnabledStates[i]);
|
||||
sampleQueues[i].discardTo(positionUs, false, trackGroupEnabledStates[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void seekTo(long positionUs) {
|
||||
/**
|
||||
* Attempts to seek to the specified position in microseconds.
|
||||
*
|
||||
* @param positionUs The seek position in microseconds.
|
||||
* @param forceReset If true then a reset is forced (i.e. in-buffer seeking is disabled).
|
||||
* @return Whether the wrapper was reset, meaning the wrapped sample queues were reset. If false,
|
||||
* an in-buffer seek was performed.
|
||||
*/
|
||||
public boolean seekToUs(long positionUs, boolean forceReset) {
|
||||
lastSeekPositionUs = positionUs;
|
||||
// If we're not forced to reset nor have a pending reset, see if we can seek within the buffer.
|
||||
if (!forceReset && !isPendingReset() && seekInsideBufferUs(positionUs)) {
|
||||
return false;
|
||||
}
|
||||
// We were unable to seek within the buffer, so need to reset.
|
||||
pendingResetPositionUs = positionUs;
|
||||
loadingFinished = false;
|
||||
mediaChunks.clear();
|
||||
if (loader.isLoading()) {
|
||||
loader.cancelLoading();
|
||||
} else {
|
||||
int sampleQueueCount = sampleQueues.size();
|
||||
for (int i = 0; i < sampleQueueCount; i++) {
|
||||
sampleQueues.valueAt(i).reset();
|
||||
for (SampleQueue sampleQueue : sampleQueues) {
|
||||
sampleQueue.reset();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public long getBufferedPositionUs() {
|
||||
@ -296,10 +320,9 @@ import java.util.LinkedList;
|
||||
if (lastCompletedMediaChunk != null) {
|
||||
bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs);
|
||||
}
|
||||
int sampleQueueCount = sampleQueues.size();
|
||||
for (int i = 0; i < sampleQueueCount; i++) {
|
||||
for (SampleQueue sampleQueue : sampleQueues) {
|
||||
bufferedPositionUs = Math.max(bufferedPositionUs,
|
||||
sampleQueues.valueAt(i).getLargestQueuedTimestampUs());
|
||||
sampleQueue.getLargestQueuedTimestampUs());
|
||||
}
|
||||
return bufferedPositionUs;
|
||||
}
|
||||
@ -310,9 +333,8 @@ import java.util.LinkedList;
|
||||
if (prepared && !releasedSynchronously) {
|
||||
// Discard as much as we can synchronously. We only do this if we're prepared, since otherwise
|
||||
// sampleQueues may still be being modified by the loading thread.
|
||||
int sampleQueueCount = sampleQueues.size();
|
||||
for (int i = 0; i < sampleQueueCount; i++) {
|
||||
sampleQueues.valueAt(i).discardToEnd();
|
||||
for (SampleQueue sampleQueue : sampleQueues) {
|
||||
sampleQueue.discardToEnd();
|
||||
}
|
||||
}
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
@ -321,9 +343,8 @@ import java.util.LinkedList;
|
||||
|
||||
@Override
|
||||
public void onLoaderReleased() {
|
||||
int sampleQueueCount = sampleQueues.size();
|
||||
for (int i = 0; i < sampleQueueCount; i++) {
|
||||
sampleQueues.valueAt(i).reset();
|
||||
for (SampleQueue sampleQueue : sampleQueues) {
|
||||
sampleQueue.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@ -337,8 +358,8 @@ import java.util.LinkedList;
|
||||
|
||||
// SampleStream implementation.
|
||||
|
||||
/* package */ boolean isReady(int group) {
|
||||
return loadingFinished || (!isPendingReset() && sampleQueues.valueAt(group).hasNextSample());
|
||||
/* package */ boolean isReady(int trackGroupIndex) {
|
||||
return loadingFinished || (!isPendingReset() && sampleQueues[trackGroupIndex].hasNextSample());
|
||||
}
|
||||
|
||||
/* package */ void maybeThrowError() throws IOException {
|
||||
@ -346,8 +367,8 @@ import java.util.LinkedList;
|
||||
chunkSource.maybeThrowError();
|
||||
}
|
||||
|
||||
/* package */ int readData(int group, FormatHolder formatHolder, DecoderInputBuffer buffer,
|
||||
boolean requireFormat) {
|
||||
/* package */ int readData(int trackGroupIndex, FormatHolder formatHolder,
|
||||
DecoderInputBuffer buffer, boolean requireFormat) {
|
||||
if (isPendingReset()) {
|
||||
return C.RESULT_NOTHING_READ;
|
||||
}
|
||||
@ -366,12 +387,12 @@ import java.util.LinkedList;
|
||||
downstreamTrackFormat = trackFormat;
|
||||
}
|
||||
|
||||
return sampleQueues.valueAt(group).read(formatHolder, buffer, requireFormat, loadingFinished,
|
||||
return sampleQueues[trackGroupIndex].read(formatHolder, buffer, requireFormat, loadingFinished,
|
||||
lastSeekPositionUs);
|
||||
}
|
||||
|
||||
/* package */ void skipData(int group, long positionUs) {
|
||||
SampleQueue sampleQueue = sampleQueues.valueAt(group);
|
||||
/* package */ void skipData(int trackGroupIndex, long positionUs) {
|
||||
SampleQueue sampleQueue = sampleQueues[trackGroupIndex];
|
||||
if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) {
|
||||
sampleQueue.advanceToEnd();
|
||||
} else {
|
||||
@ -381,8 +402,8 @@ import java.util.LinkedList;
|
||||
|
||||
private boolean finishedReadingChunk(HlsMediaChunk chunk) {
|
||||
int chunkUid = chunk.uid;
|
||||
for (int i = 0; i < sampleQueues.size(); i++) {
|
||||
if (groupEnabledStates[i] && sampleQueues.valueAt(i).peekSourceId() == chunkUid) {
|
||||
for (int i = 0; i < sampleQueues.length; i++) {
|
||||
if (trackGroupEnabledStates[i] && sampleQueues[i].peekSourceId() == chunkUid) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -462,9 +483,8 @@ import java.util.LinkedList;
|
||||
loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs,
|
||||
loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded());
|
||||
if (!released) {
|
||||
int sampleQueueCount = sampleQueues.size();
|
||||
for (int i = 0; i < sampleQueueCount; i++) {
|
||||
sampleQueues.valueAt(i).reset();
|
||||
for (SampleQueue sampleQueue : sampleQueues) {
|
||||
sampleQueue.reset();
|
||||
}
|
||||
if (enabledTrackCount > 0) {
|
||||
callback.onContinueLoadingRequested(this);
|
||||
@ -516,12 +536,12 @@ import java.util.LinkedList;
|
||||
*/
|
||||
public void init(int chunkUid, boolean shouldSpliceIn) {
|
||||
upstreamChunkUid = chunkUid;
|
||||
for (int i = 0; i < sampleQueues.size(); i++) {
|
||||
sampleQueues.valueAt(i).sourceId(chunkUid);
|
||||
for (SampleQueue sampleQueue : sampleQueues) {
|
||||
sampleQueue.sourceId(chunkUid);
|
||||
}
|
||||
if (shouldSpliceIn) {
|
||||
for (int i = 0; i < sampleQueues.size(); i++) {
|
||||
sampleQueues.valueAt(i).splice();
|
||||
for (SampleQueue sampleQueue : sampleQueues) {
|
||||
sampleQueue.splice();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -530,14 +550,19 @@ import java.util.LinkedList;
|
||||
|
||||
@Override
|
||||
public SampleQueue track(int id, int type) {
|
||||
if (sampleQueues.indexOfKey(id) >= 0) {
|
||||
return sampleQueues.get(id);
|
||||
int trackCount = sampleQueues.length;
|
||||
for (int i = 0; i < trackCount; i++) {
|
||||
if (sampleQueueTrackIds[i] == id) {
|
||||
return sampleQueues[i];
|
||||
}
|
||||
}
|
||||
SampleQueue sampleQueue = new SampleQueue(allocator);
|
||||
sampleQueue.setUpstreamFormatChangeListener(this);
|
||||
sampleQueue.sourceId(upstreamChunkUid);
|
||||
sampleQueues.put(id, sampleQueue);
|
||||
return sampleQueue;
|
||||
SampleQueue trackOutput = new SampleQueue(allocator);
|
||||
trackOutput.setUpstreamFormatChangeListener(this);
|
||||
sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1);
|
||||
sampleQueueTrackIds[trackCount] = id;
|
||||
sampleQueues = Arrays.copyOf(sampleQueues, trackCount + 1);
|
||||
sampleQueues[trackCount] = trackOutput;
|
||||
return trackOutput;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -564,9 +589,8 @@ import java.util.LinkedList;
|
||||
if (released || prepared || !sampleQueuesBuilt) {
|
||||
return;
|
||||
}
|
||||
int sampleQueueCount = sampleQueues.size();
|
||||
for (int i = 0; i < sampleQueueCount; i++) {
|
||||
if (sampleQueues.valueAt(i).getUpstreamFormat() == null) {
|
||||
for (SampleQueue sampleQueue : sampleQueues) {
|
||||
if (sampleQueue.getUpstreamFormat() == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -609,9 +633,9 @@ import java.util.LinkedList;
|
||||
// of the single track of this type.
|
||||
int primaryExtractorTrackType = PRIMARY_TYPE_NONE;
|
||||
int primaryExtractorTrackIndex = C.INDEX_UNSET;
|
||||
int extractorTrackCount = sampleQueues.size();
|
||||
int extractorTrackCount = sampleQueues.length;
|
||||
for (int i = 0; i < extractorTrackCount; i++) {
|
||||
String sampleMimeType = sampleQueues.valueAt(i).getUpstreamFormat().sampleMimeType;
|
||||
String sampleMimeType = sampleQueues[i].getUpstreamFormat().sampleMimeType;
|
||||
int trackType;
|
||||
if (MimeTypes.isVideo(sampleMimeType)) {
|
||||
trackType = PRIMARY_TYPE_VIDEO;
|
||||
@ -638,12 +662,17 @@ import java.util.LinkedList;
|
||||
|
||||
// Instantiate the necessary internal data-structures.
|
||||
primaryTrackGroupIndex = C.INDEX_UNSET;
|
||||
groupEnabledStates = new boolean[extractorTrackCount];
|
||||
trackGroupEnabledStates = new boolean[extractorTrackCount];
|
||||
trackGroupIsAudioVideoFlags = new boolean[extractorTrackCount];
|
||||
|
||||
// Construct the set of exposed track groups.
|
||||
TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount];
|
||||
for (int i = 0; i < extractorTrackCount; i++) {
|
||||
Format sampleFormat = sampleQueues.valueAt(i).getUpstreamFormat();
|
||||
Format sampleFormat = sampleQueues[i].getUpstreamFormat();
|
||||
String mimeType = sampleFormat.sampleMimeType;
|
||||
boolean isAudioVideo = MimeTypes.isVideo(mimeType) || MimeTypes.isAudio(mimeType);
|
||||
trackGroupIsAudioVideoFlags[i] = isAudioVideo;
|
||||
haveAudioVideoTrackGroups |= isAudioVideo;
|
||||
if (i == primaryExtractorTrackIndex) {
|
||||
Format[] formats = new Format[chunkSourceTrackCount];
|
||||
for (int j = 0; j < chunkSourceTrackCount; j++) {
|
||||
@ -663,12 +692,12 @@ import java.util.LinkedList;
|
||||
/**
|
||||
* Enables or disables a specified track group.
|
||||
*
|
||||
* @param group The index of the track group.
|
||||
* @param trackGroupIndex The index of the track group.
|
||||
* @param enabledState True if the group is being enabled, or false if it's being disabled.
|
||||
*/
|
||||
private void setTrackGroupEnabledState(int group, boolean enabledState) {
|
||||
Assertions.checkState(groupEnabledStates[group] != enabledState);
|
||||
groupEnabledStates[group] = enabledState;
|
||||
private void setTrackGroupEnabledState(int trackGroupIndex, boolean enabledState) {
|
||||
Assertions.checkState(trackGroupEnabledStates[trackGroupIndex] != enabledState);
|
||||
trackGroupEnabledStates[trackGroupIndex] = enabledState;
|
||||
enabledTrackCount = enabledTrackCount + (enabledState ? 1 : -1);
|
||||
}
|
||||
|
||||
@ -704,6 +733,30 @@ import java.util.LinkedList;
|
||||
return pendingResetPositionUs != C.TIME_UNSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to seek to the specified position within the sample queues.
|
||||
*
|
||||
* @param positionUs The seek position in microseconds.
|
||||
* @return Whether the in-buffer seek was successful.
|
||||
*/
|
||||
private boolean seekInsideBufferUs(long positionUs) {
|
||||
int trackCount = sampleQueues.length;
|
||||
for (int i = 0; i < trackCount; i++) {
|
||||
SampleQueue sampleQueue = sampleQueues[i];
|
||||
sampleQueue.rewind();
|
||||
boolean seekInsideQueue = sampleQueue.advanceTo(positionUs, true, false);
|
||||
// If we have AV tracks then an in-queue seek is successful if the seek into every AV queue
|
||||
// is successful. We ignore whether seeks within non-AV queues are successful in this case, as
|
||||
// they may be sparse or poorly interleaved. If we only have non-AV tracks then a seek is
|
||||
// successful only if the seek into every queue succeeds.
|
||||
if (!seekInsideQueue && (trackGroupIsAudioVideoFlags[i] || !haveAudioVideoTrackGroups)) {
|
||||
return false;
|
||||
}
|
||||
sampleQueue.discardToRead();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String getAudioCodecs(String codecs) {
|
||||
return getCodecsOfType(codecs, C.TRACK_TYPE_AUDIO);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user