Fix SampleSource limbo state by introducing explicit "selecting tracks" state.
1. SampleSource now has an explicit track selection state. This state is entered after the source is prepared, and also by calling startTrackSelection. 2. endTrackSelection commits selection changes, and is responsible for doing the right thing w.r.t starting/stopping/restarting load operations. 3. All sources now start or restart a load in the case of a new track selection. This fixes a problem where a source could be advanced by repeatedly disabling and re-enabling whilst paused. Some sources didn't restart a load in this case, since the position was unchanged, however the downstream renderer would then consume media up to the first keyframe in order to render something. Hence each disable/re-enable would advance by a keyframe. 4. This change will enable a subsequent change where we'll discard media for non-selected tracks earlier than we do currently (i.e. we'll hook the extractor to a dummy track output, so the samples will never be written to a rolling buffer). This will enable a further subsequent change where buffer contributions are per-renderer rather than per-source. Issue: #1041 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=118024436
This commit is contained in:
parent
24b2c09287
commit
b3ce415e88
@ -17,6 +17,7 @@ package com.google.android.exoplayer;
|
||||
|
||||
import com.google.android.exoplayer.ExoPlayer.ExoPlayerComponent;
|
||||
import com.google.android.exoplayer.TrackSelector.InvalidationListener;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.PriorityHandlerThread;
|
||||
import com.google.android.exoplayer.util.TraceUtil;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
@ -31,7 +32,6 @@ import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
@ -68,12 +68,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
private final StandaloneMediaClock standaloneMediaClock;
|
||||
private final long minBufferUs;
|
||||
private final long minRebufferUs;
|
||||
private final TrackSelection[] trackSelections;
|
||||
private final Handler handler;
|
||||
private final HandlerThread internalPlaybackThread;
|
||||
private final Handler eventHandler;
|
||||
private final AtomicInteger pendingSeekCount;
|
||||
|
||||
private TrackSelectionArray trackSelections;
|
||||
private TrackRenderer rendererMediaClockSource;
|
||||
private MediaClock rendererMediaClock;
|
||||
private SampleSource source;
|
||||
@ -104,7 +104,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
this.bufferedPositionUs = C.UNKNOWN_TIME_US;
|
||||
standaloneMediaClock = new StandaloneMediaClock();
|
||||
pendingSeekCount = new AtomicInteger();
|
||||
trackSelections = new TrackSelection[renderers.length];
|
||||
enabledRenderers = new TrackRenderer[0];
|
||||
|
||||
trackSelector.init(this);
|
||||
@ -281,7 +280,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
durationUs = source.getDurationUs();
|
||||
bufferedPositionUs = source.getBufferedPositionUs();
|
||||
selectTracksInternal();
|
||||
selectTracksInternal(true);
|
||||
|
||||
boolean allRenderersEnded = true;
|
||||
boolean allRenderersReadyOrEnded = true;
|
||||
@ -478,21 +477,17 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
handler.removeMessages(MSG_DO_SOME_WORK);
|
||||
handler.removeMessages(MSG_INCREMENTAL_PREPARE);
|
||||
rebuffering = false;
|
||||
trackSelections = null;
|
||||
standaloneMediaClock.stop();
|
||||
if (renderers == null) {
|
||||
return;
|
||||
}
|
||||
for (TrackRenderer renderer : renderers) {
|
||||
resetRendererInternal(renderer);
|
||||
}
|
||||
rendererMediaClock = null;
|
||||
rendererMediaClockSource = null;
|
||||
enabledRenderers = new TrackRenderer[0];
|
||||
Arrays.fill(trackSelections, null);
|
||||
source = null;
|
||||
}
|
||||
|
||||
private void resetRendererInternal(TrackRenderer renderer) {
|
||||
for (TrackRenderer renderer : renderers) {
|
||||
try {
|
||||
ensureDisabled(renderer);
|
||||
ensureStopped(renderer);
|
||||
if (renderer.getState() == TrackRenderer.STATE_ENABLED) {
|
||||
renderer.disable();
|
||||
}
|
||||
} catch (ExoPlaybackException e) {
|
||||
// There's nothing we can do.
|
||||
Log.e(TAG, "Stop failed.", e);
|
||||
@ -501,6 +496,11 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
Log.e(TAG, "Stop failed.", e);
|
||||
}
|
||||
}
|
||||
if (source != null) {
|
||||
source.release();
|
||||
source = null;
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void sendMessageInternal(int what, Object obj)
|
||||
throws ExoPlaybackException {
|
||||
@ -520,10 +520,24 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
}
|
||||
}
|
||||
|
||||
private void selectTracksInternal() throws ExoPlaybackException {
|
||||
private void selectTracksInternal(boolean initialSelection) throws ExoPlaybackException {
|
||||
TrackGroupArray groups = source.getTrackGroups();
|
||||
|
||||
Pair<TrackSelectionArray, Object> result = trackSelector.selectTracks(renderers, groups);
|
||||
TrackSelectionArray newTrackSelections = result.first;
|
||||
TrackSelectionArray newSelections = result.first;
|
||||
|
||||
if (newSelections.equals(trackSelections)) {
|
||||
trackSelector.onSelectionActivated(result.second);
|
||||
// No changes to the track selections.
|
||||
return;
|
||||
}
|
||||
|
||||
if (initialSelection) {
|
||||
Assertions.checkState(source.getState() == SampleSource.STATE_SELECTING_TRACKS);
|
||||
} else {
|
||||
Assertions.checkState(source.getState() == SampleSource.STATE_READING);
|
||||
source.startTrackSelection();
|
||||
}
|
||||
|
||||
// We disable all renderers whose track selections have changed, then enable renderers with new
|
||||
// track selections during a second pass. Doing all disables before any enables is necessary
|
||||
@ -536,37 +550,45 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
int enabledRendererCount = 0;
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
TrackRenderer renderer = renderers[i];
|
||||
TrackSelection previousTrackSelection = trackSelections[i];
|
||||
trackSelections[i] = newTrackSelections.get(i);
|
||||
if (trackSelections[i] != null) {
|
||||
TrackSelection oldSelection = trackSelections == null ? null : trackSelections.get(i);
|
||||
TrackSelection newSelection = newSelections.get(i);
|
||||
if (newSelection != null) {
|
||||
enabledRendererCount++;
|
||||
}
|
||||
rendererWasEnabledFlags[i] = renderer.getState() != TrackRenderer.STATE_DISABLED;
|
||||
if (!Util.areEqual(previousTrackSelection, trackSelections[i])) {
|
||||
if (!Util.areEqual(oldSelection, newSelection)) {
|
||||
// The track selection has changed for this renderer.
|
||||
if (rendererWasEnabledFlags[i]) {
|
||||
// We need to disable the renderer so that we can enable it with its new selection.
|
||||
if (trackSelections[i] == null && renderer == rendererMediaClockSource) {
|
||||
// We've been using rendererMediaClockSource to advance the current position, but it's
|
||||
// being disabled and won't be re-enabled. Sync standaloneMediaClock so that it can take
|
||||
if (renderer == rendererMediaClockSource) {
|
||||
// The renderer is providing the media clock.
|
||||
if (newSelection == null) {
|
||||
// The renderer won't be re-enabled. Sync standaloneMediaClock so that it can take
|
||||
// over timing responsibilities.
|
||||
standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs());
|
||||
}
|
||||
ensureDisabled(renderer);
|
||||
rendererMediaClock = null;
|
||||
rendererMediaClockSource = null;
|
||||
}
|
||||
ensureStopped(renderer);
|
||||
if (renderer.getState() == TrackRenderer.STATE_ENABLED) {
|
||||
TrackStream trackStream = renderer.disable();
|
||||
source.unselectTrack(trackStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The new selections are being activated.
|
||||
trackSelector.onSelectionActivated(result.second);
|
||||
trackSelections = newSelections;
|
||||
enabledRenderers = new TrackRenderer[enabledRendererCount];
|
||||
enabledRendererCount = 0;
|
||||
|
||||
// Enable renderers with their new track selections.
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
TrackRenderer renderer = renderers[i];
|
||||
TrackSelection trackSelection = trackSelections[i];
|
||||
if (trackSelection != null) {
|
||||
TrackSelection newSelection = trackSelections.get(i);
|
||||
if (newSelection != null) {
|
||||
enabledRenderers[enabledRendererCount++] = renderer;
|
||||
if (renderer.getState() == TrackRenderer.STATE_DISABLED) {
|
||||
// The renderer needs enabling with its new track selection.
|
||||
@ -574,11 +596,11 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
// Consider as joining only if the renderer was previously disabled.
|
||||
boolean joining = !rendererWasEnabledFlags[i] && playing;
|
||||
// Enable the source and obtain the stream for the renderer to consume.
|
||||
TrackStream trackStream = source.enable(trackSelection, positionUs);
|
||||
TrackStream trackStream = source.selectTrack(newSelection, positionUs);
|
||||
// Build an array of formats contained by the new selection.
|
||||
Format[] formats = new Format[trackSelection.length];
|
||||
Format[] formats = new Format[newSelection.length];
|
||||
for (int j = 0; j < formats.length; j++) {
|
||||
formats[j] = groups.get(trackSelections[i].group).getFormat(trackSelection.getTrack(j));
|
||||
formats[j] = groups.get(newSelection.group).getFormat(newSelection.getTrack(j));
|
||||
}
|
||||
// Enable the renderer.
|
||||
renderer.enable(formats, trackStream, positionUs, joining);
|
||||
@ -598,6 +620,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
source.endTrackSelection(positionUs);
|
||||
}
|
||||
|
||||
private void reselectTracksInternal() throws ExoPlaybackException {
|
||||
@ -605,22 +629,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
// We don't have tracks yet, so we don't care.
|
||||
return;
|
||||
}
|
||||
selectTracksInternal();
|
||||
selectTracksInternal(false);
|
||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||
}
|
||||
|
||||
private void ensureDisabled(TrackRenderer renderer) throws ExoPlaybackException {
|
||||
ensureStopped(renderer);
|
||||
if (renderer.getState() == TrackRenderer.STATE_ENABLED) {
|
||||
TrackStream trackStream = renderer.disable();
|
||||
source.disable(trackStream);
|
||||
if (renderer == rendererMediaClockSource) {
|
||||
rendererMediaClock = null;
|
||||
rendererMediaClockSource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureStopped(TrackRenderer renderer) throws ExoPlaybackException {
|
||||
if (renderer.getState() == TrackRenderer.STATE_STARTED) {
|
||||
renderer.stop();
|
||||
|
@ -71,7 +71,7 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
private final long fileDescriptorOffset;
|
||||
private final long fileDescriptorLength;
|
||||
|
||||
private boolean prepared;
|
||||
private int state;
|
||||
private long durationUs;
|
||||
private MediaExtractor extractor;
|
||||
private TrackGroupArray tracks;
|
||||
@ -97,6 +97,7 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
fileDescriptor = null;
|
||||
fileDescriptorOffset = 0;
|
||||
fileDescriptorLength = 0;
|
||||
state = STATE_UNPREPARED;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -116,13 +117,17 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
context = null;
|
||||
uri = null;
|
||||
headers = null;
|
||||
state = STATE_UNPREPARED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepare(long positionUs) throws IOException {
|
||||
if (prepared) {
|
||||
return true;
|
||||
}
|
||||
Assertions.checkState(state == STATE_UNPREPARED);
|
||||
extractor = new MediaExtractor();
|
||||
if (context != null) {
|
||||
extractor.setDataSource(context, uri, headers);
|
||||
@ -132,16 +137,16 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
durationUs = C.UNKNOWN_TIME_US;
|
||||
trackStates = new int[extractor.getTrackCount()];
|
||||
pendingResets = new boolean[trackStates.length];
|
||||
TrackGroup[] tracks = new TrackGroup[trackStates.length];
|
||||
TrackGroup[] trackArray = new TrackGroup[trackStates.length];
|
||||
for (int i = 0; i < trackStates.length; i++) {
|
||||
MediaFormat format = extractor.getTrackFormat(i);
|
||||
if (format.containsKey(MediaFormat.KEY_DURATION)) {
|
||||
durationUs = Math.max(durationUs, format.getLong(MediaFormat.KEY_DURATION));
|
||||
}
|
||||
tracks[i] = new TrackGroup(createFormat(i, format));
|
||||
trackArray[i] = new TrackGroup(createFormat(i, format));
|
||||
}
|
||||
this.tracks = new TrackGroupArray(tracks);
|
||||
prepared = true;
|
||||
tracks = new TrackGroupArray(trackArray);
|
||||
state = STATE_SELECTING_TRACKS;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -161,8 +166,14 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackStream enable(TrackSelection selection, long positionUs) {
|
||||
Assertions.checkState(prepared);
|
||||
public void startTrackSelection() {
|
||||
Assertions.checkState(state == STATE_READING);
|
||||
state = STATE_SELECTING_TRACKS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackStream selectTrack(TrackSelection selection, long positionUs) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
Assertions.checkState(selection.length == 1);
|
||||
Assertions.checkState(selection.getTrack(0) == 0);
|
||||
int track = selection.group;
|
||||
@ -170,10 +181,29 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
enabledTrackCount++;
|
||||
trackStates[track] = TRACK_STATE_ENABLED;
|
||||
extractor.selectTrack(track);
|
||||
seekToUsInternal(positionUs, positionUs != 0);
|
||||
return new TrackStreamImpl(track);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unselectTrack(TrackStream stream) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
int track = ((TrackStreamImpl) stream).track;
|
||||
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
|
||||
enabledTrackCount--;
|
||||
trackStates[track] = TRACK_STATE_DISABLED;
|
||||
extractor.unselectTrack(track);
|
||||
pendingResets[track] = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endTrackSelection(long positionUs) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
state = STATE_READING;
|
||||
if (enabledTrackCount > 0) {
|
||||
seekToUsInternal(positionUs, positionUs != 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ long readReset(int track) {
|
||||
if (pendingResets[track]) {
|
||||
pendingResets[track] = false;
|
||||
@ -183,7 +213,6 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
}
|
||||
|
||||
/* package */ int readData(int track, FormatHolder formatHolder, SampleHolder sampleHolder) {
|
||||
Assertions.checkState(prepared);
|
||||
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
|
||||
if (pendingResets[track]) {
|
||||
return TrackStream.NOTHING_READ;
|
||||
@ -223,20 +252,8 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable(TrackStream trackStream) {
|
||||
Assertions.checkState(prepared);
|
||||
int track = ((TrackStreamImpl) trackStream).track;
|
||||
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
|
||||
extractor.unselectTrack(track);
|
||||
trackStates[track] = TRACK_STATE_DISABLED;
|
||||
pendingResets[track] = false;
|
||||
enabledTrackCount--;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToUs(long positionUs) {
|
||||
Assertions.checkState(prepared);
|
||||
if (enabledTrackCount == 0) {
|
||||
return;
|
||||
}
|
||||
@ -245,7 +262,6 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
|
||||
@Override
|
||||
public long getBufferedPositionUs() {
|
||||
Assertions.checkState(prepared);
|
||||
if (enabledTrackCount == 0) {
|
||||
return C.END_OF_SOURCE_US;
|
||||
}
|
||||
@ -261,11 +277,11 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
state = STATE_RELEASED;
|
||||
if (extractor != null) {
|
||||
extractor.release();
|
||||
extractor = null;
|
||||
}
|
||||
prepared = false;
|
||||
}
|
||||
|
||||
@TargetApi(18)
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.google.android.exoplayer;
|
||||
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -28,45 +30,54 @@ public final class MultiSampleSource implements SampleSource {
|
||||
private final SampleSource[] sources;
|
||||
private final IdentityHashMap<TrackStream, Integer> trackStreamSourceIndices;
|
||||
|
||||
private boolean prepared;
|
||||
private int state;
|
||||
private long durationUs;
|
||||
private TrackGroupArray trackGroups;
|
||||
|
||||
public MultiSampleSource(SampleSource... sources) {
|
||||
this.sources = sources;
|
||||
trackStreamSourceIndices = new IdentityHashMap<>();
|
||||
state = STATE_UNPREPARED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepare(long positionUs) throws IOException {
|
||||
if (prepared) {
|
||||
return true;
|
||||
}
|
||||
Assertions.checkState(state == SampleSource.STATE_UNPREPARED);
|
||||
boolean sourcesPrepared = true;
|
||||
for (SampleSource source : sources) {
|
||||
if (source.getState() == SampleSource.STATE_UNPREPARED) {
|
||||
sourcesPrepared &= source.prepare(positionUs);
|
||||
}
|
||||
if (sourcesPrepared) {
|
||||
prepared = true;
|
||||
durationUs = C.UNKNOWN_TIME_US;
|
||||
}
|
||||
if (!sourcesPrepared) {
|
||||
return false;
|
||||
}
|
||||
durationUs = 0;
|
||||
int totalTrackGroupCount = 0;
|
||||
for (SampleSource source : sources) {
|
||||
totalTrackGroupCount += source.getTrackGroups().length;
|
||||
if (source.getDurationUs() > durationUs) {
|
||||
durationUs = source.getDurationUs();
|
||||
if (durationUs != C.UNKNOWN_TIME_US) {
|
||||
long sourceDurationUs = source.getDurationUs();
|
||||
durationUs = sourceDurationUs == C.UNKNOWN_TIME_US
|
||||
? C.UNKNOWN_TIME_US : Math.max(durationUs, sourceDurationUs);
|
||||
}
|
||||
}
|
||||
TrackGroup[] trackGroups = new TrackGroup[totalTrackGroupCount];
|
||||
TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount];
|
||||
int trackGroupIndex = 0;
|
||||
for (SampleSource source : sources) {
|
||||
int sourceTrackGroupCount = source.getTrackGroups().length;
|
||||
for (int j = 0; j < sourceTrackGroupCount; j++) {
|
||||
trackGroups[trackGroupIndex++] = source.getTrackGroups().get(j);
|
||||
trackGroupArray[trackGroupIndex++] = source.getTrackGroups().get(j);
|
||||
}
|
||||
}
|
||||
this.trackGroups = new TrackGroupArray(trackGroups);
|
||||
}
|
||||
return prepared;
|
||||
trackGroups = new TrackGroupArray(trackGroupArray);
|
||||
state = STATE_SELECTING_TRACKS;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -75,18 +86,38 @@ public final class MultiSampleSource implements SampleSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackStream enable(TrackSelection selection, long positionUs) {
|
||||
public void startTrackSelection() {
|
||||
Assertions.checkState(state == STATE_READING);
|
||||
state = STATE_SELECTING_TRACKS;
|
||||
for (SampleSource source : sources) {
|
||||
source.startTrackSelection();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackStream selectTrack(TrackSelection selection, long positionUs) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
Pair<Integer, Integer> sourceAndGroup = getSourceAndTrackGroupIndices(selection.group);
|
||||
TrackStream trackStream = sources[sourceAndGroup.first].enable(
|
||||
TrackStream trackStream = sources[sourceAndGroup.first].selectTrack(
|
||||
new TrackSelection(sourceAndGroup.second, selection.getTracks()), positionUs);
|
||||
trackStreamSourceIndices.put(trackStream, sourceAndGroup.first);
|
||||
return trackStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable(TrackStream trackStream) {
|
||||
public void unselectTrack(TrackStream trackStream) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
int sourceIndex = trackStreamSourceIndices.remove(trackStream);
|
||||
sources[sourceIndex].disable(trackStream);
|
||||
sources[sourceIndex].unselectTrack(trackStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endTrackSelection(long positionUs) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
state = STATE_READING;
|
||||
for (SampleSource source : sources) {
|
||||
source.endTrackSelection(positionUs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -129,7 +160,7 @@ public final class MultiSampleSource implements SampleSource {
|
||||
for (SampleSource source : sources) {
|
||||
source.release();
|
||||
}
|
||||
prepared = false;
|
||||
state = STATE_RELEASED;
|
||||
}
|
||||
|
||||
private Pair<Integer, Integer> getSourceAndTrackGroupIndices(int group) {
|
||||
|
@ -22,11 +22,39 @@ import java.io.IOException;
|
||||
*/
|
||||
public interface SampleSource {
|
||||
|
||||
/**
|
||||
* The source has not yet been prepared.
|
||||
*/
|
||||
int STATE_UNPREPARED = 0;
|
||||
/**
|
||||
* The source is prepared and in the track selection state.
|
||||
*/
|
||||
int STATE_SELECTING_TRACKS = 1;
|
||||
/**
|
||||
* The source is prepared and in the reading state.
|
||||
*/
|
||||
int STATE_READING = 2;
|
||||
/**
|
||||
* The source has been released.
|
||||
*/
|
||||
int STATE_RELEASED = 3;
|
||||
|
||||
/**
|
||||
* Returns the state of the source.
|
||||
*
|
||||
* @return The state of the source. One of {@link #STATE_UNPREPARED},
|
||||
* {@link #STATE_SELECTING_TRACKS}, {@link #STATE_READING} and {@link #STATE_RELEASED}.
|
||||
*/
|
||||
int getState();
|
||||
|
||||
/**
|
||||
* Prepares the source.
|
||||
* <p>
|
||||
* If preparation cannot complete immediately then the call will return {@code false} rather than
|
||||
* block. The method can be called repeatedly until the return value indicates success.
|
||||
* block and the state will remain unchanged. If true is returned the state will have changed
|
||||
* to {@link #STATE_SELECTING_TRACKS} for the initial track selection to take place.
|
||||
* <p>
|
||||
* This method should only be called when the state is {@link #STATE_UNPREPARED}.
|
||||
*
|
||||
* @param positionUs The player's current playback position.
|
||||
* @return True if the source was prepared, false otherwise.
|
||||
@ -37,7 +65,8 @@ public interface SampleSource {
|
||||
/**
|
||||
* Returns the duration of the source.
|
||||
* <p>
|
||||
* This method should only be called after the source has been prepared.
|
||||
* This method should only be called when the state is {@link #STATE_SELECTING_TRACKS} or
|
||||
* {@link #STATE_READING}.
|
||||
*
|
||||
* @return The duration of the source in microseconds, or {@link C#UNKNOWN_TIME_US} if the
|
||||
* duration is not known.
|
||||
@ -47,16 +76,63 @@ public interface SampleSource {
|
||||
/**
|
||||
* Returns the {@link TrackGroup}s exposed by the source.
|
||||
* <p>
|
||||
* This method should only be called after the source has been prepared.
|
||||
* This method should only be called when the state is {@link #STATE_SELECTING_TRACKS} or
|
||||
* {@link #STATE_READING}.
|
||||
*
|
||||
* @return The {@link TrackGroup}s.
|
||||
*/
|
||||
TrackGroupArray getTrackGroups();
|
||||
|
||||
/**
|
||||
* Enters the track selection state.
|
||||
* <p>
|
||||
* The selected tracks are initially unchanged, but may be modified by calls to
|
||||
* {@link #unselectTrack(TrackStream)} and {@link #selectTrack(TrackSelection, long)}, followed by
|
||||
* a call to {@link #endTrackSelection(long)}.
|
||||
* <p>
|
||||
* This method should only be called when the state is {@link #STATE_READING}.
|
||||
*/
|
||||
void startTrackSelection();
|
||||
|
||||
/**
|
||||
* Selects a track defined by a {@link TrackSelection}. A {@link TrackStream} is returned through
|
||||
* which the track's data can be read.
|
||||
* <p>
|
||||
* The {@link TrackSelection} must have a {@link TrackSelection#group} index distinct from those
|
||||
* of other enabled tracks, and a {@code TrackSelection#length} of 1 unless
|
||||
* {@link TrackGroup#adaptive} is true for the selected group.
|
||||
* <p>
|
||||
* This method should only be called when the state is {@link #STATE_SELECTING_TRACKS}.
|
||||
*
|
||||
* @param selection Defines the track.
|
||||
* @param positionUs The current playback position in microseconds.
|
||||
* @return A {@link TrackStream} from which the enabled track's data can be read.
|
||||
*/
|
||||
TrackStream selectTrack(TrackSelection selection, long positionUs);
|
||||
|
||||
/**
|
||||
* Unselects a track previously selected by calling {@link #selectTrack(TrackSelection, long)}.
|
||||
* <p>
|
||||
* This method should only be called when the state is {@link #STATE_SELECTING_TRACKS}.
|
||||
*
|
||||
* @param stream The {@link TrackStream} obtained from the corresponding call to
|
||||
* {@link #selectTrack(TrackSelection, long)}.
|
||||
*/
|
||||
void unselectTrack(TrackStream stream);
|
||||
|
||||
/**
|
||||
* Exits the track selection state.
|
||||
* <p>
|
||||
* This method should only be called when the state is {@link #STATE_SELECTING_TRACKS}.
|
||||
*
|
||||
* @param positionUs The current playback position in microseconds.
|
||||
*/
|
||||
void endTrackSelection(long positionUs);
|
||||
|
||||
/**
|
||||
* Indicates to the source that it should continue buffering data for its enabled tracks.
|
||||
* <p>
|
||||
* This method should only be called after the source has been prepared.
|
||||
* This method should only be called when the state is {@link #STATE_READING}.
|
||||
*
|
||||
* @param positionUs The current playback position.
|
||||
*/
|
||||
@ -65,7 +141,7 @@ public interface SampleSource {
|
||||
/**
|
||||
* Returns an estimate of the position up to which data is buffered for the enabled tracks.
|
||||
* <p>
|
||||
* This method should only be called after the source has been prepared.
|
||||
* This method should only be called when the state is {@link #STATE_READING}.
|
||||
*
|
||||
* @return An estimate of the absolute position in microseconds up to which data is buffered,
|
||||
* or {@link C#END_OF_SOURCE_US} if the track is fully buffered, or {@link C#UNKNOWN_TIME_US}
|
||||
@ -77,38 +153,17 @@ public interface SampleSource {
|
||||
/**
|
||||
* Seeks to the specified time in microseconds.
|
||||
* <p>
|
||||
* This method should only be called after the source has been prepared.
|
||||
* This method should only be called when the state is {@link #STATE_READING}.
|
||||
*
|
||||
* @param positionUs The seek position in microseconds.
|
||||
*/
|
||||
void seekToUs(long positionUs);
|
||||
|
||||
/**
|
||||
* Enables the source to read a track defined by a {@link TrackSelection}. A {@link TrackStream}
|
||||
* is returned through which the track's data can be read.
|
||||
* <p>
|
||||
* This method should only be called after the source has been prepared, and when there are no
|
||||
* other enabled tracks with the same {@link TrackSelection#group} index. Note that
|
||||
* {@code TrackSelection#tracks} must be of length 1 unless {@link TrackGroup#adaptive} is true
|
||||
* for the group.
|
||||
*
|
||||
* @param selection Defines the track.
|
||||
* @param positionUs The current playback position in microseconds.
|
||||
* @return A {@link TrackStream} from which the enabled track's data can be read.
|
||||
*/
|
||||
TrackStream enable(TrackSelection selection, long positionUs);
|
||||
|
||||
/**
|
||||
* Disables a {@link TrackStream} previously obtained from {@link #enable(TrackSelection, long)}.
|
||||
*
|
||||
* @param trackStream The {@link TrackStream} to disable.
|
||||
*/
|
||||
void disable(TrackStream trackStream);
|
||||
|
||||
/**
|
||||
* Releases the source.
|
||||
* <p>
|
||||
* This method should be called when the source is no longer required.
|
||||
* This method should be called when the source is no longer required. It may be called in any
|
||||
* state.
|
||||
*/
|
||||
void release();
|
||||
|
||||
|
@ -59,9 +59,9 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
||||
*/
|
||||
private static final int INITIAL_SAMPLE_SIZE = 1;
|
||||
|
||||
private static final int STATE_SEND_FORMAT = 0;
|
||||
private static final int STATE_SEND_SAMPLE = 1;
|
||||
private static final int STATE_END_OF_STREAM = 2;
|
||||
private static final int STREAM_STATE_SEND_FORMAT = 0;
|
||||
private static final int STREAM_STATE_SEND_SAMPLE = 1;
|
||||
private static final int STREAM_STATE_END_OF_STREAM = 2;
|
||||
|
||||
private final Uri uri;
|
||||
private final DataSource dataSource;
|
||||
@ -74,9 +74,6 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
||||
private final int eventSourceId;
|
||||
|
||||
private int state;
|
||||
private byte[] sampleData;
|
||||
private int sampleSize;
|
||||
|
||||
private long pendingResetPositionUs;
|
||||
private boolean loadingFinished;
|
||||
private Loader loader;
|
||||
@ -84,6 +81,10 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
||||
private int currentLoadableExceptionCount;
|
||||
private long currentLoadableExceptionTimestamp;
|
||||
|
||||
private int streamState;
|
||||
private byte[] sampleData;
|
||||
private int sampleSize;
|
||||
|
||||
public SingleSampleSource(Uri uri, DataSource dataSource, Format format, long durationUs) {
|
||||
this(uri, dataSource, format, durationUs, DEFAULT_MIN_LOADABLE_RETRY_COUNT);
|
||||
}
|
||||
@ -106,6 +107,12 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
||||
this.eventSourceId = eventSourceId;
|
||||
tracks = new TrackGroupArray(new TrackGroup(format));
|
||||
sampleData = new byte[INITIAL_SAMPLE_SIZE];
|
||||
state = STATE_UNPREPARED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -117,9 +124,9 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
||||
|
||||
@Override
|
||||
public boolean prepare(long positionUs) {
|
||||
if (loader == null) {
|
||||
Assertions.checkState(state == STATE_UNPREPARED);
|
||||
state = STATE_SELECTING_TRACKS;
|
||||
loader = new Loader("Loader:" + format.sampleMimeType);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -134,14 +141,33 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackStream enable(TrackSelection selection, long positionUs) {
|
||||
state = STATE_SEND_FORMAT;
|
||||
public void startTrackSelection() {
|
||||
Assertions.checkState(state == STATE_READING);
|
||||
state = STATE_SELECTING_TRACKS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackStream selectTrack(TrackSelection selection, long positionUs) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
streamState = STREAM_STATE_SEND_FORMAT;
|
||||
pendingResetPositionUs = NO_RESET;
|
||||
clearCurrentLoadableException();
|
||||
maybeStartLoading();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unselectTrack(TrackStream stream) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
streamState = STREAM_STATE_END_OF_STREAM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endTrackSelection(long positionUs) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
state = STATE_READING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void continueBuffering(long positionUs) {
|
||||
maybeStartLoading();
|
||||
@ -161,16 +187,16 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
||||
|
||||
@Override
|
||||
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
|
||||
if (state == STATE_END_OF_STREAM) {
|
||||
if (streamState == STREAM_STATE_END_OF_STREAM) {
|
||||
sampleHolder.addFlag(C.SAMPLE_FLAG_END_OF_STREAM);
|
||||
return END_OF_STREAM;
|
||||
} else if (state == STATE_SEND_FORMAT) {
|
||||
} else if (streamState == STREAM_STATE_SEND_FORMAT) {
|
||||
formatHolder.format = format;
|
||||
state = STATE_SEND_SAMPLE;
|
||||
streamState = STREAM_STATE_SEND_SAMPLE;
|
||||
return FORMAT_READ;
|
||||
}
|
||||
|
||||
Assertions.checkState(state == STATE_SEND_SAMPLE);
|
||||
Assertions.checkState(streamState == STREAM_STATE_SEND_SAMPLE);
|
||||
if (!loadingFinished) {
|
||||
return NOTHING_READ;
|
||||
} else {
|
||||
@ -179,31 +205,28 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
||||
sampleHolder.addFlag(C.SAMPLE_FLAG_SYNC);
|
||||
sampleHolder.ensureSpaceForWrite(sampleHolder.size);
|
||||
sampleHolder.data.put(sampleData, 0, sampleSize);
|
||||
state = STATE_END_OF_STREAM;
|
||||
streamState = STREAM_STATE_END_OF_STREAM;
|
||||
return SAMPLE_READ;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToUs(long positionUs) {
|
||||
if (state == STATE_END_OF_STREAM) {
|
||||
if (streamState == STREAM_STATE_END_OF_STREAM) {
|
||||
pendingResetPositionUs = positionUs;
|
||||
state = STATE_SEND_SAMPLE;
|
||||
streamState = STREAM_STATE_SEND_SAMPLE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPositionUs() {
|
||||
return state == STATE_END_OF_STREAM || loadingFinished ? C.END_OF_SOURCE_US : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable(TrackStream trackStream) {
|
||||
state = STATE_END_OF_STREAM;
|
||||
return streamState == STREAM_STATE_END_OF_STREAM || loadingFinished ? C.END_OF_SOURCE_US : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
state = STATE_RELEASED;
|
||||
streamState = STREAM_STATE_END_OF_STREAM;
|
||||
if (loader != null) {
|
||||
loader.release();
|
||||
loader = null;
|
||||
@ -213,7 +236,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
||||
// Private methods.
|
||||
|
||||
private void maybeStartLoading() {
|
||||
if (loadingFinished || state == STATE_END_OF_STREAM || loader.isLoading()) {
|
||||
if (loadingFinished || streamState == STREAM_STATE_END_OF_STREAM || loader.isLoading()) {
|
||||
return;
|
||||
}
|
||||
if (currentLoadableException != null) {
|
||||
|
@ -54,10 +54,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
||||
*/
|
||||
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3;
|
||||
|
||||
private static final int STATE_IDLE = 0;
|
||||
private static final int STATE_PREPARED = 1;
|
||||
private static final int STATE_ENABLED = 2;
|
||||
|
||||
private static final long NO_RESET_PENDING = Long.MIN_VALUE;
|
||||
|
||||
private final int eventSourceId;
|
||||
@ -83,8 +79,8 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
||||
private long durationUs;
|
||||
private Loader loader;
|
||||
private boolean loadingFinished;
|
||||
private boolean trackEnabled;
|
||||
private IOException currentLoadableException;
|
||||
private int enabledTrackCount;
|
||||
private int currentLoadableExceptionCount;
|
||||
private long currentLoadableExceptionTimestamp;
|
||||
private long currentLoadStartTimeMs;
|
||||
@ -143,27 +139,30 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
||||
mediaChunks = new LinkedList<>();
|
||||
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
|
||||
sampleQueue = new DefaultTrackOutput(loadControl.getAllocator());
|
||||
state = STATE_IDLE;
|
||||
pendingResetPositionUs = NO_RESET_PENDING;
|
||||
state = STATE_UNPREPARED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepare(long positionUs) throws IOException {
|
||||
if (state != STATE_IDLE) {
|
||||
return true;
|
||||
}
|
||||
Assertions.checkState(state == STATE_UNPREPARED);
|
||||
if (!chunkSource.prepare()) {
|
||||
return false;
|
||||
}
|
||||
durationUs = chunkSource.getDurationUs();
|
||||
TrackGroup trackGroup = chunkSource.getTracks();
|
||||
if (trackGroup != null) {
|
||||
loader = new Loader("Loader:" + trackGroup.getFormat(0).containerMimeType);
|
||||
trackGroups = new TrackGroupArray(trackGroup);
|
||||
TrackGroup tracks = chunkSource.getTracks();
|
||||
if (tracks != null) {
|
||||
loader = new Loader("Loader:" + tracks.getFormat(0).containerMimeType);
|
||||
trackGroups = new TrackGroupArray(tracks);
|
||||
} else {
|
||||
trackGroups = new TrackGroupArray();
|
||||
}
|
||||
state = STATE_PREPARED;
|
||||
state = STATE_SELECTING_TRACKS;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -174,15 +173,20 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
||||
|
||||
@Override
|
||||
public TrackGroupArray getTrackGroups() {
|
||||
Assertions.checkState(state != STATE_IDLE);
|
||||
return trackGroups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackStream enable(TrackSelection selection, long positionUs) {
|
||||
Assertions.checkState(state == STATE_PREPARED);
|
||||
Assertions.checkState(enabledTrackCount++ == 0);
|
||||
state = STATE_ENABLED;
|
||||
public void startTrackSelection() {
|
||||
Assertions.checkState(state == STATE_READING);
|
||||
state = STATE_SELECTING_TRACKS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackStream selectTrack(TrackSelection selection, long positionUs) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
Assertions.checkState(!trackEnabled);
|
||||
trackEnabled = true;
|
||||
chunkSource.enable(selection.getTracks());
|
||||
loadControl.register(this, bufferSizeContribution);
|
||||
downstreamFormat = null;
|
||||
@ -195,10 +199,10 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable(TrackStream trackStream) {
|
||||
Assertions.checkState(state == STATE_ENABLED);
|
||||
Assertions.checkState(--enabledTrackCount == 0);
|
||||
state = STATE_PREPARED;
|
||||
public void unselectTrack(TrackStream stream) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
Assertions.checkState(trackEnabled);
|
||||
trackEnabled = false;
|
||||
try {
|
||||
chunkSource.disable();
|
||||
} finally {
|
||||
@ -215,11 +219,13 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
||||
}
|
||||
|
||||
@Override
|
||||
public void continueBuffering(long positionUs) {
|
||||
Assertions.checkState(state != STATE_IDLE);
|
||||
if (state == STATE_PREPARED) {
|
||||
return;
|
||||
public void endTrackSelection(long positionUs) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
state = STATE_READING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void continueBuffering(long positionUs) {
|
||||
downstreamPositionUs = positionUs;
|
||||
chunkSource.continueBuffering(positionUs);
|
||||
updateLoadControl();
|
||||
@ -227,7 +233,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
Assertions.checkState(state == STATE_ENABLED);
|
||||
return loadingFinished || !sampleQueue.isEmpty();
|
||||
}
|
||||
|
||||
@ -242,7 +247,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
||||
|
||||
@Override
|
||||
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
|
||||
Assertions.checkState(state == STATE_ENABLED);
|
||||
if (pendingReset || isPendingReset()) {
|
||||
return NOTHING_READ;
|
||||
}
|
||||
@ -292,10 +296,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
||||
|
||||
@Override
|
||||
public void seekToUs(long positionUs) {
|
||||
Assertions.checkState(state != STATE_IDLE);
|
||||
if (state == STATE_PREPARED) {
|
||||
return;
|
||||
}
|
||||
downstreamPositionUs = positionUs;
|
||||
lastSeekPositionUs = positionUs;
|
||||
// If we're not pending a reset, see if we can seek within the sample queue.
|
||||
@ -326,8 +326,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
||||
|
||||
@Override
|
||||
public long getBufferedPositionUs() {
|
||||
Assertions.checkState(state != STATE_IDLE);
|
||||
if (state != STATE_ENABLED || loadingFinished) {
|
||||
if (!trackEnabled || loadingFinished) {
|
||||
return C.END_OF_SOURCE_US;
|
||||
} else if (isPendingReset()) {
|
||||
return pendingResetPositionUs;
|
||||
@ -340,12 +339,12 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
Assertions.checkState(state != STATE_ENABLED);
|
||||
state = STATE_RELEASED;
|
||||
trackEnabled = false;
|
||||
if (loader != null) {
|
||||
loader.release();
|
||||
loader = null;
|
||||
}
|
||||
state = STATE_IDLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -371,7 +370,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
||||
Chunk currentLoadable = currentLoadableHolder.chunk;
|
||||
notifyLoadCanceled(currentLoadable.bytesLoaded());
|
||||
clearCurrentLoadable();
|
||||
if (state == STATE_ENABLED) {
|
||||
if (trackEnabled) {
|
||||
restartFrom(pendingResetPositionUs);
|
||||
} else {
|
||||
sampleQueue.clear();
|
||||
|
@ -208,13 +208,15 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
private volatile SeekMap seekMap;
|
||||
private volatile DrmInitData drmInitData;
|
||||
|
||||
private boolean prepared;
|
||||
private int state;
|
||||
private int enabledTrackCount;
|
||||
private TrackGroupArray tracks;
|
||||
private long durationUs;
|
||||
private boolean[] pendingMediaFormat;
|
||||
private boolean[] pendingResets;
|
||||
private boolean[] trackEnabledStates;
|
||||
private boolean isFirstTrackSelection;
|
||||
private boolean newTracksSelected;
|
||||
|
||||
private long downstreamPositionUs;
|
||||
private long lastSeekPositionUs;
|
||||
@ -325,37 +327,41 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
extractorHolder = new ExtractorHolder(extractors, this);
|
||||
sampleQueues = new SparseArray<>();
|
||||
pendingResetPositionUs = NO_RESET_PENDING;
|
||||
state = STATE_UNPREPARED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepare(long positionUs) {
|
||||
if (prepared) {
|
||||
return true;
|
||||
}
|
||||
Assertions.checkState(state == STATE_UNPREPARED);
|
||||
if (loader == null) {
|
||||
loader = new Loader("Loader:ExtractorSampleSource");
|
||||
}
|
||||
|
||||
maybeStartLoading();
|
||||
if (seekMap == null || !tracksBuilt || !haveFormatsForAllTracks()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (seekMap != null && tracksBuilt && haveFormatsForAllTracks()) {
|
||||
int trackCount = sampleQueues.size();
|
||||
TrackGroup[] tracks = new TrackGroup[trackCount];
|
||||
TrackGroup[] trackArray = new TrackGroup[trackCount];
|
||||
trackEnabledStates = new boolean[trackCount];
|
||||
pendingResets = new boolean[trackCount];
|
||||
pendingMediaFormat = new boolean[trackCount];
|
||||
durationUs = seekMap.getDurationUs();
|
||||
for (int i = 0; i < trackCount; i++) {
|
||||
tracks[i] = new TrackGroup(sampleQueues.valueAt(i).getFormat());
|
||||
trackArray[i] = new TrackGroup(sampleQueues.valueAt(i).getFormat());
|
||||
}
|
||||
this.tracks = new TrackGroupArray(tracks);
|
||||
prepared = true;
|
||||
tracks = new TrackGroupArray(trackArray);
|
||||
isFirstTrackSelection = true;
|
||||
state = STATE_SELECTING_TRACKS;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDurationUs() {
|
||||
return durationUs;
|
||||
@ -367,8 +373,16 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackStream enable(TrackSelection selection, long positionUs) {
|
||||
Assertions.checkState(prepared);
|
||||
public void startTrackSelection() {
|
||||
Assertions.checkState(state == STATE_READING);
|
||||
state = STATE_SELECTING_TRACKS;
|
||||
newTracksSelected = false;
|
||||
isFirstTrackSelection = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackStream selectTrack(TrackSelection selection, long positionUs) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
Assertions.checkState(selection.length == 1);
|
||||
Assertions.checkState(selection.getTrack(0) == 0);
|
||||
int track = selection.group;
|
||||
@ -377,23 +391,23 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
trackEnabledStates[track] = true;
|
||||
pendingMediaFormat[track] = true;
|
||||
pendingResets[track] = false;
|
||||
if (enabledTrackCount == 1) {
|
||||
// Treat all enables in non-seekable media as being from t=0.
|
||||
positionUs = !seekMap.isSeekable() ? 0 : positionUs;
|
||||
downstreamPositionUs = positionUs;
|
||||
lastSeekPositionUs = positionUs;
|
||||
restartFrom(positionUs);
|
||||
}
|
||||
newTracksSelected = true;
|
||||
return new TrackStreamImpl(track);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable(TrackStream trackStream) {
|
||||
Assertions.checkState(prepared);
|
||||
int track = ((TrackStreamImpl) trackStream).track;
|
||||
public void unselectTrack(TrackStream stream) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
int track = ((TrackStreamImpl) stream).track;
|
||||
Assertions.checkState(trackEnabledStates[track]);
|
||||
enabledTrackCount--;
|
||||
trackEnabledStates[track] = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endTrackSelection(long positionUs) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
state = STATE_READING;
|
||||
if (enabledTrackCount == 0) {
|
||||
downstreamPositionUs = Long.MIN_VALUE;
|
||||
if (loader.isLoading()) {
|
||||
@ -402,12 +416,13 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
clearState();
|
||||
allocator.trim(0);
|
||||
}
|
||||
} else if (!isFirstTrackSelection && newTracksSelected) {
|
||||
seekToInternal(positionUs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void continueBuffering(long playbackPositionUs) {
|
||||
Assertions.checkState(prepared);
|
||||
if (enabledTrackCount == 0) {
|
||||
return;
|
||||
}
|
||||
@ -420,7 +435,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
}
|
||||
|
||||
/* package */ boolean isReady(int track) {
|
||||
Assertions.checkState(prepared);
|
||||
Assertions.checkState(trackEnabledStates[track]);
|
||||
return !sampleQueues.valueAt(track).isEmpty();
|
||||
|
||||
@ -490,13 +504,18 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
|
||||
@Override
|
||||
public void seekToUs(long positionUs) {
|
||||
Assertions.checkState(prepared);
|
||||
if (enabledTrackCount == 0) {
|
||||
return;
|
||||
}
|
||||
seekToInternal(positionUs);
|
||||
}
|
||||
|
||||
private void seekToInternal(long positionUs) {
|
||||
// Treat all seeks into non-seekable media as being to t=0.
|
||||
downstreamPositionUs = !seekMap.isSeekable() ? 0 : positionUs;
|
||||
lastSeekPositionUs = downstreamPositionUs;
|
||||
positionUs = !seekMap.isSeekable() ? 0 : positionUs;
|
||||
lastSeekPositionUs = positionUs;
|
||||
downstreamPositionUs = positionUs;
|
||||
Arrays.fill(pendingResets, true);
|
||||
// If we're not pending a reset, see if we can seek within the sample queues.
|
||||
boolean seekInsideBuffer = !isPendingReset();
|
||||
for (int i = 0; seekInsideBuffer && i < sampleQueues.size(); i++) {
|
||||
@ -506,9 +525,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
if (!seekInsideBuffer) {
|
||||
restartFrom(positionUs);
|
||||
}
|
||||
|
||||
// Either way, we need to send discontinuities to the downstream components.
|
||||
Arrays.fill(pendingResets, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -530,11 +546,12 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
state = STATE_RELEASED;
|
||||
enabledTrackCount = 0;
|
||||
if (loader != null) {
|
||||
loader.release();
|
||||
loader = null;
|
||||
}
|
||||
prepared = false;
|
||||
}
|
||||
|
||||
// Loader.Callback implementation.
|
||||
@ -617,7 +634,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
long elapsedMillis = SystemClock.elapsedRealtime() - currentLoadableExceptionTimestamp;
|
||||
if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) {
|
||||
currentLoadableException = null;
|
||||
if (!prepared) {
|
||||
if (state == STATE_UNPREPARED) {
|
||||
// We don't know whether we're playing an on-demand or a live stream. For a live stream
|
||||
// we need to load from the start, as outlined below. Since we might be playing a live
|
||||
// stream, play it safe and load from the start.
|
||||
@ -654,7 +671,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
sampleTimeOffsetUs = 0;
|
||||
havePendingNextSampleUs = false;
|
||||
|
||||
if (!prepared) {
|
||||
if (state == STATE_UNPREPARED) {
|
||||
loadable = createLoadableFromStart();
|
||||
} else {
|
||||
Assertions.checkState(isPendingReset());
|
||||
@ -836,6 +853,8 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
allocator.blockWhileTotalBytesAllocatedExceeds(requestedBufferSize);
|
||||
result = extractor.read(input, positionHolder);
|
||||
// TODO: Implement throttling to stop us from buffering data too often.
|
||||
// TODO: Block buffering between the point at which we have sufficient data for
|
||||
// preparation to complete and the first call to endTrackSelection.
|
||||
}
|
||||
} finally {
|
||||
if (result == Extractor.RESULT_SEEK) {
|
||||
|
@ -73,7 +73,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
private final Handler eventHandler;
|
||||
private final EventListener eventListener;
|
||||
|
||||
private boolean prepared;
|
||||
private int state;
|
||||
private boolean loadControlRegistered;
|
||||
private int enabledTrackCount;
|
||||
|
||||
@ -84,6 +84,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
private TrackGroupArray trackGroups;
|
||||
private int primaryTrackGroupIndex;
|
||||
private int[] primarySelectedTracks;
|
||||
private boolean primarySelectedTracksChanged;
|
||||
// Indexed by group.
|
||||
private boolean[] groupEnabledStates;
|
||||
private boolean[] pendingResets;
|
||||
@ -131,15 +132,20 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
chunkOperationHolder = new ChunkOperationHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepare(long positionUs) throws IOException {
|
||||
if (prepared) {
|
||||
return true;
|
||||
} else if (!chunkSource.prepare()) {
|
||||
Assertions.checkState(state == STATE_UNPREPARED);
|
||||
if (!chunkSource.prepare()) {
|
||||
return false;
|
||||
} else if (chunkSource.getTrackCount() == 0) {
|
||||
}
|
||||
if (chunkSource.getTrackCount() == 0) {
|
||||
trackGroups = new TrackGroupArray();
|
||||
prepared = true;
|
||||
state = STATE_SELECTING_TRACKS;
|
||||
return true;
|
||||
}
|
||||
if (!extractors.isEmpty()) {
|
||||
@ -148,7 +154,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
HlsExtractorWrapper extractor = extractors.getFirst();
|
||||
if (extractor.isPrepared()) {
|
||||
buildTracks(extractor);
|
||||
prepared = true;
|
||||
state = STATE_SELECTING_TRACKS;
|
||||
maybeStartLoading(); // Update the load control.
|
||||
return true;
|
||||
} else if (extractors.size() > 1) {
|
||||
@ -183,58 +189,48 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
|
||||
@Override
|
||||
public TrackGroupArray getTrackGroups() {
|
||||
Assertions.checkState(prepared);
|
||||
return trackGroups;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TrackStream enable(TrackSelection selection, long positionUs) {
|
||||
Assertions.checkState(prepared);
|
||||
public void startTrackSelection() {
|
||||
Assertions.checkState(state == STATE_READING);
|
||||
state = STATE_SELECTING_TRACKS;
|
||||
primarySelectedTracksChanged = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackStream selectTrack(TrackSelection selection, long positionUs) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
int group = selection.group;
|
||||
int[] tracks = selection.getTracks();
|
||||
setTrackGroupEnabledState(group, true);
|
||||
downstreamSampleFormats[group] = null;
|
||||
pendingResets[group] = false;
|
||||
downstreamFormat = null;
|
||||
boolean wasLoadControlRegistered = loadControlRegistered;
|
||||
if (!loadControlRegistered) {
|
||||
loadControl.register(this, bufferSizeContribution);
|
||||
loadControlRegistered = true;
|
||||
}
|
||||
// Treat enabling of a live stream as occurring at t=0 in both of the blocks below.
|
||||
positionUs = chunkSource.isLive() ? 0 : positionUs;
|
||||
if (group == primaryTrackGroupIndex && !Arrays.equals(tracks, primarySelectedTracks)) {
|
||||
// This is a primary track whose corresponding chunk source track is different to the one
|
||||
// currently selected. We need to change the selection and restart. Since other exposed tracks
|
||||
// may be enabled too, we need to implement the restart as a seek so that all downstream
|
||||
// renderers receive a discontinuity event.
|
||||
chunkSource.selectTracks(tracks);
|
||||
primarySelectedTracks = tracks;
|
||||
seekToInternal(positionUs);
|
||||
} else if (enabledTrackCount == 1) {
|
||||
lastSeekPositionUs = positionUs;
|
||||
if (wasLoadControlRegistered && downstreamPositionUs == positionUs) {
|
||||
// TODO: Address [Internal: b/21743989] to remove the need for this kind of hack.
|
||||
// This is the first track to be enabled after preparation and the position is the same as
|
||||
// was passed to prepare. In this case we can avoid restarting, which would reload the same
|
||||
// chunks as were loaded during preparation.
|
||||
maybeStartLoading();
|
||||
} else {
|
||||
downstreamPositionUs = positionUs;
|
||||
restartFrom(positionUs);
|
||||
}
|
||||
primarySelectedTracksChanged = true;
|
||||
}
|
||||
return new TrackStreamImpl(group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable(TrackStream trackStream) {
|
||||
Assertions.checkState(prepared);
|
||||
int group = ((TrackStreamImpl) trackStream).group;
|
||||
public void unselectTrack(TrackStream stream) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
int group = ((TrackStreamImpl) stream).group;
|
||||
setTrackGroupEnabledState(group, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endTrackSelection(long positionUs) {
|
||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||
state = STATE_READING;
|
||||
if (enabledTrackCount == 0) {
|
||||
chunkSource.reset();
|
||||
downstreamPositionUs = Long.MIN_VALUE;
|
||||
downstreamFormat = null;
|
||||
if (loader != null) {
|
||||
if (loadControlRegistered) {
|
||||
loadControl.unregister(this);
|
||||
loadControlRegistered = false;
|
||||
@ -246,11 +242,29 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
loadControl.trimAllocator();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!loadControlRegistered) {
|
||||
loadControl.register(this, bufferSizeContribution);
|
||||
loadControlRegistered = true;
|
||||
}
|
||||
// Treat enabling of a live stream as occurring at t=0 in both of the blocks below.
|
||||
positionUs = chunkSource.isLive() ? 0 : positionUs;
|
||||
if (primarySelectedTracksChanged) {
|
||||
// If the primary tracks change then this will affect other exposed tracks that are enabled
|
||||
// as well. Hence we implement the restart as a seek so that all downstream renderers
|
||||
// receive a discontinuity event.
|
||||
chunkSource.selectTracks(primarySelectedTracks);
|
||||
seekToInternal(positionUs);
|
||||
} else {
|
||||
lastSeekPositionUs = positionUs;
|
||||
downstreamPositionUs = positionUs;
|
||||
restartFrom(positionUs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void continueBuffering(long playbackPositionUs) {
|
||||
Assertions.checkState(prepared);
|
||||
if (enabledTrackCount == 0) {
|
||||
return;
|
||||
}
|
||||
@ -290,8 +304,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
}
|
||||
|
||||
/* package */ int readData(int group, FormatHolder formatHolder, SampleHolder sampleHolder) {
|
||||
Assertions.checkState(prepared);
|
||||
|
||||
if (pendingResets[group] || isPendingReset()) {
|
||||
return TrackStream.NOTHING_READ;
|
||||
}
|
||||
@ -355,7 +367,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
|
||||
@Override
|
||||
public void seekToUs(long positionUs) {
|
||||
Assertions.checkState(prepared);
|
||||
if (enabledTrackCount == 0) {
|
||||
return;
|
||||
}
|
||||
@ -364,7 +375,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
|
||||
@Override
|
||||
public long getBufferedPositionUs() {
|
||||
Assertions.checkState(prepared);
|
||||
if (enabledTrackCount == 0) {
|
||||
return C.END_OF_SOURCE_US;
|
||||
} else if (isPendingReset()) {
|
||||
@ -391,6 +401,8 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
state = STATE_RELEASED;
|
||||
enabledTrackCount = 0;
|
||||
if (loader != null) {
|
||||
if (loadControlRegistered) {
|
||||
loadControl.unregister(this);
|
||||
@ -399,7 +411,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
loader.release();
|
||||
loader = null;
|
||||
}
|
||||
prepared = false;
|
||||
}
|
||||
|
||||
// Loader.Callback implementation.
|
||||
@ -680,7 +691,8 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
return;
|
||||
}
|
||||
|
||||
if (loader.isLoading() || !nextLoader || (prepared && enabledTrackCount == 0)) {
|
||||
if (loader.isLoading() || !nextLoader
|
||||
|| (state != STATE_UNPREPARED && enabledTrackCount == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -730,7 +742,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||
if (isPendingReset()) {
|
||||
return pendingResetPositionUs;
|
||||
} else {
|
||||
return loadingFinished || (prepared && enabledTrackCount == 0) ? -1
|
||||
return loadingFinished || (state != STATE_UNPREPARED && enabledTrackCount == 0) ? -1
|
||||
: currentTsLoadable != null ? currentTsLoadable.endTimeUs : previousTsLoadable.endTimeUs;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user