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