Refine fix for limbo state.
This fixes some nuances with the initial solution. Mainly, that the TrackStreams returned by selectTrack could not be used safely until after endTrackSelection was invoked. It also reduces the need for member variables to track state between the track selection methods. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=118556651
This commit is contained in:
parent
305d8aa049
commit
d8e6b096c4
@ -17,7 +17,6 @@ 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;
|
||||||
@ -32,6 +31,7 @@ import android.util.Log;
|
|||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -281,7 +281,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
}
|
}
|
||||||
|
|
||||||
durationUs = source.getDurationUs();
|
durationUs = source.getDurationUs();
|
||||||
selectTracksInternal(true);
|
selectTracksInternal();
|
||||||
|
|
||||||
boolean allRenderersEnded = true;
|
boolean allRenderersEnded = true;
|
||||||
boolean allRenderersReadyOrEnded = true;
|
boolean allRenderersReadyOrEnded = true;
|
||||||
@ -531,38 +531,28 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectTracksInternal(boolean initialSelection) throws ExoPlaybackException {
|
private void selectTracksInternal() 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 newSelections = result.first;
|
TrackSelectionArray newTrackSelections = result.first;
|
||||||
|
|
||||||
if (newSelections.equals(trackSelections)) {
|
if (newTrackSelections.equals(trackSelections)) {
|
||||||
trackSelector.onSelectionActivated(result.second);
|
trackSelector.onSelectionActivated(result.second);
|
||||||
// No changes to the track selections.
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initialSelection) {
|
// Disable any renderers whose selections have changed, adding the corresponding TrackStream
|
||||||
Assertions.checkState(source.getState() == SampleSource.STATE_SELECTING_TRACKS);
|
// instances to oldStreams. Where we need to obtain a new TrackStream instance for a renderer,
|
||||||
} else {
|
// we add the corresponding TrackSelection to newSelections.
|
||||||
Assertions.checkState(source.getState() == SampleSource.STATE_READING);
|
ArrayList<TrackStream> oldStreams = new ArrayList<>();
|
||||||
source.startTrackSelection();
|
ArrayList<TrackSelection> newSelections = new ArrayList<>();
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// because the new track selection for some renderer X may have the same track group as the old
|
|
||||||
// selection for some other renderer Y. Trying to enable X before disabling Y would fail, since
|
|
||||||
// sources do not support being enabled more than once per track group at a time.
|
|
||||||
|
|
||||||
// Disable renderers whose track selections have changed.
|
|
||||||
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|
||||||
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 oldSelection = trackSelections == null ? null : trackSelections.get(i);
|
TrackSelection oldSelection = trackSelections == null ? null : trackSelections.get(i);
|
||||||
TrackSelection newSelection = newSelections.get(i);
|
TrackSelection newSelection = newTrackSelections.get(i);
|
||||||
if (newSelection != null) {
|
if (newSelection != null) {
|
||||||
enabledRendererCount++;
|
enabledRendererCount++;
|
||||||
}
|
}
|
||||||
@ -584,18 +574,23 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
ensureStopped(renderer);
|
ensureStopped(renderer);
|
||||||
if (renderer.getState() == TrackRenderer.STATE_ENABLED) {
|
if (renderer.getState() == TrackRenderer.STATE_ENABLED) {
|
||||||
TrackStream trackStream = renderer.disable();
|
TrackStream trackStream = renderer.disable();
|
||||||
source.unselectTrack(trackStream);
|
oldStreams.add(trackStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (newSelection != null) {
|
||||||
|
newSelections.add(newSelection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the source selection.
|
||||||
|
TrackStream[] newStreams = source.selectTracks(oldStreams, newSelections, positionUs);
|
||||||
trackSelector.onSelectionActivated(result.second);
|
trackSelector.onSelectionActivated(result.second);
|
||||||
trackSelections = newSelections;
|
trackSelections = newTrackSelections;
|
||||||
|
|
||||||
|
// Enable renderers with their new selections.
|
||||||
enabledRenderers = new TrackRenderer[enabledRendererCount];
|
enabledRenderers = new TrackRenderer[enabledRendererCount];
|
||||||
enabledRendererCount = 0;
|
enabledRendererCount = 0;
|
||||||
|
|
||||||
// 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 newSelection = trackSelections.get(i);
|
TrackSelection newSelection = trackSelections.get(i);
|
||||||
@ -606,15 +601,14 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
boolean playing = playWhenReady && state == ExoPlayer.STATE_READY;
|
boolean playing = playWhenReady && state == ExoPlayer.STATE_READY;
|
||||||
// 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.
|
|
||||||
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[newSelection.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(newSelection.group).getFormat(newSelection.getTrack(j));
|
formats[j] = groups.get(newSelection.group).getFormat(newSelection.getTrack(j));
|
||||||
}
|
}
|
||||||
// Enable the renderer.
|
// Enable the renderer.
|
||||||
renderer.enable(formats, trackStream, positionUs, joining);
|
int newStreamIndex = newSelections.indexOf(newSelection);
|
||||||
|
renderer.enable(formats, newStreams[newStreamIndex], positionUs, joining);
|
||||||
MediaClock mediaClock = renderer.getMediaClock();
|
MediaClock mediaClock = renderer.getMediaClock();
|
||||||
if (mediaClock != null) {
|
if (mediaClock != null) {
|
||||||
if (rendererMediaClock != null) {
|
if (rendererMediaClock != null) {
|
||||||
@ -632,7 +626,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
source.endTrackSelection(positionUs);
|
|
||||||
updateBufferedPositionUs();
|
updateBufferedPositionUs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -641,7 +634,7 @@ 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(false);
|
selectTracksInternal();
|
||||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ import java.io.FileDescriptor;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -71,7 +72,7 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
private final long fileDescriptorOffset;
|
private final long fileDescriptorOffset;
|
||||||
private final long fileDescriptorLength;
|
private final long fileDescriptorLength;
|
||||||
|
|
||||||
private int state;
|
private boolean prepared;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
private MediaExtractor extractor;
|
private MediaExtractor extractor;
|
||||||
private TrackGroupArray tracks;
|
private TrackGroupArray tracks;
|
||||||
@ -97,7 +98,6 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
fileDescriptor = null;
|
fileDescriptor = null;
|
||||||
fileDescriptorOffset = 0;
|
fileDescriptorOffset = 0;
|
||||||
fileDescriptorLength = 0;
|
fileDescriptorLength = 0;
|
||||||
state = STATE_UNPREPARED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,17 +117,13 @@ 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 {
|
||||||
Assertions.checkState(state == STATE_UNPREPARED);
|
if (prepared) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
extractor = new MediaExtractor();
|
extractor = new MediaExtractor();
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
extractor.setDataSource(context, uri, headers);
|
extractor.setDataSource(context, uri, headers);
|
||||||
@ -146,7 +142,7 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
trackArray[i] = new TrackGroup(createFormat(i, format));
|
trackArray[i] = new TrackGroup(createFormat(i, format));
|
||||||
}
|
}
|
||||||
tracks = new TrackGroupArray(trackArray);
|
tracks = new TrackGroupArray(trackArray);
|
||||||
state = STATE_SELECTING_TRACKS;
|
prepared = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,42 +162,36 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startTrackSelection() {
|
public TrackStream[] selectTracks(List<TrackStream> oldStreams,
|
||||||
Assertions.checkState(state == STATE_READING);
|
List<TrackSelection> newSelections, long positionUs) {
|
||||||
state = STATE_SELECTING_TRACKS;
|
Assertions.checkState(prepared);
|
||||||
}
|
// Unselect old tracks.
|
||||||
|
for (int i = 0; i < oldStreams.size(); i++) {
|
||||||
@Override
|
int track = ((TrackStreamImpl) oldStreams.get(i)).track;
|
||||||
public TrackStream selectTrack(TrackSelection selection, long positionUs) {
|
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
enabledTrackCount--;
|
||||||
Assertions.checkState(selection.length == 1);
|
trackStates[track] = TRACK_STATE_DISABLED;
|
||||||
Assertions.checkState(selection.getTrack(0) == 0);
|
extractor.unselectTrack(track);
|
||||||
int track = selection.group;
|
pendingResets[track] = false;
|
||||||
Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED);
|
}
|
||||||
enabledTrackCount++;
|
// Select new tracks.
|
||||||
trackStates[track] = TRACK_STATE_ENABLED;
|
TrackStream[] newStreams = new TrackStream[newSelections.size()];
|
||||||
extractor.selectTrack(track);
|
for (int i = 0; i < newStreams.length; i++) {
|
||||||
return new TrackStreamImpl(track);
|
TrackSelection selection = newSelections.get(i);
|
||||||
}
|
Assertions.checkState(selection.length == 1);
|
||||||
|
Assertions.checkState(selection.getTrack(0) == 0);
|
||||||
@Override
|
int track = selection.group;
|
||||||
public void unselectTrack(TrackStream stream) {
|
Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED);
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
enabledTrackCount++;
|
||||||
int track = ((TrackStreamImpl) stream).track;
|
trackStates[track] = TRACK_STATE_ENABLED;
|
||||||
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
|
extractor.selectTrack(track);
|
||||||
enabledTrackCount--;
|
newStreams[i] = new TrackStreamImpl(track);
|
||||||
trackStates[track] = TRACK_STATE_DISABLED;
|
}
|
||||||
extractor.unselectTrack(track);
|
// Seek if necessary.
|
||||||
pendingResets[track] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void endTrackSelection(long positionUs) {
|
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
|
||||||
state = STATE_READING;
|
|
||||||
if (enabledTrackCount > 0) {
|
if (enabledTrackCount > 0) {
|
||||||
seekToUsInternal(positionUs, positionUs != 0);
|
seekToUsInternal(positionUs, positionUs != 0);
|
||||||
}
|
}
|
||||||
|
return newStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ long readReset(int track) {
|
/* package */ long readReset(int track) {
|
||||||
@ -277,7 +267,6 @@ 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;
|
||||||
|
@ -20,8 +20,9 @@ import com.google.android.exoplayer.util.Assertions;
|
|||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.ArrayList;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combines multiple {@link SampleSource} instances.
|
* Combines multiple {@link SampleSource} instances.
|
||||||
@ -29,34 +30,29 @@ import java.util.IdentityHashMap;
|
|||||||
public final class MultiSampleSource implements SampleSource {
|
public final class MultiSampleSource implements SampleSource {
|
||||||
|
|
||||||
private final SampleSource[] sources;
|
private final SampleSource[] sources;
|
||||||
private final int[] sourceEnabledTrackCounts;
|
private final IdentityHashMap<TrackStream, SampleSource> trackStreamSources;
|
||||||
private final IdentityHashMap<TrackStream, Integer> trackStreamSourceIndices;
|
private final int[] selectedTrackCounts;
|
||||||
|
|
||||||
private int state;
|
private boolean prepared;
|
||||||
|
private boolean seenFirstTrackSelection;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
private TrackGroupArray trackGroups;
|
private TrackGroupArray trackGroups;
|
||||||
private SampleSource[] enabledSources;
|
private SampleSource[] enabledSources;
|
||||||
|
|
||||||
public MultiSampleSource(SampleSource... sources) {
|
public MultiSampleSource(SampleSource... sources) {
|
||||||
this.sources = sources;
|
this.sources = sources;
|
||||||
sourceEnabledTrackCounts = new int[sources.length];
|
trackStreamSources = new IdentityHashMap<>();
|
||||||
trackStreamSourceIndices = new IdentityHashMap<>();
|
selectedTrackCounts = new int[sources.length];
|
||||||
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 {
|
||||||
Assertions.checkState(state == SampleSource.STATE_UNPREPARED);
|
if (prepared) {
|
||||||
|
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) {
|
if (!sourcesPrepared) {
|
||||||
return false;
|
return false;
|
||||||
@ -80,7 +76,7 @@ public final class MultiSampleSource implements SampleSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
trackGroups = new TrackGroupArray(trackGroupArray);
|
trackGroups = new TrackGroupArray(trackGroupArray);
|
||||||
state = STATE_SELECTING_TRACKS;
|
prepared = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,47 +86,29 @@ public final class MultiSampleSource implements SampleSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startTrackSelection() {
|
public TrackStream[] selectTracks(List<TrackStream> oldStreams,
|
||||||
Assertions.checkState(state == STATE_READING);
|
List<TrackSelection> newSelections, long positionUs) {
|
||||||
state = STATE_SELECTING_TRACKS;
|
Assertions.checkState(prepared);
|
||||||
for (SampleSource source : sources) {
|
TrackStream[] newStreams = new TrackStream[newSelections.size()];
|
||||||
source.startTrackSelection();
|
// Select tracks for each source.
|
||||||
}
|
int enabledSourceCount = 0;
|
||||||
}
|
|
||||||
|
|
||||||
@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].selectTrack(
|
|
||||||
new TrackSelection(sourceAndGroup.second, selection.getTracks()), positionUs);
|
|
||||||
int sourceIndex = sourceAndGroup.first;
|
|
||||||
sourceEnabledTrackCounts[sourceIndex]++;
|
|
||||||
trackStreamSourceIndices.put(trackStream, sourceIndex);
|
|
||||||
return trackStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unselectTrack(TrackStream trackStream) {
|
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
|
||||||
int sourceIndex = trackStreamSourceIndices.remove(trackStream);
|
|
||||||
sources[sourceIndex].unselectTrack(trackStream);
|
|
||||||
sourceEnabledTrackCounts[sourceIndex]--;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void endTrackSelection(long positionUs) {
|
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
|
||||||
state = STATE_READING;
|
|
||||||
int newEnabledSourceCount = 0;
|
|
||||||
SampleSource[] newEnabledSources = new SampleSource[sources.length];
|
|
||||||
for (int i = 0; i < sources.length; i++) {
|
for (int i = 0; i < sources.length; i++) {
|
||||||
sources[i].endTrackSelection(positionUs);
|
selectedTrackCounts[i] += selectTracks(sources[i], oldStreams, newSelections, positionUs,
|
||||||
if (sourceEnabledTrackCounts[i] > 0) {
|
newStreams);
|
||||||
newEnabledSources[newEnabledSourceCount++] = sources[i];
|
if (selectedTrackCounts[i] > 0) {
|
||||||
|
enabledSourceCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enabledSources = Arrays.copyOf(newEnabledSources, newEnabledSourceCount);
|
// Update the enabled sources.
|
||||||
|
enabledSources = new SampleSource[enabledSourceCount];
|
||||||
|
enabledSourceCount = 0;
|
||||||
|
for (int i = 0; i < sources.length; i++) {
|
||||||
|
if (selectedTrackCounts[i] > 0) {
|
||||||
|
enabledSources[enabledSourceCount++] = sources[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seenFirstTrackSelection = true;
|
||||||
|
return newStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -173,15 +151,49 @@ public final class MultiSampleSource implements SampleSource {
|
|||||||
for (SampleSource source : sources) {
|
for (SampleSource source : sources) {
|
||||||
source.release();
|
source.release();
|
||||||
}
|
}
|
||||||
state = STATE_RELEASED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pair<Integer, Integer> getSourceAndTrackGroupIndices(int group) {
|
private int selectTracks(SampleSource source, List<TrackStream> allOldStreams,
|
||||||
|
List<TrackSelection> allNewSelections, long positionUs, TrackStream[] allNewStreams) {
|
||||||
|
// Get the subset of the old streams for the source.
|
||||||
|
ArrayList<TrackStream> oldStreams = new ArrayList<>();
|
||||||
|
for (int i = 0; i < allOldStreams.size(); i++) {
|
||||||
|
TrackStream stream = allOldStreams.get(i);
|
||||||
|
if (trackStreamSources.get(stream) == source) {
|
||||||
|
trackStreamSources.remove(stream);
|
||||||
|
oldStreams.add(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get the subset of the new selections for the source.
|
||||||
|
ArrayList<TrackSelection> newSelections = new ArrayList<>();
|
||||||
|
int[] newSelectionOriginalIndices = new int[allNewSelections.size()];
|
||||||
|
for (int i = 0; i < allNewSelections.size(); i++) {
|
||||||
|
TrackSelection selection = allNewSelections.get(i);
|
||||||
|
Pair<SampleSource, Integer> sourceAndGroup = getSourceAndGroup(selection.group);
|
||||||
|
if (sourceAndGroup.first == source) {
|
||||||
|
newSelectionOriginalIndices[newSelections.size()] = i;
|
||||||
|
newSelections.add(new TrackSelection(sourceAndGroup.second, selection.getTracks()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Do nothing if nothing has changed, except during the first selection.
|
||||||
|
if (seenFirstTrackSelection && oldStreams.isEmpty() && newSelections.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// Perform the selection.
|
||||||
|
TrackStream[] newStreams = source.selectTracks(oldStreams, newSelections, positionUs);
|
||||||
|
for (int j = 0; j < newStreams.length; j++) {
|
||||||
|
allNewStreams[newSelectionOriginalIndices[j]] = newStreams[j];
|
||||||
|
trackStreamSources.put(newStreams[j], source);
|
||||||
|
}
|
||||||
|
return newSelections.size() - oldStreams.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pair<SampleSource, Integer> getSourceAndGroup(int group) {
|
||||||
int totalTrackGroupCount = 0;
|
int totalTrackGroupCount = 0;
|
||||||
for (int i = 0; i < sources.length; i++) {
|
for (int i = 0; i < sources.length; i++) {
|
||||||
int sourceTrackGroupCount = sources[i].getTrackGroups().length;
|
int sourceTrackGroupCount = sources[i].getTrackGroups().length;
|
||||||
if (group < totalTrackGroupCount + sourceTrackGroupCount) {
|
if (group < totalTrackGroupCount + sourceTrackGroupCount) {
|
||||||
return Pair.create(i, group - totalTrackGroupCount);
|
return Pair.create(sources[i], group - totalTrackGroupCount);
|
||||||
}
|
}
|
||||||
totalTrackGroupCount += sourceTrackGroupCount;
|
totalTrackGroupCount += sourceTrackGroupCount;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer;
|
package com.google.android.exoplayer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A source of media.
|
* A source of media.
|
||||||
@ -23,41 +24,14 @@ import java.io.IOException;
|
|||||||
public interface SampleSource {
|
public interface SampleSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The source has not yet been prepared.
|
* Prepares the source, or does nothing if the source is already 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>
|
* <p>
|
||||||
* If preparation cannot complete immediately then the call will return {@code false} rather than
|
* {@link #selectTracks(List, List, long)} <b>must</b> be called after the source is prepared to
|
||||||
* block and the state will remain unchanged. If true is returned the state will have changed
|
* make an initial track selection. This is true even if the caller does not wish to select any
|
||||||
* to {@link #STATE_SELECTING_TRACKS} for the initial track selection to take place.
|
* tracks.
|
||||||
* <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 is prepared, false otherwise.
|
||||||
* @throws IOException If there's an error preparing the source.
|
* @throws IOException If there's an error preparing the source.
|
||||||
*/
|
*/
|
||||||
boolean prepare(long positionUs) throws IOException;
|
boolean prepare(long positionUs) throws IOException;
|
||||||
@ -65,8 +39,7 @@ public interface SampleSource {
|
|||||||
/**
|
/**
|
||||||
* Returns the duration of the source.
|
* Returns the duration of the source.
|
||||||
* <p>
|
* <p>
|
||||||
* This method should only be called when the state is {@link #STATE_SELECTING_TRACKS} or
|
* This method should only be called after the source has been prepared.
|
||||||
* {@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.
|
||||||
@ -76,64 +49,36 @@ 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 when the state is {@link #STATE_SELECTING_TRACKS} or
|
* This method should only be called after the source has been prepared.
|
||||||
* {@link #STATE_READING}.
|
|
||||||
*
|
*
|
||||||
* @return The {@link TrackGroup}s.
|
* @return The {@link TrackGroup}s.
|
||||||
*/
|
*/
|
||||||
TrackGroupArray getTrackGroups();
|
TrackGroupArray getTrackGroups();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enters the track selection state.
|
* Modifies the selected tracks.
|
||||||
* <p>
|
* <p>
|
||||||
* The selected tracks are initially unchanged, but may be modified by calls to
|
* {@link TrackStream}s corresponding to tracks being unselected are passed in {@code oldStreams}.
|
||||||
* {@link #unselectTrack(TrackStream)} and {@link #selectTrack(TrackSelection, long)}, followed by
|
* Tracks being selected are specified in {@code newSelections}. Each new {@link TrackSelection}
|
||||||
* a call to {@link #endTrackSelection(long)}.
|
* must have a {@link TrackSelection#group} index distinct from those of currently enabled tracks,
|
||||||
|
* except for those being unselected.
|
||||||
* <p>
|
* <p>
|
||||||
* This method should only be called when the state is {@link #STATE_READING}.
|
* This method should only be called after the source has been prepared.
|
||||||
*/
|
|
||||||
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 oldStreams {@link TrackStream}s corresponding to tracks being unselected. May be empty
|
||||||
|
* but must not be null.
|
||||||
|
* @param newSelections {@link TrackSelection}s that define tracks being selected. May be empty
|
||||||
|
* but must not be null.
|
||||||
* @param positionUs The current playback position in microseconds.
|
* @param positionUs The current playback position in microseconds.
|
||||||
* @return A {@link TrackStream} from which the enabled track's data can be read.
|
* @return The {@link TrackStream}s corresponding to each of the newly selected tracks.
|
||||||
*/
|
*/
|
||||||
TrackStream selectTrack(TrackSelection selection, long positionUs);
|
TrackStream[] selectTracks(List<TrackStream> oldStreams, List<TrackSelection> newSelections,
|
||||||
|
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 when the state is {@link #STATE_READING} and at least one
|
* This method should only be called when at least one track is selected.
|
||||||
* track is selected.
|
|
||||||
*
|
*
|
||||||
* @param positionUs The current playback position.
|
* @param positionUs The current playback position.
|
||||||
*/
|
*/
|
||||||
@ -142,8 +87,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 when the state is {@link #STATE_READING} and at least one
|
* This method should only be called when at least one track is selected.
|
||||||
* track is selected.
|
|
||||||
*
|
*
|
||||||
* @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}
|
||||||
@ -154,8 +98,7 @@ 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 when the state is {@link #STATE_READING} and at least one
|
* This method should only be called when at least one track is selected.
|
||||||
* track is selected.
|
|
||||||
*
|
*
|
||||||
* @param positionUs The seek position in microseconds.
|
* @param positionUs The seek position in microseconds.
|
||||||
*/
|
*/
|
||||||
|
@ -27,6 +27,7 @@ import android.os.SystemClock;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SampleSource} that loads the data at a given {@link Uri} as a single sample.
|
* A {@link SampleSource} that loads the data at a given {@link Uri} as a single sample.
|
||||||
@ -73,7 +74,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
private final EventListener eventListener;
|
private final EventListener eventListener;
|
||||||
private final int eventSourceId;
|
private final int eventSourceId;
|
||||||
|
|
||||||
private int state;
|
private boolean prepared;
|
||||||
private long pendingResetPositionUs;
|
private long pendingResetPositionUs;
|
||||||
private boolean loadingFinished;
|
private boolean loadingFinished;
|
||||||
private Loader loader;
|
private Loader loader;
|
||||||
@ -107,12 +108,6 @@ 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
|
||||||
@ -124,9 +119,11 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare(long positionUs) {
|
public boolean prepare(long positionUs) {
|
||||||
Assertions.checkState(state == STATE_UNPREPARED);
|
if (prepared) {
|
||||||
state = STATE_SELECTING_TRACKS;
|
return true;
|
||||||
|
}
|
||||||
loader = new Loader("Loader:" + format.sampleMimeType);
|
loader = new Loader("Loader:" + format.sampleMimeType);
|
||||||
|
prepared = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,31 +138,25 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startTrackSelection() {
|
public TrackStream[] selectTracks(List<TrackStream> oldStreams,
|
||||||
Assertions.checkState(state == STATE_READING);
|
List<TrackSelection> newSelections, long positionUs) {
|
||||||
state = STATE_SELECTING_TRACKS;
|
Assertions.checkState(prepared);
|
||||||
}
|
Assertions.checkState(oldStreams.size() <= 1);
|
||||||
|
Assertions.checkState(newSelections.size() <= 1);
|
||||||
@Override
|
// Unselect old tracks.
|
||||||
public TrackStream selectTrack(TrackSelection selection, long positionUs) {
|
if (!oldStreams.isEmpty()) {
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
streamState = STREAM_STATE_END_OF_STREAM;
|
||||||
streamState = STREAM_STATE_SEND_FORMAT;
|
}
|
||||||
pendingResetPositionUs = NO_RESET;
|
// Select new tracks.
|
||||||
clearCurrentLoadableException();
|
TrackStream[] newStreams = new TrackStream[newSelections.size()];
|
||||||
maybeStartLoading();
|
if (!newSelections.isEmpty()) {
|
||||||
return this;
|
newStreams[0] = this;
|
||||||
}
|
streamState = STREAM_STATE_SEND_FORMAT;
|
||||||
|
pendingResetPositionUs = NO_RESET;
|
||||||
@Override
|
clearCurrentLoadableException();
|
||||||
public void unselectTrack(TrackStream stream) {
|
maybeStartLoading();
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
}
|
||||||
streamState = STREAM_STATE_END_OF_STREAM;
|
return newStreams;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void endTrackSelection(long positionUs) {
|
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
|
||||||
state = STATE_READING;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -225,7 +216,6 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
state = STATE_RELEASED;
|
|
||||||
streamState = STREAM_STATE_END_OF_STREAM;
|
streamState = STREAM_STATE_END_OF_STREAM;
|
||||||
if (loader != null) {
|
if (loader != null) {
|
||||||
loader.release();
|
loader.release();
|
||||||
|
@ -68,12 +68,13 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
private final EventListener eventListener;
|
private final EventListener eventListener;
|
||||||
private final int minLoadableRetryCount;
|
private final int minLoadableRetryCount;
|
||||||
|
|
||||||
private int state;
|
private boolean prepared;
|
||||||
private long downstreamPositionUs;
|
private long downstreamPositionUs;
|
||||||
private long lastSeekPositionUs;
|
private long lastSeekPositionUs;
|
||||||
private long pendingResetPositionUs;
|
private long pendingResetPositionUs;
|
||||||
private long lastPerformedBufferOperation;
|
private long lastPerformedBufferOperation;
|
||||||
private boolean pendingReset;
|
private boolean pendingReset;
|
||||||
|
private boolean loadControlRegistered;
|
||||||
|
|
||||||
private TrackGroupArray trackGroups;
|
private TrackGroupArray trackGroups;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
@ -140,17 +141,13 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
|
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
|
||||||
sampleQueue = new DefaultTrackOutput(loadControl.getAllocator());
|
sampleQueue = new DefaultTrackOutput(loadControl.getAllocator());
|
||||||
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 {
|
||||||
Assertions.checkState(state == STATE_UNPREPARED);
|
if (prepared) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (!chunkSource.prepare()) {
|
if (!chunkSource.prepare()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -162,7 +159,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
} else {
|
} else {
|
||||||
trackGroups = new TrackGroupArray();
|
trackGroups = new TrackGroupArray();
|
||||||
}
|
}
|
||||||
state = STATE_SELECTING_TRACKS;
|
prepared = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,36 +174,31 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startTrackSelection() {
|
public TrackStream[] selectTracks(List<TrackStream> oldStreams,
|
||||||
Assertions.checkState(state == STATE_READING);
|
List<TrackSelection> newSelections, long positionUs) {
|
||||||
state = STATE_SELECTING_TRACKS;
|
Assertions.checkState(prepared);
|
||||||
}
|
Assertions.checkState(oldStreams.size() <= 1);
|
||||||
|
Assertions.checkState(newSelections.size() <= 1);
|
||||||
@Override
|
// Unselect old tracks.
|
||||||
public TrackStream selectTrack(TrackSelection selection, long positionUs) {
|
if (!oldStreams.isEmpty()) {
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
Assertions.checkState(trackEnabled);
|
||||||
Assertions.checkState(!trackEnabled);
|
trackEnabled = false;
|
||||||
trackEnabled = true;
|
|
||||||
chunkSource.enable(selection.getTracks());
|
|
||||||
loadControl.register(this, bufferSizeContribution);
|
|
||||||
downstreamFormat = null;
|
|
||||||
downstreamSampleFormat = null;
|
|
||||||
downstreamPositionUs = positionUs;
|
|
||||||
lastSeekPositionUs = positionUs;
|
|
||||||
pendingReset = false;
|
|
||||||
restartFrom(positionUs);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unselectTrack(TrackStream stream) {
|
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
|
||||||
Assertions.checkState(trackEnabled);
|
|
||||||
trackEnabled = false;
|
|
||||||
try {
|
|
||||||
chunkSource.disable();
|
chunkSource.disable();
|
||||||
} finally {
|
}
|
||||||
loadControl.unregister(this);
|
// Select new tracks.
|
||||||
|
TrackStream[] newStreams = new TrackStream[newSelections.size()];
|
||||||
|
if (!newSelections.isEmpty()) {
|
||||||
|
Assertions.checkState(!trackEnabled);
|
||||||
|
trackEnabled = true;
|
||||||
|
chunkSource.enable(newSelections.get(0).getTracks());
|
||||||
|
newStreams[0] = this;
|
||||||
|
}
|
||||||
|
// Cancel or start requests as necessary.
|
||||||
|
if (!trackEnabled && loader != null) {
|
||||||
|
if (loadControlRegistered) {
|
||||||
|
loadControl.unregister(this);
|
||||||
|
loadControlRegistered = false;
|
||||||
|
}
|
||||||
if (loader.isLoading()) {
|
if (loader.isLoading()) {
|
||||||
loader.cancelLoading();
|
loader.cancelLoading();
|
||||||
} else {
|
} else {
|
||||||
@ -215,13 +207,19 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
clearCurrentLoadable();
|
clearCurrentLoadable();
|
||||||
loadControl.trimAllocator();
|
loadControl.trimAllocator();
|
||||||
}
|
}
|
||||||
|
} else if (trackEnabled) {
|
||||||
|
if (!loadControlRegistered) {
|
||||||
|
loadControl.register(this, bufferSizeContribution);
|
||||||
|
loadControlRegistered = true;
|
||||||
|
}
|
||||||
|
downstreamFormat = null;
|
||||||
|
downstreamSampleFormat = null;
|
||||||
|
downstreamPositionUs = positionUs;
|
||||||
|
lastSeekPositionUs = positionUs;
|
||||||
|
pendingReset = false;
|
||||||
|
restartFrom(positionUs);
|
||||||
}
|
}
|
||||||
}
|
return newStreams;
|
||||||
|
|
||||||
@Override
|
|
||||||
public void endTrackSelection(long positionUs) {
|
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
|
||||||
state = STATE_READING;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -339,7 +337,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
state = STATE_RELEASED;
|
prepared = false;
|
||||||
trackEnabled = false;
|
trackEnabled = false;
|
||||||
if (loader != null) {
|
if (loader != null) {
|
||||||
loader.release();
|
loader.release();
|
||||||
|
@ -208,15 +208,14 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
private volatile SeekMap seekMap;
|
private volatile SeekMap seekMap;
|
||||||
private volatile DrmInitData drmInitData;
|
private volatile DrmInitData drmInitData;
|
||||||
|
|
||||||
private int state;
|
private boolean prepared;
|
||||||
|
private boolean seenFirstTrackSelection;
|
||||||
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;
|
||||||
@ -327,26 +326,20 @@ 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) {
|
||||||
Assertions.checkState(state == STATE_UNPREPARED);
|
if (prepared) {
|
||||||
|
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()) {
|
if (seekMap == null || !tracksBuilt || !haveFormatsForAllTracks()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int trackCount = sampleQueues.size();
|
int trackCount = sampleQueues.size();
|
||||||
TrackGroup[] trackArray = new TrackGroup[trackCount];
|
TrackGroup[] trackArray = new TrackGroup[trackCount];
|
||||||
trackEnabledStates = new boolean[trackCount];
|
trackEnabledStates = new boolean[trackCount];
|
||||||
@ -357,8 +350,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
trackArray[i] = new TrackGroup(sampleQueues.valueAt(i).getFormat());
|
trackArray[i] = new TrackGroup(sampleQueues.valueAt(i).getFormat());
|
||||||
}
|
}
|
||||||
tracks = new TrackGroupArray(trackArray);
|
tracks = new TrackGroupArray(trackArray);
|
||||||
isFirstTrackSelection = true;
|
prepared = true;
|
||||||
state = STATE_SELECTING_TRACKS;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,41 +365,31 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startTrackSelection() {
|
public TrackStream[] selectTracks(List<TrackStream> oldStreams,
|
||||||
Assertions.checkState(state == STATE_READING);
|
List<TrackSelection> newSelections, long positionUs) {
|
||||||
state = STATE_SELECTING_TRACKS;
|
Assertions.checkState(prepared);
|
||||||
newTracksSelected = false;
|
// Unselect old tracks.
|
||||||
isFirstTrackSelection = false;
|
for (int i = 0; i < oldStreams.size(); i++) {
|
||||||
}
|
int track = ((TrackStreamImpl) oldStreams.get(i)).track;
|
||||||
|
Assertions.checkState(trackEnabledStates[track]);
|
||||||
@Override
|
enabledTrackCount--;
|
||||||
public TrackStream selectTrack(TrackSelection selection, long positionUs) {
|
trackEnabledStates[track] = false;
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
}
|
||||||
Assertions.checkState(selection.length == 1);
|
// Select new tracks.
|
||||||
Assertions.checkState(selection.getTrack(0) == 0);
|
TrackStream[] newStreams = new TrackStream[newSelections.size()];
|
||||||
int track = selection.group;
|
for (int i = 0; i < newStreams.length; i++) {
|
||||||
Assertions.checkState(!trackEnabledStates[track]);
|
TrackSelection selection = newSelections.get(i);
|
||||||
enabledTrackCount++;
|
Assertions.checkState(selection.length == 1);
|
||||||
trackEnabledStates[track] = true;
|
Assertions.checkState(selection.getTrack(0) == 0);
|
||||||
pendingMediaFormat[track] = true;
|
int track = selection.group;
|
||||||
pendingResets[track] = false;
|
Assertions.checkState(!trackEnabledStates[track]);
|
||||||
newTracksSelected = true;
|
enabledTrackCount++;
|
||||||
return new TrackStreamImpl(track);
|
trackEnabledStates[track] = true;
|
||||||
}
|
pendingMediaFormat[track] = true;
|
||||||
|
pendingResets[track] = false;
|
||||||
@Override
|
newStreams[i] = new TrackStreamImpl(track);
|
||||||
public void unselectTrack(TrackStream stream) {
|
}
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
// Cancel or start requests as necessary.
|
||||||
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) {
|
if (enabledTrackCount == 0) {
|
||||||
downstreamPositionUs = Long.MIN_VALUE;
|
downstreamPositionUs = Long.MIN_VALUE;
|
||||||
if (loader.isLoading()) {
|
if (loader.isLoading()) {
|
||||||
@ -416,9 +398,11 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
clearState();
|
clearState();
|
||||||
allocator.trim(0);
|
allocator.trim(0);
|
||||||
}
|
}
|
||||||
} else if (isFirstTrackSelection ? positionUs != 0 : newTracksSelected) {
|
} else if (seenFirstTrackSelection ? newStreams.length > 0 : positionUs != 0) {
|
||||||
seekToInternal(positionUs);
|
seekToInternal(positionUs);
|
||||||
}
|
}
|
||||||
|
seenFirstTrackSelection = true;
|
||||||
|
return newStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -540,7 +524,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
state = STATE_RELEASED;
|
|
||||||
enabledTrackCount = 0;
|
enabledTrackCount = 0;
|
||||||
if (loader != null) {
|
if (loader != null) {
|
||||||
loader.release();
|
loader.release();
|
||||||
@ -628,7 +611,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 (state == STATE_UNPREPARED) {
|
if (!prepared) {
|
||||||
// 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.
|
||||||
@ -665,7 +648,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
sampleTimeOffsetUs = 0;
|
sampleTimeOffsetUs = 0;
|
||||||
havePendingNextSampleUs = false;
|
havePendingNextSampleUs = false;
|
||||||
|
|
||||||
if (state == STATE_UNPREPARED) {
|
if (!prepared) {
|
||||||
loadable = createLoadableFromStart();
|
loadable = createLoadableFromStart();
|
||||||
} else {
|
} else {
|
||||||
Assertions.checkState(isPendingReset());
|
Assertions.checkState(isPendingReset());
|
||||||
|
@ -39,6 +39,7 @@ import android.os.SystemClock;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SampleSource} for HLS streams.
|
* A {@link SampleSource} for HLS streams.
|
||||||
@ -73,7 +74,8 @@ 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 int state;
|
private boolean prepared;
|
||||||
|
private boolean seenFirstTrackSelection;
|
||||||
private boolean loadControlRegistered;
|
private boolean loadControlRegistered;
|
||||||
private int enabledTrackCount;
|
private int enabledTrackCount;
|
||||||
|
|
||||||
@ -83,9 +85,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
// Indexed by track (as exposed by this source).
|
// Indexed by track (as exposed by this source).
|
||||||
private TrackGroupArray trackGroups;
|
private TrackGroupArray trackGroups;
|
||||||
private int primaryTrackGroupIndex;
|
private int primaryTrackGroupIndex;
|
||||||
private boolean isFirstTrackSelection;
|
|
||||||
private boolean newTracksSelected;
|
|
||||||
private boolean primaryTracksDeselected;
|
|
||||||
// Indexed by group.
|
// Indexed by group.
|
||||||
private boolean[] groupEnabledStates;
|
private boolean[] groupEnabledStates;
|
||||||
private boolean[] pendingResets;
|
private boolean[] pendingResets;
|
||||||
@ -133,21 +132,17 @@ 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 {
|
||||||
Assertions.checkState(state == STATE_UNPREPARED);
|
if (prepared) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (!chunkSource.prepare()) {
|
if (!chunkSource.prepare()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (chunkSource.getTrackCount() == 0) {
|
if (chunkSource.getTrackCount() == 0) {
|
||||||
trackGroups = new TrackGroupArray();
|
trackGroups = new TrackGroupArray();
|
||||||
state = STATE_SELECTING_TRACKS;
|
prepared = true;
|
||||||
isFirstTrackSelection = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!extractors.isEmpty()) {
|
if (!extractors.isEmpty()) {
|
||||||
@ -156,9 +151,8 @@ 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);
|
||||||
state = STATE_SELECTING_TRACKS;
|
|
||||||
isFirstTrackSelection = true;
|
|
||||||
maybeStartLoading(); // Update the load control.
|
maybeStartLoading(); // Update the load control.
|
||||||
|
prepared = true;
|
||||||
return true;
|
return true;
|
||||||
} else if (extractors.size() > 1) {
|
} else if (extractors.size() > 1) {
|
||||||
extractors.removeFirst().clear();
|
extractors.removeFirst().clear();
|
||||||
@ -195,42 +189,31 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
return trackGroups;
|
return trackGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startTrackSelection() {
|
public TrackStream[] selectTracks(List<TrackStream> oldStreams,
|
||||||
Assertions.checkState(state == STATE_READING);
|
List<TrackSelection> newSelections, long positionUs) {
|
||||||
state = STATE_SELECTING_TRACKS;
|
Assertions.checkState(prepared);
|
||||||
isFirstTrackSelection = false;
|
// Unselect old tracks.
|
||||||
newTracksSelected = false;
|
for (int i = 0; i < oldStreams.size(); i++) {
|
||||||
primaryTracksDeselected = false;
|
int group = ((TrackStreamImpl) oldStreams.get(i)).group;
|
||||||
}
|
setTrackGroupEnabledState(group, 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;
|
|
||||||
newTracksSelected = true;
|
|
||||||
if (group == primaryTrackGroupIndex) {
|
|
||||||
primaryTracksDeselected |= chunkSource.selectTracks(tracks);
|
|
||||||
}
|
}
|
||||||
return new TrackStreamImpl(group);
|
// Select new tracks.
|
||||||
}
|
boolean primaryTracksDeselected = false;
|
||||||
|
TrackStream[] newStreams = new TrackStream[newSelections.size()];
|
||||||
@Override
|
for (int i = 0; i < newStreams.length; i++) {
|
||||||
public void unselectTrack(TrackStream stream) {
|
TrackSelection selection = newSelections.get(i);
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
int group = selection.group;
|
||||||
int group = ((TrackStreamImpl) stream).group;
|
int[] tracks = selection.getTracks();
|
||||||
setTrackGroupEnabledState(group, false);
|
setTrackGroupEnabledState(group, true);
|
||||||
}
|
downstreamSampleFormats[group] = null;
|
||||||
|
pendingResets[group] = false;
|
||||||
@Override
|
if (group == primaryTrackGroupIndex) {
|
||||||
public void endTrackSelection(long positionUs) {
|
primaryTracksDeselected |= chunkSource.selectTracks(tracks);
|
||||||
Assertions.checkState(state == STATE_SELECTING_TRACKS);
|
}
|
||||||
state = STATE_READING;
|
newStreams[i] = new TrackStreamImpl(group);
|
||||||
|
}
|
||||||
|
// Cancel or start requests as necessary.
|
||||||
if (enabledTrackCount == 0) {
|
if (enabledTrackCount == 0) {
|
||||||
chunkSource.reset();
|
chunkSource.reset();
|
||||||
downstreamPositionUs = Long.MIN_VALUE;
|
downstreamPositionUs = Long.MIN_VALUE;
|
||||||
@ -247,13 +230,15 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
loadControl.trimAllocator();
|
loadControl.trimAllocator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (primaryTracksDeselected || (!isFirstTrackSelection && newTracksSelected)) {
|
} else if (primaryTracksDeselected || (seenFirstTrackSelection && newStreams.length > 0)) {
|
||||||
if (!loadControlRegistered) {
|
if (!loadControlRegistered) {
|
||||||
loadControl.register(this, bufferSizeContribution);
|
loadControl.register(this, bufferSizeContribution);
|
||||||
loadControlRegistered = true;
|
loadControlRegistered = true;
|
||||||
}
|
}
|
||||||
seekToInternal(positionUs);
|
seekToInternal(positionUs);
|
||||||
}
|
}
|
||||||
|
seenFirstTrackSelection = true;
|
||||||
|
return newStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -386,7 +371,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
state = STATE_RELEASED;
|
|
||||||
enabledTrackCount = 0;
|
enabledTrackCount = 0;
|
||||||
if (loader != null) {
|
if (loader != null) {
|
||||||
if (loadControlRegistered) {
|
if (loadControlRegistered) {
|
||||||
@ -678,8 +662,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loader.isLoading() || !nextLoader
|
if (loader.isLoading() || !nextLoader || (prepared && enabledTrackCount == 0)) {
|
||||||
|| (state != STATE_UNPREPARED && enabledTrackCount == 0)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -729,7 +712,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
if (isPendingReset()) {
|
if (isPendingReset()) {
|
||||||
return pendingResetPositionUs;
|
return pendingResetPositionUs;
|
||||||
} else {
|
} else {
|
||||||
return loadingFinished || (state != STATE_UNPREPARED && enabledTrackCount == 0) ? -1
|
return loadingFinished || (prepared && enabledTrackCount == 0) ? -1
|
||||||
: currentTsLoadable != null ? currentTsLoadable.endTimeUs : previousTsLoadable.endTimeUs;
|
: currentTsLoadable != null ? currentTsLoadable.endTimeUs : previousTsLoadable.endTimeUs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user