diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java index c0160cac8d..b263d3a323 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java @@ -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 + ", " diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index 2f0f4789ec..ffac3b506f 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -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) { diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacPlaybackTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacPlaybackTest.java index 121a440efb..6b6dc19f73 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacPlaybackTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacPlaybackTest.java @@ -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. diff --git a/extensions/opus/src/androidTest/java/com/google/android/exoplayer/ext/opus/OpusPlaybackTest.java b/extensions/opus/src/androidTest/java/com/google/android/exoplayer/ext/opus/OpusPlaybackTest.java index edb9203d6b..6167ade9f9 100644 --- a/extensions/opus/src/androidTest/java/com/google/android/exoplayer/ext/opus/OpusPlaybackTest.java +++ b/extensions/opus/src/androidTest/java/com/google/android/exoplayer/ext/opus/OpusPlaybackTest.java @@ -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. diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java index a1f614676a..af4e195f77 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java @@ -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. diff --git a/library/src/main/java/com/google/android/exoplayer/DefaultBufferingControl.java b/library/src/main/java/com/google/android/exoplayer/DefaultLoadControl.java similarity index 66% rename from library/src/main/java/com/google/android/exoplayer/DefaultBufferingControl.java rename to library/src/main/java/com/google/android/exoplayer/DefaultLoadControl.java index b7abb3bf6c..c6ddffb3b6 100644 --- a/library/src/main/java/com/google/android/exoplayer/DefaultBufferingControl.java +++ b/library/src/main/java/com/google/android/exoplayer/DefaultLoadControl.java @@ -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); - } - }); - } - } - } diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java index a97519ca4b..3397e75df2 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java @@ -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. * diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerFactory.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerFactory.java index 736cb8164a..fc85ef388a 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerFactory.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerFactory.java @@ -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); } } diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java index 3de3022d92..8c5d783086 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java @@ -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) { diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java index 087af4de51..8151ddb578 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java @@ -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 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() { diff --git a/library/src/main/java/com/google/android/exoplayer/BufferingControl.java b/library/src/main/java/com/google/android/exoplayer/LoadControl.java similarity index 88% rename from library/src/main/java/com/google/android/exoplayer/BufferingControl.java rename to library/src/main/java/com/google/android/exoplayer/LoadControl.java index 57e7e539a9..e28e1b0282 100644 --- a/library/src/main/java/com/google/android/exoplayer/BufferingControl.java +++ b/library/src/main/java/com/google/android/exoplayer/LoadControl.java @@ -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); } diff --git a/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java b/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java index 2c1e97cc43..b7f14b8531 100644 --- a/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java @@ -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); diff --git a/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java b/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java index cb33823dc9..b4429a46c6 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java +++ b/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java @@ -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(); diff --git a/playbacktests/src/main/java/com/google/android/exoplayer/playbacktests/util/ExoHostedTest.java b/playbacktests/src/main/java/com/google/android/exoplayer/playbacktests/util/ExoHostedTest.java index 29af83d4ca..2433358bb6 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer/playbacktests/util/ExoHostedTest.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer/playbacktests/util/ExoHostedTest.java @@ -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) {