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 f9bc64d3e4..18708a96e0 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 @@ -17,6 +17,7 @@ package com.google.android.exoplayer.demo; import com.google.android.exoplayer.AspectRatioFrameLayout; import com.google.android.exoplayer.C; +import com.google.android.exoplayer.DefaultBufferingPolicy; import com.google.android.exoplayer.DefaultTrackSelectionPolicy; import com.google.android.exoplayer.DefaultTrackSelector; import com.google.android.exoplayer.DefaultTrackSelector.TrackInfo; @@ -266,8 +267,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, trackSelector.addListener(this); trackSelector.addListener(eventLogger); trackSelectionHelper = new TrackSelectionHelper(trackSelector); - player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, drmSessionManager, - preferExtensionDecoders); + player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultBufferingPolicy(), + drmSessionManager, preferExtensionDecoders); player.addListener(this); player.addListener(eventLogger); player.setDebugListener(eventLogger); diff --git a/library/src/main/java/com/google/android/exoplayer/BufferingPolicy.java b/library/src/main/java/com/google/android/exoplayer/BufferingPolicy.java new file mode 100644 index 0000000000..1b122dbd36 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/BufferingPolicy.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer; + +import com.google.android.exoplayer.upstream.Allocator; + +/** + * A media buffering policy. + */ +public interface BufferingPolicy { + + /** + * Invoked by the player to update the playback position. + * + * @param playbackPositionUs The current playback position in microseconds. + */ + void setPlaybackPosition(long playbackPositionUs); + + /** + * Invoked by the player to determine whether sufficient media is buffered for playback to be + * started or resumed. + * + * @param bufferedPositionUs The position up to which media is buffered. + * @param rebuffering Whether the player is re-buffering. + * @return True if playback should be allowed to start or resume. False otherwise. + */ + boolean haveSufficientBuffer(long bufferedPositionUs, boolean rebuffering); + + /** + * Invoked by the player when a track selection occurs. + * + * @param renderers The renderers. + * @param trackGroups The available {@link TrackGroup}s. + * @param trackSelections The {@link TrackSelection}s that were made. + */ + void onTrackSelections(TrackRenderer[] renderers, TrackGroupArray trackGroups, + TrackSelectionArray trackSelections); + + /** + * Invoked by the player when a reset occurs, meaning all renderers have been disabled. + */ + void reset(); + + /** + * Returns a {@link LoadControl} that a {@link SampleSource} can use to control loads according to + * this policy. + * + * @return The {@link LoadControl}. + */ + LoadControl getLoadControl(); + + /** + * Coordinates multiple loaders of time series data. + */ + interface LoadControl { + + /** + * Registers a loader. + * + * @param loader The loader being registered. + */ + void register(Object loader); + + /** + * Unregisters a loader. + * + * @param loader The loader being unregistered. + */ + void unregister(Object loader); + + /** + * Gets the {@link Allocator} that loaders should use to obtain memory allocations into which + * data can be loaded. + * + * @return The {@link Allocator} to use. + */ + Allocator getAllocator(); + + /** + * Invoked by a loader to update the control with its current state. + *

+ * This method must be called by a registered loader whenever its state changes. This is true + * even if the registered loader does not itself wish to start its next load (since the state of + * the loader will still affect whether other registered loaders are allowed to proceed). + * + * @param loader The loader invoking the update. + * @param nextLoadPositionUs The loader's next load position, or {@link C#UNSET_TIME_US} if + * finished, failed, or if the next load position is not yet known. + * @param loading Whether the loader is currently loading data. + * @return True if the loader is allowed to start its next load. False otherwise. + */ + boolean update(Object loader, long nextLoadPositionUs, boolean loading); + + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/DefaultLoadControl.java b/library/src/main/java/com/google/android/exoplayer/DefaultBufferingPolicy.java similarity index 61% rename from library/src/main/java/com/google/android/exoplayer/DefaultLoadControl.java rename to library/src/main/java/com/google/android/exoplayer/DefaultBufferingPolicy.java index 0e23c872e4..f760306e58 100644 --- a/library/src/main/java/com/google/android/exoplayer/DefaultLoadControl.java +++ b/library/src/main/java/com/google/android/exoplayer/DefaultBufferingPolicy.java @@ -15,9 +15,11 @@ */ package com.google.android.exoplayer; +import com.google.android.exoplayer.BufferingPolicy.LoadControl; import com.google.android.exoplayer.upstream.Allocator; import com.google.android.exoplayer.upstream.DefaultAllocator; import com.google.android.exoplayer.upstream.NetworkLock; +import com.google.android.exoplayer.util.Util; import android.os.Handler; @@ -40,10 +42,10 @@ import java.util.List; * itself as a task with priority {@link NetworkLock#STREAMING_PRIORITY} during loading periods, * and unregistering itself during draining periods. */ -public final class DefaultLoadControl implements LoadControl { +public final class DefaultBufferingPolicy implements BufferingPolicy, LoadControl { /** - * Interface definition for a callback to be notified of {@link DefaultLoadControl} events. + * Interface definition for a callback to be notified of {@link DefaultBufferingPolicy} events. */ public interface EventListener { @@ -56,8 +58,29 @@ public final class DefaultLoadControl implements LoadControl { } - public static final int DEFAULT_LOW_WATERMARK_MS = 15000; - public static final int DEFAULT_HIGH_WATERMARK_MS = 30000; + /** + * The default minimum duration of media that the player will attempt to ensure is buffered at all + * times, in milliseconds. + */ + public static final int DEFAULT_MIN_BUFFER_MS = 15000; + + /** + * The default maximum duration of media that the player will attempt to buffer, in milliseconds. + */ + public static final int DEFAULT_MAX_BUFFER_MS = 30000; + + /** + * The default duration of media that must be buffered for playback to start or resume following a + * user action such as a seek, in milliseconds. + */ + public static final int DEFAULT_BUFFER_FOR_PLAYBACK_MS = 2500; + + /** + * 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. + */ + public static final int DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 5000; private static final int ABOVE_HIGH_WATERMARK = 0; private static final int BETWEEN_WATERMARKS = 1; @@ -69,22 +92,35 @@ public final class DefaultLoadControl implements LoadControl { private final Handler eventHandler; private final EventListener eventListener; - private final long lowWatermarkUs; - private final long highWatermarkUs; + private final long minBufferUs; + private final long maxBufferUs; + private final long bufferForPlaybackUs; + private final long bufferForPlaybackAfterRebufferUs; - private long maxLoadStartPositionUs; private int targetBufferSize; private boolean targetBufferSizeReached; private boolean fillingBuffers; private boolean streamingPrioritySet; + private long playbackPositionUs; + private long maxLoadStartPositionUs; + + /** + * Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. + */ + public DefaultBufferingPolicy() { + this(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 DefaultLoadControl(DefaultAllocator allocator) { - this(allocator, null, null); + public DefaultBufferingPolicy(Handler eventHandler, EventListener eventListener) { + this(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE), eventHandler, eventListener); } /** @@ -95,10 +131,10 @@ public final class DefaultLoadControl implements LoadControl { * 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 DefaultLoadControl(DefaultAllocator allocator, Handler eventHandler, + public DefaultBufferingPolicy(DefaultAllocator allocator, Handler eventHandler, EventListener eventListener) { - this(allocator, eventHandler, eventListener, DEFAULT_LOW_WATERMARK_MS, - DEFAULT_HIGH_WATERMARK_MS); + this(allocator, eventHandler, eventListener, DEFAULT_MIN_BUFFER_MS, DEFAULT_MAX_BUFFER_MS, + DEFAULT_BUFFER_FOR_PLAYBACK_MS, DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS); } /** @@ -108,39 +144,81 @@ public final class DefaultLoadControl implements LoadControl { * @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. - * @param lowWatermarkMs The minimum duration of media that can be buffered for the control to - * be in the draining state. If less media is buffered, then the control will transition to - * the filling state. - * @param highWatermarkMs The minimum duration of media that can be buffered for the control to - * transition from filling to draining. + * @param minBufferMs The minimum duration of media that the player will attempt to ensure is + * buffered at all times, in milliseconds. + * @param maxBufferMs The maximum duration of media that the player will attempt buffer, in + * milliseconds. + * @param bufferForPlaybackMs The duration of media that must be buffered for playback to start or + * resume following a user action such as a seek, in milliseconds. + * @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. */ - public DefaultLoadControl(DefaultAllocator allocator, Handler eventHandler, - EventListener eventListener, int lowWatermarkMs, int highWatermarkMs) { + public DefaultBufferingPolicy(DefaultAllocator allocator, Handler eventHandler, + EventListener eventListener, int minBufferMs, int maxBufferMs, long bufferForPlaybackMs, + long bufferForPlaybackAfterRebufferMs) { this.allocator = allocator; this.eventHandler = eventHandler; this.eventListener = eventListener; - this.loaders = new ArrayList<>(); - this.loaderStates = new HashMap<>(); - this.lowWatermarkUs = lowWatermarkMs * 1000L; - this.highWatermarkUs = highWatermarkMs * 1000L; + this.minBufferUs = minBufferMs * 1000L; + this.maxBufferUs = maxBufferMs * 1000L; + this.bufferForPlaybackUs = bufferForPlaybackMs * 1000L; + this.bufferForPlaybackAfterRebufferUs = bufferForPlaybackAfterRebufferMs * 1000L; + loaders = new ArrayList<>(); + loaderStates = new HashMap<>(); + } + + // BufferingPolicy implementation. + + @Override + public void setPlaybackPosition(long playbackPositionUs) { + this.playbackPositionUs = playbackPositionUs; } @Override - public void register(Object loader, int bufferSizeContribution) { - loaders.add(loader); - loaderStates.put(loader, new LoaderState(bufferSizeContribution)); - targetBufferSize += bufferSizeContribution; + public boolean haveSufficientBuffer(long bufferedPositionUs, boolean rebuffering) { + long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs; + return minBufferDurationUs <= 0 + || bufferedPositionUs == C.END_OF_SOURCE_US + || bufferedPositionUs >= playbackPositionUs + minBufferDurationUs; + } + + @Override + public void onTrackSelections(TrackRenderer[] renderers, TrackGroupArray trackGroups, + TrackSelectionArray trackSelections) { + targetBufferSize = 0; + for (int i = 0; i < renderers.length; i++) { + if (trackSelections.get(i) != null) { + targetBufferSize += Util.getDefaultBufferSize(renderers[i].getTrackType()); + } + } allocator.setTargetBufferSize(targetBufferSize); + targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize; + updateControlState(); + } + + @Override + public void reset() { + targetBufferSize = 0; + } + + @Override + public LoadControl getLoadControl() { + return this; + } + + // LoadControl implementation. + + @Override + public void register(Object loader) { + loaders.add(loader); + loaderStates.put(loader, new LoaderState()); } @Override public void unregister(Object loader) { loaders.remove(loader); - LoaderState state = loaderStates.remove(loader); - targetBufferSize -= state.bufferSizeContribution; - allocator.setTargetBufferSize(targetBufferSize); - targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize; - updateControlState(); + loaderStates.remove(loader); } @Override @@ -149,8 +227,7 @@ public final class DefaultLoadControl implements LoadControl { } @Override - public boolean update(Object loader, long playbackPositionUs, long nextLoadPositionUs, - boolean loading) { + public boolean update(Object loader, long nextLoadPositionUs, boolean loading) { // Update the loader state. int loaderBufferState = getLoaderBufferState(playbackPositionUs, nextLoadPositionUs); LoaderState loaderState = loaderStates.get(loader); @@ -182,8 +259,8 @@ public final class DefaultLoadControl implements LoadControl { return ABOVE_HIGH_WATERMARK; } else { long timeUntilNextLoadPosition = nextLoadPositionUs - playbackPositionUs; - return timeUntilNextLoadPosition > highWatermarkUs ? ABOVE_HIGH_WATERMARK : - timeUntilNextLoadPosition < lowWatermarkUs ? BELOW_LOW_WATERMARK : + return timeUntilNextLoadPosition > maxBufferUs ? ABOVE_HIGH_WATERMARK : + timeUntilNextLoadPosition < minBufferUs ? BELOW_LOW_WATERMARK : BETWEEN_WATERMARKS; } } @@ -239,14 +316,11 @@ public final class DefaultLoadControl implements LoadControl { private static class LoaderState { - public final int bufferSizeContribution; - public int bufferState; public boolean loading; public long nextLoadPositionUs; - public LoaderState(int bufferSizeContribution) { - this.bufferSizeContribution = bufferSizeContribution; + public LoaderState() { bufferState = ABOVE_HIGH_WATERMARK; loading = false; nextLoadPositionUs = C.UNSET_TIME_US; 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 6251622f63..c2d6692170 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerFactory.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerFactory.java @@ -25,19 +25,6 @@ import android.os.Looper; */ public final class ExoPlayerFactory { - /** - * The default minimum duration of data that must be buffered for playback to start or resume - * following a user action such as a seek. - */ - public static final int DEFAULT_MIN_BUFFER_MS = 2500; - - /** - * The default minimum duration of data that must be buffered for playback to resume - * after a player-invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and - * not due to a user action such as starting playback or seeking). - */ - public static final int DEFAULT_MIN_REBUFFER_MS = 5000; - /** * The default maximum duration for which a video renderer can attempt to seamlessly join an * ongoing playback. @@ -55,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, null); + return newSimpleInstance(context, trackSelector, new DefaultBufferingPolicy(), null); } /** @@ -65,12 +52,13 @@ public final class ExoPlayerFactory { * * @param context A {@link Context}. * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param bufferingPolicy The {@link BufferingPolicy} 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, - DrmSessionManager drmSessionManager) { - return newSimpleInstance(context, trackSelector, drmSessionManager, false); + BufferingPolicy bufferingPolicy, DrmSessionManager drmSessionManager) { + return newSimpleInstance(context, trackSelector, bufferingPolicy, drmSessionManager, false); } /** @@ -80,6 +68,7 @@ public final class ExoPlayerFactory { * * @param context A {@link Context}. * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param bufferingPolicy The {@link BufferingPolicy} 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 @@ -87,9 +76,10 @@ 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, - DrmSessionManager drmSessionManager, boolean preferExtensionDecoders) { - return newSimpleInstance(context, trackSelector, drmSessionManager, preferExtensionDecoders, - DEFAULT_MIN_BUFFER_MS, DEFAULT_MIN_REBUFFER_MS, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS); + BufferingPolicy bufferingPolicy, DrmSessionManager drmSessionManager, + boolean preferExtensionDecoders) { + return newSimpleInstance(context, trackSelector, bufferingPolicy, drmSessionManager, + preferExtensionDecoders, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS); } /** @@ -99,42 +89,20 @@ public final class ExoPlayerFactory { * * @param context A {@link Context}. * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param bufferingPolicy The {@link BufferingPolicy} 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 * available extensions over those defined in the core library. Note that extensions must be * included in the application build for setting this flag to have any effect. - * @param minBufferMs A minimum duration of data that must be buffered for playback to start - * or resume following a user action such as a seek. - * @param minRebufferMs A minimum duration of data that must be buffered for playback to resume - * after a player-invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and - * not due to a user action such as starting playback or seeking). * @param allowedVideoJoiningTimeMs The maximum duration for which a video renderer can attempt to * seamlessly join an ongoing playback. */ public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, - DrmSessionManager drmSessionManager, boolean preferExtensionDecoders, int minBufferMs, - int minRebufferMs, long allowedVideoJoiningTimeMs) { - return new SimpleExoPlayer(context, trackSelector, drmSessionManager, preferExtensionDecoders, - minBufferMs, minRebufferMs, allowedVideoJoiningTimeMs); - } - - /** - * Obtains an {@link ExoPlayer} instance. - *

- * Must be called from a thread that has an associated {@link Looper}. - * - * @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 minBufferMs A minimum duration of data that must be buffered for playback to start - * or resume following a user action such as a seek. - * @param minRebufferMs A minimum duration of data that must be buffered for playback to resume - * after a player-invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and - * not due to a user action such as starting playback or seeking). - */ - public static ExoPlayer newInstance(TrackRenderer[] renderers, TrackSelector trackSelector, - int minBufferMs, int minRebufferMs) { - return new ExoPlayerImpl(renderers, trackSelector, minBufferMs, minRebufferMs); + BufferingPolicy bufferingPolicy, DrmSessionManager drmSessionManager, + boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) { + return new SimpleExoPlayer(context, trackSelector, bufferingPolicy, drmSessionManager, + preferExtensionDecoders, allowedVideoJoiningTimeMs); } /** @@ -146,8 +114,21 @@ 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 new ExoPlayerImpl(renderers, trackSelector, DEFAULT_MIN_BUFFER_MS, - DEFAULT_MIN_REBUFFER_MS); + return newInstance(renderers, trackSelector, new DefaultBufferingPolicy()); + } + + /** + * Obtains an {@link ExoPlayer} instance. + *

+ * Must be called from a thread that has an associated {@link Looper}. + * + * @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 bufferingPolicy The {@link BufferingPolicy} that will be used by the instance. + */ + public static ExoPlayer newInstance(TrackRenderer[] renderers, TrackSelector trackSelector, + BufferingPolicy bufferingPolicy) { + return new ExoPlayerImpl(renderers, trackSelector, bufferingPolicy); } } 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 f673899cb2..bab2257ebc 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java @@ -55,15 +55,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 minBufferMs A minimum duration of data that must be buffered for playback to start - * or resume following a user action such as a seek. - * @param minRebufferMs A minimum duration of data that must be buffered for playback to resume - * after a player invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and - * not due to a user action such as starting playback or seeking). + * @param bufferingPolicy The {@link BufferingPolicy} that will be used by the instance. */ @SuppressLint("HandlerLeak") - public ExoPlayerImpl(TrackRenderer[] renderers, TrackSelector trackSelector, int minBufferMs, - int minRebufferMs) { + public ExoPlayerImpl(TrackRenderer[] renderers, TrackSelector trackSelector, + BufferingPolicy bufferingPolicy) { Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION); Assertions.checkNotNull(renderers); Assertions.checkState(renderers.length > 0); @@ -76,7 +72,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ExoPlayerImpl.this.handleEvent(msg); } }; - internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, minBufferMs, minRebufferMs, + internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, bufferingPolicy, playWhenReady, eventHandler); playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(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 dfb71c9240..104d7ca3ea 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer; +import com.google.android.exoplayer.BufferingPolicy.LoadControl; import com.google.android.exoplayer.ExoPlayer.ExoPlayerMessage; import com.google.android.exoplayer.TrackSelector.InvalidationListener; import com.google.android.exoplayer.util.PriorityHandlerThread; @@ -90,9 +91,8 @@ import java.util.ArrayList; private static final int MAXIMUM_BUFFER_AHEAD_SOURCES = 100; private final TrackSelector trackSelector; + private final BufferingPolicy bufferingPolicy; private final StandaloneMediaClock standaloneMediaClock; - private final long minBufferUs; - private final long minRebufferUs; private final Handler handler; private final HandlerThread internalPlaybackThread; private final Handler eventHandler; @@ -114,10 +114,9 @@ import java.util.ArrayList; private long internalPositionUs; public ExoPlayerImplInternal(TrackRenderer[] renderers, TrackSelector trackSelector, - int minBufferMs, int minRebufferMs, boolean playWhenReady, Handler eventHandler) { + BufferingPolicy bufferingPolicy, boolean playWhenReady, Handler eventHandler) { this.trackSelector = trackSelector; - this.minBufferUs = minBufferMs * 1000L; - this.minRebufferUs = minRebufferMs * 1000L; + this.bufferingPolicy = bufferingPolicy; this.playWhenReady = playWhenReady; this.eventHandler = eventHandler; this.state = ExoPlayer.STATE_IDLE; @@ -277,16 +276,6 @@ import java.util.ArrayList; return renderer.isReady() || renderer.isEnded(); } - private boolean haveSufficientBuffer() { - // TODO[playlists]: Take into account the buffered position in the timeline. - long minBufferDurationUs = rebuffering ? minRebufferUs : minBufferUs; - return minBufferDurationUs <= 0 - || playbackInfo.bufferedPositionUs == C.END_OF_SOURCE_US - || playbackInfo.bufferedPositionUs >= playbackInfo.positionUs + minBufferDurationUs - || (playbackInfo.durationUs != C.UNSET_TIME_US - && playbackInfo.bufferedPositionUs >= playbackInfo.durationUs); - } - private void setSourceProviderInternal(SampleSourceProvider sourceProvider) { try { resetInternal(); @@ -359,6 +348,7 @@ import java.util.ArrayList; } playbackInfo.positionUs = positionUs; elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; + bufferingPolicy.setPlaybackPosition(positionUs); // Update the buffered position. long bufferedPositionUs; @@ -412,7 +402,7 @@ import java.util.ArrayList; stopRenderers(); } else if (state == ExoPlayer.STATE_BUFFERING) { if ((enabledRenderers.length > 0 ? allRenderersReadyOrEnded : timeline.isReady()) - && haveSufficientBuffer()) { + && bufferingPolicy.haveSufficientBuffer(playbackInfo.bufferedPositionUs, rebuffering)) { setState(ExoPlayer.STATE_READY); if (playWhenReady) { startRenderers(); @@ -520,6 +510,7 @@ import java.util.ArrayList; enabledRenderers = new TrackRenderer[0]; sampleSourceProvider = null; timeline.reset(); + bufferingPolicy.reset(); } private void sendMessagesInternal(ExoPlayerMessage[] messages) throws ExoPlaybackException { @@ -628,10 +619,11 @@ import java.util.ArrayList; // Continue preparation. // TODO[playlists]: Add support for setting the start position to play in a source. long startPositionUs = playingSource == null ? playbackInfo.positionUs : 0; - if (bufferingSource.prepare(startPositionUs)) { + if (bufferingSource.prepare(startPositionUs, bufferingPolicy.getLoadControl())) { Pair result = trackSelector.selectTracks(renderers, bufferingSource.sampleSource.getTrackGroups()); - bufferingSource.selectTracks(result.first, result.second, startPositionUs); + bufferingSource.selectTracks(result.first, result.second, startPositionUs, + bufferingPolicy, renderers); if (playingSource == null) { // This is the first prepared source, so start playing it. readingSource = bufferingSource; @@ -937,13 +929,8 @@ import java.util.ArrayList; || sampleSource.getBufferedPositionUs() == C.END_OF_SOURCE_US); } - public void setNextSource(Source nextSource) { - this.nextSource = nextSource; - nextSource.offsetUs = offsetUs + sampleSource.getDurationUs(); - } - - public boolean prepare(long startPositionUs) throws IOException { - if (sampleSource.prepare(startPositionUs)) { + public boolean prepare(long startPositionUs, LoadControl loadControl) throws IOException { + if (sampleSource.prepare(startPositionUs, loadControl)) { prepared = true; return true; } else { @@ -951,8 +938,14 @@ import java.util.ArrayList; } } + public void setNextSource(Source nextSource) { + this.nextSource = nextSource; + nextSource.offsetUs = offsetUs + sampleSource.getDurationUs(); + } + public void selectTracks(TrackSelectionArray newTrackSelections, Object trackSelectionData, - long positionUs) throws ExoPlaybackException { + long positionUs, BufferingPolicy bufferingPolicy, TrackRenderer[] renderers) + throws ExoPlaybackException { this.trackSelectionData = trackSelectionData; if (newTrackSelections.equals(trackSelections)) { return; @@ -974,6 +967,9 @@ import java.util.ArrayList; } TrackStream[] newStreams = sampleSource.selectTracks(oldStreams, newSelections, positionUs); updateTrackStreams(newTrackSelections, newSelections, newStreams); + + bufferingPolicy.onTrackSelections(renderers, sampleSource.getTrackGroups(), + newTrackSelections); } public void updateTrackStreams(TrackSelectionArray newTrackSelections, diff --git a/library/src/main/java/com/google/android/exoplayer/LoadControl.java b/library/src/main/java/com/google/android/exoplayer/LoadControl.java deleted file mode 100644 index 18a3b35990..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/LoadControl.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer; - -import com.google.android.exoplayer.upstream.Allocator; - -/** - * Coordinates multiple loaders of time series data. - */ -public interface LoadControl { - - /** - * Registers a loader. - * - * @param loader The loader being registered. - * @param bufferSizeContribution For instances whose {@link Allocator} maintains a pool of memory - * for the purpose of satisfying allocation requests, this is a hint indicating the loader's - * desired contribution to the size of the pool, in bytes. - */ - void register(Object loader, int bufferSizeContribution); - - /** - * Unregisters a loader. - * - * @param loader The loader being unregistered. - */ - void unregister(Object loader); - - /** - * Gets the {@link Allocator} that loaders should use to obtain memory allocations into which - * data can be loaded. - * - * @return The {@link Allocator} to use. - */ - Allocator getAllocator(); - - /** - * Invoked by a loader to update the control with its current state. - *

- * This method must be called by a registered loader whenever its state changes. This is true - * even if the registered loader does not itself wish to start its next load (since the state of - * the loader will still affect whether other registered loaders are allowed to proceed). - * - * @param loader The loader invoking the update. - * @param playbackPositionUs The loader's playback position. - * @param nextLoadPositionUs The loader's next load position. {@link C#UNSET_TIME_US} if finished, - * failed, or if the next load position is not yet known. - * @param loading Whether the loader is currently loading data. - * @return True if the loader is allowed to start its next load. False otherwise. - */ - boolean update(Object loader, long playbackPositionUs, long nextLoadPositionUs, boolean loading); - -} diff --git a/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java b/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java index 234fb37258..27deef1a00 100644 --- a/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer; +import com.google.android.exoplayer.BufferingPolicy.LoadControl; import com.google.android.exoplayer.util.Assertions; import android.util.Pair; @@ -46,13 +47,13 @@ public final class MultiSampleSource implements SampleSource { } @Override - public boolean prepare(long positionUs) throws IOException { + public boolean prepare(long positionUs, LoadControl loadControl) throws IOException { if (prepared) { return true; } boolean sourcesPrepared = true; for (SampleSource source : sources) { - sourcesPrepared &= source.prepare(positionUs); + sourcesPrepared &= source.prepare(positionUs, loadControl); } if (!sourcesPrepared) { return false; diff --git a/library/src/main/java/com/google/android/exoplayer/SampleSource.java b/library/src/main/java/com/google/android/exoplayer/SampleSource.java index c8ed5334ac..824f080220 100644 --- a/library/src/main/java/com/google/android/exoplayer/SampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/SampleSource.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer; +import com.google.android.exoplayer.BufferingPolicy.LoadControl; + import java.io.IOException; import java.util.List; @@ -31,10 +33,11 @@ public interface SampleSource { * tracks. * * @param positionUs The player's current playback position. + * @param loadControl A {@link LoadControl} to determine when to load data. * @return True if the source is prepared, false otherwise. * @throws IOException If there's an error preparing the source. */ - boolean prepare(long positionUs) throws IOException; + boolean prepare(long positionUs, LoadControl loadControl) throws IOException; /** * Returns the duration of the source in microseconds, or {@link C#UNSET_TIME_US} if not known. 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 c4bb467970..c6834215ae 100644 --- a/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java @@ -111,8 +111,8 @@ public final class SimpleExoPlayer implements ExoPlayer { private CodecCounters audioCodecCounters; /* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector, - DrmSessionManager drmSessionManager, boolean preferExtensionDecoders, int minBufferMs, - int minRebufferMs, long allowedVideoJoiningTimeMs) { + BufferingPolicy bufferingPolicy, DrmSessionManager drmSessionManager, + boolean preferExtensionDecoders, long allowedVideoJoiningTimeMs) { mainHandler = new Handler(); bandwidthMeter = new DefaultBandwidthMeter(); componentListener = new ComponentListener(); @@ -145,7 +145,7 @@ public final class SimpleExoPlayer implements ExoPlayer { this.audioRendererCount = audioRendererCount; // Build the player and associated objects. - player = new ExoPlayerImpl(renderers, trackSelector, minBufferMs, minRebufferMs); + player = new ExoPlayerImpl(renderers, trackSelector, bufferingPolicy); } /** diff --git a/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java b/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java index 4f228724bc..e08a2bb3ef 100644 --- a/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer; +import com.google.android.exoplayer.BufferingPolicy.LoadControl; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.Loader; @@ -108,7 +109,8 @@ public final class SingleSampleSource implements SampleSource, TrackStream, // SampleSource implementation. @Override - public boolean prepare(long positionUs) { + public boolean prepare(long positionUs, LoadControl loadControl) { + // TODO: Use the load control. return true; } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStream.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStream.java index 8b194e2cda..c69c06f8e9 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStream.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkTrackStream.java @@ -16,11 +16,11 @@ package com.google.android.exoplayer.chunk; import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher; +import com.google.android.exoplayer.BufferingPolicy.LoadControl; import com.google.android.exoplayer.C; import com.google.android.exoplayer.DecoderInputBuffer; import com.google.android.exoplayer.Format; import com.google.android.exoplayer.FormatHolder; -import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.TrackStream; import com.google.android.exoplayer.extractor.DefaultTrackOutput; import com.google.android.exoplayer.upstream.Loader; @@ -64,14 +64,13 @@ public class ChunkTrackStream implements TrackStream, * @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants. * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained. * @param loadControl Controls when the source is permitted to load data. - * @param bufferSizeContribution The contribution of this source to the media buffer, in bytes. * @param positionUs The position from which to start loading media. * @param minLoadableRetryCount The minimum number of times that the source should retry a load * before propagating an error. * @param eventDispatcher A dispatcher to notify of events. */ - public ChunkTrackStream(int trackType, T chunkSource, LoadControl loadControl, - int bufferSizeContribution, long positionUs, int minLoadableRetryCount, + public ChunkTrackStream(int trackType, T chunkSource, LoadControl loadControl, long positionUs, + int minLoadableRetryCount, EventDispatcher eventDispatcher) { this.trackType = trackType; this.chunkSource = chunkSource; @@ -87,7 +86,7 @@ public class ChunkTrackStream implements TrackStream, readingEnabled = true; downstreamPositionUs = positionUs; lastSeekPositionUs = positionUs; - loadControl.register(this, bufferSizeContribution); + loadControl.register(this); restartFrom(positionUs); } @@ -287,7 +286,7 @@ public class ChunkTrackStream implements TrackStream, lastPreferredQueueSizeEvaluationTimeMs = now; } - boolean isNext = loadControl.update(this, downstreamPositionUs, getNextLoadPositionUs(), false); + boolean isNext = loadControl.update(this, getNextLoadPositionUs(), false); if (!isNext) { return; } @@ -301,7 +300,7 @@ public class ChunkTrackStream implements TrackStream, if (endOfStream) { loadingFinished = true; - loadControl.update(this, downstreamPositionUs, C.UNSET_TIME_US, false); + loadControl.update(this, C.UNSET_TIME_US, false); return; } @@ -320,7 +319,7 @@ public class ChunkTrackStream implements TrackStream, loadable.formatEvaluatorTrigger, loadable.formatEvaluatorData, loadable.startTimeUs, loadable.endTimeUs, elapsedRealtimeMs); // Update the load control again to indicate that we're now loading. - loadControl.update(this, downstreamPositionUs, getNextLoadPositionUs(), true); + loadControl.update(this, getNextLoadPositionUs(), true); } /** diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java index ad80a9b15e..7f88d3d061 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashSampleSource.java @@ -17,10 +17,9 @@ package com.google.android.exoplayer.dash; import com.google.android.exoplayer.AdaptiveSourceEventListener; import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher; +import com.google.android.exoplayer.BufferingPolicy.LoadControl; import com.google.android.exoplayer.C; -import com.google.android.exoplayer.DefaultLoadControl; import com.google.android.exoplayer.Format; -import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackGroup; @@ -39,7 +38,6 @@ import com.google.android.exoplayer.dash.mpd.UtcTimingElement; import com.google.android.exoplayer.upstream.BandwidthMeter; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSourceFactory; -import com.google.android.exoplayer.upstream.DefaultAllocator; import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader.Callback; import com.google.android.exoplayer.upstream.ParsingLoadable; @@ -76,7 +74,6 @@ public final class DashSampleSource implements SampleSource { private final DataSourceFactory dataSourceFactory; private final BandwidthMeter bandwidthMeter; private final EventDispatcher eventDispatcher; - private final LoadControl loadControl; private final Loader loader; private final DataSource dataSource; private final MediaPresentationDescriptionParser manifestParser; @@ -87,6 +84,7 @@ public final class DashSampleSource implements SampleSource { private long manifestLoadEndTimestamp; private MediaPresentationDescription manifest; + private LoadControl loadControl; private boolean prepared; private long durationUs; private long elapsedRealtimeOffset; @@ -101,7 +99,6 @@ public final class DashSampleSource implements SampleSource { this.dataSourceFactory = dataSourceFactory; this.bandwidthMeter = bandwidthMeter; eventDispatcher = new EventDispatcher(eventHandler, eventListener); - loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE)); loader = new Loader("Loader:DashSampleSource"); dataSource = dataSourceFactory.createDataSource(); manifestParser = new MediaPresentationDescriptionParser(); @@ -110,10 +107,11 @@ public final class DashSampleSource implements SampleSource { } @Override - public boolean prepare(long positionUs) throws IOException { + public boolean prepare(long positionUs, LoadControl loadControl) throws IOException { if (prepared) { return true; } + this.loadControl = loadControl; loader.maybeThrowError(); if (!loader.isLoading() && manifest == null) { startLoadingManifest(); @@ -361,13 +359,12 @@ public final class DashSampleSource implements SampleSource { AdaptationSet adaptationSet = manifest.getPeriod(0).adaptationSets.get( adaptationSetIndex); int adaptationSetType = adaptationSet.type; - int bufferSize = Util.getDefaultBufferSize(adaptationSetType); DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter); DashChunkSource chunkSource = new DashChunkSource(loader, manifest, adaptationSetIndex, trackGroups.get(selection.group), selectedTracks, dataSource, adaptiveEvaluator, elapsedRealtimeOffset); - return new ChunkTrackStream<>(adaptationSetType, chunkSource, loadControl, bufferSize, - positionUs, MIN_LOADABLE_RETRY_COUNT, eventDispatcher); + return new ChunkTrackStream<>(adaptationSetType, chunkSource, loadControl, positionUs, + MIN_LOADABLE_RETRY_COUNT, eventDispatcher); } @SuppressWarnings("unchecked") diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java index 7060fe69ec..b251e90249 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java @@ -15,11 +15,10 @@ */ package com.google.android.exoplayer.extractor; +import com.google.android.exoplayer.BufferingPolicy.LoadControl; import com.google.android.exoplayer.C; import com.google.android.exoplayer.DecoderInputBuffer; -import com.google.android.exoplayer.DefaultLoadControl; import com.google.android.exoplayer.FormatHolder; -import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackGroup; @@ -30,7 +29,6 @@ import com.google.android.exoplayer.upstream.BandwidthMeter; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSourceFactory; import com.google.android.exoplayer.upstream.DataSpec; -import com.google.android.exoplayer.upstream.DefaultAllocator; import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.util.Assertions; @@ -128,13 +126,13 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu private final EventListener eventListener; private final DataSource dataSource; private final ConditionVariable loadCondition; - private final LoadControl loadControl; private final Loader loader; private final ExtractorHolder extractorHolder; private volatile boolean tracksBuilt; private volatile SeekMap seekMap; + private LoadControl loadControl; private boolean prepared; private boolean seenFirstTrackSelection; private boolean notifyReset; @@ -190,7 +188,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu this.eventHandler = eventHandler; dataSource = dataSourceFactory.createDataSource(bandwidthMeter); loadCondition = new ConditionVariable(); - loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE)); loader = new Loader("Loader:ExtractorSampleSource"); extractorHolder = new ExtractorHolder(extractors, this); pendingResetPositionUs = C.UNSET_TIME_US; @@ -307,10 +304,11 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu // SampleSource implementation. @Override - public boolean prepare(long positionUs) throws IOException { + public boolean prepare(long positionUs, LoadControl loadControl) throws IOException { if (prepared) { return true; } + this.loadControl = loadControl; if (seekMap != null && tracksBuilt && haveFormatsForAllTracks()) { loadCondition.close(); int trackCount = sampleQueues.length; @@ -388,7 +386,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu } } else { if (!tracksWereEnabled) { - loadControl.register(this, C.DEFAULT_MUXED_BUFFER_SIZE); + loadControl.register(this); loadCondition.open(); } if (seenFirstTrackSelection ? newStreams.length > 0 : positionUs != 0) { @@ -401,7 +399,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu @Override public void continueBuffering(long playbackPositionUs) { - if (loadControl.update(this, playbackPositionUs, getBufferedPositionUs(), loader.isLoading())) { + if (loadControl.update(this, getBufferedPositionUs(), loader.isLoading())) { loadCondition.open(); } else { loadCondition.close(); diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java index bf71747171..447ace0ef3 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java @@ -17,10 +17,9 @@ package com.google.android.exoplayer.hls; import com.google.android.exoplayer.AdaptiveSourceEventListener; import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher; +import com.google.android.exoplayer.BufferingPolicy.LoadControl; import com.google.android.exoplayer.C; -import com.google.android.exoplayer.DefaultLoadControl; import com.google.android.exoplayer.Format; -import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackGroup; @@ -36,7 +35,6 @@ import com.google.android.exoplayer.hls.playlist.Variant; import com.google.android.exoplayer.upstream.BandwidthMeter; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSourceFactory; -import com.google.android.exoplayer.upstream.DefaultAllocator; import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.ParsingLoadable; import com.google.android.exoplayer.util.MimeTypes; @@ -66,13 +64,13 @@ public final class HlsSampleSource implements SampleSource, private final DataSourceFactory dataSourceFactory; private final BandwidthMeter bandwidthMeter; private final EventDispatcher eventDispatcher; - private final LoadControl loadControl; private final IdentityHashMap trackStreamSources; private final PtsTimestampAdjusterProvider timestampAdjusterProvider; private final Loader manifestFetcher; private final DataSource manifestDataSource; private final HlsPlaylistParser manifestParser; + private LoadControl loadControl; private boolean seenFirstTrackSelection; private long durationUs; private boolean isLive; @@ -89,7 +87,6 @@ public final class HlsSampleSource implements SampleSource, this.bandwidthMeter = bandwidthMeter; eventDispatcher = new EventDispatcher(eventHandler, eventListener); - loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE)); timestampAdjusterProvider = new PtsTimestampAdjusterProvider(); trackStreamSources = new IdentityHashMap<>(); @@ -99,11 +96,12 @@ public final class HlsSampleSource implements SampleSource, } @Override - public boolean prepare(long positionUs) throws IOException { + public boolean prepare(long positionUs, LoadControl loadControl) throws IOException { if (trackGroups != null) { return true; } + this.loadControl = loadControl; if (trackStreamWrappers == null) { manifestFetcher.maybeThrowError(); if (!manifestFetcher.isLoading()) { @@ -268,8 +266,7 @@ public final class HlsSampleSource implements SampleSource, Format.NO_VALUE); Variant[] variants = new Variant[] {new Variant(playlist.baseUri, format, null)}; trackStreamWrappers.add(buildTrackStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, - new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter), C.DEFAULT_MUXED_BUFFER_SIZE, - null, null)); + new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter), null, null)); return trackStreamWrappers; } @@ -302,8 +299,8 @@ public final class HlsSampleSource implements SampleSource, Variant[] variants = new Variant[selectedVariants.size()]; selectedVariants.toArray(variants); trackStreamWrappers.add(buildTrackStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, - new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter), C.DEFAULT_MUXED_BUFFER_SIZE, - masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat)); + new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter), masterPlaylist.muxedAudioFormat, + masterPlaylist.muxedCaptionFormat)); // Build the audio stream wrapper if applicable. List audioVariants = masterPlaylist.audios; @@ -311,7 +308,7 @@ public final class HlsSampleSource implements SampleSource, variants = new Variant[audioVariants.size()]; audioVariants.toArray(variants); trackStreamWrappers.add(buildTrackStreamWrapper(C.TRACK_TYPE_AUDIO, baseUri, variants, null, - C.DEFAULT_AUDIO_BUFFER_SIZE, null, null)); + null, null)); } // Build the text stream wrapper if applicable. @@ -320,20 +317,20 @@ public final class HlsSampleSource implements SampleSource, variants = new Variant[subtitleVariants.size()]; subtitleVariants.toArray(variants); trackStreamWrappers.add(buildTrackStreamWrapper(C.TRACK_TYPE_TEXT, baseUri, variants, null, - C.DEFAULT_TEXT_BUFFER_SIZE, null, null)); + null, null)); } return trackStreamWrappers; } private HlsTrackStreamWrapper buildTrackStreamWrapper(int trackType, String baseUri, - Variant[] variants, FormatEvaluator formatEvaluator, int bufferSize, Format muxedAudioFormat, + Variant[] variants, FormatEvaluator formatEvaluator, Format muxedAudioFormat, Format muxedCaptionFormat) { DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter); HlsChunkSource defaultChunkSource = new HlsChunkSource(baseUri, variants, dataSource, timestampAdjusterProvider, formatEvaluator); - return new HlsTrackStreamWrapper(trackType, defaultChunkSource, loadControl, bufferSize, - muxedAudioFormat, muxedCaptionFormat, MIN_LOADABLE_RETRY_COUNT, eventDispatcher); + return new HlsTrackStreamWrapper(trackType, defaultChunkSource, loadControl, muxedAudioFormat, + muxedCaptionFormat, MIN_LOADABLE_RETRY_COUNT, eventDispatcher); } private int selectTracks(HlsTrackStreamWrapper trackStreamWrapper, diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java index 3c73a97ee6..eb819dd68f 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsTrackStreamWrapper.java @@ -16,11 +16,11 @@ package com.google.android.exoplayer.hls; import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher; +import com.google.android.exoplayer.BufferingPolicy.LoadControl; import com.google.android.exoplayer.C; import com.google.android.exoplayer.DecoderInputBuffer; import com.google.android.exoplayer.Format; import com.google.android.exoplayer.FormatHolder; -import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.TrackGroup; import com.google.android.exoplayer.TrackGroupArray; import com.google.android.exoplayer.TrackSelection; @@ -54,7 +54,6 @@ import java.util.List; private final int trackType; private final HlsChunkSource chunkSource; private final LoadControl loadControl; - private final int bufferSizeContribution; private final Format muxedAudioFormat; private final Format muxedCaptionFormat; private final int minLoadableRetryCount; @@ -88,7 +87,6 @@ import java.util.List; * @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants. * @param chunkSource A {@link HlsChunkSource} from which chunks to load are obtained. * @param loadControl Controls when the source is permitted to load data. - * @param bufferSizeContribution The contribution of this source to the media buffer, in bytes. * @param muxedAudioFormat If HLS master playlist indicates that the stream contains muxed audio, * this is the audio {@link Format} as defined by the playlist. * @param muxedCaptionFormat If HLS master playlist indicates that the stream contains muxed @@ -98,12 +96,11 @@ import java.util.List; * @param eventDispatcher A dispatcher to notify of events. */ public HlsTrackStreamWrapper(int trackType, HlsChunkSource chunkSource, LoadControl loadControl, - int bufferSizeContribution, Format muxedAudioFormat, Format muxedCaptionFormat, - int minLoadableRetryCount, EventDispatcher eventDispatcher) { + Format muxedAudioFormat, Format muxedCaptionFormat, int minLoadableRetryCount, + EventDispatcher eventDispatcher) { this.trackType = trackType; this.chunkSource = chunkSource; this.loadControl = loadControl; - this.bufferSizeContribution = bufferSizeContribution; this.muxedAudioFormat = muxedAudioFormat; this.muxedCaptionFormat = muxedCaptionFormat; this.minLoadableRetryCount = minLoadableRetryCount; @@ -210,7 +207,7 @@ import java.util.List; loader.cancelLoading(); } } else if (!tracksWereEnabled) { - loadControl.register(this, bufferSizeContribution); + loadControl.register(this); } return newStreams; } @@ -516,7 +513,7 @@ import java.util.List; private void maybeStartLoading() { boolean shouldStartLoading = !prepared || (enabledTrackCount > 0 - && loadControl.update(this, downstreamPositionUs, getNextLoadPositionUs(), false)); + && loadControl.update(this, getNextLoadPositionUs(), false)); if (!shouldStartLoading) { return; } @@ -531,7 +528,7 @@ import java.util.List; if (endOfStream) { loadingFinished = true; if (prepared) { - loadControl.update(this, downstreamPositionUs, C.UNSET_TIME_US, false); + loadControl.update(this, C.UNSET_TIME_US, false); } return; } @@ -552,7 +549,7 @@ import java.util.List; loadable.endTimeUs, elapsedRealtimeMs); if (prepared) { // Update the load control again to indicate that we're now loading. - loadControl.update(this, downstreamPositionUs, getNextLoadPositionUs(), true); + loadControl.update(this, getNextLoadPositionUs(), true); } } diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java index 2b4f3fada1..d458287db3 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java @@ -17,10 +17,9 @@ package com.google.android.exoplayer.smoothstreaming; import com.google.android.exoplayer.AdaptiveSourceEventListener; import com.google.android.exoplayer.AdaptiveSourceEventListener.EventDispatcher; +import com.google.android.exoplayer.BufferingPolicy.LoadControl; import com.google.android.exoplayer.C; -import com.google.android.exoplayer.DefaultLoadControl; import com.google.android.exoplayer.Format; -import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackGroup; @@ -36,7 +35,6 @@ import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.Stre import com.google.android.exoplayer.upstream.BandwidthMeter; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSourceFactory; -import com.google.android.exoplayer.upstream.DefaultAllocator; import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.ParsingLoadable; import com.google.android.exoplayer.util.Util; @@ -68,7 +66,6 @@ public final class SmoothStreamingSampleSource implements SampleSource, private final DataSourceFactory dataSourceFactory; private final BandwidthMeter bandwidthMeter; private final EventDispatcher eventDispatcher; - private final LoadControl loadControl; private final Loader manifestLoader; private final DataSource manifestDataSource; private final SmoothStreamingManifestParser manifestParser; @@ -76,6 +73,7 @@ public final class SmoothStreamingSampleSource implements SampleSource, private long manifestLoadStartTimestamp; private SmoothStreamingManifest manifest; + private LoadControl loadControl; private boolean prepared; private long durationUs; private TrackEncryptionBox[] trackEncryptionBoxes; @@ -92,7 +90,6 @@ public final class SmoothStreamingSampleSource implements SampleSource, this.dataSourceFactory = dataSourceFactory; this.bandwidthMeter = bandwidthMeter; this.eventDispatcher = new EventDispatcher(eventHandler, eventListener); - loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE)); trackStreams = newTrackStreamArray(0); manifestDataSource = dataSourceFactory.createDataSource(); manifestParser = new SmoothStreamingManifestParser(); @@ -100,10 +97,11 @@ public final class SmoothStreamingSampleSource implements SampleSource, } @Override - public boolean prepare(long positionUs) throws IOException { + public boolean prepare(long positionUs, LoadControl loadControl) throws IOException { if (prepared) { return true; } + this.loadControl = loadControl; manifestLoader.maybeThrowError(); if (!manifestLoader.isLoading()) { startLoadingManifest(); @@ -282,13 +280,12 @@ public final class SmoothStreamingSampleSource implements SampleSource, int streamElementIndex = trackGroupElementIndices[selection.group]; StreamElement streamElement = manifest.streamElements[streamElementIndex]; int streamElementType = streamElement.type; - int bufferSize = Util.getDefaultBufferSize(streamElementType); DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter); SmoothStreamingChunkSource chunkSource = new SmoothStreamingChunkSource(manifestLoader, manifest, streamElementIndex, trackGroups.get(selection.group), selectedTracks, dataSource, adaptiveEvaluator, trackEncryptionBoxes); - return new ChunkTrackStream<>(streamElementType, chunkSource, loadControl, bufferSize, - positionUs, MIN_LOADABLE_RETRY_COUNT, eventDispatcher); + return new ChunkTrackStream<>(streamElementType, chunkSource, loadControl, positionUs, + MIN_LOADABLE_RETRY_COUNT, eventDispatcher); } @SuppressWarnings("unchecked") 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 66b8f320bf..29af83d4ca 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 @@ -36,6 +36,7 @@ import android.os.Handler; import android.os.SystemClock; import android.util.Log; import android.view.Surface; + import junit.framework.Assert; /** @@ -276,8 +277,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen @SuppressWarnings("unused") protected SimpleExoPlayer buildExoPlayer(HostActivity host, Surface surface, DefaultTrackSelector trackSelector) { - SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(host, trackSelector, null, false, - ExoPlayerFactory.DEFAULT_MIN_BUFFER_MS, ExoPlayerFactory.DEFAULT_MIN_REBUFFER_MS, 0); + SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(host, trackSelector); player.setSurface(surface); return player; }