Commit c2c41558 by andrewlewis Committed by Oliver Woodman

Merge the internal timeline into ExoPlayerImplInternal.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=129325922
parent ee565300
...@@ -86,8 +86,7 @@ import java.util.ArrayList; ...@@ -86,8 +86,7 @@ import java.util.ArrayList;
private static final int MSG_PERIOD_PREPARED = 6; private static final int MSG_PERIOD_PREPARED = 6;
private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 7; private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 7;
private static final int MSG_TRACK_SELECTION_INVALIDATED = 8; private static final int MSG_TRACK_SELECTION_INVALIDATED = 8;
private static final int MSG_SOURCE_INVALIDATED = 9; private static final int MSG_CUSTOM = 9;
private static final int MSG_CUSTOM = 10;
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;
...@@ -100,13 +99,14 @@ import java.util.ArrayList; ...@@ -100,13 +99,14 @@ import java.util.ArrayList;
*/ */
private static final int MAXIMUM_BUFFER_AHEAD_PERIODS = 100; private static final int MAXIMUM_BUFFER_AHEAD_PERIODS = 100;
private final Renderer[] renderers;
private final RendererCapabilities[] rendererCapabilities;
private final TrackSelector trackSelector; private final TrackSelector trackSelector;
private final LoadControl loadControl; private final LoadControl loadControl;
private final StandaloneMediaClock standaloneMediaClock; private final StandaloneMediaClock standaloneMediaClock;
private final Handler handler; private final Handler handler;
private final HandlerThread internalPlaybackThread; private final HandlerThread internalPlaybackThread;
private final Handler eventHandler; private final Handler eventHandler;
private final InternalTimeline internalTimeline;
private PlaybackInfo playbackInfo; private PlaybackInfo playbackInfo;
private Renderer rendererMediaClockSource; private Renderer rendererMediaClockSource;
...@@ -124,9 +124,20 @@ import java.util.ArrayList; ...@@ -124,9 +124,20 @@ import java.util.ArrayList;
private long internalPositionUs; private long internalPositionUs;
private boolean isTimelineReady;
private boolean isTimelineEnded;
private int bufferAheadPeriodCount;
private Period playingPeriod;
private Period readingPeriod;
private Period loadingPeriod;
private long playingPeriodEndPositionUs;
private Timeline timeline;
public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector, public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector,
LoadControl loadControl, boolean playWhenReady, Handler eventHandler, LoadControl loadControl, boolean playWhenReady, Handler eventHandler,
PlaybackInfo playbackInfo) { PlaybackInfo playbackInfo) {
this.renderers = renderers;
this.trackSelector = trackSelector; this.trackSelector = trackSelector;
this.loadControl = loadControl; this.loadControl = loadControl;
this.playWhenReady = playWhenReady; this.playWhenReady = playWhenReady;
...@@ -134,13 +145,15 @@ import java.util.ArrayList; ...@@ -134,13 +145,15 @@ import java.util.ArrayList;
this.state = ExoPlayer.STATE_IDLE; this.state = ExoPlayer.STATE_IDLE;
this.playbackInfo = playbackInfo; this.playbackInfo = playbackInfo;
rendererCapabilities = new RendererCapabilities[renderers.length];
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
renderers[i].setIndex(i); renderers[i].setIndex(i);
rendererCapabilities[i] = renderers[i].getCapabilities();
} }
playingPeriodEndPositionUs = C.UNSET_TIME_US;
standaloneMediaClock = new StandaloneMediaClock(); standaloneMediaClock = new StandaloneMediaClock();
enabledRenderers = new Renderer[0]; enabledRenderers = new Renderer[0];
internalTimeline = new InternalTimeline(renderers);
trackSelector.init(this); trackSelector.init(this);
...@@ -259,21 +272,17 @@ import java.util.ArrayList; ...@@ -259,21 +272,17 @@ import java.util.ArrayList;
return true; return true;
} }
case MSG_PERIOD_PREPARED: { case MSG_PERIOD_PREPARED: {
internalTimeline.handlePeriodPrepared((MediaPeriod) msg.obj); handlePeriodPrepared((MediaPeriod) msg.obj);
return true; return true;
} }
case MSG_SOURCE_CONTINUE_LOADING_REQUESTED: { case MSG_SOURCE_CONTINUE_LOADING_REQUESTED: {
internalTimeline.handleContinueLoadingRequested((MediaPeriod) msg.obj); handleContinueLoadingRequested((MediaPeriod) msg.obj);
return true; return true;
} }
case MSG_TRACK_SELECTION_INVALIDATED: { case MSG_TRACK_SELECTION_INVALIDATED: {
reselectTracksInternal(); reselectTracksInternal();
return true; return true;
} }
case MSG_SOURCE_INVALIDATED: {
internalTimeline.invalidate((Timeline) msg.obj);
return true;
}
case MSG_CUSTOM: { case MSG_CUSTOM: {
sendMessagesInternal((ExoPlayerMessage[]) msg.obj); sendMessagesInternal((ExoPlayerMessage[]) msg.obj);
return true; return true;
...@@ -305,7 +314,7 @@ import java.util.ArrayList; ...@@ -305,7 +314,7 @@ import java.util.ArrayList;
@Override @Override
public void onTimelineChanged(Timeline timeline) { public void onTimelineChanged(Timeline timeline) {
try { try {
internalTimeline.invalidate(timeline); handleSourceInvalidated(timeline);
} catch (ExoPlaybackException | IOException e) { } catch (ExoPlaybackException | IOException e) {
Log.e(TAG, "Error handling timeline change.", e); Log.e(TAG, "Error handling timeline change.", e);
eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget(); eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget();
...@@ -379,10 +388,10 @@ import java.util.ArrayList; ...@@ -379,10 +388,10 @@ import java.util.ArrayList;
} }
private void updatePlaybackPositions() throws ExoPlaybackException { private void updatePlaybackPositions() throws ExoPlaybackException {
MediaPeriod mediaPeriod = internalTimeline.getPeriod(); if (playingPeriod == null) {
if (mediaPeriod == null) {
return; return;
} }
MediaPeriod mediaPeriod = playingPeriod.mediaPeriod;
// Update the duration. // Update the duration.
if (playbackInfo.durationUs == C.UNSET_TIME_US) { if (playbackInfo.durationUs == C.UNSET_TIME_US) {
...@@ -400,7 +409,7 @@ import java.util.ArrayList; ...@@ -400,7 +409,7 @@ import java.util.ArrayList;
} else { } else {
internalPositionUs = standaloneMediaClock.getPositionUs(); internalPositionUs = standaloneMediaClock.getPositionUs();
} }
positionUs = internalPositionUs - internalTimeline.playingPeriod.offsetUs; positionUs = internalPositionUs - playingPeriod.offsetUs;
} }
playbackInfo.positionUs = positionUs; playbackInfo.positionUs = positionUs;
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
...@@ -418,10 +427,10 @@ import java.util.ArrayList; ...@@ -418,10 +427,10 @@ import java.util.ArrayList;
private void doSomeWork() throws ExoPlaybackException, IOException { private void doSomeWork() throws ExoPlaybackException, IOException {
long operationStartTimeMs = SystemClock.elapsedRealtime(); long operationStartTimeMs = SystemClock.elapsedRealtime();
internalTimeline.updatePeriods(); updatePeriods();
if (internalTimeline.getPeriod() == null) { if (playingPeriod == null) {
// We're still waiting for the first source to be prepared. // We're still waiting for the first period to be prepared.
internalTimeline.maybeThrowPeriodPrepareError(); maybeThrowPeriodPrepareError();
scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS); scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS);
return; return;
} }
...@@ -447,23 +456,25 @@ import java.util.ArrayList; ...@@ -447,23 +456,25 @@ import java.util.ArrayList;
} }
if (!allRenderersReadyOrEnded) { if (!allRenderersReadyOrEnded) {
internalTimeline.maybeThrowPeriodPrepareError(); maybeThrowPeriodPrepareError();
} }
if (allRenderersEnded && (playbackInfo.durationUs == C.UNSET_TIME_US if (allRenderersEnded
|| playbackInfo.durationUs <= playbackInfo.positionUs) && internalTimeline.isEnded) { && (playbackInfo.durationUs == C.UNSET_TIME_US
|| playbackInfo.durationUs <= playbackInfo.positionUs)
&& isTimelineEnded) {
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 : internalTimeline.isReady) if ((enabledRenderers.length > 0 ? allRenderersReadyOrEnded : isTimelineReady)
&& internalTimeline.haveSufficientBuffer(rebuffering)) { && haveSufficientBuffer(rebuffering)) {
setState(ExoPlayer.STATE_READY); setState(ExoPlayer.STATE_READY);
if (playWhenReady) { if (playWhenReady) {
startRenderers(); startRenderers();
} }
} }
} else if (state == ExoPlayer.STATE_READY) { } else if (state == ExoPlayer.STATE_READY) {
if (enabledRenderers.length > 0 ? !allRenderersReadyOrEnded : !internalTimeline.isReady) { if (enabledRenderers.length > 0 ? !allRenderersReadyOrEnded : !isTimelineReady) {
rebuffering = playWhenReady; rebuffering = playWhenReady;
setState(ExoPlayer.STATE_BUFFERING); setState(ExoPlayer.STATE_BUFFERING);
stopRenderers(); stopRenderers();
...@@ -525,7 +536,52 @@ import java.util.ArrayList; ...@@ -525,7 +536,52 @@ import java.util.ArrayList;
stopRenderers(); stopRenderers();
rebuffering = false; rebuffering = false;
positionUs = internalTimeline.seekTo(periodIndex, positionUs); if (positionUs == C.UNSET_TIME_US) {
// We don't know where to seek to yet, so clear the whole timeline.
periodIndex = Timeline.NO_PERIOD_INDEX;
}
// Clear the timeline, but keep the requested period if it is already prepared.
Period period = playingPeriod;
Period newPlayingPeriod = null;
while (period != null) {
if (period.index == periodIndex && period.prepared) {
newPlayingPeriod = period;
} else {
period.release();
}
period = period.nextPeriod;
}
// Update loaded periods.
bufferAheadPeriodCount = 0;
if (newPlayingPeriod != null) {
newPlayingPeriod.nextPeriod = null;
setPlayingPeriod(newPlayingPeriod);
updateTimelineState();
readingPeriod = playingPeriod;
loadingPeriod = playingPeriod;
if (playingPeriod.hasEnabledTracks) {
positionUs = playingPeriod.mediaPeriod.seekToUs(positionUs);
}
resetInternalPosition(positionUs);
maybeContinueLoading();
} else {
for (Renderer renderer : enabledRenderers) {
renderer.disable();
}
enabledRenderers = new Renderer[0];
rendererMediaClock = null;
rendererMediaClockSource = null;
playingPeriod = null;
readingPeriod = null;
loadingPeriod = null;
if (positionUs != C.UNSET_TIME_US) {
resetInternalPosition(positionUs);
}
}
// Update the expose playback information.
if (periodIndex != playbackInfo.periodIndex) { if (periodIndex != playbackInfo.periodIndex) {
playbackInfo = new PlaybackInfo(periodIndex); playbackInfo = new PlaybackInfo(periodIndex);
playbackInfo.startPositionUs = positionUs; playbackInfo.startPositionUs = positionUs;
...@@ -544,9 +600,8 @@ import java.util.ArrayList; ...@@ -544,9 +600,8 @@ import java.util.ArrayList;
} }
private void resetInternalPosition(long periodPositionUs) throws ExoPlaybackException { private void resetInternalPosition(long periodPositionUs) throws ExoPlaybackException {
long sourceOffsetUs = long periodOffsetUs = playingPeriod == null ? 0 : playingPeriod.offsetUs;
internalTimeline.playingPeriod == null ? 0 : internalTimeline.playingPeriod.offsetUs; internalPositionUs = periodOffsetUs + periodPositionUs;
internalPositionUs = sourceOffsetUs + periodPositionUs;
standaloneMediaClock.setPositionUs(internalPositionUs); standaloneMediaClock.setPositionUs(internalPositionUs);
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
renderer.resetPosition(internalPositionUs); renderer.resetPosition(internalPositionUs);
...@@ -587,7 +642,15 @@ import java.util.ArrayList; ...@@ -587,7 +642,15 @@ import java.util.ArrayList;
mediaSource.releaseSource(); mediaSource.releaseSource();
mediaSource = null; mediaSource = null;
} }
internalTimeline.reset(); releasePeriodsFrom(playingPeriod != null ? playingPeriod : loadingPeriod);
playingPeriodEndPositionUs = C.UNSET_TIME_US;
isTimelineReady = false;
isTimelineEnded = false;
playingPeriod = null;
readingPeriod = null;
loadingPeriod = null;
timeline = null;
bufferAheadPeriodCount = 0;
loadControl.reset(); loadControl.reset();
setIsLoading(false); setIsLoading(false);
} }
...@@ -616,563 +679,460 @@ import java.util.ArrayList; ...@@ -616,563 +679,460 @@ import java.util.ArrayList;
} }
private void reselectTracksInternal() throws ExoPlaybackException { private void reselectTracksInternal() throws ExoPlaybackException {
if (internalTimeline.getPeriod() == null) { if (playingPeriod == null) {
// We don't have tracks yet, so we don't care. // We don't have tracks yet, so we don't care.
return; return;
} }
internalTimeline.reselectTracks(); // Reselect tracks on each period in turn, until the selection changes.
updatePlaybackPositions(); Period period = playingPeriod;
handler.sendEmptyMessage(MSG_DO_SOME_WORK); boolean selectionsChangedForReadPeriod = true;
} while (true) {
if (period == null || !period.prepared) {
// TODO[playlists]: Merge this into the outer class. // The reselection did not change any prepared periods.
/** return;
* Keeps track of the {@link Period}s of media being played in the timeline. }
*/ if (period.selectTracks()) {
private final class InternalTimeline { // Selected tracks have changed for this period.
break;
private final Renderer[] renderers; }
private final RendererCapabilities[] rendererCapabilities; if (period == readingPeriod) {
// The track reselection didn't affect any period that has been read.
public boolean isReady; selectionsChangedForReadPeriod = false;
public boolean isEnded; }
period = period.nextPeriod;
private int bufferAheadPeriodCount; }
private Period playingPeriod;
private Period readingPeriod;
private Period loadingPeriod;
private long playingPeriodEndPositionUs; if (selectionsChangedForReadPeriod) {
// Release everything after the playing period because a renderer may have read data from a
// track whose selection has now changed.
releasePeriodsFrom(playingPeriod.nextPeriod);
playingPeriod.nextPeriod = null;
readingPeriod = playingPeriod;
loadingPeriod = playingPeriod;
playingPeriodEndPositionUs = C.UNSET_TIME_US;
bufferAheadPeriodCount = 0;
private Timeline timeline; // Update streams for the new selection, recreating all streams if reading ahead.
boolean recreateStreams = readingPeriod != playingPeriod;
TrackSelectionArray playingPeriodOldTrackSelections = playingPeriod.periodTrackSelections;
playingPeriod.updatePeriodTrackSelection(playbackInfo.positionUs, loadControl,
recreateStreams);
public InternalTimeline(Renderer[] renderers) { int enabledRendererCount = 0;
this.renderers = renderers; boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
rendererCapabilities = new RendererCapabilities[renderers.length];
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
rendererCapabilities[i] = renderers[i].getCapabilities(); Renderer renderer = renderers[i];
rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED;
TrackSelection oldSelection = playingPeriodOldTrackSelections.get(i);
TrackSelection newSelection = playingPeriod.trackSelections.get(i);
if (newSelection != null) {
enabledRendererCount++;
}
if (rendererWasEnabledFlags[i]
&& (recreateStreams || !Util.areEqual(oldSelection, newSelection))) {
// We need to disable the renderer so that we can enable it with its new stream.
if (renderer == rendererMediaClockSource) {
// The renderer is providing the media clock.
if (newSelection == null) {
// The renderer won't be re-enabled. Sync standaloneMediaClock so that it can take
// over timing responsibilities.
standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs());
}
rendererMediaClock = null;
rendererMediaClockSource = null;
}
ensureStopped(renderer);
renderer.disable();
}
} }
playingPeriodEndPositionUs = C.UNSET_TIME_US; trackSelector.onSelectionActivated(playingPeriod.trackSelectionData);
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
} else {
// Release and re-prepare/buffer periods after the one whose selection changed.
loadingPeriod = period;
period = loadingPeriod.nextPeriod;
while (period != null) {
period.release();
period = period.nextPeriod;
bufferAheadPeriodCount--;
}
loadingPeriod.nextPeriod = null;
long positionUs = Math.max(0, internalPositionUs - loadingPeriod.offsetUs);
loadingPeriod.updatePeriodTrackSelection(positionUs, loadControl, false);
} }
maybeContinueLoading();
updatePlaybackPositions();
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
public MediaPeriod getPeriod() throws ExoPlaybackException { public boolean haveSufficientBuffer(boolean rebuffering) {
return playingPeriod == null ? null : playingPeriod.mediaPeriod; if (loadingPeriod == null) {
return false;
} }
long positionUs = internalPositionUs - loadingPeriod.offsetUs;
public boolean haveSufficientBuffer(boolean rebuffering) { long bufferedPositionUs =
if (loadingPeriod == null) { !loadingPeriod.prepared ? 0 : loadingPeriod.mediaPeriod.getBufferedPositionUs();
return false; if (bufferedPositionUs == C.END_OF_SOURCE_US) {
if (loadingPeriod.isLast) {
return true;
} }
long positionUs = internalPositionUs - loadingPeriod.offsetUs; bufferedPositionUs = loadingPeriod.mediaPeriod.getDurationUs();
long bufferedPositionUs = !loadingPeriod.prepared ? 0
: loadingPeriod.mediaPeriod.getBufferedPositionUs();
if (bufferedPositionUs == C.END_OF_SOURCE_US) {
if (loadingPeriod.isLast) {
return true;
}
bufferedPositionUs = loadingPeriod.mediaPeriod.getDurationUs();
}
return loadControl.shouldStartPlayback(bufferedPositionUs - positionUs, rebuffering);
} }
return loadControl.shouldStartPlayback(bufferedPositionUs - positionUs, rebuffering);
}
public void maybeThrowPeriodPrepareError() throws IOException { public void maybeThrowPeriodPrepareError() throws IOException {
if (loadingPeriod != null && !loadingPeriod.prepared if (loadingPeriod != null && !loadingPeriod.prepared
&& (readingPeriod == null || readingPeriod.nextPeriod == loadingPeriod)) { && (readingPeriod == null || readingPeriod.nextPeriod == loadingPeriod)) {
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
if (!renderer.hasReadStreamToEnd()) { if (!renderer.hasReadStreamToEnd()) {
return; return;
}
} }
loadingPeriod.mediaPeriod.maybeThrowPrepareError();
} }
loadingPeriod.mediaPeriod.maybeThrowPrepareError();
} }
}
public void invalidate(Timeline timeline) throws ExoPlaybackException, IOException { public void handleSourceInvalidated(Timeline timeline) throws ExoPlaybackException, IOException {
Timeline oldTimeline = this.timeline; Timeline oldTimeline = this.timeline;
this.timeline = timeline; this.timeline = timeline;
eventHandler.obtainMessage(MSG_TIMELINE_CHANGED, timeline).sendToTarget(); eventHandler.obtainMessage(MSG_TIMELINE_CHANGED, timeline).sendToTarget();
// Update the loaded periods to take into account the new timeline. // Update the loaded periods to take into account the new timeline.
if (playingPeriod != null) { if (playingPeriod != null) {
int index = timeline.getIndexOfPeriod(playingPeriod.id); int index = timeline.getIndexOfPeriod(playingPeriod.id);
if (index == Timeline.NO_PERIOD_INDEX) { if (index == Timeline.NO_PERIOD_INDEX) {
int newPlayingPeriodIndex = int newPlayingPeriodIndex =
mediaSource.getNewPlayingPeriodIndex(playingPeriod.index, oldTimeline); mediaSource.getNewPlayingPeriodIndex(playingPeriod.index, oldTimeline);
if (newPlayingPeriodIndex == Timeline.NO_PERIOD_INDEX) { if (newPlayingPeriodIndex == Timeline.NO_PERIOD_INDEX) {
// There is no period to play, so stop the player. // There is no period to play, so stop the player.
stopInternal(); stopInternal();
return;
}
// Release all loaded periods and seek to the new playing period index.
releasePeriodsFrom(playingPeriod);
playingPeriod = null;
MediaSource.Position defaultStartPosition =
mediaSource.getDefaultStartPosition(newPlayingPeriodIndex);
if (defaultStartPosition != null) {
seekToPeriodPosition(defaultStartPosition.periodIndex, defaultStartPosition.positionUs);
} else {
seekToPeriodPosition(newPlayingPeriodIndex, C.UNSET_TIME_US);
}
return; return;
} }
// The playing period is also in the new timeline. Update index and isLast on each loaded // Release all loaded periods and seek to the new playing period index.
// period until a period is found that has changed. releasePeriodsFrom(playingPeriod);
int periodCount = timeline.getPeriodCount(); playingPeriod = null;
playingPeriod.index = index;
playingPeriod.isLast = timeline.isFinal() && index == periodCount - 1;
Period previousPeriod = playingPeriod;
boolean seenReadingPeriod = false;
bufferAheadPeriodCount = 0;
while (previousPeriod.nextPeriod != null) {
Period period = previousPeriod.nextPeriod;
index++;
if (!period.id.equals(timeline.getPeriodId(index))) {
if (!seenReadingPeriod) {
// Renderers may have read a period that has been removed, so release all loaded
// periods and seek to the playing period index.
index = playingPeriod.index;
releasePeriodsFrom(playingPeriod);
playingPeriod = null;
seekToPeriodPosition(index, 0);
return;
}
// Update the loading period to be the latest period that is still valid.
loadingPeriod = previousPeriod;
loadingPeriod.nextPeriod = null;
// Release the rest of the timeline.
releasePeriodsFrom(period);
break;
}
bufferAheadPeriodCount++; MediaSource.Position defaultStartPosition =
period.index = index; mediaSource.getDefaultStartPosition(newPlayingPeriodIndex);
period.isLast = timeline.isFinal() && index == periodCount - 1; if (defaultStartPosition != null) {
if (period == readingPeriod) { seekToPeriodPosition(defaultStartPosition.periodIndex, defaultStartPosition.positionUs);
seenReadingPeriod = true;
}
previousPeriod = period;
}
} else if (loadingPeriod != null) {
Object id = loadingPeriod.id;
int index = timeline.getIndexOfPeriod(id);
if (index == Timeline.NO_PERIOD_INDEX) {
loadingPeriod.release();
loadingPeriod = null;
bufferAheadPeriodCount = 0;
} else { } else {
int periodCount = timeline.getPeriodCount(); seekToPeriodPosition(newPlayingPeriodIndex, C.UNSET_TIME_US);
loadingPeriod.index = index;
loadingPeriod.isLast = timeline.isFinal() && index == periodCount - 1;
}
}
// TODO[playlists]: Signal the identifier discontinuity, even if the index hasn't changed.
if (oldTimeline != null) {
int newPlayingIndex = playingPeriod != null ? playingPeriod.index
: loadingPeriod != null ? loadingPeriod.index
: mediaSource.getNewPlayingPeriodIndex(playbackInfo.periodIndex, oldTimeline);
if (newPlayingIndex != Timeline.NO_PERIOD_INDEX
&& newPlayingIndex != playbackInfo.periodIndex) {
long oldPositionUs = playbackInfo.positionUs;
playbackInfo = new PlaybackInfo(newPlayingIndex);
playbackInfo.startPositionUs = oldPositionUs;
updatePlaybackPositions();
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
} }
}
}
public void updatePeriods() throws ExoPlaybackException, IOException {
if (timeline == null) {
// We're waiting to get information about periods.
return; return;
} }
// Update the loading period. // The playing period is also in the new timeline. Update index and isLast on each loaded
if (loadingPeriod == null || (loadingPeriod.isFullyBuffered() && !loadingPeriod.isLast // period until a period is found that has changed.
&& bufferAheadPeriodCount < MAXIMUM_BUFFER_AHEAD_PERIODS)) { int periodCount = timeline.getPeriodCount();
int periodIndex = playingPeriod.index = index;
loadingPeriod == null ? playbackInfo.periodIndex : loadingPeriod.index + 1; playingPeriod.isLast = timeline.isFinal() && index == periodCount - 1;
long startPositionUs = playbackInfo.positionUs;
if (loadingPeriod != null || startPositionUs == C.UNSET_TIME_US) {
// We are starting to load the next period or seeking to the default position, so request
// a period and position from the source.
MediaSource.Position defaultStartPosition =
mediaSource.getDefaultStartPosition(periodIndex);
if (defaultStartPosition != null) {
periodIndex = defaultStartPosition.periodIndex;
startPositionUs = defaultStartPosition.positionUs;
} else {
startPositionUs = C.UNSET_TIME_US;
}
}
MediaPeriod mediaPeriod; Period previousPeriod = playingPeriod;
if (startPositionUs != C.UNSET_TIME_US boolean seenReadingPeriod = false;
&& (mediaPeriod = mediaSource.createPeriod(periodIndex)) != null) { bufferAheadPeriodCount = 0;
Period newPeriod = new Period(renderers, rendererCapabilities, trackSelector, mediaPeriod, while (previousPeriod.nextPeriod != null) {
timeline.getPeriodId(periodIndex), periodIndex, startPositionUs); Period period = previousPeriod.nextPeriod;
newPeriod.isLast = timeline.isFinal() && periodIndex == timeline.getPeriodCount() - 1; index++;
if (loadingPeriod != null) { if (!period.id.equals(timeline.getPeriodId(index))) {
loadingPeriod.setNextPeriod(newPeriod); if (!seenReadingPeriod) {
// Renderers may have read a period that has been removed, so release all loaded periods
// and seek to the playing period index.
index = playingPeriod.index;
releasePeriodsFrom(playingPeriod);
playingPeriod = null;
seekToPeriodPosition(index, 0);
return;
} }
bufferAheadPeriodCount++;
loadingPeriod = newPeriod;
setIsLoading(true);
loadingPeriod.mediaPeriod.preparePeriod(ExoPlayerImplInternal.this,
loadControl.getAllocator(), startPositionUs);
}
}
if (loadingPeriod == null || loadingPeriod.isFullyBuffered()) { // Update the loading period to be the latest period that is still valid.
setIsLoading(false); loadingPeriod = previousPeriod;
} else if (loadingPeriod != null && loadingPeriod.needsContinueLoading) { loadingPeriod.nextPeriod = null;
maybeContinueLoading();
}
if (playingPeriod == null) { // Release the rest of the timeline.
// We're waiting for the first period to be prepared. releasePeriodsFrom(period);
return; break;
}
// Update the playing and reading periods.
if (playingPeriodEndPositionUs == C.UNSET_TIME_US && playingPeriod.isFullyBuffered()) {
playingPeriodEndPositionUs = playingPeriod.offsetUs
+ playingPeriod.mediaPeriod.getDurationUs();
}
while (playingPeriod != readingPeriod && playingPeriod.nextPeriod != null
&& internalPositionUs >= playingPeriod.nextPeriod.offsetUs) {
// All enabled renderers' streams have been read to the end, and the playback position
// reached the end of the playing period, so advance playback to the next period.
playingPeriod.release();
setPlayingPeriod(playingPeriod.nextPeriod);
bufferAheadPeriodCount--;
playbackInfo = new PlaybackInfo(playingPeriod.index);
playbackInfo.startPositionUs = playingPeriod.startPositionUs;
updatePlaybackPositions();
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
}
updateTimelineState();
if (readingPeriod == null) {
// The renderers have their final SampleStreams.
return;
}
for (Renderer renderer : enabledRenderers) {
if (!renderer.hasReadStreamToEnd()) {
return;
}
}
if (readingPeriod.nextPeriod != null && readingPeriod.nextPeriod.prepared) {
TrackSelectionArray oldTrackSelections = readingPeriod.trackSelections;
readingPeriod = readingPeriod.nextPeriod;
TrackSelectionArray newTrackSelections = readingPeriod.trackSelections;
for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i];
TrackSelection oldSelection = oldTrackSelections.get(i);
TrackSelection newSelection = newTrackSelections.get(i);
if (oldSelection != null) {
if (newSelection != null) {
// Replace the renderer's SampleStream so the transition to playing the next period
// can be seamless.
Format[] formats = new Format[newSelection.length()];
for (int j = 0; j < formats.length; j++) {
formats[j] = newSelection.getFormat(j);
}
renderer.replaceStream(formats, readingPeriod.sampleStreams[i],
readingPeriod.offsetUs);
} else {
// The renderer will be disabled when transitioning to playing the next period. Mark
// the SampleStream as final to play out any remaining data.
renderer.setCurrentStreamIsFinal();
}
}
}
} else if (readingPeriod.isLast) {
readingPeriod = null;
for (Renderer renderer : enabledRenderers) {
renderer.setCurrentStreamIsFinal();
} }
}
}
public void handlePeriodPrepared(MediaPeriod period) throws ExoPlaybackException { bufferAheadPeriodCount++;
if (loadingPeriod == null || loadingPeriod.mediaPeriod != period) { period.index = index;
// Stale event. period.isLast = timeline.isFinal() && index == periodCount - 1;
return; if (period == readingPeriod) {
} seenReadingPeriod = true;
loadingPeriod.handlePrepared(loadingPeriod.startPositionUs, loadControl);
if (playingPeriod == null) {
// This is the first prepared period, so start playing it.
readingPeriod = loadingPeriod;
setPlayingPeriod(readingPeriod);
if (playbackInfo.startPositionUs == C.UNSET_TIME_US) {
// Update the playback info when seeking to a default position.
playbackInfo = new PlaybackInfo(playingPeriod.index);
playbackInfo.startPositionUs = playingPeriod.startPositionUs;
resetInternalPosition(playbackInfo.startPositionUs);
updatePlaybackPositions();
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
} }
updateTimelineState(); previousPeriod = period;
} }
maybeContinueLoading(); } else if (loadingPeriod != null) {
} Object id = loadingPeriod.id;
int index = timeline.getIndexOfPeriod(id);
public void handleContinueLoadingRequested(MediaPeriod period) { if (index == Timeline.NO_PERIOD_INDEX) {
if (loadingPeriod == null || loadingPeriod.mediaPeriod != period) { loadingPeriod.release();
return; loadingPeriod = null;
bufferAheadPeriodCount = 0;
} else {
int periodCount = timeline.getPeriodCount();
loadingPeriod.index = index;
loadingPeriod.isLast = timeline.isFinal() && index == periodCount - 1;
} }
maybeContinueLoading();
} }
private void maybeContinueLoading() { // TODO[playlists]: Signal the identifier discontinuity, even if the index hasn't changed.
long nextLoadPositionUs = loadingPeriod.mediaPeriod.getNextLoadPositionUs(); if (oldTimeline != null) {
if (nextLoadPositionUs != C.END_OF_SOURCE_US) { int newPlayingIndex = playingPeriod != null ? playingPeriod.index
long positionUs = internalPositionUs - loadingPeriod.offsetUs; : loadingPeriod != null ? loadingPeriod.index
long bufferedDurationUs = nextLoadPositionUs - positionUs; : mediaSource.getNewPlayingPeriodIndex(playbackInfo.periodIndex, oldTimeline);
boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs); if (newPlayingIndex != Timeline.NO_PERIOD_INDEX
setIsLoading(continueLoading); && newPlayingIndex != playbackInfo.periodIndex) {
if (continueLoading) { long oldPositionUs = playbackInfo.positionUs;
loadingPeriod.needsContinueLoading = false; playbackInfo = new PlaybackInfo(newPlayingIndex);
loadingPeriod.mediaPeriod.continueLoading(positionUs); playbackInfo.startPositionUs = oldPositionUs;
} else { updatePlaybackPositions();
loadingPeriod.needsContinueLoading = true; eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
}
} else {
setIsLoading(false);
} }
} }
}
public long seekTo(int periodIndex, long seekPositionUs) throws ExoPlaybackException { public void updatePeriods() throws ExoPlaybackException, IOException {
if (seekPositionUs == C.UNSET_TIME_US) { if (timeline == null) {
// We don't know where to seek to yet, so clear the whole timeline. // We're waiting to get information about periods.
periodIndex = Timeline.NO_PERIOD_INDEX; return;
} }
// Clear the timeline, but keep the requested period if it is already prepared. // Update the loading period.
Period period = playingPeriod; if (loadingPeriod == null || (loadingPeriod.isFullyBuffered() && !loadingPeriod.isLast
Period newPlayingPeriod = null; && bufferAheadPeriodCount < MAXIMUM_BUFFER_AHEAD_PERIODS)) {
while (period != null) { int periodIndex = loadingPeriod == null ? playbackInfo.periodIndex : loadingPeriod.index + 1;
if (period.index == periodIndex && period.prepared) { long startPositionUs = playbackInfo.positionUs;
newPlayingPeriod = period; if (loadingPeriod != null || startPositionUs == C.UNSET_TIME_US) {
// We are starting to load the next period or seeking to the default position, so request a
// period and position from the source.
MediaSource.Position defaultStartPosition =
mediaSource.getDefaultStartPosition(periodIndex);
if (defaultStartPosition != null) {
periodIndex = defaultStartPosition.periodIndex;
startPositionUs = defaultStartPosition.positionUs;
} else { } else {
period.release(); startPositionUs = C.UNSET_TIME_US;
} }
period = period.nextPeriod;
} }
bufferAheadPeriodCount = 0; MediaPeriod mediaPeriod;
if (newPlayingPeriod != null) { if (startPositionUs != C.UNSET_TIME_US
newPlayingPeriod.nextPeriod = null; && (mediaPeriod = mediaSource.createPeriod(periodIndex)) != null) {
setPlayingPeriod(newPlayingPeriod); Period newPeriod = new Period(renderers, rendererCapabilities, trackSelector, mediaPeriod,
updateTimelineState(); timeline.getPeriodId(periodIndex), periodIndex, startPositionUs);
readingPeriod = playingPeriod; newPeriod.isLast = timeline.isFinal() && periodIndex == timeline.getPeriodCount() - 1;
loadingPeriod = playingPeriod; if (loadingPeriod != null) {
if (playingPeriod.hasEnabledTracks) { loadingPeriod.setNextPeriod(newPeriod);
seekPositionUs = playingPeriod.mediaPeriod.seekToUs(seekPositionUs);
}
resetInternalPosition(seekPositionUs);
maybeContinueLoading();
} else {
for (Renderer renderer : enabledRenderers) {
renderer.disable();
}
enabledRenderers = new Renderer[0];
rendererMediaClock = null;
rendererMediaClockSource = null;
playingPeriod = null;
readingPeriod = null;
loadingPeriod = null;
if (seekPositionUs != C.UNSET_TIME_US) {
resetInternalPosition(seekPositionUs);
} }
bufferAheadPeriodCount++;
loadingPeriod = newPeriod;
setIsLoading(true);
loadingPeriod.mediaPeriod.preparePeriod(this, loadControl.getAllocator(), startPositionUs);
} }
return seekPositionUs;
} }
public void reselectTracks() throws ExoPlaybackException { if (loadingPeriod == null || loadingPeriod.isFullyBuffered()) {
// Reselect tracks on each period in turn, until the selection changes. setIsLoading(false);
Period period = playingPeriod; } else if (loadingPeriod != null && loadingPeriod.needsContinueLoading) {
boolean selectionsChangedForReadPeriod = true; maybeContinueLoading();
while (true) { }
if (period == null || !period.prepared) {
// The reselection did not change any prepared periods.
return;
}
if (period.selectTracks()) {
// Selected tracks have changed for this period.
break;
}
if (period == readingPeriod) {
// The track reselection didn't affect any period that has been read.
selectionsChangedForReadPeriod = false;
}
period = period.nextPeriod;
}
if (selectionsChangedForReadPeriod) { if (playingPeriod == null) {
// Release everything after the playing period because a renderer may have read data from a // We're waiting for the first period to be prepared.
// track whose selection has now changed. return;
releasePeriodsFrom(playingPeriod.nextPeriod); }
playingPeriod.nextPeriod = null;
readingPeriod = playingPeriod;
loadingPeriod = playingPeriod;
playingPeriodEndPositionUs = C.UNSET_TIME_US;
bufferAheadPeriodCount = 0;
// Update streams for the new selection, recreating all streams if reading ahead. // Update the playing and reading periods.
boolean recreateStreams = readingPeriod != playingPeriod; if (playingPeriodEndPositionUs == C.UNSET_TIME_US && playingPeriod.isFullyBuffered()) {
TrackSelectionArray playingPeriodOldTrackSelections = playingPeriod.periodTrackSelections; playingPeriodEndPositionUs = playingPeriod.offsetUs
playingPeriod.updatePeriodTrackSelection(playbackInfo.positionUs, loadControl, + playingPeriod.mediaPeriod.getDurationUs();
recreateStreams); }
while (playingPeriod != readingPeriod && playingPeriod.nextPeriod != null
int enabledRendererCount = 0; && internalPositionUs >= playingPeriod.nextPeriod.offsetUs) {
boolean[] rendererWasEnabledFlags = new boolean[renderers.length]; // All enabled renderers' streams have been read to the end, and the playback position reached
for (int i = 0; i < renderers.length; i++) { // the end of the playing period, so advance playback to the next period.
Renderer renderer = renderers[i]; playingPeriod.release();
rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED; setPlayingPeriod(playingPeriod.nextPeriod);
TrackSelection oldSelection = playingPeriodOldTrackSelections.get(i); bufferAheadPeriodCount--;
TrackSelection newSelection = playingPeriod.trackSelections.get(i); playbackInfo = new PlaybackInfo(playingPeriod.index);
playbackInfo.startPositionUs = playingPeriod.startPositionUs;
updatePlaybackPositions();
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
}
updateTimelineState();
if (readingPeriod == null) {
// The renderers have their final SampleStreams.
return;
}
for (Renderer renderer : enabledRenderers) {
if (!renderer.hasReadStreamToEnd()) {
return;
}
}
if (readingPeriod.nextPeriod != null && readingPeriod.nextPeriod.prepared) {
TrackSelectionArray oldTrackSelections = readingPeriod.trackSelections;
readingPeriod = readingPeriod.nextPeriod;
TrackSelectionArray newTrackSelections = readingPeriod.trackSelections;
for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i];
TrackSelection oldSelection = oldTrackSelections.get(i);
TrackSelection newSelection = newTrackSelections.get(i);
if (oldSelection != null) {
if (newSelection != null) { if (newSelection != null) {
enabledRendererCount++; // Replace the renderer's SampleStream so the transition to playing the next period can
} // be seamless.
if (rendererWasEnabledFlags[i] Format[] formats = new Format[newSelection.length()];
&& (recreateStreams || !Util.areEqual(oldSelection, newSelection))) { for (int j = 0; j < formats.length; j++) {
// We need to disable the renderer so that we can enable it with its new stream. formats[j] = newSelection.getFormat(j);
if (renderer == rendererMediaClockSource) {
// The renderer is providing the media clock.
if (newSelection == null) {
// The renderer won't be re-enabled. Sync standaloneMediaClock so that it can take
// over timing responsibilities.
standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs());
}
rendererMediaClock = null;
rendererMediaClockSource = null;
} }
ensureStopped(renderer); renderer.replaceStream(formats, readingPeriod.sampleStreams[i], readingPeriod.offsetUs);
renderer.disable(); } else {
// The renderer will be disabled when transitioning to playing the next period. Mark the
// SampleStream as final to play out any remaining data.
renderer.setCurrentStreamIsFinal();
} }
} }
trackSelector.onSelectionActivated(playingPeriod.trackSelectionData);
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
} else {
// Release and re-prepare/buffer periods after the one whose selection changed.
loadingPeriod = period;
period = loadingPeriod.nextPeriod;
while (period != null) {
period.release();
period = period.nextPeriod;
bufferAheadPeriodCount--;
}
loadingPeriod.nextPeriod = null;
long positionUs = Math.max(0, internalPositionUs - loadingPeriod.offsetUs);
loadingPeriod.updatePeriodTrackSelection(positionUs, loadControl, false);
} }
maybeContinueLoading(); } else if (readingPeriod.isLast) {
}
public void reset() {
releasePeriodsFrom(playingPeriod != null ? playingPeriod : loadingPeriod);
playingPeriodEndPositionUs = C.UNSET_TIME_US;
isReady = false;
isEnded = false;
playingPeriod = null;
readingPeriod = null; readingPeriod = null;
loadingPeriod = null; for (Renderer renderer : enabledRenderers) {
timeline = null; renderer.setCurrentStreamIsFinal();
bufferAheadPeriodCount = 0; }
} }
}
private void releasePeriodsFrom(Period period) { public void handlePeriodPrepared(MediaPeriod period) throws ExoPlaybackException {
while (period != null) { if (loadingPeriod == null || loadingPeriod.mediaPeriod != period) {
period.release(); // Stale event.
period = period.nextPeriod; return;
}
loadingPeriod.handlePrepared(loadingPeriod.startPositionUs, loadControl);
if (playingPeriod == null) {
// This is the first prepared period, so start playing it.
readingPeriod = loadingPeriod;
setPlayingPeriod(readingPeriod);
if (playbackInfo.startPositionUs == C.UNSET_TIME_US) {
// Update the playback info when seeking to a default position.
playbackInfo = new PlaybackInfo(playingPeriod.index);
playbackInfo.startPositionUs = playingPeriod.startPositionUs;
resetInternalPosition(playbackInfo.startPositionUs);
updatePlaybackPositions();
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
} }
updateTimelineState();
} }
maybeContinueLoading();
}
private void setPlayingPeriod(Period period) throws ExoPlaybackException { public void handleContinueLoadingRequested(MediaPeriod period) {
int enabledRendererCount = 0; if (loadingPeriod == null || loadingPeriod.mediaPeriod != period) {
boolean[] rendererWasEnabledFlags = new boolean[renderers.length]; return;
for (int i = 0; i < renderers.length; i++) { }
Renderer renderer = renderers[i]; maybeContinueLoading();
rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED; }
TrackSelection newSelection = period.trackSelections.get(i);
if (newSelection != null) { private void maybeContinueLoading() {
// The renderer should be enabled when playing the new period. long nextLoadPositionUs = loadingPeriod.mediaPeriod.getNextLoadPositionUs();
enabledRendererCount++; if (nextLoadPositionUs != C.END_OF_SOURCE_US) {
} else if (rendererWasEnabledFlags[i]) { long positionUs = internalPositionUs - loadingPeriod.offsetUs;
// The renderer should be disabled when playing the new period. long bufferedDurationUs = nextLoadPositionUs - positionUs;
if (renderer == rendererMediaClockSource) { boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs);
// Sync standaloneMediaClock so that it can take over timing responsibilities. setIsLoading(continueLoading);
standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs()); if (continueLoading) {
rendererMediaClock = null; loadingPeriod.needsContinueLoading = false;
rendererMediaClockSource = null; loadingPeriod.mediaPeriod.continueLoading(positionUs);
} } else {
ensureStopped(renderer); loadingPeriod.needsContinueLoading = true;
renderer.disable();
}
} }
} else {
setIsLoading(false);
}
}
trackSelector.onSelectionActivated(period.trackSelectionData); private void releasePeriodsFrom(Period period) {
playingPeriod = period; while (period != null) {
playingPeriodEndPositionUs = C.UNSET_TIME_US; period.release();
enableRenderers(rendererWasEnabledFlags, enabledRendererCount); period = period.nextPeriod;
} }
}
private void updateTimelineState() { private void setPlayingPeriod(Period period) throws ExoPlaybackException {
isReady = playingPeriodEndPositionUs == C.UNSET_TIME_US int enabledRendererCount = 0;
|| internalPositionUs < playingPeriodEndPositionUs boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|| (playingPeriod.nextPeriod != null && playingPeriod.nextPeriod.prepared); for (int i = 0; i < renderers.length; i++) {
isEnded = playingPeriod.isLast; Renderer renderer = renderers[i];
rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED;
TrackSelection newSelection = period.trackSelections.get(i);
if (newSelection != null) {
// The renderer should be enabled when playing the new period.
enabledRendererCount++;
} else if (rendererWasEnabledFlags[i]) {
// The renderer should be disabled when playing the new period.
if (renderer == rendererMediaClockSource) {
// Sync standaloneMediaClock so that it can take over timing responsibilities.
standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs());
rendererMediaClock = null;
rendererMediaClockSource = null;
}
ensureStopped(renderer);
renderer.disable();
}
} }
private void enableRenderers(boolean[] rendererWasEnabledFlags, int enabledRendererCount) trackSelector.onSelectionActivated(period.trackSelectionData);
throws ExoPlaybackException { playingPeriod = period;
enabledRenderers = new Renderer[enabledRendererCount]; playingPeriodEndPositionUs = C.UNSET_TIME_US;
enabledRendererCount = 0; enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
for (int i = 0; i < renderers.length; i++) { }
Renderer renderer = renderers[i];
TrackSelection newSelection = playingPeriod.trackSelections.get(i); private void updateTimelineState() {
if (newSelection != null) { isTimelineReady = playingPeriodEndPositionUs == C.UNSET_TIME_US
enabledRenderers[enabledRendererCount++] = renderer; || internalPositionUs < playingPeriodEndPositionUs
if (renderer.getState() == Renderer.STATE_DISABLED) { || (playingPeriod.nextPeriod != null && playingPeriod.nextPeriod.prepared);
// The renderer needs enabling with its new track selection. isTimelineEnded = playingPeriod.isLast;
boolean playing = playWhenReady && state == ExoPlayer.STATE_READY; }
// Consider as joining only if the renderer was previously disabled.
boolean joining = !rendererWasEnabledFlags[i] && playing; private void enableRenderers(boolean[] rendererWasEnabledFlags, int enabledRendererCount)
// Build an array of formats contained by the selection. throws ExoPlaybackException {
Format[] formats = new Format[newSelection.length()]; enabledRenderers = new Renderer[enabledRendererCount];
for (int j = 0; j < formats.length; j++) { enabledRendererCount = 0;
formats[j] = newSelection.getFormat(j); for (int i = 0; i < renderers.length; i++) {
} Renderer renderer = renderers[i];
// Enable the renderer. TrackSelection newSelection = playingPeriod.trackSelections.get(i);
renderer.enable(formats, playingPeriod.sampleStreams[i], internalPositionUs, joining, if (newSelection != null) {
playingPeriod.offsetUs); enabledRenderers[enabledRendererCount++] = renderer;
MediaClock mediaClock = renderer.getMediaClock(); if (renderer.getState() == Renderer.STATE_DISABLED) {
if (mediaClock != null) { // The renderer needs enabling with its new track selection.
if (rendererMediaClock != null) { boolean playing = playWhenReady && state == ExoPlayer.STATE_READY;
throw ExoPlaybackException.createForUnexpected( // Consider as joining only if the renderer was previously disabled.
new IllegalStateException("Multiple renderer media clocks enabled.")); boolean joining = !rendererWasEnabledFlags[i] && playing;
} // Build an array of formats contained by the selection.
rendererMediaClock = mediaClock; Format[] formats = new Format[newSelection.length()];
rendererMediaClockSource = renderer; for (int j = 0; j < formats.length; j++) {
} formats[j] = newSelection.getFormat(j);
// Start the renderer if playing. }
if (playing) { // Enable the renderer.
renderer.start(); renderer.enable(formats, playingPeriod.sampleStreams[i], internalPositionUs, joining,
playingPeriod.offsetUs);
MediaClock mediaClock = renderer.getMediaClock();
if (mediaClock != null) {
if (rendererMediaClock != null) {
throw ExoPlaybackException.createForUnexpected(
new IllegalStateException("Multiple renderer media clocks enabled."));
} }
rendererMediaClock = mediaClock;
rendererMediaClockSource = renderer;
}
// Start the renderer if playing.
if (playing) {
renderer.start();
} }
} }
} }
} }
} }
/** /**
...@@ -1220,8 +1180,8 @@ import java.util.ArrayList; ...@@ -1220,8 +1180,8 @@ import java.util.ArrayList;
} }
public boolean isFullyBuffered() { public boolean isFullyBuffered() {
return prepared && (!hasEnabledTracks return prepared
|| mediaPeriod.getBufferedPositionUs() == C.END_OF_SOURCE_US); && (!hasEnabledTracks || mediaPeriod.getBufferedPositionUs() == C.END_OF_SOURCE_US);
} }
public void handlePrepared(long positionUs, LoadControl loadControl) public void handlePrepared(long positionUs, LoadControl loadControl)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment