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.ExoPlayer.ExoPlayerComponent;
|
||||||
import com.google.android.exoplayer.TrackSelector.InvalidationListener;
|
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.PriorityHandlerThread;
|
||||||
import com.google.android.exoplayer.util.TraceUtil;
|
import com.google.android.exoplayer.util.TraceUtil;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
@ -31,7 +32,6 @@ import android.util.Log;
|
|||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,12 +68,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
private final StandaloneMediaClock standaloneMediaClock;
|
private final StandaloneMediaClock standaloneMediaClock;
|
||||||
private final long minBufferUs;
|
private final long minBufferUs;
|
||||||
private final long minRebufferUs;
|
private final long minRebufferUs;
|
||||||
private final TrackSelection[] trackSelections;
|
|
||||||
private final Handler handler;
|
private final Handler handler;
|
||||||
private final HandlerThread internalPlaybackThread;
|
private final HandlerThread internalPlaybackThread;
|
||||||
private final Handler eventHandler;
|
private final Handler eventHandler;
|
||||||
private final AtomicInteger pendingSeekCount;
|
private final AtomicInteger pendingSeekCount;
|
||||||
|
|
||||||
|
private TrackSelectionArray trackSelections;
|
||||||
private TrackRenderer rendererMediaClockSource;
|
private TrackRenderer rendererMediaClockSource;
|
||||||
private MediaClock rendererMediaClock;
|
private MediaClock rendererMediaClock;
|
||||||
private SampleSource source;
|
private SampleSource source;
|
||||||
@ -104,7 +104,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
this.bufferedPositionUs = C.UNKNOWN_TIME_US;
|
this.bufferedPositionUs = C.UNKNOWN_TIME_US;
|
||||||
standaloneMediaClock = new StandaloneMediaClock();
|
standaloneMediaClock = new StandaloneMediaClock();
|
||||||
pendingSeekCount = new AtomicInteger();
|
pendingSeekCount = new AtomicInteger();
|
||||||
trackSelections = new TrackSelection[renderers.length];
|
|
||||||
enabledRenderers = new TrackRenderer[0];
|
enabledRenderers = new TrackRenderer[0];
|
||||||
|
|
||||||
trackSelector.init(this);
|
trackSelector.init(this);
|
||||||
@ -281,7 +280,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
|
|
||||||
durationUs = source.getDurationUs();
|
durationUs = source.getDurationUs();
|
||||||
bufferedPositionUs = source.getBufferedPositionUs();
|
bufferedPositionUs = source.getBufferedPositionUs();
|
||||||
selectTracksInternal();
|
selectTracksInternal(true);
|
||||||
|
|
||||||
boolean allRenderersEnded = true;
|
boolean allRenderersEnded = true;
|
||||||
boolean allRenderersReadyOrEnded = true;
|
boolean allRenderersReadyOrEnded = true;
|
||||||
@ -478,21 +477,17 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
handler.removeMessages(MSG_DO_SOME_WORK);
|
handler.removeMessages(MSG_DO_SOME_WORK);
|
||||||
handler.removeMessages(MSG_INCREMENTAL_PREPARE);
|
handler.removeMessages(MSG_INCREMENTAL_PREPARE);
|
||||||
rebuffering = false;
|
rebuffering = false;
|
||||||
|
trackSelections = null;
|
||||||
standaloneMediaClock.stop();
|
standaloneMediaClock.stop();
|
||||||
if (renderers == null) {
|
rendererMediaClock = null;
|
||||||
return;
|
rendererMediaClockSource = null;
|
||||||
}
|
|
||||||
for (TrackRenderer renderer : renderers) {
|
|
||||||
resetRendererInternal(renderer);
|
|
||||||
}
|
|
||||||
enabledRenderers = new TrackRenderer[0];
|
enabledRenderers = new TrackRenderer[0];
|
||||||
Arrays.fill(trackSelections, null);
|
for (TrackRenderer renderer : renderers) {
|
||||||
source = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetRendererInternal(TrackRenderer renderer) {
|
|
||||||
try {
|
try {
|
||||||
ensureDisabled(renderer);
|
ensureStopped(renderer);
|
||||||
|
if (renderer.getState() == TrackRenderer.STATE_ENABLED) {
|
||||||
|
renderer.disable();
|
||||||
|
}
|
||||||
} catch (ExoPlaybackException e) {
|
} catch (ExoPlaybackException e) {
|
||||||
// There's nothing we can do.
|
// There's nothing we can do.
|
||||||
Log.e(TAG, "Stop failed.", e);
|
Log.e(TAG, "Stop failed.", e);
|
||||||
@ -501,6 +496,11 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
Log.e(TAG, "Stop failed.", e);
|
Log.e(TAG, "Stop failed.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (source != null) {
|
||||||
|
source.release();
|
||||||
|
source = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private <T> void sendMessageInternal(int what, Object obj)
|
private <T> void sendMessageInternal(int what, Object obj)
|
||||||
throws ExoPlaybackException {
|
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();
|
TrackGroupArray groups = source.getTrackGroups();
|
||||||
|
|
||||||
Pair<TrackSelectionArray, Object> result = trackSelector.selectTracks(renderers, groups);
|
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
|
// 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
|
// 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;
|
int enabledRendererCount = 0;
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
TrackRenderer renderer = renderers[i];
|
TrackRenderer renderer = renderers[i];
|
||||||
TrackSelection previousTrackSelection = trackSelections[i];
|
TrackSelection oldSelection = trackSelections == null ? null : trackSelections.get(i);
|
||||||
trackSelections[i] = newTrackSelections.get(i);
|
TrackSelection newSelection = newSelections.get(i);
|
||||||
if (trackSelections[i] != null) {
|
if (newSelection != null) {
|
||||||
enabledRendererCount++;
|
enabledRendererCount++;
|
||||||
}
|
}
|
||||||
rendererWasEnabledFlags[i] = renderer.getState() != TrackRenderer.STATE_DISABLED;
|
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.
|
// The track selection has changed for this renderer.
|
||||||
if (rendererWasEnabledFlags[i]) {
|
if (rendererWasEnabledFlags[i]) {
|
||||||
// We need to disable the renderer so that we can enable it with its new selection.
|
// We need to disable the renderer so that we can enable it with its new selection.
|
||||||
if (trackSelections[i] == null && renderer == rendererMediaClockSource) {
|
if (renderer == rendererMediaClockSource) {
|
||||||
// We've been using rendererMediaClockSource to advance the current position, but it's
|
// The renderer is providing the media clock.
|
||||||
// being disabled and won't be re-enabled. Sync standaloneMediaClock so that it can take
|
if (newSelection == null) {
|
||||||
|
// The renderer won't be re-enabled. Sync standaloneMediaClock so that it can take
|
||||||
// over timing responsibilities.
|
// over timing responsibilities.
|
||||||
standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs());
|
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);
|
trackSelector.onSelectionActivated(result.second);
|
||||||
|
trackSelections = newSelections;
|
||||||
enabledRenderers = new TrackRenderer[enabledRendererCount];
|
enabledRenderers = new TrackRenderer[enabledRendererCount];
|
||||||
enabledRendererCount = 0;
|
enabledRendererCount = 0;
|
||||||
|
|
||||||
// Enable renderers with their new track selections.
|
// Enable renderers with their new track selections.
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
TrackRenderer renderer = renderers[i];
|
TrackRenderer renderer = renderers[i];
|
||||||
TrackSelection trackSelection = trackSelections[i];
|
TrackSelection newSelection = trackSelections.get(i);
|
||||||
if (trackSelection != null) {
|
if (newSelection != null) {
|
||||||
enabledRenderers[enabledRendererCount++] = renderer;
|
enabledRenderers[enabledRendererCount++] = renderer;
|
||||||
if (renderer.getState() == TrackRenderer.STATE_DISABLED) {
|
if (renderer.getState() == TrackRenderer.STATE_DISABLED) {
|
||||||
// The renderer needs enabling with its new track selection.
|
// 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.
|
// Consider as joining only if the renderer was previously disabled.
|
||||||
boolean joining = !rendererWasEnabledFlags[i] && playing;
|
boolean joining = !rendererWasEnabledFlags[i] && playing;
|
||||||
// Enable the source and obtain the stream for the renderer to consume.
|
// 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.
|
// 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++) {
|
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.
|
// Enable the renderer.
|
||||||
renderer.enable(formats, trackStream, positionUs, joining);
|
renderer.enable(formats, trackStream, positionUs, joining);
|
||||||
@ -598,6 +620,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
source.endTrackSelection(positionUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reselectTracksInternal() throws ExoPlaybackException {
|
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.
|
// We don't have tracks yet, so we don't care.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
selectTracksInternal();
|
selectTracksInternal(false);
|
||||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
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 {
|
private void ensureStopped(TrackRenderer renderer) throws ExoPlaybackException {
|
||||||
if (renderer.getState() == TrackRenderer.STATE_STARTED) {
|
if (renderer.getState() == TrackRenderer.STATE_STARTED) {
|
||||||
renderer.stop();
|
renderer.stop();
|
||||||
|
@ -71,7 +71,7 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
private final long fileDescriptorOffset;
|
private final long fileDescriptorOffset;
|
||||||
private final long fileDescriptorLength;
|
private final long fileDescriptorLength;
|
||||||
|
|
||||||
private boolean prepared;
|
private int state;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
private MediaExtractor extractor;
|
private MediaExtractor extractor;
|
||||||
private TrackGroupArray tracks;
|
private TrackGroupArray tracks;
|
||||||
@ -97,6 +97,7 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
fileDescriptor = null;
|
fileDescriptor = null;
|
||||||
fileDescriptorOffset = 0;
|
fileDescriptorOffset = 0;
|
||||||
fileDescriptorLength = 0;
|
fileDescriptorLength = 0;
|
||||||
|
state = STATE_UNPREPARED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -116,13 +117,17 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
context = null;
|
context = null;
|
||||||
uri = null;
|
uri = null;
|
||||||
headers = null;
|
headers = null;
|
||||||
|
state = STATE_UNPREPARED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getState() {
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare(long positionUs) throws IOException {
|
public boolean prepare(long positionUs) throws IOException {
|
||||||
if (prepared) {
|
Assertions.checkState(state == STATE_UNPREPARED);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
extractor = new MediaExtractor();
|
extractor = new MediaExtractor();
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
extractor.setDataSource(context, uri, headers);
|
extractor.setDataSource(context, uri, headers);
|
||||||
@ -132,16 +137,16 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
durationUs = C.UNKNOWN_TIME_US;
|
durationUs = C.UNKNOWN_TIME_US;
|
||||||
trackStates = new int[extractor.getTrackCount()];
|
trackStates = new int[extractor.getTrackCount()];
|
||||||
pendingResets = new boolean[trackStates.length];
|
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++) {
|
for (int i = 0; i < trackStates.length; i++) {
|
||||||
MediaFormat format = extractor.getTrackFormat(i);
|
MediaFormat format = extractor.getTrackFormat(i);
|
||||||
if (format.containsKey(MediaFormat.KEY_DURATION)) {
|
if (format.containsKey(MediaFormat.KEY_DURATION)) {
|
||||||
durationUs = Math.max(durationUs, format.getLong(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);
|
tracks = new TrackGroupArray(trackArray);
|
||||||
prepared = true;
|
state = STATE_SELECTING_TRACKS;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,8 +166,14 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TrackStream enable(TrackSelection selection, long positionUs) {
|
public void startTrackSelection() {
|
||||||
Assertions.checkState(prepared);
|
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.length == 1);
|
||||||
Assertions.checkState(selection.getTrack(0) == 0);
|
Assertions.checkState(selection.getTrack(0) == 0);
|
||||||
int track = selection.group;
|
int track = selection.group;
|
||||||
@ -170,10 +181,29 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
enabledTrackCount++;
|
enabledTrackCount++;
|
||||||
trackStates[track] = TRACK_STATE_ENABLED;
|
trackStates[track] = TRACK_STATE_ENABLED;
|
||||||
extractor.selectTrack(track);
|
extractor.selectTrack(track);
|
||||||
seekToUsInternal(positionUs, positionUs != 0);
|
|
||||||
return new TrackStreamImpl(track);
|
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) {
|
/* package */ long readReset(int track) {
|
||||||
if (pendingResets[track]) {
|
if (pendingResets[track]) {
|
||||||
pendingResets[track] = false;
|
pendingResets[track] = false;
|
||||||
@ -183,7 +213,6 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* package */ int readData(int track, FormatHolder formatHolder, SampleHolder sampleHolder) {
|
/* package */ int readData(int track, FormatHolder formatHolder, SampleHolder sampleHolder) {
|
||||||
Assertions.checkState(prepared);
|
|
||||||
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
|
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
|
||||||
if (pendingResets[track]) {
|
if (pendingResets[track]) {
|
||||||
return TrackStream.NOTHING_READ;
|
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
|
@Override
|
||||||
public void seekToUs(long positionUs) {
|
public void seekToUs(long positionUs) {
|
||||||
Assertions.checkState(prepared);
|
|
||||||
if (enabledTrackCount == 0) {
|
if (enabledTrackCount == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -245,7 +262,6 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getBufferedPositionUs() {
|
public long getBufferedPositionUs() {
|
||||||
Assertions.checkState(prepared);
|
|
||||||
if (enabledTrackCount == 0) {
|
if (enabledTrackCount == 0) {
|
||||||
return C.END_OF_SOURCE_US;
|
return C.END_OF_SOURCE_US;
|
||||||
}
|
}
|
||||||
@ -261,11 +277,11 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
|
state = STATE_RELEASED;
|
||||||
if (extractor != null) {
|
if (extractor != null) {
|
||||||
extractor.release();
|
extractor.release();
|
||||||
extractor = null;
|
extractor = null;
|
||||||
}
|
}
|
||||||
prepared = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(18)
|
@TargetApi(18)
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer;
|
package com.google.android.exoplayer;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -28,45 +30,54 @@ public final class MultiSampleSource implements SampleSource {
|
|||||||
private final SampleSource[] sources;
|
private final SampleSource[] sources;
|
||||||
private final IdentityHashMap<TrackStream, Integer> trackStreamSourceIndices;
|
private final IdentityHashMap<TrackStream, Integer> trackStreamSourceIndices;
|
||||||
|
|
||||||
private boolean prepared;
|
private int state;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
private TrackGroupArray trackGroups;
|
private TrackGroupArray trackGroups;
|
||||||
|
|
||||||
public MultiSampleSource(SampleSource... sources) {
|
public MultiSampleSource(SampleSource... sources) {
|
||||||
this.sources = sources;
|
this.sources = sources;
|
||||||
trackStreamSourceIndices = new IdentityHashMap<>();
|
trackStreamSourceIndices = new IdentityHashMap<>();
|
||||||
|
state = STATE_UNPREPARED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getState() {
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare(long positionUs) throws IOException {
|
public boolean prepare(long positionUs) throws IOException {
|
||||||
if (prepared) {
|
Assertions.checkState(state == SampleSource.STATE_UNPREPARED);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
boolean sourcesPrepared = true;
|
boolean sourcesPrepared = true;
|
||||||
for (SampleSource source : sources) {
|
for (SampleSource source : sources) {
|
||||||
|
if (source.getState() == SampleSource.STATE_UNPREPARED) {
|
||||||
sourcesPrepared &= source.prepare(positionUs);
|
sourcesPrepared &= source.prepare(positionUs);
|
||||||
}
|
}
|
||||||
if (sourcesPrepared) {
|
}
|
||||||
prepared = true;
|
if (!sourcesPrepared) {
|
||||||
durationUs = C.UNKNOWN_TIME_US;
|
return false;
|
||||||
|
}
|
||||||
|
durationUs = 0;
|
||||||
int totalTrackGroupCount = 0;
|
int totalTrackGroupCount = 0;
|
||||||
for (SampleSource source : sources) {
|
for (SampleSource source : sources) {
|
||||||
totalTrackGroupCount += source.getTrackGroups().length;
|
totalTrackGroupCount += source.getTrackGroups().length;
|
||||||
if (source.getDurationUs() > durationUs) {
|
if (durationUs != C.UNKNOWN_TIME_US) {
|
||||||
durationUs = source.getDurationUs();
|
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;
|
int trackGroupIndex = 0;
|
||||||
for (SampleSource source : sources) {
|
for (SampleSource source : sources) {
|
||||||
int sourceTrackGroupCount = source.getTrackGroups().length;
|
int sourceTrackGroupCount = source.getTrackGroups().length;
|
||||||
for (int j = 0; j < sourceTrackGroupCount; j++) {
|
for (int j = 0; j < sourceTrackGroupCount; j++) {
|
||||||
trackGroups[trackGroupIndex++] = source.getTrackGroups().get(j);
|
trackGroupArray[trackGroupIndex++] = source.getTrackGroups().get(j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.trackGroups = new TrackGroupArray(trackGroups);
|
trackGroups = new TrackGroupArray(trackGroupArray);
|
||||||
}
|
state = STATE_SELECTING_TRACKS;
|
||||||
return prepared;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -75,18 +86,38 @@ public final class MultiSampleSource implements SampleSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
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);
|
new TrackSelection(sourceAndGroup.second, selection.getTracks()), positionUs);
|
||||||
trackStreamSourceIndices.put(trackStream, sourceAndGroup.first);
|
trackStreamSourceIndices.put(trackStream, sourceAndGroup.first);
|
||||||
return trackStream;
|
return trackStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disable(TrackStream trackStream) {
|
public void unselectTrack(TrackStream trackStream) {
|
||||||
|
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||||
int sourceIndex = trackStreamSourceIndices.remove(trackStream);
|
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
|
@Override
|
||||||
@ -129,7 +160,7 @@ public final class MultiSampleSource implements SampleSource {
|
|||||||
for (SampleSource source : sources) {
|
for (SampleSource source : sources) {
|
||||||
source.release();
|
source.release();
|
||||||
}
|
}
|
||||||
prepared = false;
|
state = STATE_RELEASED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pair<Integer, Integer> getSourceAndTrackGroupIndices(int group) {
|
private Pair<Integer, Integer> getSourceAndTrackGroupIndices(int group) {
|
||||||
|
@ -22,11 +22,39 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public interface SampleSource {
|
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.
|
* Prepares the source.
|
||||||
* <p>
|
* <p>
|
||||||
* If preparation cannot complete immediately then the call will return {@code false} rather than
|
* 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.
|
* @param positionUs The player's current playback position.
|
||||||
* @return True if the source was prepared, false otherwise.
|
* @return True if the source was prepared, false otherwise.
|
||||||
@ -37,7 +65,8 @@ public interface SampleSource {
|
|||||||
/**
|
/**
|
||||||
* Returns the duration of the source.
|
* Returns the duration of the source.
|
||||||
* <p>
|
* <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
|
* @return The duration of the source in microseconds, or {@link C#UNKNOWN_TIME_US} if the
|
||||||
* duration is not known.
|
* duration is not known.
|
||||||
@ -47,16 +76,63 @@ public interface SampleSource {
|
|||||||
/**
|
/**
|
||||||
* Returns the {@link TrackGroup}s exposed by the source.
|
* Returns the {@link TrackGroup}s exposed by the source.
|
||||||
* <p>
|
* <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.
|
* @return The {@link TrackGroup}s.
|
||||||
*/
|
*/
|
||||||
TrackGroupArray getTrackGroups();
|
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.
|
* Indicates to the source that it should continue buffering data for its enabled tracks.
|
||||||
* <p>
|
* <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.
|
* @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.
|
* Returns an estimate of the position up to which data is buffered for the enabled tracks.
|
||||||
* <p>
|
* <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,
|
* @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}
|
* 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.
|
* Seeks to the specified time in microseconds.
|
||||||
* <p>
|
* <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.
|
* @param positionUs The seek position in microseconds.
|
||||||
*/
|
*/
|
||||||
void seekToUs(long positionUs);
|
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.
|
* Releases the source.
|
||||||
* <p>
|
* <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();
|
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 INITIAL_SAMPLE_SIZE = 1;
|
||||||
|
|
||||||
private static final int STATE_SEND_FORMAT = 0;
|
private static final int STREAM_STATE_SEND_FORMAT = 0;
|
||||||
private static final int STATE_SEND_SAMPLE = 1;
|
private static final int STREAM_STATE_SEND_SAMPLE = 1;
|
||||||
private static final int STATE_END_OF_STREAM = 2;
|
private static final int STREAM_STATE_END_OF_STREAM = 2;
|
||||||
|
|
||||||
private final Uri uri;
|
private final Uri uri;
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
@ -74,9 +74,6 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
private final int eventSourceId;
|
private final int eventSourceId;
|
||||||
|
|
||||||
private int state;
|
private int state;
|
||||||
private byte[] sampleData;
|
|
||||||
private int sampleSize;
|
|
||||||
|
|
||||||
private long pendingResetPositionUs;
|
private long pendingResetPositionUs;
|
||||||
private boolean loadingFinished;
|
private boolean loadingFinished;
|
||||||
private Loader loader;
|
private Loader loader;
|
||||||
@ -84,6 +81,10 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
private int currentLoadableExceptionCount;
|
private int currentLoadableExceptionCount;
|
||||||
private long currentLoadableExceptionTimestamp;
|
private long currentLoadableExceptionTimestamp;
|
||||||
|
|
||||||
|
private int streamState;
|
||||||
|
private byte[] sampleData;
|
||||||
|
private int sampleSize;
|
||||||
|
|
||||||
public SingleSampleSource(Uri uri, DataSource dataSource, Format format, long durationUs) {
|
public SingleSampleSource(Uri uri, DataSource dataSource, Format format, long durationUs) {
|
||||||
this(uri, dataSource, format, durationUs, DEFAULT_MIN_LOADABLE_RETRY_COUNT);
|
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;
|
this.eventSourceId = eventSourceId;
|
||||||
tracks = new TrackGroupArray(new TrackGroup(format));
|
tracks = new TrackGroupArray(new TrackGroup(format));
|
||||||
sampleData = new byte[INITIAL_SAMPLE_SIZE];
|
sampleData = new byte[INITIAL_SAMPLE_SIZE];
|
||||||
|
state = STATE_UNPREPARED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getState() {
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -117,9 +124,9 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare(long positionUs) {
|
public boolean prepare(long positionUs) {
|
||||||
if (loader == null) {
|
Assertions.checkState(state == STATE_UNPREPARED);
|
||||||
|
state = STATE_SELECTING_TRACKS;
|
||||||
loader = new Loader("Loader:" + format.sampleMimeType);
|
loader = new Loader("Loader:" + format.sampleMimeType);
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,14 +141,33 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TrackStream enable(TrackSelection selection, long positionUs) {
|
public void startTrackSelection() {
|
||||||
state = STATE_SEND_FORMAT;
|
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;
|
pendingResetPositionUs = NO_RESET;
|
||||||
clearCurrentLoadableException();
|
clearCurrentLoadableException();
|
||||||
maybeStartLoading();
|
maybeStartLoading();
|
||||||
return this;
|
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
|
@Override
|
||||||
public void continueBuffering(long positionUs) {
|
public void continueBuffering(long positionUs) {
|
||||||
maybeStartLoading();
|
maybeStartLoading();
|
||||||
@ -161,16 +187,16 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
|
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);
|
sampleHolder.addFlag(C.SAMPLE_FLAG_END_OF_STREAM);
|
||||||
return END_OF_STREAM;
|
return END_OF_STREAM;
|
||||||
} else if (state == STATE_SEND_FORMAT) {
|
} else if (streamState == STREAM_STATE_SEND_FORMAT) {
|
||||||
formatHolder.format = format;
|
formatHolder.format = format;
|
||||||
state = STATE_SEND_SAMPLE;
|
streamState = STREAM_STATE_SEND_SAMPLE;
|
||||||
return FORMAT_READ;
|
return FORMAT_READ;
|
||||||
}
|
}
|
||||||
|
|
||||||
Assertions.checkState(state == STATE_SEND_SAMPLE);
|
Assertions.checkState(streamState == STREAM_STATE_SEND_SAMPLE);
|
||||||
if (!loadingFinished) {
|
if (!loadingFinished) {
|
||||||
return NOTHING_READ;
|
return NOTHING_READ;
|
||||||
} else {
|
} else {
|
||||||
@ -179,31 +205,28 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
sampleHolder.addFlag(C.SAMPLE_FLAG_SYNC);
|
sampleHolder.addFlag(C.SAMPLE_FLAG_SYNC);
|
||||||
sampleHolder.ensureSpaceForWrite(sampleHolder.size);
|
sampleHolder.ensureSpaceForWrite(sampleHolder.size);
|
||||||
sampleHolder.data.put(sampleData, 0, sampleSize);
|
sampleHolder.data.put(sampleData, 0, sampleSize);
|
||||||
state = STATE_END_OF_STREAM;
|
streamState = STREAM_STATE_END_OF_STREAM;
|
||||||
return SAMPLE_READ;
|
return SAMPLE_READ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seekToUs(long positionUs) {
|
public void seekToUs(long positionUs) {
|
||||||
if (state == STATE_END_OF_STREAM) {
|
if (streamState == STREAM_STATE_END_OF_STREAM) {
|
||||||
pendingResetPositionUs = positionUs;
|
pendingResetPositionUs = positionUs;
|
||||||
state = STATE_SEND_SAMPLE;
|
streamState = STREAM_STATE_SEND_SAMPLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getBufferedPositionUs() {
|
public long getBufferedPositionUs() {
|
||||||
return state == STATE_END_OF_STREAM || loadingFinished ? C.END_OF_SOURCE_US : 0;
|
return streamState == STREAM_STATE_END_OF_STREAM || loadingFinished ? C.END_OF_SOURCE_US : 0;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disable(TrackStream trackStream) {
|
|
||||||
state = STATE_END_OF_STREAM;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
|
state = STATE_RELEASED;
|
||||||
|
streamState = STREAM_STATE_END_OF_STREAM;
|
||||||
if (loader != null) {
|
if (loader != null) {
|
||||||
loader.release();
|
loader.release();
|
||||||
loader = null;
|
loader = null;
|
||||||
@ -213,7 +236,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
// Private methods.
|
// Private methods.
|
||||||
|
|
||||||
private void maybeStartLoading() {
|
private void maybeStartLoading() {
|
||||||
if (loadingFinished || state == STATE_END_OF_STREAM || loader.isLoading()) {
|
if (loadingFinished || streamState == STREAM_STATE_END_OF_STREAM || loader.isLoading()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currentLoadableException != null) {
|
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;
|
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 static final long NO_RESET_PENDING = Long.MIN_VALUE;
|
||||||
|
|
||||||
private final int eventSourceId;
|
private final int eventSourceId;
|
||||||
@ -83,8 +79,8 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
private long durationUs;
|
private long durationUs;
|
||||||
private Loader loader;
|
private Loader loader;
|
||||||
private boolean loadingFinished;
|
private boolean loadingFinished;
|
||||||
|
private boolean trackEnabled;
|
||||||
private IOException currentLoadableException;
|
private IOException currentLoadableException;
|
||||||
private int enabledTrackCount;
|
|
||||||
private int currentLoadableExceptionCount;
|
private int currentLoadableExceptionCount;
|
||||||
private long currentLoadableExceptionTimestamp;
|
private long currentLoadableExceptionTimestamp;
|
||||||
private long currentLoadStartTimeMs;
|
private long currentLoadStartTimeMs;
|
||||||
@ -143,27 +139,30 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
mediaChunks = new LinkedList<>();
|
mediaChunks = new LinkedList<>();
|
||||||
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
|
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
|
||||||
sampleQueue = new DefaultTrackOutput(loadControl.getAllocator());
|
sampleQueue = new DefaultTrackOutput(loadControl.getAllocator());
|
||||||
state = STATE_IDLE;
|
|
||||||
pendingResetPositionUs = NO_RESET_PENDING;
|
pendingResetPositionUs = NO_RESET_PENDING;
|
||||||
|
state = STATE_UNPREPARED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getState() {
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare(long positionUs) throws IOException {
|
public boolean prepare(long positionUs) throws IOException {
|
||||||
if (state != STATE_IDLE) {
|
Assertions.checkState(state == STATE_UNPREPARED);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!chunkSource.prepare()) {
|
if (!chunkSource.prepare()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
durationUs = chunkSource.getDurationUs();
|
durationUs = chunkSource.getDurationUs();
|
||||||
TrackGroup trackGroup = chunkSource.getTracks();
|
TrackGroup tracks = chunkSource.getTracks();
|
||||||
if (trackGroup != null) {
|
if (tracks != null) {
|
||||||
loader = new Loader("Loader:" + trackGroup.getFormat(0).containerMimeType);
|
loader = new Loader("Loader:" + tracks.getFormat(0).containerMimeType);
|
||||||
trackGroups = new TrackGroupArray(trackGroup);
|
trackGroups = new TrackGroupArray(tracks);
|
||||||
} else {
|
} else {
|
||||||
trackGroups = new TrackGroupArray();
|
trackGroups = new TrackGroupArray();
|
||||||
}
|
}
|
||||||
state = STATE_PREPARED;
|
state = STATE_SELECTING_TRACKS;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,15 +173,20 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TrackGroupArray getTrackGroups() {
|
public TrackGroupArray getTrackGroups() {
|
||||||
Assertions.checkState(state != STATE_IDLE);
|
|
||||||
return trackGroups;
|
return trackGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TrackStream enable(TrackSelection selection, long positionUs) {
|
public void startTrackSelection() {
|
||||||
Assertions.checkState(state == STATE_PREPARED);
|
Assertions.checkState(state == STATE_READING);
|
||||||
Assertions.checkState(enabledTrackCount++ == 0);
|
state = STATE_SELECTING_TRACKS;
|
||||||
state = STATE_ENABLED;
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TrackStream selectTrack(TrackSelection selection, long positionUs) {
|
||||||
|
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||||
|
Assertions.checkState(!trackEnabled);
|
||||||
|
trackEnabled = true;
|
||||||
chunkSource.enable(selection.getTracks());
|
chunkSource.enable(selection.getTracks());
|
||||||
loadControl.register(this, bufferSizeContribution);
|
loadControl.register(this, bufferSizeContribution);
|
||||||
downstreamFormat = null;
|
downstreamFormat = null;
|
||||||
@ -195,10 +199,10 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disable(TrackStream trackStream) {
|
public void unselectTrack(TrackStream stream) {
|
||||||
Assertions.checkState(state == STATE_ENABLED);
|
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||||
Assertions.checkState(--enabledTrackCount == 0);
|
Assertions.checkState(trackEnabled);
|
||||||
state = STATE_PREPARED;
|
trackEnabled = false;
|
||||||
try {
|
try {
|
||||||
chunkSource.disable();
|
chunkSource.disable();
|
||||||
} finally {
|
} finally {
|
||||||
@ -215,11 +219,13 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void continueBuffering(long positionUs) {
|
public void endTrackSelection(long positionUs) {
|
||||||
Assertions.checkState(state != STATE_IDLE);
|
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||||
if (state == STATE_PREPARED) {
|
state = STATE_READING;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void continueBuffering(long positionUs) {
|
||||||
downstreamPositionUs = positionUs;
|
downstreamPositionUs = positionUs;
|
||||||
chunkSource.continueBuffering(positionUs);
|
chunkSource.continueBuffering(positionUs);
|
||||||
updateLoadControl();
|
updateLoadControl();
|
||||||
@ -227,7 +233,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isReady() {
|
public boolean isReady() {
|
||||||
Assertions.checkState(state == STATE_ENABLED);
|
|
||||||
return loadingFinished || !sampleQueue.isEmpty();
|
return loadingFinished || !sampleQueue.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,7 +247,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
|
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
|
||||||
Assertions.checkState(state == STATE_ENABLED);
|
|
||||||
if (pendingReset || isPendingReset()) {
|
if (pendingReset || isPendingReset()) {
|
||||||
return NOTHING_READ;
|
return NOTHING_READ;
|
||||||
}
|
}
|
||||||
@ -292,10 +296,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seekToUs(long positionUs) {
|
public void seekToUs(long positionUs) {
|
||||||
Assertions.checkState(state != STATE_IDLE);
|
|
||||||
if (state == STATE_PREPARED) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
downstreamPositionUs = positionUs;
|
downstreamPositionUs = positionUs;
|
||||||
lastSeekPositionUs = positionUs;
|
lastSeekPositionUs = positionUs;
|
||||||
// If we're not pending a reset, see if we can seek within the sample queue.
|
// 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
|
@Override
|
||||||
public long getBufferedPositionUs() {
|
public long getBufferedPositionUs() {
|
||||||
Assertions.checkState(state != STATE_IDLE);
|
if (!trackEnabled || loadingFinished) {
|
||||||
if (state != STATE_ENABLED || loadingFinished) {
|
|
||||||
return C.END_OF_SOURCE_US;
|
return C.END_OF_SOURCE_US;
|
||||||
} else if (isPendingReset()) {
|
} else if (isPendingReset()) {
|
||||||
return pendingResetPositionUs;
|
return pendingResetPositionUs;
|
||||||
@ -340,12 +339,12 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
Assertions.checkState(state != STATE_ENABLED);
|
state = STATE_RELEASED;
|
||||||
|
trackEnabled = false;
|
||||||
if (loader != null) {
|
if (loader != null) {
|
||||||
loader.release();
|
loader.release();
|
||||||
loader = null;
|
loader = null;
|
||||||
}
|
}
|
||||||
state = STATE_IDLE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -371,7 +370,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
Chunk currentLoadable = currentLoadableHolder.chunk;
|
Chunk currentLoadable = currentLoadableHolder.chunk;
|
||||||
notifyLoadCanceled(currentLoadable.bytesLoaded());
|
notifyLoadCanceled(currentLoadable.bytesLoaded());
|
||||||
clearCurrentLoadable();
|
clearCurrentLoadable();
|
||||||
if (state == STATE_ENABLED) {
|
if (trackEnabled) {
|
||||||
restartFrom(pendingResetPositionUs);
|
restartFrom(pendingResetPositionUs);
|
||||||
} else {
|
} else {
|
||||||
sampleQueue.clear();
|
sampleQueue.clear();
|
||||||
|
@ -208,13 +208,15 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
private volatile SeekMap seekMap;
|
private volatile SeekMap seekMap;
|
||||||
private volatile DrmInitData drmInitData;
|
private volatile DrmInitData drmInitData;
|
||||||
|
|
||||||
private boolean prepared;
|
private int state;
|
||||||
private int enabledTrackCount;
|
private int enabledTrackCount;
|
||||||
private TrackGroupArray tracks;
|
private TrackGroupArray tracks;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
private boolean[] pendingMediaFormat;
|
private boolean[] pendingMediaFormat;
|
||||||
private boolean[] pendingResets;
|
private boolean[] pendingResets;
|
||||||
private boolean[] trackEnabledStates;
|
private boolean[] trackEnabledStates;
|
||||||
|
private boolean isFirstTrackSelection;
|
||||||
|
private boolean newTracksSelected;
|
||||||
|
|
||||||
private long downstreamPositionUs;
|
private long downstreamPositionUs;
|
||||||
private long lastSeekPositionUs;
|
private long lastSeekPositionUs;
|
||||||
@ -325,37 +327,41 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
extractorHolder = new ExtractorHolder(extractors, this);
|
extractorHolder = new ExtractorHolder(extractors, this);
|
||||||
sampleQueues = new SparseArray<>();
|
sampleQueues = new SparseArray<>();
|
||||||
pendingResetPositionUs = NO_RESET_PENDING;
|
pendingResetPositionUs = NO_RESET_PENDING;
|
||||||
|
state = STATE_UNPREPARED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getState() {
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare(long positionUs) {
|
public boolean prepare(long positionUs) {
|
||||||
if (prepared) {
|
Assertions.checkState(state == STATE_UNPREPARED);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (loader == null) {
|
if (loader == null) {
|
||||||
loader = new Loader("Loader:ExtractorSampleSource");
|
loader = new Loader("Loader:ExtractorSampleSource");
|
||||||
}
|
}
|
||||||
|
|
||||||
maybeStartLoading();
|
maybeStartLoading();
|
||||||
|
if (seekMap == null || !tracksBuilt || !haveFormatsForAllTracks()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (seekMap != null && tracksBuilt && haveFormatsForAllTracks()) {
|
|
||||||
int trackCount = sampleQueues.size();
|
int trackCount = sampleQueues.size();
|
||||||
TrackGroup[] tracks = new TrackGroup[trackCount];
|
TrackGroup[] trackArray = new TrackGroup[trackCount];
|
||||||
trackEnabledStates = new boolean[trackCount];
|
trackEnabledStates = new boolean[trackCount];
|
||||||
pendingResets = new boolean[trackCount];
|
pendingResets = new boolean[trackCount];
|
||||||
pendingMediaFormat = new boolean[trackCount];
|
pendingMediaFormat = new boolean[trackCount];
|
||||||
durationUs = seekMap.getDurationUs();
|
durationUs = seekMap.getDurationUs();
|
||||||
for (int i = 0; i < trackCount; i++) {
|
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);
|
tracks = new TrackGroupArray(trackArray);
|
||||||
prepared = true;
|
isFirstTrackSelection = true;
|
||||||
|
state = STATE_SELECTING_TRACKS;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getDurationUs() {
|
public long getDurationUs() {
|
||||||
return durationUs;
|
return durationUs;
|
||||||
@ -367,8 +373,16 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TrackStream enable(TrackSelection selection, long positionUs) {
|
public void startTrackSelection() {
|
||||||
Assertions.checkState(prepared);
|
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.length == 1);
|
||||||
Assertions.checkState(selection.getTrack(0) == 0);
|
Assertions.checkState(selection.getTrack(0) == 0);
|
||||||
int track = selection.group;
|
int track = selection.group;
|
||||||
@ -377,23 +391,23 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
trackEnabledStates[track] = true;
|
trackEnabledStates[track] = true;
|
||||||
pendingMediaFormat[track] = true;
|
pendingMediaFormat[track] = true;
|
||||||
pendingResets[track] = false;
|
pendingResets[track] = false;
|
||||||
if (enabledTrackCount == 1) {
|
newTracksSelected = true;
|
||||||
// Treat all enables in non-seekable media as being from t=0.
|
|
||||||
positionUs = !seekMap.isSeekable() ? 0 : positionUs;
|
|
||||||
downstreamPositionUs = positionUs;
|
|
||||||
lastSeekPositionUs = positionUs;
|
|
||||||
restartFrom(positionUs);
|
|
||||||
}
|
|
||||||
return new TrackStreamImpl(track);
|
return new TrackStreamImpl(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disable(TrackStream trackStream) {
|
public void unselectTrack(TrackStream stream) {
|
||||||
Assertions.checkState(prepared);
|
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||||
int track = ((TrackStreamImpl) trackStream).track;
|
int track = ((TrackStreamImpl) stream).track;
|
||||||
Assertions.checkState(trackEnabledStates[track]);
|
Assertions.checkState(trackEnabledStates[track]);
|
||||||
enabledTrackCount--;
|
enabledTrackCount--;
|
||||||
trackEnabledStates[track] = false;
|
trackEnabledStates[track] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endTrackSelection(long positionUs) {
|
||||||
|
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||||
|
state = STATE_READING;
|
||||||
if (enabledTrackCount == 0) {
|
if (enabledTrackCount == 0) {
|
||||||
downstreamPositionUs = Long.MIN_VALUE;
|
downstreamPositionUs = Long.MIN_VALUE;
|
||||||
if (loader.isLoading()) {
|
if (loader.isLoading()) {
|
||||||
@ -402,12 +416,13 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
clearState();
|
clearState();
|
||||||
allocator.trim(0);
|
allocator.trim(0);
|
||||||
}
|
}
|
||||||
|
} else if (!isFirstTrackSelection && newTracksSelected) {
|
||||||
|
seekToInternal(positionUs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void continueBuffering(long playbackPositionUs) {
|
public void continueBuffering(long playbackPositionUs) {
|
||||||
Assertions.checkState(prepared);
|
|
||||||
if (enabledTrackCount == 0) {
|
if (enabledTrackCount == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -420,7 +435,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* package */ boolean isReady(int track) {
|
/* package */ boolean isReady(int track) {
|
||||||
Assertions.checkState(prepared);
|
|
||||||
Assertions.checkState(trackEnabledStates[track]);
|
Assertions.checkState(trackEnabledStates[track]);
|
||||||
return !sampleQueues.valueAt(track).isEmpty();
|
return !sampleQueues.valueAt(track).isEmpty();
|
||||||
|
|
||||||
@ -490,13 +504,18 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seekToUs(long positionUs) {
|
public void seekToUs(long positionUs) {
|
||||||
Assertions.checkState(prepared);
|
|
||||||
if (enabledTrackCount == 0) {
|
if (enabledTrackCount == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
seekToInternal(positionUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seekToInternal(long positionUs) {
|
||||||
// Treat all seeks into non-seekable media as being to t=0.
|
// Treat all seeks into non-seekable media as being to t=0.
|
||||||
downstreamPositionUs = !seekMap.isSeekable() ? 0 : positionUs;
|
positionUs = !seekMap.isSeekable() ? 0 : positionUs;
|
||||||
lastSeekPositionUs = downstreamPositionUs;
|
lastSeekPositionUs = positionUs;
|
||||||
|
downstreamPositionUs = positionUs;
|
||||||
|
Arrays.fill(pendingResets, true);
|
||||||
// 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 sample queues.
|
||||||
boolean seekInsideBuffer = !isPendingReset();
|
boolean seekInsideBuffer = !isPendingReset();
|
||||||
for (int i = 0; seekInsideBuffer && i < sampleQueues.size(); i++) {
|
for (int i = 0; seekInsideBuffer && i < sampleQueues.size(); i++) {
|
||||||
@ -506,9 +525,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
if (!seekInsideBuffer) {
|
if (!seekInsideBuffer) {
|
||||||
restartFrom(positionUs);
|
restartFrom(positionUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Either way, we need to send discontinuities to the downstream components.
|
|
||||||
Arrays.fill(pendingResets, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -530,11 +546,12 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
|
state = STATE_RELEASED;
|
||||||
|
enabledTrackCount = 0;
|
||||||
if (loader != null) {
|
if (loader != null) {
|
||||||
loader.release();
|
loader.release();
|
||||||
loader = null;
|
loader = null;
|
||||||
}
|
}
|
||||||
prepared = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loader.Callback implementation.
|
// Loader.Callback implementation.
|
||||||
@ -617,7 +634,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
long elapsedMillis = SystemClock.elapsedRealtime() - currentLoadableExceptionTimestamp;
|
long elapsedMillis = SystemClock.elapsedRealtime() - currentLoadableExceptionTimestamp;
|
||||||
if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) {
|
if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) {
|
||||||
currentLoadableException = null;
|
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 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
|
// 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.
|
// stream, play it safe and load from the start.
|
||||||
@ -654,7 +671,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
sampleTimeOffsetUs = 0;
|
sampleTimeOffsetUs = 0;
|
||||||
havePendingNextSampleUs = false;
|
havePendingNextSampleUs = false;
|
||||||
|
|
||||||
if (!prepared) {
|
if (state == STATE_UNPREPARED) {
|
||||||
loadable = createLoadableFromStart();
|
loadable = createLoadableFromStart();
|
||||||
} else {
|
} else {
|
||||||
Assertions.checkState(isPendingReset());
|
Assertions.checkState(isPendingReset());
|
||||||
@ -836,6 +853,8 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
allocator.blockWhileTotalBytesAllocatedExceeds(requestedBufferSize);
|
allocator.blockWhileTotalBytesAllocatedExceeds(requestedBufferSize);
|
||||||
result = extractor.read(input, positionHolder);
|
result = extractor.read(input, positionHolder);
|
||||||
// TODO: Implement throttling to stop us from buffering data too often.
|
// 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 {
|
} finally {
|
||||||
if (result == Extractor.RESULT_SEEK) {
|
if (result == Extractor.RESULT_SEEK) {
|
||||||
|
@ -73,7 +73,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
private final Handler eventHandler;
|
private final Handler eventHandler;
|
||||||
private final EventListener eventListener;
|
private final EventListener eventListener;
|
||||||
|
|
||||||
private boolean prepared;
|
private int state;
|
||||||
private boolean loadControlRegistered;
|
private boolean loadControlRegistered;
|
||||||
private int enabledTrackCount;
|
private int enabledTrackCount;
|
||||||
|
|
||||||
@ -84,6 +84,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
private TrackGroupArray trackGroups;
|
private TrackGroupArray trackGroups;
|
||||||
private int primaryTrackGroupIndex;
|
private int primaryTrackGroupIndex;
|
||||||
private int[] primarySelectedTracks;
|
private int[] primarySelectedTracks;
|
||||||
|
private boolean primarySelectedTracksChanged;
|
||||||
// Indexed by group.
|
// Indexed by group.
|
||||||
private boolean[] groupEnabledStates;
|
private boolean[] groupEnabledStates;
|
||||||
private boolean[] pendingResets;
|
private boolean[] pendingResets;
|
||||||
@ -131,15 +132,20 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
chunkOperationHolder = new ChunkOperationHolder();
|
chunkOperationHolder = new ChunkOperationHolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare(long positionUs) throws IOException {
|
public boolean prepare(long positionUs) throws IOException {
|
||||||
if (prepared) {
|
Assertions.checkState(state == STATE_UNPREPARED);
|
||||||
return true;
|
if (!chunkSource.prepare()) {
|
||||||
} else if (!chunkSource.prepare()) {
|
|
||||||
return false;
|
return false;
|
||||||
} else if (chunkSource.getTrackCount() == 0) {
|
}
|
||||||
|
if (chunkSource.getTrackCount() == 0) {
|
||||||
trackGroups = new TrackGroupArray();
|
trackGroups = new TrackGroupArray();
|
||||||
prepared = true;
|
state = STATE_SELECTING_TRACKS;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!extractors.isEmpty()) {
|
if (!extractors.isEmpty()) {
|
||||||
@ -148,7 +154,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
HlsExtractorWrapper extractor = extractors.getFirst();
|
HlsExtractorWrapper extractor = extractors.getFirst();
|
||||||
if (extractor.isPrepared()) {
|
if (extractor.isPrepared()) {
|
||||||
buildTracks(extractor);
|
buildTracks(extractor);
|
||||||
prepared = true;
|
state = STATE_SELECTING_TRACKS;
|
||||||
maybeStartLoading(); // Update the load control.
|
maybeStartLoading(); // Update the load control.
|
||||||
return true;
|
return true;
|
||||||
} else if (extractors.size() > 1) {
|
} else if (extractors.size() > 1) {
|
||||||
@ -183,58 +189,48 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TrackGroupArray getTrackGroups() {
|
public TrackGroupArray getTrackGroups() {
|
||||||
Assertions.checkState(prepared);
|
|
||||||
return trackGroups;
|
return trackGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TrackStream enable(TrackSelection selection, long positionUs) {
|
public void startTrackSelection() {
|
||||||
Assertions.checkState(prepared);
|
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 group = selection.group;
|
||||||
int[] tracks = selection.getTracks();
|
int[] tracks = selection.getTracks();
|
||||||
setTrackGroupEnabledState(group, true);
|
setTrackGroupEnabledState(group, true);
|
||||||
downstreamSampleFormats[group] = null;
|
downstreamSampleFormats[group] = null;
|
||||||
pendingResets[group] = false;
|
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)) {
|
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;
|
primarySelectedTracks = tracks;
|
||||||
seekToInternal(positionUs);
|
primarySelectedTracksChanged = true;
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return new TrackStreamImpl(group);
|
return new TrackStreamImpl(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disable(TrackStream trackStream) {
|
public void unselectTrack(TrackStream stream) {
|
||||||
Assertions.checkState(prepared);
|
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||||
int group = ((TrackStreamImpl) trackStream).group;
|
int group = ((TrackStreamImpl) stream).group;
|
||||||
setTrackGroupEnabledState(group, false);
|
setTrackGroupEnabledState(group, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endTrackSelection(long positionUs) {
|
||||||
|
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
||||||
|
state = STATE_READING;
|
||||||
if (enabledTrackCount == 0) {
|
if (enabledTrackCount == 0) {
|
||||||
chunkSource.reset();
|
chunkSource.reset();
|
||||||
downstreamPositionUs = Long.MIN_VALUE;
|
downstreamPositionUs = Long.MIN_VALUE;
|
||||||
|
downstreamFormat = null;
|
||||||
|
if (loader != null) {
|
||||||
if (loadControlRegistered) {
|
if (loadControlRegistered) {
|
||||||
loadControl.unregister(this);
|
loadControl.unregister(this);
|
||||||
loadControlRegistered = false;
|
loadControlRegistered = false;
|
||||||
@ -246,11 +242,29 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
loadControl.trimAllocator();
|
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
|
@Override
|
||||||
public void continueBuffering(long playbackPositionUs) {
|
public void continueBuffering(long playbackPositionUs) {
|
||||||
Assertions.checkState(prepared);
|
|
||||||
if (enabledTrackCount == 0) {
|
if (enabledTrackCount == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -290,8 +304,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* package */ int readData(int group, FormatHolder formatHolder, SampleHolder sampleHolder) {
|
/* package */ int readData(int group, FormatHolder formatHolder, SampleHolder sampleHolder) {
|
||||||
Assertions.checkState(prepared);
|
|
||||||
|
|
||||||
if (pendingResets[group] || isPendingReset()) {
|
if (pendingResets[group] || isPendingReset()) {
|
||||||
return TrackStream.NOTHING_READ;
|
return TrackStream.NOTHING_READ;
|
||||||
}
|
}
|
||||||
@ -355,7 +367,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seekToUs(long positionUs) {
|
public void seekToUs(long positionUs) {
|
||||||
Assertions.checkState(prepared);
|
|
||||||
if (enabledTrackCount == 0) {
|
if (enabledTrackCount == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -364,7 +375,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getBufferedPositionUs() {
|
public long getBufferedPositionUs() {
|
||||||
Assertions.checkState(prepared);
|
|
||||||
if (enabledTrackCount == 0) {
|
if (enabledTrackCount == 0) {
|
||||||
return C.END_OF_SOURCE_US;
|
return C.END_OF_SOURCE_US;
|
||||||
} else if (isPendingReset()) {
|
} else if (isPendingReset()) {
|
||||||
@ -391,6 +401,8 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
|
state = STATE_RELEASED;
|
||||||
|
enabledTrackCount = 0;
|
||||||
if (loader != null) {
|
if (loader != null) {
|
||||||
if (loadControlRegistered) {
|
if (loadControlRegistered) {
|
||||||
loadControl.unregister(this);
|
loadControl.unregister(this);
|
||||||
@ -399,7 +411,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
loader.release();
|
loader.release();
|
||||||
loader = null;
|
loader = null;
|
||||||
}
|
}
|
||||||
prepared = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loader.Callback implementation.
|
// Loader.Callback implementation.
|
||||||
@ -680,7 +691,8 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loader.isLoading() || !nextLoader || (prepared && enabledTrackCount == 0)) {
|
if (loader.isLoading() || !nextLoader
|
||||||
|
|| (state != STATE_UNPREPARED && enabledTrackCount == 0)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -730,7 +742,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
if (isPendingReset()) {
|
if (isPendingReset()) {
|
||||||
return pendingResetPositionUs;
|
return pendingResetPositionUs;
|
||||||
} else {
|
} else {
|
||||||
return loadingFinished || (prepared && enabledTrackCount == 0) ? -1
|
return loadingFinished || (state != STATE_UNPREPARED && enabledTrackCount == 0) ? -1
|
||||||
: currentTsLoadable != null ? currentTsLoadable.endTimeUs : previousTsLoadable.endTimeUs;
|
: currentTsLoadable != null ? currentTsLoadable.endTimeUs : previousTsLoadable.endTimeUs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user