mirror of
https://github.com/androidx/media.git
synced 2025-05-03 21:57:46 +08:00
Event based preparation.
- Removes the load delay that was previously present after source preparation. - Prevents premature failure in the case that the buffering source fails to prepare. - SampleSource.Callback will get a second method in a subsequent CL, approximately meaning "tell me if I can load more stuff". This will remove the need for LoadControl and the complexity that comes with it. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=126172573
This commit is contained in:
parent
76f426e944
commit
ca81444f95
@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer;
|
package com.google.android.exoplayer;
|
||||||
|
|
||||||
import com.google.android.exoplayer.BufferingPolicy.LoadControl;
|
|
||||||
import com.google.android.exoplayer.ExoPlayer.ExoPlayerMessage;
|
import com.google.android.exoplayer.ExoPlayer.ExoPlayerMessage;
|
||||||
import com.google.android.exoplayer.TrackSelector.InvalidationListener;
|
import com.google.android.exoplayer.TrackSelector.InvalidationListener;
|
||||||
import com.google.android.exoplayer.util.PriorityHandlerThread;
|
import com.google.android.exoplayer.util.PriorityHandlerThread;
|
||||||
@ -38,7 +37,8 @@ import java.util.ArrayList;
|
|||||||
*/
|
*/
|
||||||
// TODO[REFACTOR]: Make sure renderer errors that will prevent prepare from being called again are
|
// TODO[REFACTOR]: Make sure renderer errors that will prevent prepare from being called again are
|
||||||
// always propagated properly.
|
// always propagated properly.
|
||||||
/* package */ final class ExoPlayerImplInternal implements Handler.Callback, InvalidationListener {
|
/* package */ final class ExoPlayerImplInternal implements Handler.Callback, SampleSource.Callback,
|
||||||
|
InvalidationListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Playback position information which is read on the application's thread by
|
* Playback position information which is read on the application's thread by
|
||||||
@ -76,8 +76,9 @@ import java.util.ArrayList;
|
|||||||
private static final int MSG_SEEK_TO = 3;
|
private static final int MSG_SEEK_TO = 3;
|
||||||
private static final int MSG_STOP = 4;
|
private static final int MSG_STOP = 4;
|
||||||
private static final int MSG_RELEASE = 5;
|
private static final int MSG_RELEASE = 5;
|
||||||
private static final int MSG_TRACK_SELECTION_INVALIDATED = 6;
|
private static final int MSG_SOURCE_PREPARED = 6;
|
||||||
private static final int MSG_CUSTOM = 7;
|
private static final int MSG_TRACK_SELECTION_INVALIDATED = 7;
|
||||||
|
private static final int MSG_CUSTOM = 8;
|
||||||
|
|
||||||
private static final int PREPARING_SOURCE_INTERVAL_MS = 10;
|
private static final int PREPARING_SOURCE_INTERVAL_MS = 10;
|
||||||
private static final int RENDERING_INTERVAL_MS = 10;
|
private static final int RENDERING_INTERVAL_MS = 10;
|
||||||
@ -203,6 +204,13 @@ import java.util.ArrayList;
|
|||||||
handler.sendEmptyMessage(MSG_TRACK_SELECTION_INVALIDATED);
|
handler.sendEmptyMessage(MSG_TRACK_SELECTION_INVALIDATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SampleSource.Callback implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSourcePrepared(SampleSource source) {
|
||||||
|
handler.obtainMessage(MSG_SOURCE_PREPARED, source).sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
// Handler.Callback implementation.
|
// Handler.Callback implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -233,14 +241,18 @@ import java.util.ArrayList;
|
|||||||
releaseInternal();
|
releaseInternal();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case MSG_CUSTOM: {
|
case MSG_SOURCE_PREPARED: {
|
||||||
sendMessagesInternal((ExoPlayerMessage[]) msg.obj);
|
timeline.handleSourcePrepared((SampleSource) msg.obj);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case MSG_TRACK_SELECTION_INVALIDATED: {
|
case MSG_TRACK_SELECTION_INVALIDATED: {
|
||||||
reselectTracksInternal();
|
reselectTracksInternal();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case MSG_CUSTOM: {
|
||||||
|
sendMessagesInternal((ExoPlayerMessage[]) msg.obj);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -368,7 +380,8 @@ import java.util.ArrayList;
|
|||||||
|
|
||||||
timeline.updateSources();
|
timeline.updateSources();
|
||||||
if (timeline.getSampleSource() == null) {
|
if (timeline.getSampleSource() == null) {
|
||||||
// We're still waiting for the source to be prepared.
|
// We're still waiting for the first source to be prepared.
|
||||||
|
timeline.maybeThrowSourcePrepareError();
|
||||||
scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS);
|
scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -393,15 +406,16 @@ import java.util.ArrayList;
|
|||||||
allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded;
|
allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Have timeline.updateSources() above return whether the timeline is ready, and remove
|
if (!allRenderersReadyOrEnded) {
|
||||||
// timeline.isReady(). This will avoid any inconsistencies that could arise due to the playback
|
timeline.maybeThrowSourcePrepareError();
|
||||||
// position update. We could probably return [ENDED|READY|BUFFERING] and get rid of isEnded too.
|
}
|
||||||
|
|
||||||
if (allRenderersEnded && (playbackInfo.durationUs == C.UNSET_TIME_US
|
if (allRenderersEnded && (playbackInfo.durationUs == C.UNSET_TIME_US
|
||||||
|| playbackInfo.durationUs <= playbackInfo.positionUs) && timeline.isEnded()) {
|
|| playbackInfo.durationUs <= playbackInfo.positionUs) && timeline.isEnded) {
|
||||||
setState(ExoPlayer.STATE_ENDED);
|
setState(ExoPlayer.STATE_ENDED);
|
||||||
stopRenderers();
|
stopRenderers();
|
||||||
} else if (state == ExoPlayer.STATE_BUFFERING) {
|
} else if (state == ExoPlayer.STATE_BUFFERING) {
|
||||||
if ((enabledRenderers.length > 0 ? allRenderersReadyOrEnded : timeline.isReady())
|
if ((enabledRenderers.length > 0 ? allRenderersReadyOrEnded : timeline.isReady)
|
||||||
&& bufferingPolicy.haveSufficientBuffer(playbackInfo.bufferedPositionUs, rebuffering)) {
|
&& bufferingPolicy.haveSufficientBuffer(playbackInfo.bufferedPositionUs, rebuffering)) {
|
||||||
setState(ExoPlayer.STATE_READY);
|
setState(ExoPlayer.STATE_READY);
|
||||||
if (playWhenReady) {
|
if (playWhenReady) {
|
||||||
@ -409,7 +423,7 @@ import java.util.ArrayList;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (state == ExoPlayer.STATE_READY) {
|
} else if (state == ExoPlayer.STATE_READY) {
|
||||||
if (enabledRenderers.length > 0 ? !allRenderersReadyOrEnded : !timeline.isReady()) {
|
if (enabledRenderers.length > 0 ? !allRenderersReadyOrEnded : !timeline.isReady) {
|
||||||
rebuffering = playWhenReady;
|
rebuffering = playWhenReady;
|
||||||
setState(ExoPlayer.STATE_BUFFERING);
|
setState(ExoPlayer.STATE_BUFFERING);
|
||||||
stopRenderers();
|
stopRenderers();
|
||||||
@ -558,6 +572,9 @@ import java.util.ArrayList;
|
|||||||
private final ArrayList<TrackStream> oldStreams;
|
private final ArrayList<TrackStream> oldStreams;
|
||||||
private final ArrayList<TrackSelection> newSelections;
|
private final ArrayList<TrackSelection> newSelections;
|
||||||
|
|
||||||
|
public boolean isReady;
|
||||||
|
public boolean isEnded;
|
||||||
|
|
||||||
private Source playingSource;
|
private Source playingSource;
|
||||||
private Source readingSource;
|
private Source readingSource;
|
||||||
private Source bufferingSource;
|
private Source bufferingSource;
|
||||||
@ -577,19 +594,16 @@ import java.util.ArrayList;
|
|||||||
return playingSource == null ? null : playingSource.sampleSource;
|
return playingSource == null ? null : playingSource.sampleSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEnded() {
|
public void maybeThrowSourcePrepareError() throws IOException {
|
||||||
if (playingSource == null) {
|
if (bufferingSource != null && !bufferingSource.prepared
|
||||||
return false;
|
&& (readingSource == null || readingSource.nextSource == bufferingSource)) {
|
||||||
|
for (TrackRenderer renderer : enabledRenderers) {
|
||||||
|
if (!renderer.hasReadStreamToEnd()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bufferingSource.sampleSource.maybeThrowPrepareError();
|
||||||
}
|
}
|
||||||
int sourceCount = sampleSourceProvider.getSourceCount();
|
|
||||||
return sourceCount != SampleSourceProvider.UNKNOWN_SOURCE_COUNT
|
|
||||||
&& playingSource.index == sourceCount - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isReady() {
|
|
||||||
return playingSourceEndPositionUs == C.UNSET_TIME_US
|
|
||||||
|| internalPositionUs < playingSourceEndPositionUs
|
|
||||||
|| (playingSource.nextSource != null && playingSource.nextSource.prepared);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateSources() throws ExoPlaybackException, IOException {
|
public void updateSources() throws ExoPlaybackException, IOException {
|
||||||
@ -611,36 +625,24 @@ import java.util.ArrayList;
|
|||||||
bufferingSource.setNextSource(newSource);
|
bufferingSource.setNextSource(newSource);
|
||||||
}
|
}
|
||||||
bufferingSource = newSource;
|
bufferingSource = newSource;
|
||||||
|
long startPositionUs = playingSource == null ? playbackInfo.positionUs : 0;
|
||||||
|
sampleSource.prepare(ExoPlayerImplInternal.this, bufferingPolicy.getLoadControl(),
|
||||||
|
startPositionUs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bufferingSource != null) {
|
|
||||||
if (!bufferingSource.prepared) {
|
|
||||||
// Continue preparation.
|
|
||||||
// TODO[playlists]: Add support for setting the start position to play in a source.
|
|
||||||
long startPositionUs = playingSource == null ? playbackInfo.positionUs : 0;
|
|
||||||
if (bufferingSource.prepare(startPositionUs, bufferingPolicy.getLoadControl())) {
|
|
||||||
Pair<TrackSelectionArray, Object> result = trackSelector.selectTracks(renderers,
|
|
||||||
bufferingSource.sampleSource.getTrackGroups());
|
|
||||||
bufferingSource.selectTracks(result.first, result.second, startPositionUs,
|
|
||||||
bufferingPolicy, renderers);
|
|
||||||
if (playingSource == null) {
|
|
||||||
// This is the first prepared source, so start playing it.
|
|
||||||
readingSource = bufferingSource;
|
|
||||||
setPlayingSource(readingSource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bufferingSource.hasEnabledTracks) {
|
|
||||||
long sourcePositionUs = internalPositionUs - bufferingSource.offsetUs;
|
|
||||||
bufferingSource.sampleSource.continueBuffering(sourcePositionUs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the playing and reading sources.
|
if (bufferingSource != null && bufferingSource.hasEnabledTracks) {
|
||||||
|
long sourcePositionUs = internalPositionUs - bufferingSource.offsetUs;
|
||||||
|
bufferingSource.sampleSource.continueBuffering(sourcePositionUs);
|
||||||
|
}
|
||||||
|
|
||||||
if (playingSource == null) {
|
if (playingSource == null) {
|
||||||
|
// We're waiting for the first source to be prepared.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the playing and reading sources.
|
||||||
if (playingSourceEndPositionUs == C.UNSET_TIME_US && playingSource.isFullyBuffered()) {
|
if (playingSourceEndPositionUs == C.UNSET_TIME_US && playingSource.isFullyBuffered()) {
|
||||||
playingSourceEndPositionUs = playingSource.offsetUs
|
playingSourceEndPositionUs = playingSource.offsetUs
|
||||||
+ playingSource.sampleSource.getDurationUs();
|
+ playingSource.sampleSource.getDurationUs();
|
||||||
@ -656,6 +658,7 @@ import java.util.ArrayList;
|
|||||||
updatePlaybackPositions();
|
updatePlaybackPositions();
|
||||||
eventHandler.obtainMessage(MSG_SOURCE_CHANGED, playbackInfo).sendToTarget();
|
eventHandler.obtainMessage(MSG_SOURCE_CHANGED, playbackInfo).sendToTarget();
|
||||||
}
|
}
|
||||||
|
updateTimelineState();
|
||||||
if (readingSource == null) {
|
if (readingSource == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -698,6 +701,23 @@ import java.util.ArrayList;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void handleSourcePrepared(SampleSource sampleSource) throws ExoPlaybackException {
|
||||||
|
if (bufferingSource == null || bufferingSource.sampleSource != sampleSource) {
|
||||||
|
// Stale event.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long startPositionUs = playingSource == null ? playbackInfo.positionUs : 0;
|
||||||
|
Pair<TrackSelectionArray, Object> result = trackSelector.selectTracks(renderers,
|
||||||
|
bufferingSource.sampleSource.getTrackGroups());
|
||||||
|
bufferingSource.handlePrepared(result.first, result.second, startPositionUs,
|
||||||
|
bufferingPolicy, renderers);
|
||||||
|
if (playingSource == null) {
|
||||||
|
// This is the first prepared source, so start playing it.
|
||||||
|
setPlayingSource(bufferingSource);
|
||||||
|
updateTimelineState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void seekToSource(int sourceIndex) throws ExoPlaybackException {
|
public void seekToSource(int sourceIndex) throws ExoPlaybackException {
|
||||||
// Clear the timeline, but keep the requested source if it is already prepared.
|
// Clear the timeline, but keep the requested source if it is already prepared.
|
||||||
Source source = playingSource;
|
Source source = playingSource;
|
||||||
@ -714,6 +734,7 @@ import java.util.ArrayList;
|
|||||||
if (newPlayingSource != null) {
|
if (newPlayingSource != null) {
|
||||||
newPlayingSource.nextSource = null;
|
newPlayingSource.nextSource = null;
|
||||||
setPlayingSource(newPlayingSource);
|
setPlayingSource(newPlayingSource);
|
||||||
|
updateTimelineState();
|
||||||
readingSource = playingSource;
|
readingSource = playingSource;
|
||||||
bufferingSource = playingSource;
|
bufferingSource = playingSource;
|
||||||
} else {
|
} else {
|
||||||
@ -794,6 +815,8 @@ import java.util.ArrayList;
|
|||||||
source.release();
|
source.release();
|
||||||
source = source.nextSource;
|
source = source.nextSource;
|
||||||
}
|
}
|
||||||
|
isReady = false;
|
||||||
|
isEnded = false;
|
||||||
playingSource = null;
|
playingSource = null;
|
||||||
readingSource = null;
|
readingSource = null;
|
||||||
bufferingSource = null;
|
bufferingSource = null;
|
||||||
@ -812,6 +835,15 @@ import java.util.ArrayList;
|
|||||||
enableRenderers(source.trackSelections, enabledRendererCount);
|
enableRenderers(source.trackSelections, enabledRendererCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateTimelineState() {
|
||||||
|
isReady = playingSourceEndPositionUs == C.UNSET_TIME_US
|
||||||
|
|| internalPositionUs < playingSourceEndPositionUs
|
||||||
|
|| (playingSource.nextSource != null && playingSource.nextSource.prepared);
|
||||||
|
int sourceCount = sampleSourceProvider.getSourceCount();
|
||||||
|
isEnded = sourceCount != SampleSourceProvider.UNKNOWN_SOURCE_COUNT
|
||||||
|
&& playingSource.index == sourceCount - 1;
|
||||||
|
}
|
||||||
|
|
||||||
private int disableRenderers(boolean sourceTransition, TrackSelectionArray newTrackSelections)
|
private int disableRenderers(boolean sourceTransition, TrackSelectionArray newTrackSelections)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
// Disable any renderers whose selections have changed, adding the corresponding TrackStream
|
// Disable any renderers whose selections have changed, adding the corresponding TrackStream
|
||||||
@ -924,28 +956,20 @@ import java.util.ArrayList;
|
|||||||
trackStreams = new TrackStream[rendererCount];
|
trackStreams = new TrackStream[rendererCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFullyBuffered() {
|
|
||||||
return prepared && (!hasEnabledTracks
|
|
||||||
|| sampleSource.getBufferedPositionUs() == C.END_OF_SOURCE_US);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean prepare(long startPositionUs, LoadControl loadControl) throws IOException {
|
|
||||||
if (sampleSource.prepare(startPositionUs, loadControl)) {
|
|
||||||
prepared = true;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNextSource(Source nextSource) {
|
public void setNextSource(Source nextSource) {
|
||||||
this.nextSource = nextSource;
|
this.nextSource = nextSource;
|
||||||
nextSource.offsetUs = offsetUs + sampleSource.getDurationUs();
|
nextSource.offsetUs = offsetUs + sampleSource.getDurationUs();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectTracks(TrackSelectionArray newTrackSelections, Object trackSelectionData,
|
public boolean isFullyBuffered() {
|
||||||
|
return prepared && (!hasEnabledTracks
|
||||||
|
|| sampleSource.getBufferedPositionUs() == C.END_OF_SOURCE_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handlePrepared(TrackSelectionArray newTrackSelections, Object trackSelectionData,
|
||||||
long positionUs, BufferingPolicy bufferingPolicy, TrackRenderer[] renderers)
|
long positionUs, BufferingPolicy bufferingPolicy, TrackRenderer[] renderers)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
|
prepared = true;
|
||||||
this.trackSelectionData = trackSelectionData;
|
this.trackSelectionData = trackSelectionData;
|
||||||
if (newTrackSelections.equals(trackSelections)) {
|
if (newTrackSelections.equals(trackSelections)) {
|
||||||
return;
|
return;
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
package com.google.android.exoplayer;
|
package com.google.android.exoplayer;
|
||||||
|
|
||||||
import com.google.android.exoplayer.BufferingPolicy.LoadControl;
|
import com.google.android.exoplayer.BufferingPolicy.LoadControl;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
|
||||||
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
@ -28,57 +27,40 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* Combines multiple {@link SampleSource} instances.
|
* Combines multiple {@link SampleSource} instances.
|
||||||
*/
|
*/
|
||||||
public final class MultiSampleSource implements SampleSource {
|
public final class MultiSampleSource implements SampleSource, SampleSource.Callback {
|
||||||
|
|
||||||
private final SampleSource[] sources;
|
private final SampleSource[] sources;
|
||||||
private final IdentityHashMap<TrackStream, SampleSource> trackStreamSources;
|
private final IdentityHashMap<TrackStream, SampleSource> trackStreamSources;
|
||||||
private final int[] selectedTrackCounts;
|
private final int[] selectedTrackCounts;
|
||||||
|
|
||||||
private boolean prepared;
|
private Callback callback;
|
||||||
private boolean seenFirstTrackSelection;
|
private int pendingChildPrepareCount;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
private TrackGroupArray trackGroups;
|
private TrackGroupArray trackGroups;
|
||||||
|
|
||||||
|
private boolean seenFirstTrackSelection;
|
||||||
private SampleSource[] enabledSources;
|
private SampleSource[] enabledSources;
|
||||||
|
|
||||||
public MultiSampleSource(SampleSource... sources) {
|
public MultiSampleSource(SampleSource... sources) {
|
||||||
this.sources = sources;
|
this.sources = sources;
|
||||||
|
pendingChildPrepareCount = sources.length;
|
||||||
trackStreamSources = new IdentityHashMap<>();
|
trackStreamSources = new IdentityHashMap<>();
|
||||||
selectedTrackCounts = new int[sources.length];
|
selectedTrackCounts = new int[sources.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare(long positionUs, LoadControl loadControl) throws IOException {
|
public void prepare(Callback callback, LoadControl loadControl, long positionUs) {
|
||||||
if (prepared) {
|
this.callback = callback;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
boolean sourcesPrepared = true;
|
|
||||||
for (SampleSource source : sources) {
|
for (SampleSource source : sources) {
|
||||||
sourcesPrepared &= source.prepare(positionUs, loadControl);
|
source.prepare(this, loadControl, positionUs);
|
||||||
}
|
}
|
||||||
if (!sourcesPrepared) {
|
}
|
||||||
return false;
|
|
||||||
}
|
@Override
|
||||||
durationUs = 0;
|
public void maybeThrowPrepareError() throws IOException {
|
||||||
int totalTrackGroupCount = 0;
|
|
||||||
for (SampleSource source : sources) {
|
for (SampleSource source : sources) {
|
||||||
totalTrackGroupCount += source.getTrackGroups().length;
|
source.maybeThrowPrepareError();
|
||||||
if (durationUs != C.UNSET_TIME_US) {
|
|
||||||
long sourceDurationUs = source.getDurationUs();
|
|
||||||
durationUs = sourceDurationUs == C.UNSET_TIME_US
|
|
||||||
? C.UNSET_TIME_US : Math.max(durationUs, sourceDurationUs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount];
|
|
||||||
int trackGroupIndex = 0;
|
|
||||||
for (SampleSource source : sources) {
|
|
||||||
int sourceTrackGroupCount = source.getTrackGroups().length;
|
|
||||||
for (int j = 0; j < sourceTrackGroupCount; j++) {
|
|
||||||
trackGroupArray[trackGroupIndex++] = source.getTrackGroups().get(j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trackGroups = new TrackGroupArray(trackGroupArray);
|
|
||||||
prepared = true;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -94,13 +76,12 @@ public final class MultiSampleSource implements SampleSource {
|
|||||||
@Override
|
@Override
|
||||||
public TrackStream[] selectTracks(List<TrackStream> oldStreams,
|
public TrackStream[] selectTracks(List<TrackStream> oldStreams,
|
||||||
List<TrackSelection> newSelections, long positionUs) {
|
List<TrackSelection> newSelections, long positionUs) {
|
||||||
Assertions.checkState(prepared);
|
|
||||||
TrackStream[] newStreams = new TrackStream[newSelections.size()];
|
TrackStream[] newStreams = new TrackStream[newSelections.size()];
|
||||||
// Select tracks for each source.
|
// Select tracks for each source.
|
||||||
int enabledSourceCount = 0;
|
int enabledSourceCount = 0;
|
||||||
for (int i = 0; i < sources.length; i++) {
|
for (int i = 0; i < sources.length; i++) {
|
||||||
selectedTrackCounts[i] += selectTracks(sources[i], oldStreams, newSelections, positionUs,
|
selectedTrackCounts[i] += selectTracks(sources[i], oldStreams, newSelections, positionUs,
|
||||||
newStreams);
|
newStreams, seenFirstTrackSelection);
|
||||||
if (selectedTrackCounts[i] > 0) {
|
if (selectedTrackCounts[i] > 0) {
|
||||||
enabledSourceCount++;
|
enabledSourceCount++;
|
||||||
}
|
}
|
||||||
@ -166,10 +147,40 @@ public final class MultiSampleSource implements SampleSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SampleSource.Callback implementation
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSourcePrepared(SampleSource ignored) {
|
||||||
|
if (--pendingChildPrepareCount > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
durationUs = 0;
|
||||||
|
int totalTrackGroupCount = 0;
|
||||||
|
for (SampleSource source : sources) {
|
||||||
|
totalTrackGroupCount += source.getTrackGroups().length;
|
||||||
|
if (durationUs != C.UNSET_TIME_US) {
|
||||||
|
long sourceDurationUs = source.getDurationUs();
|
||||||
|
durationUs = sourceDurationUs == C.UNSET_TIME_US
|
||||||
|
? C.UNSET_TIME_US : Math.max(durationUs, sourceDurationUs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount];
|
||||||
|
int trackGroupIndex = 0;
|
||||||
|
for (SampleSource source : sources) {
|
||||||
|
int sourceTrackGroupCount = source.getTrackGroups().length;
|
||||||
|
for (int j = 0; j < sourceTrackGroupCount; j++) {
|
||||||
|
trackGroupArray[trackGroupIndex++] = source.getTrackGroups().get(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trackGroups = new TrackGroupArray(trackGroupArray);
|
||||||
|
callback.onSourcePrepared(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
private int selectTracks(SampleSource source, List<TrackStream> allOldStreams,
|
private int selectTracks(SampleSource source, List<TrackStream> allOldStreams,
|
||||||
List<TrackSelection> allNewSelections, long positionUs, TrackStream[] allNewStreams) {
|
List<TrackSelection> allNewSelections, long positionUs, TrackStream[] allNewStreams,
|
||||||
|
boolean seenFirstTrackSelection) {
|
||||||
// Get the subset of the old streams for the source.
|
// Get the subset of the old streams for the source.
|
||||||
ArrayList<TrackStream> oldStreams = new ArrayList<>();
|
ArrayList<TrackStream> oldStreams = new ArrayList<>();
|
||||||
for (int i = 0; i < allOldStreams.size(); i++) {
|
for (int i = 0; i < allOldStreams.size(); i++) {
|
||||||
|
@ -26,18 +26,45 @@ import java.util.List;
|
|||||||
public interface SampleSource {
|
public interface SampleSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares the source, or does nothing if the source is already prepared.
|
* A callback to be notified of {@link SampleSource} events.
|
||||||
* <p>
|
|
||||||
* {@link #selectTracks(List, List, long)} <b>must</b> be called after the source is prepared to
|
|
||||||
* make an initial track selection. This is true even if the caller does not wish to select any
|
|
||||||
* tracks.
|
|
||||||
*
|
|
||||||
* @param positionUs The player's current playback position.
|
|
||||||
* @param loadControl A {@link LoadControl} to determine when to load data.
|
|
||||||
* @return True if the source is prepared, false otherwise.
|
|
||||||
* @throws IOException If there's an error preparing the source.
|
|
||||||
*/
|
*/
|
||||||
boolean prepare(long positionUs, LoadControl loadControl) throws IOException;
|
interface Callback {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked by the source when preparation completes.
|
||||||
|
* <p>
|
||||||
|
* May be called from any thread. After invoking this method, the source can expect
|
||||||
|
* {@link #selectTracks(List, List, long)} to be invoked when the initial track selection.
|
||||||
|
*
|
||||||
|
* @param source The prepared source.
|
||||||
|
*/
|
||||||
|
void onSourcePrepared(SampleSource source);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts preparation of the source.
|
||||||
|
* <p>
|
||||||
|
* {@link Callback#onSourcePrepared(SampleSource)} is invoked when preparation completes. If
|
||||||
|
* preparation fails, {@link #maybeThrowPrepareError()} will throw an {@link IOException} if
|
||||||
|
* invoked.
|
||||||
|
*
|
||||||
|
* @param callback A callback to receive updates from the source.
|
||||||
|
* @param loadControl A {@link LoadControl} to determine when to load data.
|
||||||
|
* @param positionUs The player's current playback position.
|
||||||
|
* @return True if the source is prepared, false otherwise.
|
||||||
|
*/
|
||||||
|
void prepare(Callback callback, LoadControl loadControl, long positionUs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws an error that's preventing the source from becoming prepared. Does nothing if no such
|
||||||
|
* error exists.
|
||||||
|
* <p>
|
||||||
|
* This method should only be called before the source has completed preparation.
|
||||||
|
*
|
||||||
|
* @throws IOException The underlying error.
|
||||||
|
*/
|
||||||
|
void maybeThrowPrepareError() throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the duration of the source in microseconds, or {@link C#UNSET_TIME_US} if not known.
|
* Returns the duration of the source in microseconds, or {@link C#UNSET_TIME_US} if not known.
|
||||||
|
@ -109,9 +109,13 @@ public final class SingleSampleSource implements SampleSource, TrackStream,
|
|||||||
// SampleSource implementation.
|
// SampleSource implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare(long positionUs, LoadControl loadControl) {
|
public void prepare(Callback callback, LoadControl loadControl, long positionUs) {
|
||||||
// TODO: Use the load control.
|
callback.onSourcePrepared(this);
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void maybeThrowPrepareError() throws IOException {
|
||||||
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -112,6 +112,10 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
|
|||||||
private boolean readEndOfStream;
|
private boolean readEndOfStream;
|
||||||
private boolean streamIsFinal;
|
private boolean streamIsFinal;
|
||||||
|
|
||||||
|
public TrackRenderer() {
|
||||||
|
readEndOfStream = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the index of this renderer within the player.
|
* Sets the index of this renderer within the player.
|
||||||
*
|
*
|
||||||
|
@ -70,8 +70,7 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
|
|||||||
* @param eventDispatcher A dispatcher to notify of events.
|
* @param eventDispatcher A dispatcher to notify of events.
|
||||||
*/
|
*/
|
||||||
public ChunkTrackStream(int trackType, T chunkSource, LoadControl loadControl, long positionUs,
|
public ChunkTrackStream(int trackType, T chunkSource, LoadControl loadControl, long positionUs,
|
||||||
int minLoadableRetryCount,
|
int minLoadableRetryCount, EventDispatcher eventDispatcher) {
|
||||||
EventDispatcher eventDispatcher) {
|
|
||||||
this.trackType = trackType;
|
this.trackType = trackType;
|
||||||
this.chunkSource = chunkSource;
|
this.chunkSource = chunkSource;
|
||||||
this.loadControl = loadControl;
|
this.loadControl = loadControl;
|
||||||
|
@ -39,7 +39,6 @@ import com.google.android.exoplayer.upstream.BandwidthMeter;
|
|||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DataSourceFactory;
|
import com.google.android.exoplayer.upstream.DataSourceFactory;
|
||||||
import com.google.android.exoplayer.upstream.Loader;
|
import com.google.android.exoplayer.upstream.Loader;
|
||||||
import com.google.android.exoplayer.upstream.Loader.Callback;
|
|
||||||
import com.google.android.exoplayer.upstream.ParsingLoadable;
|
import com.google.android.exoplayer.upstream.ParsingLoadable;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
@ -84,6 +83,7 @@ public final class DashSampleSource implements SampleSource {
|
|||||||
private long manifestLoadEndTimestamp;
|
private long manifestLoadEndTimestamp;
|
||||||
private MediaPresentationDescription manifest;
|
private MediaPresentationDescription manifest;
|
||||||
|
|
||||||
|
private Callback callback;
|
||||||
private LoadControl loadControl;
|
private LoadControl loadControl;
|
||||||
private boolean prepared;
|
private boolean prepared;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
@ -107,16 +107,15 @@ public final class DashSampleSource implements SampleSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare(long positionUs, LoadControl loadControl) throws IOException {
|
public void prepare(Callback callback, LoadControl loadControl, long positionUs) {
|
||||||
if (prepared) {
|
this.callback = callback;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
this.loadControl = loadControl;
|
this.loadControl = loadControl;
|
||||||
|
startLoadingManifest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void maybeThrowPrepareError() throws IOException {
|
||||||
loader.maybeThrowError();
|
loader.maybeThrowError();
|
||||||
if (!loader.isLoading() && manifest == null) {
|
|
||||||
startLoadingManifest();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -231,6 +230,7 @@ public final class DashSampleSource implements SampleSource {
|
|||||||
resolveUtcTimingElement(manifest.utcTiming);
|
resolveUtcTimingElement(manifest.utcTiming);
|
||||||
} else {
|
} else {
|
||||||
prepared = true;
|
prepared = true;
|
||||||
|
callback.onSourcePrepared(this);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (ChunkTrackStream<DashChunkSource> trackStream : trackStreams) {
|
for (ChunkTrackStream<DashChunkSource> trackStream : trackStreams) {
|
||||||
@ -308,16 +308,18 @@ public final class DashSampleSource implements SampleSource {
|
|||||||
private void onUtcTimestampResolved(long elapsedRealtimeOffsetMs) {
|
private void onUtcTimestampResolved(long elapsedRealtimeOffsetMs) {
|
||||||
this.elapsedRealtimeOffset = elapsedRealtimeOffsetMs;
|
this.elapsedRealtimeOffset = elapsedRealtimeOffsetMs;
|
||||||
prepared = true;
|
prepared = true;
|
||||||
|
callback.onSourcePrepared(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onUtcTimestampResolutionError(IOException error) {
|
private void onUtcTimestampResolutionError(IOException error) {
|
||||||
Log.e(TAG, "Failed to resolve UtcTiming element.", error);
|
Log.e(TAG, "Failed to resolve UtcTiming element.", error);
|
||||||
// Be optimistic and continue in the hope that the device clock is correct.
|
// Be optimistic and continue in the hope that the device clock is correct.
|
||||||
prepared = true;
|
prepared = true;
|
||||||
|
callback.onSourcePrepared(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> void startLoading(ParsingLoadable<T> loadable, Callback<ParsingLoadable<T>> callback,
|
private <T> void startLoading(ParsingLoadable<T> loadable,
|
||||||
int minRetryCount) {
|
Loader.Callback<ParsingLoadable<T>> callback, int minRetryCount) {
|
||||||
long elapsedRealtimeMs = loader.startLoading(loadable, callback, minRetryCount);
|
long elapsedRealtimeMs = loader.startLoading(loadable, callback, minRetryCount);
|
||||||
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,20 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
*/
|
*/
|
||||||
public final class DefaultTrackOutput implements TrackOutput {
|
public final class DefaultTrackOutput implements TrackOutput {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener for changes to the upstream format.
|
||||||
|
*/
|
||||||
|
public interface UpstreamFormatChangedListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked on the loading thread when an upstream format change occurs.
|
||||||
|
*
|
||||||
|
* @param format The new upstream format.
|
||||||
|
*/
|
||||||
|
void onUpstreamFormatChanged(Format format);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private static final int INITIAL_SCRATCH_SIZE = 32;
|
private static final int INITIAL_SCRATCH_SIZE = 32;
|
||||||
|
|
||||||
private static final int STATE_ENABLED = 0;
|
private static final int STATE_ENABLED = 0;
|
||||||
@ -64,6 +78,7 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||||||
private int lastAllocationOffset;
|
private int lastAllocationOffset;
|
||||||
private boolean needKeyframe;
|
private boolean needKeyframe;
|
||||||
private boolean pendingSplice;
|
private boolean pendingSplice;
|
||||||
|
private UpstreamFormatChangedListener upstreamFormatChangeListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param allocator An {@link Allocator} from which allocations for sample data can be obtained.
|
* @param allocator An {@link Allocator} from which allocations for sample data can be obtained.
|
||||||
@ -391,6 +406,15 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||||||
|
|
||||||
// Called by the loading thread.
|
// Called by the loading thread.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a listener to be notified of changes to the upstream format.
|
||||||
|
*
|
||||||
|
* @param listener The listener.
|
||||||
|
*/
|
||||||
|
public void setUpstreamFormatChangeListener(UpstreamFormatChangedListener listener) {
|
||||||
|
upstreamFormatChangeListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like {@link #format(Format)}, but with an offset that will be added to the timestamps of
|
* Like {@link #format(Format)}, but with an offset that will be added to the timestamps of
|
||||||
* samples subsequently queued to the buffer. The offset is also used to adjust
|
* samples subsequently queued to the buffer. The offset is also used to adjust
|
||||||
@ -407,7 +431,11 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void format(Format format) {
|
public void format(Format format) {
|
||||||
infoQueue.format(getAdjustedSampleFormat(format, sampleOffsetUs));
|
Format adjustedFormat = getAdjustedSampleFormat(format, sampleOffsetUs);
|
||||||
|
infoQueue.format(adjustedFormat);
|
||||||
|
if (upstreamFormatChangeListener != null) {
|
||||||
|
upstreamFormatChangeListener.onUpstreamFormatChanged(adjustedFormat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer.extractor;
|
|||||||
import com.google.android.exoplayer.BufferingPolicy.LoadControl;
|
import com.google.android.exoplayer.BufferingPolicy.LoadControl;
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.DecoderInputBuffer;
|
import com.google.android.exoplayer.DecoderInputBuffer;
|
||||||
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.FormatHolder;
|
import com.google.android.exoplayer.FormatHolder;
|
||||||
import com.google.android.exoplayer.ParserException;
|
import com.google.android.exoplayer.ParserException;
|
||||||
import com.google.android.exoplayer.SampleSource;
|
import com.google.android.exoplayer.SampleSource;
|
||||||
@ -25,6 +26,7 @@ import com.google.android.exoplayer.TrackGroup;
|
|||||||
import com.google.android.exoplayer.TrackGroupArray;
|
import com.google.android.exoplayer.TrackGroupArray;
|
||||||
import com.google.android.exoplayer.TrackSelection;
|
import com.google.android.exoplayer.TrackSelection;
|
||||||
import com.google.android.exoplayer.TrackStream;
|
import com.google.android.exoplayer.TrackStream;
|
||||||
|
import com.google.android.exoplayer.extractor.DefaultTrackOutput.UpstreamFormatChangedListener;
|
||||||
import com.google.android.exoplayer.upstream.BandwidthMeter;
|
import com.google.android.exoplayer.upstream.BandwidthMeter;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DataSourceFactory;
|
import com.google.android.exoplayer.upstream.DataSourceFactory;
|
||||||
@ -71,7 +73,8 @@ import java.util.List;
|
|||||||
* from {@link Extractor#sniff(ExtractorInput)} will be used.
|
* from {@link Extractor#sniff(ExtractorInput)} will be used.
|
||||||
*/
|
*/
|
||||||
public final class ExtractorSampleSource implements SampleSource, ExtractorOutput,
|
public final class ExtractorSampleSource implements SampleSource, ExtractorOutput,
|
||||||
Loader.Callback<ExtractorSampleSource.ExtractingLoadable> {
|
Loader.Callback<ExtractorSampleSource.ExtractingLoadable>,
|
||||||
|
UpstreamFormatChangedListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface definition for a callback to be notified of {@link ExtractorSampleSource} events.
|
* Interface definition for a callback to be notified of {@link ExtractorSampleSource} events.
|
||||||
@ -129,11 +132,12 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
private final Loader loader;
|
private final Loader loader;
|
||||||
private final ExtractorHolder extractorHolder;
|
private final ExtractorHolder extractorHolder;
|
||||||
|
|
||||||
private volatile boolean tracksBuilt;
|
private Callback callback;
|
||||||
private volatile SeekMap seekMap;
|
|
||||||
|
|
||||||
private LoadControl loadControl;
|
private LoadControl loadControl;
|
||||||
|
private SeekMap seekMap;
|
||||||
|
private boolean tracksBuilt;
|
||||||
private boolean prepared;
|
private boolean prepared;
|
||||||
|
|
||||||
private boolean seenFirstTrackSelection;
|
private boolean seenFirstTrackSelection;
|
||||||
private boolean notifyReset;
|
private boolean notifyReset;
|
||||||
private int enabledTrackCount;
|
private int enabledTrackCount;
|
||||||
@ -304,31 +308,16 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
// SampleSource implementation.
|
// SampleSource implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare(long positionUs, LoadControl loadControl) throws IOException {
|
public void prepare(Callback callback, LoadControl loadControl, long positionUs) {
|
||||||
if (prepared) {
|
this.callback = callback;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
this.loadControl = loadControl;
|
this.loadControl = loadControl;
|
||||||
if (seekMap != null && tracksBuilt && haveFormatsForAllTracks()) {
|
loadCondition.open();
|
||||||
loadCondition.close();
|
startLoading();
|
||||||
int trackCount = sampleQueues.length;
|
}
|
||||||
TrackGroup[] trackArray = new TrackGroup[trackCount];
|
|
||||||
trackEnabledStates = new boolean[trackCount];
|
@Override
|
||||||
durationUs = seekMap.getDurationUs();
|
public void maybeThrowPrepareError() throws IOException {
|
||||||
for (int i = 0; i < trackCount; i++) {
|
|
||||||
trackArray[i] = new TrackGroup(sampleQueues[i].getUpstreamFormat());
|
|
||||||
}
|
|
||||||
tracks = new TrackGroupArray(trackArray);
|
|
||||||
prepared = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// We're not prepared.
|
|
||||||
maybeThrowError();
|
maybeThrowError();
|
||||||
if (!loader.isLoading()) {
|
|
||||||
loadCondition.open();
|
|
||||||
startLoading();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -521,6 +510,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
public TrackOutput track(int id) {
|
public TrackOutput track(int id) {
|
||||||
sampleQueues = Arrays.copyOf(sampleQueues, sampleQueues.length + 1);
|
sampleQueues = Arrays.copyOf(sampleQueues, sampleQueues.length + 1);
|
||||||
DefaultTrackOutput sampleQueue = new DefaultTrackOutput(loadControl.getAllocator());
|
DefaultTrackOutput sampleQueue = new DefaultTrackOutput(loadControl.getAllocator());
|
||||||
|
sampleQueue.setUpstreamFormatChangeListener(this);
|
||||||
sampleQueues[sampleQueues.length - 1] = sampleQueue;
|
sampleQueues[sampleQueues.length - 1] = sampleQueue;
|
||||||
return sampleQueue;
|
return sampleQueue;
|
||||||
}
|
}
|
||||||
@ -528,15 +518,46 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
@Override
|
@Override
|
||||||
public void endTracks() {
|
public void endTracks() {
|
||||||
tracksBuilt = true;
|
tracksBuilt = true;
|
||||||
|
maybeFinishPrepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seekMap(SeekMap seekMap) {
|
public void seekMap(SeekMap seekMap) {
|
||||||
this.seekMap = seekMap;
|
this.seekMap = seekMap;
|
||||||
|
maybeFinishPrepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamFormatChangedListener implementation
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpstreamFormatChanged(Format format) {
|
||||||
|
maybeFinishPrepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
|
private void maybeFinishPrepare() {
|
||||||
|
if (seekMap == null || !tracksBuilt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (DefaultTrackOutput sampleQueue : sampleQueues) {
|
||||||
|
if (sampleQueue.getUpstreamFormat() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadCondition.close();
|
||||||
|
int trackCount = sampleQueues.length;
|
||||||
|
TrackGroup[] trackArray = new TrackGroup[trackCount];
|
||||||
|
trackEnabledStates = new boolean[trackCount];
|
||||||
|
durationUs = seekMap.getDurationUs();
|
||||||
|
for (int i = 0; i < trackCount; i++) {
|
||||||
|
trackArray[i] = new TrackGroup(sampleQueues[i].getUpstreamFormat());
|
||||||
|
}
|
||||||
|
tracks = new TrackGroupArray(trackArray);
|
||||||
|
prepared = true;
|
||||||
|
callback.onSourcePrepared(this);
|
||||||
|
}
|
||||||
|
|
||||||
private void copyLengthFromLoader(ExtractingLoadable loadable) {
|
private void copyLengthFromLoader(ExtractingLoadable loadable) {
|
||||||
if (length == C.LENGTH_UNBOUNDED) {
|
if (length == C.LENGTH_UNBOUNDED) {
|
||||||
length = loadable.length;
|
length = loadable.length;
|
||||||
@ -618,15 +639,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
return largestQueuedTimestampUs;
|
return largestQueuedTimestampUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean haveFormatsForAllTracks() {
|
|
||||||
for (DefaultTrackOutput sampleQueue : sampleQueues) {
|
|
||||||
if (sampleQueue.getUpstreamFormat() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPendingReset() {
|
private boolean isPendingReset() {
|
||||||
return pendingResetPositionUs != C.UNSET_TIME_US;
|
return pendingResetPositionUs != C.UNSET_TIME_US;
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ import java.util.List;
|
|||||||
* A {@link SampleSource} for HLS streams.
|
* A {@link SampleSource} for HLS streams.
|
||||||
*/
|
*/
|
||||||
public final class HlsSampleSource implements SampleSource,
|
public final class HlsSampleSource implements SampleSource,
|
||||||
Loader.Callback<ParsingLoadable<HlsPlaylist>> {
|
Loader.Callback<ParsingLoadable<HlsPlaylist>>, HlsTrackStreamWrapper.Callback {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The minimum number of times to retry loading data prior to failing.
|
* The minimum number of times to retry loading data prior to failing.
|
||||||
@ -70,7 +70,11 @@ public final class HlsSampleSource implements SampleSource,
|
|||||||
private final DataSource manifestDataSource;
|
private final DataSource manifestDataSource;
|
||||||
private final HlsPlaylistParser manifestParser;
|
private final HlsPlaylistParser manifestParser;
|
||||||
|
|
||||||
|
private Callback callback;
|
||||||
private LoadControl loadControl;
|
private LoadControl loadControl;
|
||||||
|
private long preparePositionUs;
|
||||||
|
private int pendingPrepareCount;
|
||||||
|
|
||||||
private boolean seenFirstTrackSelection;
|
private boolean seenFirstTrackSelection;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
private boolean isLive;
|
private boolean isLive;
|
||||||
@ -96,50 +100,26 @@ public final class HlsSampleSource implements SampleSource,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare(long positionUs, LoadControl loadControl) throws IOException {
|
public void prepare(Callback callback, LoadControl loadControl, long positionUs) {
|
||||||
if (trackGroups != null) {
|
this.callback = callback;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loadControl = loadControl;
|
this.loadControl = loadControl;
|
||||||
|
this.preparePositionUs = positionUs;
|
||||||
|
ParsingLoadable<HlsPlaylist> loadable = new ParsingLoadable<>(manifestDataSource,
|
||||||
|
manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
|
||||||
|
long elapsedRealtimeMs = manifestFetcher.startLoading(loadable, this,
|
||||||
|
MIN_LOADABLE_RETRY_COUNT);
|
||||||
|
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void maybeThrowPrepareError() throws IOException {
|
||||||
if (trackStreamWrappers == null) {
|
if (trackStreamWrappers == null) {
|
||||||
manifestFetcher.maybeThrowError();
|
manifestFetcher.maybeThrowError();
|
||||||
if (!manifestFetcher.isLoading()) {
|
} else {
|
||||||
ParsingLoadable<HlsPlaylist> loadable = new ParsingLoadable<>(manifestDataSource,
|
for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
|
||||||
manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
|
trackStreamWrapper.maybeThrowPrepareError();
|
||||||
long elapsedRealtimeMs = manifestFetcher.startLoading(loadable, this,
|
|
||||||
MIN_LOADABLE_RETRY_COUNT);
|
|
||||||
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean trackStreamWrappersPrepared = true;
|
|
||||||
for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
|
|
||||||
trackStreamWrappersPrepared &= trackStreamWrapper.prepare(positionUs);
|
|
||||||
}
|
|
||||||
if (!trackStreamWrappersPrepared) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The wrapper at index 0 is the one of type TRACK_TYPE_DEFAULT.
|
|
||||||
durationUs = trackStreamWrappers[0].getDurationUs();
|
|
||||||
isLive = trackStreamWrappers[0].isLive();
|
|
||||||
|
|
||||||
int totalTrackGroupCount = 0;
|
|
||||||
for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
|
|
||||||
totalTrackGroupCount += trackStreamWrapper.getTrackGroups().length;
|
|
||||||
}
|
|
||||||
TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount];
|
|
||||||
int trackGroupIndex = 0;
|
|
||||||
for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
|
|
||||||
int wrapperTrackGroupCount = trackStreamWrapper.getTrackGroups().length;
|
|
||||||
for (int j = 0; j < wrapperTrackGroupCount; j++) {
|
|
||||||
trackGroupArray[trackGroupIndex++] = trackStreamWrapper.getTrackGroups().get(j);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trackGroups = new TrackGroupArray(trackGroupArray);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -237,6 +217,10 @@ public final class HlsSampleSource implements SampleSource,
|
|||||||
trackStreamWrappers = new HlsTrackStreamWrapper[trackStreamWrapperList.size()];
|
trackStreamWrappers = new HlsTrackStreamWrapper[trackStreamWrapperList.size()];
|
||||||
trackStreamWrapperList.toArray(trackStreamWrappers);
|
trackStreamWrapperList.toArray(trackStreamWrappers);
|
||||||
selectedTrackCounts = new int[trackStreamWrappers.length];
|
selectedTrackCounts = new int[trackStreamWrappers.length];
|
||||||
|
pendingPrepareCount = trackStreamWrappers.length;
|
||||||
|
for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
|
||||||
|
trackStreamWrapper.prepare();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -255,6 +239,34 @@ public final class HlsSampleSource implements SampleSource,
|
|||||||
return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
|
return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HlsTrackStreamWrapper callback.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrepared() {
|
||||||
|
if (--pendingPrepareCount > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The wrapper at index 0 is the one of type TRACK_TYPE_DEFAULT.
|
||||||
|
durationUs = trackStreamWrappers[0].getDurationUs();
|
||||||
|
isLive = trackStreamWrappers[0].isLive();
|
||||||
|
|
||||||
|
int totalTrackGroupCount = 0;
|
||||||
|
for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
|
||||||
|
totalTrackGroupCount += trackStreamWrapper.getTrackGroups().length;
|
||||||
|
}
|
||||||
|
TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount];
|
||||||
|
int trackGroupIndex = 0;
|
||||||
|
for (HlsTrackStreamWrapper trackStreamWrapper : trackStreamWrappers) {
|
||||||
|
int wrapperTrackGroupCount = trackStreamWrapper.getTrackGroups().length;
|
||||||
|
for (int j = 0; j < wrapperTrackGroupCount; j++) {
|
||||||
|
trackGroupArray[trackGroupIndex++] = trackStreamWrapper.getTrackGroups().get(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trackGroups = new TrackGroupArray(trackGroupArray);
|
||||||
|
callback.onSourcePrepared(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
private List<HlsTrackStreamWrapper> buildTrackStreamWrappers(HlsPlaylist playlist) {
|
private List<HlsTrackStreamWrapper> buildTrackStreamWrappers(HlsPlaylist playlist) {
|
||||||
@ -296,16 +308,18 @@ public final class HlsSampleSource implements SampleSource,
|
|||||||
} else {
|
} else {
|
||||||
// Leave the enabled variants unchanged. They're likely either all video or all audio.
|
// Leave the enabled variants unchanged. They're likely either all video or all audio.
|
||||||
}
|
}
|
||||||
Variant[] variants = new Variant[selectedVariants.size()];
|
if (!selectedVariants.isEmpty()) {
|
||||||
selectedVariants.toArray(variants);
|
Variant[] variants = new Variant[selectedVariants.size()];
|
||||||
trackStreamWrappers.add(buildTrackStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants,
|
selectedVariants.toArray(variants);
|
||||||
new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter), masterPlaylist.muxedAudioFormat,
|
trackStreamWrappers.add(buildTrackStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants,
|
||||||
masterPlaylist.muxedCaptionFormat));
|
new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter), masterPlaylist.muxedAudioFormat,
|
||||||
|
masterPlaylist.muxedCaptionFormat));
|
||||||
|
}
|
||||||
|
|
||||||
// Build the audio stream wrapper if applicable.
|
// Build the audio stream wrapper if applicable.
|
||||||
List<Variant> audioVariants = masterPlaylist.audios;
|
List<Variant> audioVariants = masterPlaylist.audios;
|
||||||
if (!audioVariants.isEmpty()) {
|
if (!audioVariants.isEmpty()) {
|
||||||
variants = new Variant[audioVariants.size()];
|
Variant[] variants = new Variant[audioVariants.size()];
|
||||||
audioVariants.toArray(variants);
|
audioVariants.toArray(variants);
|
||||||
trackStreamWrappers.add(buildTrackStreamWrapper(C.TRACK_TYPE_AUDIO, baseUri, variants, null,
|
trackStreamWrappers.add(buildTrackStreamWrapper(C.TRACK_TYPE_AUDIO, baseUri, variants, null,
|
||||||
null, null));
|
null, null));
|
||||||
@ -314,7 +328,7 @@ public final class HlsSampleSource implements SampleSource,
|
|||||||
// Build the text stream wrapper if applicable.
|
// Build the text stream wrapper if applicable.
|
||||||
List<Variant> subtitleVariants = masterPlaylist.subtitles;
|
List<Variant> subtitleVariants = masterPlaylist.subtitles;
|
||||||
if (!subtitleVariants.isEmpty()) {
|
if (!subtitleVariants.isEmpty()) {
|
||||||
variants = new Variant[subtitleVariants.size()];
|
Variant[] variants = new Variant[subtitleVariants.size()];
|
||||||
subtitleVariants.toArray(variants);
|
subtitleVariants.toArray(variants);
|
||||||
trackStreamWrappers.add(buildTrackStreamWrapper(C.TRACK_TYPE_TEXT, baseUri, variants, null,
|
trackStreamWrappers.add(buildTrackStreamWrapper(C.TRACK_TYPE_TEXT, baseUri, variants, null,
|
||||||
null, null));
|
null, null));
|
||||||
@ -329,8 +343,9 @@ public final class HlsSampleSource implements SampleSource,
|
|||||||
DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||||
HlsChunkSource defaultChunkSource = new HlsChunkSource(baseUri, variants, dataSource,
|
HlsChunkSource defaultChunkSource = new HlsChunkSource(baseUri, variants, dataSource,
|
||||||
timestampAdjusterProvider, formatEvaluator);
|
timestampAdjusterProvider, formatEvaluator);
|
||||||
return new HlsTrackStreamWrapper(trackType, defaultChunkSource, loadControl, muxedAudioFormat,
|
return new HlsTrackStreamWrapper(trackType, this, defaultChunkSource, loadControl,
|
||||||
muxedCaptionFormat, MIN_LOADABLE_RETRY_COUNT, eventDispatcher);
|
preparePositionUs, muxedAudioFormat, muxedCaptionFormat, MIN_LOADABLE_RETRY_COUNT,
|
||||||
|
eventDispatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int selectTracks(HlsTrackStreamWrapper trackStreamWrapper,
|
private int selectTracks(HlsTrackStreamWrapper trackStreamWrapper,
|
||||||
|
@ -28,6 +28,7 @@ import com.google.android.exoplayer.TrackStream;
|
|||||||
import com.google.android.exoplayer.chunk.Chunk;
|
import com.google.android.exoplayer.chunk.Chunk;
|
||||||
import com.google.android.exoplayer.chunk.ChunkHolder;
|
import com.google.android.exoplayer.chunk.ChunkHolder;
|
||||||
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
|
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
|
||||||
|
import com.google.android.exoplayer.extractor.DefaultTrackOutput.UpstreamFormatChangedListener;
|
||||||
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer.extractor.SeekMap;
|
import com.google.android.exoplayer.extractor.SeekMap;
|
||||||
import com.google.android.exoplayer.upstream.Loader;
|
import com.google.android.exoplayer.upstream.Loader;
|
||||||
@ -44,7 +45,20 @@ import java.util.List;
|
|||||||
* Loads {@link HlsMediaChunk}s obtained from a {@link HlsChunkSource}, and provides
|
* Loads {@link HlsMediaChunk}s obtained from a {@link HlsChunkSource}, and provides
|
||||||
* {@link TrackStream}s from which the loaded media can be consumed.
|
* {@link TrackStream}s from which the loaded media can be consumed.
|
||||||
*/
|
*/
|
||||||
/* package */ final class HlsTrackStreamWrapper implements Loader.Callback<Chunk>, ExtractorOutput {
|
/* package */ final class HlsTrackStreamWrapper implements Loader.Callback<Chunk>, ExtractorOutput,
|
||||||
|
UpstreamFormatChangedListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback to be notified of events.
|
||||||
|
*/
|
||||||
|
public interface Callback {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when the wrapper has been prepared.
|
||||||
|
*/
|
||||||
|
void onPrepared();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private static final int PRIMARY_TYPE_NONE = 0;
|
private static final int PRIMARY_TYPE_NONE = 0;
|
||||||
private static final int PRIMARY_TYPE_TEXT = 1;
|
private static final int PRIMARY_TYPE_TEXT = 1;
|
||||||
@ -52,6 +66,7 @@ import java.util.List;
|
|||||||
private static final int PRIMARY_TYPE_VIDEO = 3;
|
private static final int PRIMARY_TYPE_VIDEO = 3;
|
||||||
|
|
||||||
private final int trackType;
|
private final int trackType;
|
||||||
|
private final Callback callback;
|
||||||
private final HlsChunkSource chunkSource;
|
private final HlsChunkSource chunkSource;
|
||||||
private final LoadControl loadControl;
|
private final LoadControl loadControl;
|
||||||
private final Format muxedAudioFormat;
|
private final Format muxedAudioFormat;
|
||||||
@ -85,8 +100,10 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants.
|
* @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants.
|
||||||
|
* @param callback A callback for the wrapper.
|
||||||
* @param chunkSource A {@link HlsChunkSource} from which chunks to load are obtained.
|
* @param chunkSource A {@link HlsChunkSource} from which chunks to load are obtained.
|
||||||
* @param loadControl Controls when the source is permitted to load data.
|
* @param loadControl Controls when the source is permitted to load data.
|
||||||
|
* @param positionUs The position from which to start loading media.
|
||||||
* @param muxedAudioFormat If HLS master playlist indicates that the stream contains muxed audio,
|
* @param muxedAudioFormat If HLS master playlist indicates that the stream contains muxed audio,
|
||||||
* this is the audio {@link Format} as defined by the playlist.
|
* this is the audio {@link Format} as defined by the playlist.
|
||||||
* @param muxedCaptionFormat If HLS master playlist indicates that the stream contains muxed
|
* @param muxedCaptionFormat If HLS master playlist indicates that the stream contains muxed
|
||||||
@ -95,10 +112,11 @@ import java.util.List;
|
|||||||
* before propagating an error.
|
* before propagating an error.
|
||||||
* @param eventDispatcher A dispatcher to notify of events.
|
* @param eventDispatcher A dispatcher to notify of events.
|
||||||
*/
|
*/
|
||||||
public HlsTrackStreamWrapper(int trackType, HlsChunkSource chunkSource, LoadControl loadControl,
|
public HlsTrackStreamWrapper(int trackType, Callback callback, HlsChunkSource chunkSource,
|
||||||
Format muxedAudioFormat, Format muxedCaptionFormat, int minLoadableRetryCount,
|
LoadControl loadControl, long positionUs, Format muxedAudioFormat, Format muxedCaptionFormat,
|
||||||
EventDispatcher eventDispatcher) {
|
int minLoadableRetryCount, EventDispatcher eventDispatcher) {
|
||||||
this.trackType = trackType;
|
this.trackType = trackType;
|
||||||
|
this.callback = callback;
|
||||||
this.chunkSource = chunkSource;
|
this.chunkSource = chunkSource;
|
||||||
this.loadControl = loadControl;
|
this.loadControl = loadControl;
|
||||||
this.muxedAudioFormat = muxedAudioFormat;
|
this.muxedAudioFormat = muxedAudioFormat;
|
||||||
@ -110,44 +128,16 @@ import java.util.List;
|
|||||||
sampleQueues = new SparseArray<>();
|
sampleQueues = new SparseArray<>();
|
||||||
mediaChunks = new LinkedList<>();
|
mediaChunks = new LinkedList<>();
|
||||||
readingEnabled = true;
|
readingEnabled = true;
|
||||||
pendingResetPositionUs = C.UNSET_TIME_US;
|
pendingResetPositionUs = positionUs;
|
||||||
|
downstreamPositionUs = positionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean prepare(long positionUs) throws IOException {
|
public void prepare() {
|
||||||
if (prepared) {
|
maybeStartLoading();
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
if (chunkSource.getTrackCount() == 0) {
|
public void maybeThrowPrepareError() throws IOException {
|
||||||
trackGroups = new TrackGroupArray();
|
|
||||||
prepared = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (sampleQueuesBuilt) {
|
|
||||||
boolean canBuildTracks = true;
|
|
||||||
int sampleQueueCount = sampleQueues.size();
|
|
||||||
for (int i = 0; i < sampleQueueCount; i++) {
|
|
||||||
if (sampleQueues.valueAt(i).getUpstreamFormat() == null) {
|
|
||||||
canBuildTracks = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (canBuildTracks) {
|
|
||||||
buildTracks();
|
|
||||||
prepared = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We're not prepared.
|
|
||||||
maybeThrowError();
|
maybeThrowError();
|
||||||
if (!loader.isLoading()) {
|
|
||||||
// We're going to have to start loading a chunk to get what we need for preparation. We should
|
|
||||||
// attempt to load the chunk at positionUs, so that we'll already be loading the correct chunk
|
|
||||||
// in the common case where the renderer is subsequently enabled at this position.
|
|
||||||
pendingResetPositionUs = positionUs;
|
|
||||||
downstreamPositionUs = positionUs;
|
|
||||||
maybeStartLoading();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getDurationUs() {
|
public long getDurationUs() {
|
||||||
@ -376,6 +366,7 @@ import java.util.List;
|
|||||||
return sampleQueues.get(id);
|
return sampleQueues.get(id);
|
||||||
}
|
}
|
||||||
DefaultTrackOutput trackOutput = new DefaultTrackOutput(loadControl.getAllocator());
|
DefaultTrackOutput trackOutput = new DefaultTrackOutput(loadControl.getAllocator());
|
||||||
|
trackOutput.setUpstreamFormatChangeListener(this);
|
||||||
sampleQueues.put(id, trackOutput);
|
sampleQueues.put(id, trackOutput);
|
||||||
return trackOutput;
|
return trackOutput;
|
||||||
}
|
}
|
||||||
@ -383,6 +374,7 @@ import java.util.List;
|
|||||||
@Override
|
@Override
|
||||||
public void endTracks() {
|
public void endTracks() {
|
||||||
sampleQueuesBuilt = true;
|
sampleQueuesBuilt = true;
|
||||||
|
maybeFinishPrepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -390,8 +382,30 @@ import java.util.List;
|
|||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpstreamFormatChangedListener implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpstreamFormatChanged(Format format) {
|
||||||
|
maybeFinishPrepare();
|
||||||
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
|
private void maybeFinishPrepare() {
|
||||||
|
if (!sampleQueuesBuilt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int sampleQueueCount = sampleQueues.size();
|
||||||
|
for (int i = 0; i < sampleQueueCount; i++) {
|
||||||
|
if (sampleQueues.valueAt(i).getUpstreamFormat() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildTracks();
|
||||||
|
prepared = true;
|
||||||
|
callback.onPrepared();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds tracks that are exposed by this {@link HlsTrackStreamWrapper} instance, as well as
|
* Builds tracks that are exposed by this {@link HlsTrackStreamWrapper} instance, as well as
|
||||||
* internal data-structures required for operation.
|
* internal data-structures required for operation.
|
||||||
|
@ -73,6 +73,7 @@ public final class SmoothStreamingSampleSource implements SampleSource,
|
|||||||
private long manifestLoadStartTimestamp;
|
private long manifestLoadStartTimestamp;
|
||||||
private SmoothStreamingManifest manifest;
|
private SmoothStreamingManifest manifest;
|
||||||
|
|
||||||
|
private Callback callback;
|
||||||
private LoadControl loadControl;
|
private LoadControl loadControl;
|
||||||
private boolean prepared;
|
private boolean prepared;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
@ -97,16 +98,15 @@ public final class SmoothStreamingSampleSource implements SampleSource,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean prepare(long positionUs, LoadControl loadControl) throws IOException {
|
public void prepare(Callback callback, LoadControl loadControl, long positionUs) {
|
||||||
if (prepared) {
|
this.callback = callback;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
this.loadControl = loadControl;
|
this.loadControl = loadControl;
|
||||||
|
startLoadingManifest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void maybeThrowPrepareError() throws IOException {
|
||||||
manifestLoader.maybeThrowError();
|
manifestLoader.maybeThrowError();
|
||||||
if (!manifestLoader.isLoading()) {
|
|
||||||
startLoadingManifest();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -218,6 +218,7 @@ public final class SmoothStreamingSampleSource implements SampleSource,
|
|||||||
new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId)};
|
new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId)};
|
||||||
}
|
}
|
||||||
prepared = true;
|
prepared = true;
|
||||||
|
callback.onSourcePrepared(this);
|
||||||
} else {
|
} else {
|
||||||
for (ChunkTrackStream<SmoothStreamingChunkSource> trackStream : trackStreams) {
|
for (ChunkTrackStream<SmoothStreamingChunkSource> trackStream : trackStreams) {
|
||||||
trackStream.getChunkSource().updateManifest(manifest);
|
trackStream.getChunkSource().updateManifest(manifest);
|
||||||
|
@ -46,7 +46,7 @@ public final class Loader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface definition of an object that can be loaded using a {@link Loader}.
|
* An object that can be loaded using a {@link Loader}.
|
||||||
*/
|
*/
|
||||||
public interface Loadable {
|
public interface Loadable {
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ public final class Loader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface definition for a callback to be notified of {@link Loader} events.
|
* A callback to be notified of {@link Loader} events.
|
||||||
*/
|
*/
|
||||||
public interface Callback<T extends Loadable> {
|
public interface Callback<T extends Loadable> {
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user