Report loading on/off changes via ExoPlayer.

Also attempt to clear up naming a little, using "buffering" to
mean the user visible buffering state, and "loading" to mean a
source being loaded.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=126693592
This commit is contained in:
olly 2016-07-06 06:47:29 -07:00 committed by Oliver Woodman
parent f4239eb571
commit 19d65a7a9d
14 changed files with 180 additions and 156 deletions

View file

@ -66,6 +66,11 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb
// ExoPlayer.EventListener
@Override
public void onLoadingChanged(boolean isLoading) {
Log.d(TAG, "loading [" + isLoading + "]");
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int state) {
Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", "

View file

@ -18,7 +18,7 @@ package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.AspectRatioFrameLayout;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ConcatenatingSampleSourceProvider;
import com.google.android.exoplayer.DefaultBufferingControl;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.DefaultTrackSelectionPolicy;
import com.google.android.exoplayer.DefaultTrackSelector;
import com.google.android.exoplayer.DefaultTrackSelector.TrackInfo;
@ -268,8 +268,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
trackSelector.addListener(this);
trackSelector.addListener(eventLogger);
trackSelectionHelper = new TrackSelectionHelper(trackSelector);
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector,
new DefaultBufferingControl(), drmSessionManager, preferExtensionDecoders);
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultLoadControl(),
drmSessionManager, preferExtensionDecoders);
player.addListener(this);
player.addListener(eventLogger);
player.setDebugListener(eventLogger);
@ -388,6 +388,11 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
// ExoPlayer.EventListener implementation
@Override
public void onLoadingChanged(boolean isLoading) {
// Do nothing.
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == ExoPlayer.STATE_ENDED) {

View file

@ -88,6 +88,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
Looper.loop();
}
@Override
public void onLoadingChanged(boolean isLoading) {
// Do nothing.
}
@Override
public void onPlayWhenReadyCommitted () {
// Do nothing.

View file

@ -88,6 +88,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
Looper.loop();
}
@Override
public void onLoadingChanged(boolean isLoading) {
// Do nothing.
}
@Override
public void onPlayWhenReadyCommitted () {
// Do nothing.

View file

@ -107,6 +107,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
Looper.loop();
}
@Override
public void onLoadingChanged(boolean isLoading) {
// Do nothing.
}
@Override
public void onPlayWhenReadyCommitted () {
// Do nothing.

View file

@ -19,26 +19,10 @@ import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.util.Util;
import android.os.Handler;
/**
* The default {@link BufferingControl} implementation.
* The default {@link LoadControl} implementation.
*/
public final class DefaultBufferingControl implements BufferingControl {
/**
* Interface definition for a callback to be notified of {@link DefaultBufferingControl} events.
*/
public interface EventListener {
/**
* Invoked when the control transitions from a buffering to a draining state or vice versa.
*
* @param buffering Whether the control is now in the buffering state.
*/
void onBufferingChanged(boolean buffering);
}
public final class DefaultLoadControl implements LoadControl {
/**
* The default minimum duration of media that the player will attempt to ensure is buffered at all
@ -69,8 +53,6 @@ public final class DefaultBufferingControl implements BufferingControl {
private static final int BELOW_LOW_WATERMARK = 2;
private final DefaultAllocator allocator;
private final Handler eventHandler;
private final EventListener eventListener;
private final long minBufferUs;
private final long maxBufferUs;
@ -83,7 +65,7 @@ public final class DefaultBufferingControl implements BufferingControl {
/**
* Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class.
*/
public DefaultBufferingControl() {
public DefaultLoadControl() {
this(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
}
@ -92,22 +74,9 @@ public final class DefaultBufferingControl implements BufferingControl {
*
* @param allocator The {@link DefaultAllocator} used by the loader.
*/
public DefaultBufferingControl(DefaultAllocator allocator) {
this(allocator, null, null);
}
/**
* Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class.
*
* @param allocator The {@link DefaultAllocator} used by the loader.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public DefaultBufferingControl(DefaultAllocator allocator, Handler eventHandler,
EventListener eventListener) {
public DefaultLoadControl(DefaultAllocator allocator) {
this(allocator, DEFAULT_MIN_BUFFER_MS, DEFAULT_MAX_BUFFER_MS, DEFAULT_BUFFER_FOR_PLAYBACK_MS,
DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS, eventHandler, eventListener);
DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS);
}
/**
@ -123,16 +92,10 @@ public final class DefaultBufferingControl implements BufferingControl {
* @param bufferForPlaybackAfterRebufferMs The default duration of media that must be buffered for
* playback to resume after a player-invoked rebuffer (i.e. a rebuffer that occurs due to
* buffer depletion rather than a user action), in milliseconds.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public DefaultBufferingControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs,
long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs, Handler eventHandler,
EventListener eventListener) {
public DefaultLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs,
long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs) {
this.allocator = allocator;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
minBufferUs = minBufferMs * 1000L;
maxBufferUs = maxBufferMs * 1000L;
bufferForPlaybackUs = bufferForPlaybackMs * 1000L;
@ -154,7 +117,7 @@ public final class DefaultBufferingControl implements BufferingControl {
@Override
public void reset() {
targetBufferSize = 0;
setBuffering(false);
isBuffering = false;
}
@Override
@ -169,20 +132,12 @@ public final class DefaultBufferingControl implements BufferingControl {
}
@Override
public boolean shouldContinueBuffering(long bufferedDurationUs) {
public boolean shouldContinueLoading(long bufferedDurationUs) {
int bufferTimeState = getBufferTimeState(bufferedDurationUs);
boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
boolean shouldBuffer = bufferTimeState == BELOW_LOW_WATERMARK
isBuffering = bufferTimeState == BELOW_LOW_WATERMARK
|| (bufferTimeState == BETWEEN_WATERMARKS && isBuffering && !targetBufferSizeReached);
setBuffering(shouldBuffer);
return shouldBuffer;
}
private void setBuffering(boolean isBuffering) {
if (this.isBuffering != isBuffering) {
this.isBuffering = isBuffering;
notifyBufferingChanged(isBuffering);
}
return isBuffering;
}
private int getBufferTimeState(long bufferedDurationUs) {
@ -190,15 +145,4 @@ public final class DefaultBufferingControl implements BufferingControl {
: (bufferedDurationUs < minBufferUs ? BELOW_LOW_WATERMARK : BETWEEN_WATERMARKS);
}
private void notifyBufferingChanged(final boolean buffering) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onBufferingChanged(buffering);
}
});
}
}
}

View file

@ -97,6 +97,13 @@ public interface ExoPlayer {
*/
interface EventListener {
/**
* Invoked when the player starts or stops loading the source.
*
* @param isLoading Whether the source is currently being loaded.
*/
void onLoadingChanged(boolean isLoading);
/**
* Invoked when the value returned from either {@link ExoPlayer#getPlayWhenReady()} or
* {@link ExoPlayer#getPlaybackState()} changes.
@ -269,6 +276,13 @@ public interface ExoPlayer {
*/
boolean isPlayWhenReadyCommitted();
/**
* Whether the player is currently loading the source.
*
* @return True if the player is currently loading the source. False otherwise.
*/
boolean isLoading();
/**
* Seeks to a position specified in milliseconds in the current source.
*

View file

@ -42,7 +42,7 @@ public final class ExoPlayerFactory {
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector) {
return newSimpleInstance(context, trackSelector, new DefaultBufferingControl(), null);
return newSimpleInstance(context, trackSelector, new DefaultLoadControl(), null);
}
/**
@ -52,13 +52,13 @@ public final class ExoPlayerFactory {
*
* @param context A {@link Context}.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param bufferingControl The {@link BufferingControl} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
BufferingControl bufferingControl, DrmSessionManager drmSessionManager) {
return newSimpleInstance(context, trackSelector, bufferingControl, drmSessionManager, false);
LoadControl loadControl, DrmSessionManager drmSessionManager) {
return newSimpleInstance(context, trackSelector, loadControl, drmSessionManager, false);
}
/**
@ -68,7 +68,7 @@ public final class ExoPlayerFactory {
*
* @param context A {@link Context}.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param bufferingControl The {@link BufferingControl} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
* @param preferExtensionDecoders True to prefer {@link TrackRenderer} instances defined in
@ -76,9 +76,9 @@ public final class ExoPlayerFactory {
* included in the application build for setting this flag to have any effect.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
BufferingControl bufferingControl, DrmSessionManager drmSessionManager,
LoadControl loadControl, DrmSessionManager drmSessionManager,
boolean preferExtensionDecoders) {
return newSimpleInstance(context, trackSelector, bufferingControl, drmSessionManager,
return newSimpleInstance(context, trackSelector, loadControl, drmSessionManager,
preferExtensionDecoders, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS);
}
@ -89,7 +89,7 @@ public final class ExoPlayerFactory {
*
* @param context A {@link Context}.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param bufferingControl The {@link BufferingControl} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
* @param preferExtensionDecoders True to prefer {@link TrackRenderer} instances defined in
@ -99,9 +99,9 @@ public final class ExoPlayerFactory {
* seamlessly join an ongoing playback.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
BufferingControl bufferingControl, DrmSessionManager drmSessionManager,
LoadControl loadControl, DrmSessionManager drmSessionManager,
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) {
return new SimpleExoPlayer(context, trackSelector, bufferingControl, drmSessionManager,
return new SimpleExoPlayer(context, trackSelector, loadControl, drmSessionManager,
preferExtensionDecoders, allowedVideoJoiningTimeMs);
}
@ -114,7 +114,7 @@ public final class ExoPlayerFactory {
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
*/
public static ExoPlayer newInstance(TrackRenderer[] renderers, TrackSelector trackSelector) {
return newInstance(renderers, trackSelector, new DefaultBufferingControl());
return newInstance(renderers, trackSelector, new DefaultLoadControl());
}
/**
@ -124,11 +124,11 @@ public final class ExoPlayerFactory {
*
* @param renderers The {@link TrackRenderer}s that will be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param bufferingControl The {@link BufferingControl} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
*/
public static ExoPlayer newInstance(TrackRenderer[] renderers, TrackSelector trackSelector,
BufferingControl bufferingControl) {
return new ExoPlayerImpl(renderers, trackSelector, bufferingControl);
LoadControl loadControl) {
return new ExoPlayerImpl(renderers, trackSelector, loadControl);
}
}

View file

@ -41,6 +41,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
private int playbackState;
private int pendingPlayWhenReadyAcks;
private int pendingSetSourceProviderAndSeekAcks;
private boolean isLoading;
// Playback information when there is no pending seek/set source operation.
private PlaybackInfo playbackInfo;
@ -55,11 +56,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
*
* @param renderers The {@link TrackRenderer}s that will be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param bufferingControl The {@link BufferingControl} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
*/
@SuppressLint("HandlerLeak")
public ExoPlayerImpl(TrackRenderer[] renderers, TrackSelector trackSelector,
BufferingControl bufferingControl) {
LoadControl loadControl) {
Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION);
Assertions.checkNotNull(renderers);
Assertions.checkState(renderers.length > 0);
@ -72,8 +73,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
ExoPlayerImpl.this.handleEvent(msg);
}
};
internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, bufferingControl,
playWhenReady, eventHandler);
internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, loadControl, playWhenReady,
eventHandler);
playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(0);
}
@ -132,6 +133,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
return pendingPlayWhenReadyAcks == 0;
}
@Override
public boolean isLoading() {
return isLoading;
}
@Override
public void seekTo(long positionMs) {
seekTo(getCurrentSourceIndex(), positionMs);
@ -221,6 +227,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
break;
}
case ExoPlayerImplInternal.MSG_LOADING_CHANGED: {
isLoading = msg.arg1 != 0;
for (EventListener listener : listeners) {
listener.onLoadingChanged(isLoading);
}
break;
}
case ExoPlayerImplInternal.MSG_SET_PLAY_WHEN_READY_ACK: {
pendingPlayWhenReadyAcks--;
if (pendingPlayWhenReadyAcks == 0) {

View file

@ -35,8 +35,6 @@ import java.util.ArrayList;
/**
* Implements the internal behavior of {@link ExoPlayerImpl}.
*/
// TODO[REFACTOR]: Make sure renderer errors that will prevent prepare from being called again are
// always propagated properly.
/* package */ final class ExoPlayerImplInternal implements Handler.Callback, SampleSource.Callback,
InvalidationListener {
@ -63,11 +61,12 @@ import java.util.ArrayList;
// External messages
public static final int MSG_STATE_CHANGED = 1;
public static final int MSG_SET_PLAY_WHEN_READY_ACK = 2;
public static final int MSG_SET_SOURCE_PROVIDER_ACK = 3;
public static final int MSG_SEEK_ACK = 4;
public static final int MSG_SOURCE_CHANGED = 5;
public static final int MSG_ERROR = 6;
public static final int MSG_LOADING_CHANGED = 2;
public static final int MSG_SET_PLAY_WHEN_READY_ACK = 3;
public static final int MSG_SET_SOURCE_PROVIDER_ACK = 4;
public static final int MSG_SEEK_ACK = 5;
public static final int MSG_SOURCE_CHANGED = 6;
public static final int MSG_ERROR = 7;
// Internal messages
private static final int MSG_SET_SOURCE_PROVIDER = 0;
@ -93,7 +92,7 @@ import java.util.ArrayList;
private static final int MAXIMUM_BUFFER_AHEAD_SOURCES = 100;
private final TrackSelector trackSelector;
private final BufferingControl bufferingControl;
private final LoadControl loadControl;
private final StandaloneMediaClock standaloneMediaClock;
private final Handler handler;
private final HandlerThread internalPlaybackThread;
@ -108,6 +107,7 @@ import java.util.ArrayList;
private boolean released;
private boolean playWhenReady;
private boolean rebuffering;
private boolean isLoading;
private int state;
private int customMessagesSent;
private int customMessagesProcessed;
@ -116,9 +116,9 @@ import java.util.ArrayList;
private long internalPositionUs;
public ExoPlayerImplInternal(TrackRenderer[] renderers, TrackSelector trackSelector,
BufferingControl bufferingControl, boolean playWhenReady, Handler eventHandler) {
LoadControl loadControl, boolean playWhenReady, Handler eventHandler) {
this.trackSelector = trackSelector;
this.bufferingControl = bufferingControl;
this.loadControl = loadControl;
this.playWhenReady = playWhenReady;
this.eventHandler = eventHandler;
this.state = ExoPlayer.STATE_IDLE;
@ -294,6 +294,13 @@ import java.util.ArrayList;
}
}
private void setIsLoading(boolean isLoading) {
if (this.isLoading != isLoading) {
this.isLoading = isLoading;
eventHandler.obtainMessage(MSG_LOADING_CHANGED, isLoading ? 1 : 0, 0).sendToTarget();
}
}
private void setSourceProviderInternal(SampleSourceProvider sourceProvider) {
try {
resetInternal();
@ -524,7 +531,8 @@ import java.util.ArrayList;
enabledRenderers = new TrackRenderer[0];
sampleSourceProvider = null;
timeline.reset();
bufferingControl.reset();
loadControl.reset();
setIsLoading(false);
}
private void sendMessagesInternal(ExoPlayerMessage[] messages) throws ExoPlaybackException {
@ -572,7 +580,7 @@ import java.util.ArrayList;
private Source playingSource;
private Source readingSource;
private Source bufferingSource;
private Source loadingSource;
private int pendingSourceIndex;
private long playingSourceEndPositionUs;
@ -587,62 +595,65 @@ import java.util.ArrayList;
}
public boolean haveSufficientBuffer(boolean rebuffering) {
if (bufferingSource == null) {
if (loadingSource == null) {
return false;
}
long positionUs = internalPositionUs - bufferingSource.offsetUs;
long bufferedPositionUs = !bufferingSource.prepared ? 0
: bufferingSource.sampleSource.getBufferedPositionUs();
long positionUs = internalPositionUs - loadingSource.offsetUs;
long bufferedPositionUs = !loadingSource.prepared ? 0
: loadingSource.sampleSource.getBufferedPositionUs();
if (bufferedPositionUs == C.END_OF_SOURCE_US) {
int sourceCount = sampleSourceProvider.getSourceCount();
if (sourceCount != SampleSourceProvider.UNKNOWN_SOURCE_COUNT
&& bufferingSource.index == sourceCount - 1) {
&& loadingSource.index == sourceCount - 1) {
return true;
}
bufferedPositionUs = bufferingSource.sampleSource.getDurationUs();
bufferedPositionUs = loadingSource.sampleSource.getDurationUs();
}
return bufferingControl.shouldStartPlayback(bufferedPositionUs - positionUs, rebuffering);
return loadControl.shouldStartPlayback(bufferedPositionUs - positionUs, rebuffering);
}
public void maybeThrowSourcePrepareError() throws IOException {
if (bufferingSource != null && !bufferingSource.prepared
&& (readingSource == null || readingSource.nextSource == bufferingSource)) {
if (loadingSource != null && !loadingSource.prepared
&& (readingSource == null || readingSource.nextSource == loadingSource)) {
for (TrackRenderer renderer : enabledRenderers) {
if (!renderer.hasReadStreamToEnd()) {
return;
}
}
bufferingSource.sampleSource.maybeThrowPrepareError();
loadingSource.sampleSource.maybeThrowPrepareError();
}
}
public void updateSources() throws ExoPlaybackException, IOException {
// TODO[playlists]: Let sample source providers invalidate sources that are already buffering.
// TODO[playlists]: Let sample source providers invalidate sources that are already loaded.
// Update the buffering source.
// Update the loading source.
int sourceCount = sampleSourceProvider.getSourceCount();
if (bufferingSource == null
|| (bufferingSource.isFullyBuffered() && bufferingSource.index
if (loadingSource == null
|| (loadingSource.isFullyBuffered() && loadingSource.index
- (playingSource != null ? playingSource.index : 0) < MAXIMUM_BUFFER_AHEAD_SOURCES)) {
// Try and obtain the next source to start buffering.
int sourceIndex = bufferingSource == null ? pendingSourceIndex : bufferingSource.index + 1;
// Try and obtain the next source to start loading.
int sourceIndex = loadingSource == null ? pendingSourceIndex : loadingSource.index + 1;
if (sourceCount == SampleSourceProvider.UNKNOWN_SOURCE_COUNT || sourceIndex < sourceCount) {
// Attempt to create the next source.
SampleSource sampleSource = sampleSourceProvider.createSource(sourceIndex);
if (sampleSource != null) {
Source newSource = new Source(renderers, trackSelector, sampleSource, sourceIndex);
if (bufferingSource != null) {
bufferingSource.setNextSource(newSource);
if (loadingSource != null) {
loadingSource.setNextSource(newSource);
}
bufferingSource = newSource;
loadingSource = newSource;
long startPositionUs = playingSource == null ? playbackInfo.positionUs : 0;
bufferingSource.sampleSource.prepare(ExoPlayerImplInternal.this,
bufferingControl.getAllocator(), startPositionUs);
setIsLoading(true);
loadingSource.sampleSource.prepare(ExoPlayerImplInternal.this,
loadControl.getAllocator(), startPositionUs);
}
}
}
if (bufferingSource != null && bufferingSource.needsContinueLoading) {
if (loadingSource == null || loadingSource.isFullyBuffered()) {
setIsLoading(false);
} else if (loadingSource != null && loadingSource.needsContinueLoading) {
maybeContinueLoading();
}
@ -713,15 +724,15 @@ import java.util.ArrayList;
}
public void handleSourcePrepared(SampleSource source) throws ExoPlaybackException {
if (bufferingSource == null || bufferingSource.sampleSource != source) {
if (loadingSource == null || loadingSource.sampleSource != source) {
// Stale event.
return;
}
long startPositionUs = playingSource == null ? playbackInfo.positionUs : 0;
bufferingSource.handlePrepared(startPositionUs, bufferingControl);
loadingSource.handlePrepared(startPositionUs, loadControl);
if (playingSource == null) {
// This is the first prepared source, so start playing it.
readingSource = bufferingSource;
readingSource = loadingSource;
setPlayingSource(readingSource);
updateTimelineState();
}
@ -729,24 +740,27 @@ import java.util.ArrayList;
}
public void handleContinueLoadingRequested(SampleSource source) {
if (bufferingSource == null || bufferingSource.sampleSource != source) {
if (loadingSource == null || loadingSource.sampleSource != source) {
return;
}
maybeContinueLoading();
}
private void maybeContinueLoading() {
long nextLoadPositionUs = bufferingSource.sampleSource.getNextLoadPositionUs();
long nextLoadPositionUs = loadingSource.sampleSource.getNextLoadPositionUs();
if (nextLoadPositionUs != C.END_OF_SOURCE_US) {
long positionUs = internalPositionUs - bufferingSource.offsetUs;
long positionUs = internalPositionUs - loadingSource.offsetUs;
long bufferedDurationUs = nextLoadPositionUs - positionUs;
boolean continueBuffering = bufferingControl.shouldContinueBuffering(bufferedDurationUs);
if (continueBuffering) {
bufferingSource.needsContinueLoading = false;
bufferingSource.sampleSource.continueLoading(positionUs);
boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs);
setIsLoading(continueLoading);
if (continueLoading) {
loadingSource.needsContinueLoading = false;
loadingSource.sampleSource.continueLoading(positionUs);
} else {
bufferingSource.needsContinueLoading = true;
loadingSource.needsContinueLoading = true;
}
} else {
setIsLoading(false);
}
}
@ -768,7 +782,7 @@ import java.util.ArrayList;
setPlayingSource(newPlayingSource);
updateTimelineState();
readingSource = playingSource;
bufferingSource = playingSource;
loadingSource = playingSource;
if (playingSource.hasEnabledTracks) {
seekPositionUs = playingSource.sampleSource.seekToUs(seekPositionUs);
}
@ -782,7 +796,7 @@ import java.util.ArrayList;
enabledRenderers = new TrackRenderer[0];
playingSource = null;
readingSource = null;
bufferingSource = null;
loadingSource = null;
pendingSourceIndex = sourceIndex;
resetInternalPosition(seekPositionUs);
}
@ -818,13 +832,13 @@ import java.util.ArrayList;
}
playingSource.nextSource = null;
readingSource = playingSource;
bufferingSource = playingSource;
loadingSource = playingSource;
playingSourceEndPositionUs = C.UNSET_TIME_US;
// Update streams for the new selection, recreating all streams if reading ahead.
boolean recreateStreams = readingSource != playingSource;
TrackSelectionArray playingSourceOldTrackSelections = playingSource.sourceTrackSelections;
playingSource.updateSourceTrackSelection(playbackInfo.positionUs, bufferingControl,
playingSource.updateSourceTrackSelection(playbackInfo.positionUs, loadControl,
recreateStreams);
int enabledRendererCount = 0;
@ -858,21 +872,21 @@ import java.util.ArrayList;
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
} else {
// Release and re-prepare/buffer sources after the one whose selection changed.
bufferingSource = source;
source = bufferingSource.nextSource;
loadingSource = source;
source = loadingSource.nextSource;
while (source != null) {
source.release();
source = source.nextSource;
}
bufferingSource.nextSource = null;
long positionUs = Math.max(0, internalPositionUs - bufferingSource.offsetUs);
bufferingSource.updateSourceTrackSelection(positionUs, bufferingControl, false);
loadingSource.nextSource = null;
long positionUs = Math.max(0, internalPositionUs - loadingSource.offsetUs);
loadingSource.updateSourceTrackSelection(positionUs, loadControl, false);
}
maybeContinueLoading();
}
public void reset() {
Source source = playingSource != null ? playingSource : bufferingSource;
Source source = playingSource != null ? playingSource : loadingSource;
while (source != null) {
source.release();
source = source.nextSource;
@ -881,7 +895,7 @@ import java.util.ArrayList;
isEnded = false;
playingSource = null;
readingSource = null;
bufferingSource = null;
loadingSource = null;
playingSourceEndPositionUs = C.UNSET_TIME_US;
pendingSourceIndex = 0;
playbackInfo = new PlaybackInfo(0);
@ -995,7 +1009,6 @@ import java.util.ArrayList;
int index) {
this.renderers = renderers;
this.trackSelector = trackSelector;
this.sampleSource = sampleSource;
this.index = index;
trackStreams = new TrackStream[renderers.length];
@ -1011,11 +1024,11 @@ import java.util.ArrayList;
|| sampleSource.getBufferedPositionUs() == C.END_OF_SOURCE_US);
}
public void handlePrepared(long positionUs, BufferingControl bufferingControl)
public void handlePrepared(long positionUs, LoadControl loadControl)
throws ExoPlaybackException {
prepared = true;
selectTracks();
updateSourceTrackSelection(positionUs, bufferingControl, false);
updateSourceTrackSelection(positionUs, loadControl, false);
}
public boolean selectTracks() throws ExoPlaybackException {
@ -1030,7 +1043,7 @@ import java.util.ArrayList;
return true;
}
public void updateSourceTrackSelection(long positionUs, BufferingControl bufferingControl,
public void updateSourceTrackSelection(long positionUs, LoadControl loadControl,
boolean forceRecreateStreams) throws ExoPlaybackException {
// Populate lists of streams that are being disabled/newly enabled.
ArrayList<TrackStream> oldStreams = new ArrayList<>();
@ -1069,7 +1082,7 @@ import java.util.ArrayList;
}
// The track selection has changed.
bufferingControl.onTrackSelections(renderers, sampleSource.getTrackGroups(), trackSelections);
loadControl.onTrackSelections(renderers, sampleSource.getTrackGroups(), trackSelections);
}
public void release() {

View file

@ -20,7 +20,7 @@ import com.google.android.exoplayer.upstream.Allocator;
/**
* Controls buffering of media.
*/
public interface BufferingControl {
public interface LoadControl {
/**
* Invoked by the player when a track selection occurs.
@ -55,11 +55,11 @@ public interface BufferingControl {
boolean shouldStartPlayback(long bufferedDurationUs, boolean rebuffering);
/**
* Invoked by the player to determine whether buffering should continue.
* Invoked by the player to determine whether it should continue to load the source.
*
* @param bufferedDurationUs The duration of media that's currently buffered.
* @return True if the buffering should continue. False otherwise.
* @return True if the loading should continue. False otherwise.
*/
boolean shouldContinueBuffering(long bufferedDurationUs);
boolean shouldContinueLoading(long bufferedDurationUs);
}

View file

@ -111,7 +111,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
private CodecCounters audioCodecCounters;
/* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector,
BufferingControl bufferingControl, DrmSessionManager drmSessionManager,
LoadControl loadControl, DrmSessionManager drmSessionManager,
boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) {
mainHandler = new Handler();
bandwidthMeter = new DefaultBandwidthMeter();
@ -145,7 +145,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
this.audioRendererCount = audioRendererCount;
// Build the player and associated objects.
player = new ExoPlayerImpl(renderers, trackSelector, bufferingControl);
player = new ExoPlayerImpl(renderers, trackSelector, loadControl);
}
/**
@ -338,6 +338,11 @@ public final class SimpleExoPlayer implements ExoPlayer {
return player.isPlayWhenReadyCommitted();
}
@Override
public boolean isLoading() {
return player.isLoading();
}
@Override
public void seekTo(long positionMs) {
player.seekTo(positionMs);

View file

@ -153,6 +153,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe
// ExoPlayer.EventListener implementation
@Override
public void onLoadingChanged(boolean isLoading) {
// Do nothing.
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
updateTextView();

View file

@ -174,7 +174,12 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
assertPassed(audioCodecCounters, videoCodecCounters);
}
// ExoPlayer.Listener
// ExoPlayer.EventListener
@Override
public void onLoadingChanged(boolean isLoading) {
// Do nothing.
}
@Override
public final void onPlayerStateChanged(boolean playWhenReady, int playbackState) {