diff options
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/DefaultLoadControl.java')
-rw-r--r-- | mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/DefaultLoadControl.java | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/DefaultLoadControl.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/DefaultLoadControl.java new file mode 100644 index 0000000000..ad5350a722 --- /dev/null +++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/DefaultLoadControl.java @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2016 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 org.mozilla.thirdparty.com.google.android.exoplayer2; + +import org.mozilla.thirdparty.com.google.android.exoplayer2.source.TrackGroupArray; +import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.Allocator; +import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DefaultAllocator; +import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions; +import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util; + +/** + * The default {@link LoadControl} implementation. + */ +public class DefaultLoadControl implements LoadControl { + + /** + * The default minimum duration of media that the player will attempt to ensure is buffered at all + * times, in milliseconds. This value is only applied to playbacks without video. + */ + public static final int DEFAULT_MIN_BUFFER_MS = 15000; + + /** + * The default maximum duration of media that the player will attempt to buffer, in milliseconds. + * For playbacks with video, this is also the default minimum duration of media that the player + * will attempt to ensure is buffered. + */ + public static final int DEFAULT_MAX_BUFFER_MS = 50000; + + /** + * 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 rebuffer, in + * milliseconds. A rebuffer is defined to be caused by buffer depletion rather than a user action. + */ + public static final int DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 5000; + + /** + * The default target buffer size in bytes. The value ({@link C#LENGTH_UNSET}) means that the load + * control will calculate the target buffer size based on the selected tracks. + */ + public static final int DEFAULT_TARGET_BUFFER_BYTES = C.LENGTH_UNSET; + + /** The default prioritization of buffer time constraints over size constraints. */ + public static final boolean DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS = true; + + /** The default back buffer duration in milliseconds. */ + public static final int DEFAULT_BACK_BUFFER_DURATION_MS = 0; + + /** The default for whether the back buffer is retained from the previous keyframe. */ + public static final boolean DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME = false; + + /** A default size in bytes for a video buffer. */ + public static final int DEFAULT_VIDEO_BUFFER_SIZE = 500 * C.DEFAULT_BUFFER_SEGMENT_SIZE; + + /** A default size in bytes for an audio buffer. */ + public static final int DEFAULT_AUDIO_BUFFER_SIZE = 54 * C.DEFAULT_BUFFER_SEGMENT_SIZE; + + /** A default size in bytes for a text buffer. */ + public static final int DEFAULT_TEXT_BUFFER_SIZE = 2 * C.DEFAULT_BUFFER_SEGMENT_SIZE; + + /** A default size in bytes for a metadata buffer. */ + public static final int DEFAULT_METADATA_BUFFER_SIZE = 2 * C.DEFAULT_BUFFER_SEGMENT_SIZE; + + /** A default size in bytes for a camera motion buffer. */ + public static final int DEFAULT_CAMERA_MOTION_BUFFER_SIZE = 2 * C.DEFAULT_BUFFER_SEGMENT_SIZE; + + /** A default size in bytes for a muxed buffer (e.g. containing video, audio and text). */ + public static final int DEFAULT_MUXED_BUFFER_SIZE = + DEFAULT_VIDEO_BUFFER_SIZE + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE; + + /** Builder for {@link DefaultLoadControl}. */ + public static final class Builder { + + private DefaultAllocator allocator; + private int minBufferAudioMs; + private int minBufferVideoMs; + private int maxBufferMs; + private int bufferForPlaybackMs; + private int bufferForPlaybackAfterRebufferMs; + private int targetBufferBytes; + private boolean prioritizeTimeOverSizeThresholds; + private int backBufferDurationMs; + private boolean retainBackBufferFromKeyframe; + private boolean createDefaultLoadControlCalled; + + /** Constructs a new instance. */ + public Builder() { + minBufferAudioMs = DEFAULT_MIN_BUFFER_MS; + minBufferVideoMs = DEFAULT_MAX_BUFFER_MS; + maxBufferMs = DEFAULT_MAX_BUFFER_MS; + bufferForPlaybackMs = DEFAULT_BUFFER_FOR_PLAYBACK_MS; + bufferForPlaybackAfterRebufferMs = DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS; + targetBufferBytes = DEFAULT_TARGET_BUFFER_BYTES; + prioritizeTimeOverSizeThresholds = DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS; + backBufferDurationMs = DEFAULT_BACK_BUFFER_DURATION_MS; + retainBackBufferFromKeyframe = DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME; + } + + /** + * Sets the {@link DefaultAllocator} used by the loader. + * + * @param allocator The {@link DefaultAllocator}. + * @return This builder, for convenience. + * @throws IllegalStateException If {@link #createDefaultLoadControl()} has already been called. + */ + public Builder setAllocator(DefaultAllocator allocator) { + Assertions.checkState(!createDefaultLoadControlCalled); + this.allocator = allocator; + return this; + } + + /** + * Sets the buffer duration parameters. + * + * @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 to 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 rebuffer, in milliseconds. A rebuffer is defined to be + * caused by buffer depletion rather than a user action. + * @return This builder, for convenience. + * @throws IllegalStateException If {@link #createDefaultLoadControl()} has already been called. + */ + public Builder setBufferDurationsMs( + int minBufferMs, + int maxBufferMs, + int bufferForPlaybackMs, + int bufferForPlaybackAfterRebufferMs) { + Assertions.checkState(!createDefaultLoadControlCalled); + assertGreaterOrEqual(bufferForPlaybackMs, 0, "bufferForPlaybackMs", "0"); + assertGreaterOrEqual( + bufferForPlaybackAfterRebufferMs, 0, "bufferForPlaybackAfterRebufferMs", "0"); + assertGreaterOrEqual(minBufferMs, bufferForPlaybackMs, "minBufferMs", "bufferForPlaybackMs"); + assertGreaterOrEqual( + minBufferMs, + bufferForPlaybackAfterRebufferMs, + "minBufferMs", + "bufferForPlaybackAfterRebufferMs"); + assertGreaterOrEqual(maxBufferMs, minBufferMs, "maxBufferMs", "minBufferMs"); + this.minBufferAudioMs = minBufferMs; + this.minBufferVideoMs = minBufferMs; + this.maxBufferMs = maxBufferMs; + this.bufferForPlaybackMs = bufferForPlaybackMs; + this.bufferForPlaybackAfterRebufferMs = bufferForPlaybackAfterRebufferMs; + return this; + } + + /** + * Sets the target buffer size in bytes. If set to {@link C#LENGTH_UNSET}, the target buffer + * size will be calculated based on the selected tracks. + * + * @param targetBufferBytes The target buffer size in bytes. + * @return This builder, for convenience. + * @throws IllegalStateException If {@link #createDefaultLoadControl()} has already been called. + */ + public Builder setTargetBufferBytes(int targetBufferBytes) { + Assertions.checkState(!createDefaultLoadControlCalled); + this.targetBufferBytes = targetBufferBytes; + return this; + } + + /** + * Sets whether the load control prioritizes buffer time constraints over buffer size + * constraints. + * + * @param prioritizeTimeOverSizeThresholds Whether the load control prioritizes buffer time + * constraints over buffer size constraints. + * @return This builder, for convenience. + * @throws IllegalStateException If {@link #createDefaultLoadControl()} has already been called. + */ + public Builder setPrioritizeTimeOverSizeThresholds(boolean prioritizeTimeOverSizeThresholds) { + Assertions.checkState(!createDefaultLoadControlCalled); + this.prioritizeTimeOverSizeThresholds = prioritizeTimeOverSizeThresholds; + return this; + } + + /** + * Sets the back buffer duration, and whether the back buffer is retained from the previous + * keyframe. + * + * @param backBufferDurationMs The back buffer duration in milliseconds. + * @param retainBackBufferFromKeyframe Whether the back buffer is retained from the previous + * keyframe. + * @return This builder, for convenience. + * @throws IllegalStateException If {@link #createDefaultLoadControl()} has already been called. + */ + public Builder setBackBuffer(int backBufferDurationMs, boolean retainBackBufferFromKeyframe) { + Assertions.checkState(!createDefaultLoadControlCalled); + assertGreaterOrEqual(backBufferDurationMs, 0, "backBufferDurationMs", "0"); + this.backBufferDurationMs = backBufferDurationMs; + this.retainBackBufferFromKeyframe = retainBackBufferFromKeyframe; + return this; + } + + /** Creates a {@link DefaultLoadControl}. */ + public DefaultLoadControl createDefaultLoadControl() { + Assertions.checkState(!createDefaultLoadControlCalled); + createDefaultLoadControlCalled = true; + if (allocator == null) { + allocator = new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE); + } + return new DefaultLoadControl( + allocator, + minBufferAudioMs, + minBufferVideoMs, + maxBufferMs, + bufferForPlaybackMs, + bufferForPlaybackAfterRebufferMs, + targetBufferBytes, + prioritizeTimeOverSizeThresholds, + backBufferDurationMs, + retainBackBufferFromKeyframe); + } + } + + private final DefaultAllocator allocator; + + private final long minBufferAudioUs; + private final long minBufferVideoUs; + private final long maxBufferUs; + private final long bufferForPlaybackUs; + private final long bufferForPlaybackAfterRebufferUs; + private final int targetBufferBytesOverwrite; + private final boolean prioritizeTimeOverSizeThresholds; + private final long backBufferDurationUs; + private final boolean retainBackBufferFromKeyframe; + + private int targetBufferSize; + private boolean isBuffering; + private boolean hasVideo; + + /** Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. */ + @SuppressWarnings("deprecation") + public DefaultLoadControl() { + this(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE)); + } + + /** @deprecated Use {@link Builder} instead. */ + @Deprecated + public DefaultLoadControl(DefaultAllocator allocator) { + this( + allocator, + /* minBufferAudioMs= */ DEFAULT_MIN_BUFFER_MS, + /* minBufferVideoMs= */ DEFAULT_MAX_BUFFER_MS, + DEFAULT_MAX_BUFFER_MS, + DEFAULT_BUFFER_FOR_PLAYBACK_MS, + DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS, + DEFAULT_TARGET_BUFFER_BYTES, + DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS, + DEFAULT_BACK_BUFFER_DURATION_MS, + DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME); + } + + /** @deprecated Use {@link Builder} instead. */ + @Deprecated + public DefaultLoadControl( + DefaultAllocator allocator, + int minBufferMs, + int maxBufferMs, + int bufferForPlaybackMs, + int bufferForPlaybackAfterRebufferMs, + int targetBufferBytes, + boolean prioritizeTimeOverSizeThresholds) { + this( + allocator, + /* minBufferAudioMs= */ minBufferMs, + /* minBufferVideoMs= */ minBufferMs, + maxBufferMs, + bufferForPlaybackMs, + bufferForPlaybackAfterRebufferMs, + targetBufferBytes, + prioritizeTimeOverSizeThresholds, + DEFAULT_BACK_BUFFER_DURATION_MS, + DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME); + } + + protected DefaultLoadControl( + DefaultAllocator allocator, + int minBufferAudioMs, + int minBufferVideoMs, + int maxBufferMs, + int bufferForPlaybackMs, + int bufferForPlaybackAfterRebufferMs, + int targetBufferBytes, + boolean prioritizeTimeOverSizeThresholds, + int backBufferDurationMs, + boolean retainBackBufferFromKeyframe) { + assertGreaterOrEqual(bufferForPlaybackMs, 0, "bufferForPlaybackMs", "0"); + assertGreaterOrEqual( + bufferForPlaybackAfterRebufferMs, 0, "bufferForPlaybackAfterRebufferMs", "0"); + assertGreaterOrEqual( + minBufferAudioMs, bufferForPlaybackMs, "minBufferAudioMs", "bufferForPlaybackMs"); + assertGreaterOrEqual( + minBufferVideoMs, bufferForPlaybackMs, "minBufferVideoMs", "bufferForPlaybackMs"); + assertGreaterOrEqual( + minBufferAudioMs, + bufferForPlaybackAfterRebufferMs, + "minBufferAudioMs", + "bufferForPlaybackAfterRebufferMs"); + assertGreaterOrEqual( + minBufferVideoMs, + bufferForPlaybackAfterRebufferMs, + "minBufferVideoMs", + "bufferForPlaybackAfterRebufferMs"); + assertGreaterOrEqual(maxBufferMs, minBufferAudioMs, "maxBufferMs", "minBufferAudioMs"); + assertGreaterOrEqual(maxBufferMs, minBufferVideoMs, "maxBufferMs", "minBufferVideoMs"); + assertGreaterOrEqual(backBufferDurationMs, 0, "backBufferDurationMs", "0"); + + this.allocator = allocator; + this.minBufferAudioUs = C.msToUs(minBufferAudioMs); + this.minBufferVideoUs = C.msToUs(minBufferVideoMs); + this.maxBufferUs = C.msToUs(maxBufferMs); + this.bufferForPlaybackUs = C.msToUs(bufferForPlaybackMs); + this.bufferForPlaybackAfterRebufferUs = C.msToUs(bufferForPlaybackAfterRebufferMs); + this.targetBufferBytesOverwrite = targetBufferBytes; + this.prioritizeTimeOverSizeThresholds = prioritizeTimeOverSizeThresholds; + this.backBufferDurationUs = C.msToUs(backBufferDurationMs); + this.retainBackBufferFromKeyframe = retainBackBufferFromKeyframe; + } + + @Override + public void onPrepared() { + reset(false); + } + + @Override + public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, + TrackSelectionArray trackSelections) { + hasVideo = hasVideo(renderers, trackSelections); + targetBufferSize = + targetBufferBytesOverwrite == C.LENGTH_UNSET + ? calculateTargetBufferSize(renderers, trackSelections) + : targetBufferBytesOverwrite; + allocator.setTargetBufferSize(targetBufferSize); + } + + @Override + public void onStopped() { + reset(true); + } + + @Override + public void onReleased() { + reset(true); + } + + @Override + public Allocator getAllocator() { + return allocator; + } + + @Override + public long getBackBufferDurationUs() { + return backBufferDurationUs; + } + + @Override + public boolean retainBackBufferFromKeyframe() { + return retainBackBufferFromKeyframe; + } + + @Override + public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) { + boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize; + long minBufferUs = hasVideo ? minBufferVideoUs : minBufferAudioUs; + if (playbackSpeed > 1) { + // The playback speed is faster than real time, so scale up the minimum required media + // duration to keep enough media buffered for a playout duration of minBufferUs. + long mediaDurationMinBufferUs = + Util.getMediaDurationForPlayoutDuration(minBufferUs, playbackSpeed); + minBufferUs = Math.min(mediaDurationMinBufferUs, maxBufferUs); + } + if (bufferedDurationUs < minBufferUs) { + isBuffering = prioritizeTimeOverSizeThresholds || !targetBufferSizeReached; + } else if (bufferedDurationUs >= maxBufferUs || targetBufferSizeReached) { + isBuffering = false; + } // Else don't change the buffering state + return isBuffering; + } + + @Override + public boolean shouldStartPlayback( + long bufferedDurationUs, float playbackSpeed, boolean rebuffering) { + bufferedDurationUs = Util.getPlayoutDurationForMediaDuration(bufferedDurationUs, playbackSpeed); + long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs; + return minBufferDurationUs <= 0 + || bufferedDurationUs >= minBufferDurationUs + || (!prioritizeTimeOverSizeThresholds + && allocator.getTotalBytesAllocated() >= targetBufferSize); + } + + /** + * Calculate target buffer size in bytes based on the selected tracks. The player will try not to + * exceed this target buffer. Only used when {@code targetBufferBytes} is {@link C#LENGTH_UNSET}. + * + * @param renderers The renderers for which the track were selected. + * @param trackSelectionArray The selected tracks. + * @return The target buffer size in bytes. + */ + protected int calculateTargetBufferSize( + Renderer[] renderers, TrackSelectionArray trackSelectionArray) { + int targetBufferSize = 0; + for (int i = 0; i < renderers.length; i++) { + if (trackSelectionArray.get(i) != null) { + targetBufferSize += getDefaultBufferSize(renderers[i].getTrackType()); + } + } + return targetBufferSize; + } + + private void reset(boolean resetAllocator) { + targetBufferSize = 0; + isBuffering = false; + if (resetAllocator) { + allocator.reset(); + } + } + + private static int getDefaultBufferSize(int trackType) { + switch (trackType) { + case C.TRACK_TYPE_DEFAULT: + return DEFAULT_MUXED_BUFFER_SIZE; + case C.TRACK_TYPE_AUDIO: + return DEFAULT_AUDIO_BUFFER_SIZE; + case C.TRACK_TYPE_VIDEO: + return DEFAULT_VIDEO_BUFFER_SIZE; + case C.TRACK_TYPE_TEXT: + return DEFAULT_TEXT_BUFFER_SIZE; + case C.TRACK_TYPE_METADATA: + return DEFAULT_METADATA_BUFFER_SIZE; + case C.TRACK_TYPE_CAMERA_MOTION: + return DEFAULT_CAMERA_MOTION_BUFFER_SIZE; + case C.TRACK_TYPE_NONE: + return 0; + default: + throw new IllegalArgumentException(); + } + } + + private static boolean hasVideo(Renderer[] renderers, TrackSelectionArray trackSelectionArray) { + for (int i = 0; i < renderers.length; i++) { + if (renderers[i].getTrackType() == C.TRACK_TYPE_VIDEO && trackSelectionArray.get(i) != null) { + return true; + } + } + return false; + } + + private static void assertGreaterOrEqual(int value1, int value2, String name1, String name2) { + Assertions.checkArgument(value1 >= value2, name1 + " cannot be less than " + name2); + } +} |