diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1098e1155e..1defae4356 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,6 +7,8 @@ * Added `TextComponent.getCurrentCues` because the current cues are no longer forwarded to a new `TextOutput` in `SimpleExoPlayer` automatically. + * Add additional options to `SimpleExoPlayer.Builder` that were previously + only accessible via setters. * Add opt-in to verify correct thread usage with `SimpleExoPlayer.setThrowsWhenUsingWrongThread(true)` ([#4463](https://github.com/google/ExoPlayer/issues/4463)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index d779037817..b4cd9a399d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -146,6 +146,8 @@ public interface ExoPlayer extends Player { private Looper looper; @Nullable private AnalyticsCollector analyticsCollector; private boolean useLazyPreparation; + private SeekParameters seekParameters; + private boolean pauseAtEndOfMediaItems; private boolean buildCalled; private long releaseTimeoutMs; @@ -166,6 +168,8 @@ public interface ExoPlayer extends Player { * Looper} *
Note that this constructor is only useful if you try to ensure that ExoPlayer's default - * components can be removed by ProGuard or R8. For most components except renderers, there is - * only a marginal benefit of doing that. + *
Note that this constructor is only useful to try and ensure that ExoPlayer's default + * components can be removed by ProGuard or R8. * * @param renderers The {@link Renderer Renderers} to be used by the player. * @param trackSelector A {@link TrackSelector}. * @param mediaSourceFactory A {@link MediaSourceFactory}. * @param loadControl A {@link LoadControl}. * @param bandwidthMeter A {@link BandwidthMeter}. - * @param looper A {@link Looper} that must be used for all calls to the player. - * @param analyticsCollector An {@link AnalyticsCollector}. - * @param useLazyPreparation Whether media sources should be initialized lazily. - * @param clock A {@link Clock}. Should always be {@link Clock#DEFAULT}. */ public Builder( Renderer[] renderers, TrackSelector trackSelector, MediaSourceFactory mediaSourceFactory, LoadControl loadControl, - BandwidthMeter bandwidthMeter, - Looper looper, - @Nullable AnalyticsCollector analyticsCollector, - boolean useLazyPreparation, - Clock clock) { + BandwidthMeter bandwidthMeter) { Assertions.checkArgument(renderers.length > 0); this.renderers = renderers; this.trackSelector = trackSelector; this.mediaSourceFactory = mediaSourceFactory; this.loadControl = loadControl; this.bandwidthMeter = bandwidthMeter; - this.looper = looper; - this.analyticsCollector = analyticsCollector; - this.useLazyPreparation = useLazyPreparation; - this.clock = clock; + looper = Util.getLooper(); + useLazyPreparation = true; + seekParameters = SeekParameters.DEFAULT; + clock = Clock.DEFAULT; } /** @@ -347,6 +338,37 @@ public interface ExoPlayer extends Player { return this; } + /** + * Sets the parameters that control how seek operations are performed. + * + * @param seekParameters The {@link SeekParameters}. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder setSeekParameters(SeekParameters seekParameters) { + Assertions.checkState(!buildCalled); + this.seekParameters = seekParameters; + return this; + } + + /** + * Sets whether to pause playback at the end of each media item. + * + *
This means the player will pause at the end of each window in the current {@link + * #getCurrentTimeline() timeline}. Listeners will be informed by a call to {@link + * Player.EventListener#onPlayWhenReadyChanged(boolean, int)} with the reason {@link + * Player#PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM} when this happens. + * + * @param pauseAtEndOfMediaItems Whether to pause playback at the end of each media item. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) { + Assertions.checkState(!buildCalled); + this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems; + return this; + } + /** * Sets the {@link Clock} that will be used by the player. Should only be set for testing * purposes. @@ -379,6 +401,8 @@ public interface ExoPlayer extends Player { bandwidthMeter, analyticsCollector, useLazyPreparation, + seekParameters, + pauseAtEndOfMediaItems, clock, looper); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java index 32d00d90c1..2c07593aaa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java @@ -258,6 +258,8 @@ public final class ExoPlayerFactory { bandwidthMeter, /* analyticsCollector= */ null, /* useLazyPreparation= */ true, + SeekParameters.DEFAULT, + /* pauseAtEndOfMediaItems= */ false, Clock.DEFAULT, applicationLooper); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 543e72b2dd..26357a18dc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -109,6 +109,8 @@ import java.util.concurrent.TimeoutException; * @param useLazyPreparation Whether playlist items are prepared lazily. If false, all manifest * loads and other initial preparation steps happen immediately. If true, these initial * preparations are triggered only when the player starts buffering the media. + * @param seekParameters The {@link SeekParameters}. + * @param pauseAtEndOfMediaItems Whether to pause playback at the end of each media item. * @param clock The {@link Clock}. * @param applicationLooper The {@link Looper} that must be used for all calls to the player and * which is used to call listeners on. @@ -122,6 +124,8 @@ import java.util.concurrent.TimeoutException; BandwidthMeter bandwidthMeter, @Nullable AnalyticsCollector analyticsCollector, boolean useLazyPreparation, + SeekParameters seekParameters, + boolean pauseAtEndOfMediaItems, Clock clock, Looper applicationLooper) { Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " [" @@ -131,6 +135,8 @@ import java.util.concurrent.TimeoutException; this.trackSelector = checkNotNull(trackSelector); this.mediaSourceFactory = mediaSourceFactory; this.useLazyPreparation = useLazyPreparation; + this.seekParameters = seekParameters; + this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems; repeatMode = Player.REPEAT_MODE_OFF; listeners = new CopyOnWriteArrayList<>(); mediaSourceHolders = new ArrayList<>(); @@ -142,7 +148,6 @@ import java.util.concurrent.TimeoutException; null); period = new Timeline.Period(); playbackSpeed = Player.DEFAULT_PLAYBACK_SPEED; - seekParameters = SeekParameters.DEFAULT; maskingWindowIndex = C.INDEX_UNSET; applicationHandler = new Handler(applicationLooper) { @@ -166,6 +171,8 @@ import java.util.concurrent.TimeoutException; repeatMode, shuffleModeEnabled, analyticsCollector, + seekParameters, + pauseAtEndOfMediaItems, applicationHandler, clock); internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper()); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 96e8f3d8ac..53c8a5d080 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -146,6 +146,8 @@ import java.util.concurrent.atomic.AtomicBoolean; @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled, @Nullable AnalyticsCollector analyticsCollector, + SeekParameters seekParameters, + boolean pauseAtEndOfWindow, Handler eventHandler, Clock clock) { this.renderers = renderers; @@ -155,6 +157,8 @@ import java.util.concurrent.atomic.AtomicBoolean; this.bandwidthMeter = bandwidthMeter; this.repeatMode = repeatMode; this.shuffleModeEnabled = shuffleModeEnabled; + this.seekParameters = seekParameters; + this.pauseAtEndOfWindow = pauseAtEndOfWindow; this.eventHandler = eventHandler; this.clock = clock; this.queue = new MediaPeriodQueue(); @@ -162,7 +166,6 @@ import java.util.concurrent.atomic.AtomicBoolean; backBufferDurationUs = loadControl.getBackBufferDurationUs(); retainBackBufferFromKeyframe = loadControl.retainBackBufferFromKeyframe(); - seekParameters = SeekParameters.DEFAULT; playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult); playbackInfoUpdate = new PlaybackInfoUpdate(playbackInfo); rendererCapabilities = new RendererCapabilities[renderers.length]; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 338df091b8..d1f0cfc798 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -99,7 +99,16 @@ public class SimpleExoPlayer extends BasePlayer private BandwidthMeter bandwidthMeter; private AnalyticsCollector analyticsCollector; private Looper looper; + @Nullable private PriorityTaskManager priorityTaskManager; + private AudioAttributes audioAttributes; + private boolean handleAudioFocus; + @C.WakeMode private int wakeMode; + private boolean handleAudioBecomingNoisy; + private boolean skipSilenceEnabled; + @Renderer.VideoScalingMode private int videoScalingMode; private boolean useLazyPreparation; + private SeekParameters seekParameters; + private boolean pauseAtEndOfMediaItems; private boolean throwWhenStuckBuffering; private boolean buildCalled; @@ -122,7 +131,15 @@ public class SimpleExoPlayer extends BasePlayer * Looper} of the application's main thread if the current thread doesn't have a {@link * Looper} *
Note that this constructor is only useful if you try to ensure that ExoPlayer's default - * components can be removed by ProGuard or R8. For most components except renderers, there is - * only a marginal benefit of doing that. + *
Note that this constructor is only useful to try and ensure that ExoPlayer's default + * components can be removed by ProGuard or R8. * * @param context A {@link Context}. * @param renderersFactory A factory for creating {@link Renderer Renderers} to be used by the @@ -169,12 +182,7 @@ public class SimpleExoPlayer extends BasePlayer * @param mediaSourceFactory A {@link MediaSourceFactory}. * @param loadControl A {@link LoadControl}. * @param bandwidthMeter A {@link BandwidthMeter}. - * @param looper A {@link Looper} that must be used for all calls to the player. * @param analyticsCollector An {@link AnalyticsCollector}. - * @param useLazyPreparation Whether playlist items should be prepared lazily. If false, all - * initial preparation steps (e.g., manifest loads) happen immediately. If true, these - * initial preparations are triggered only when the player starts buffering the media. - * @param clock A {@link Clock}. Should always be {@link Clock#DEFAULT}. */ public Builder( Context context, @@ -183,20 +191,21 @@ public class SimpleExoPlayer extends BasePlayer MediaSourceFactory mediaSourceFactory, LoadControl loadControl, BandwidthMeter bandwidthMeter, - Looper looper, - AnalyticsCollector analyticsCollector, - boolean useLazyPreparation, - Clock clock) { + AnalyticsCollector analyticsCollector) { this.context = context; this.renderersFactory = renderersFactory; this.trackSelector = trackSelector; this.mediaSourceFactory = mediaSourceFactory; this.loadControl = loadControl; this.bandwidthMeter = bandwidthMeter; - this.looper = looper; this.analyticsCollector = analyticsCollector; - this.useLazyPreparation = useLazyPreparation; - this.clock = clock; + looper = Util.getLooper(); + audioAttributes = AudioAttributes.DEFAULT; + wakeMode = C.WAKE_MODE_NONE; + videoScalingMode = Renderer.VIDEO_SCALING_MODE_DEFAULT; + useLazyPreparation = true; + seekParameters = SeekParameters.DEFAULT; + clock = Clock.DEFAULT; } /** @@ -278,6 +287,111 @@ public class SimpleExoPlayer extends BasePlayer return this; } + /** + * Sets an {@link PriorityTaskManager} that will be used by the player. + * + *
The priority {@link C#PRIORITY_PLAYBACK} will be set while the player is loading. + * + * @param priorityTaskManager A {@link PriorityTaskManager}, or null to not use one. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) { + Assertions.checkState(!buildCalled); + this.priorityTaskManager = priorityTaskManager; + return this; + } + + /** + * Sets {@link AudioAttributes} that will be used by the player and whether to handle audio + * focus. + * + *
If audio focus should be handled, the {@link AudioAttributes#usage} must be {@link + * C#USAGE_MEDIA} or {@link C#USAGE_GAME}. Other usages will throw an {@link + * IllegalArgumentException}. + * + * @param audioAttributes {@link AudioAttributes}. + * @param handleAudioFocus Whether the player should hanlde audio focus. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) { + Assertions.checkState(!buildCalled); + this.audioAttributes = audioAttributes; + this.handleAudioFocus = handleAudioFocus; + return this; + } + + /** + * Sets the {@link C.WakeMode} that will be used by the player. + * + *
Enabling this feature requires the {@link android.Manifest.permission#WAKE_LOCK} + * permission. It should be used together with a foreground {@link android.app.Service} for use + * cases where playback occurs and the screen is off (e.g. background audio playback). It is not + * useful when the screen will be kept on during playback (e.g. foreground video playback). + * + *
When enabled, the locks ({@link android.os.PowerManager.WakeLock} / {@link + * android.net.wifi.WifiManager.WifiLock}) will be held whenever the player is in the {@link + * #STATE_READY} or {@link #STATE_BUFFERING} states with {@code playWhenReady = true}. The locks + * held depend on the specified {@link C.WakeMode}. + * + * @param wakeMode A {@link C.WakeMode}. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder setWakeMode(@C.WakeMode int wakeMode) { + Assertions.checkState(!buildCalled); + this.wakeMode = wakeMode; + return this; + } + + /** + * Sets whether the player should pause automatically when audio is rerouted from a headset to + * device speakers. See the audio + * becoming noisy documentation for more information. + * + * @param handleAudioBecomingNoisy Whether the player should pause automatically when audio is + * rerouted from a headset to device speakers. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder setHandleAudioBecomingNoisy(boolean handleAudioBecomingNoisy) { + Assertions.checkState(!buildCalled); + this.handleAudioBecomingNoisy = handleAudioBecomingNoisy; + return this; + } + + /** + * Sets whether silences silences in the audio stream is enabled. + * + * @param skipSilenceEnabled Whether skipping silences is enabled. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder setSkipSilenceEnabled(boolean skipSilenceEnabled) { + Assertions.checkState(!buildCalled); + this.skipSilenceEnabled = skipSilenceEnabled; + return this; + } + + /** + * Sets the {@link Renderer.VideoScalingMode} that will be used by the player. + * + *
Note that the scaling mode only applies if a {@link MediaCodec}-based video {@link + * Renderer} is enabled and if the output surface is owned by a {@link + * android.view.SurfaceView}. + * + * @param videoScalingMode A {@link Renderer.VideoScalingMode}. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder setVideoScalingMode(@Renderer.VideoScalingMode int videoScalingMode) { + Assertions.checkState(!buildCalled); + this.videoScalingMode = videoScalingMode; + return this; + } + /** * Sets whether media sources should be initialized lazily. * @@ -295,6 +409,37 @@ public class SimpleExoPlayer extends BasePlayer return this; } + /** + * Sets the parameters that control how seek operations are performed. + * + * @param seekParameters The {@link SeekParameters}. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder setSeekParameters(SeekParameters seekParameters) { + Assertions.checkState(!buildCalled); + this.seekParameters = seekParameters; + return this; + } + + /** + * Sets whether to pause playback at the end of each media item. + * + *
This means the player will pause at the end of each window in the current {@link + * #getCurrentTimeline() timeline}. Listeners will be informed by a call to {@link + * Player.EventListener#onPlayWhenReadyChanged(boolean, int)} with the reason {@link + * Player#PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM} when this happens. + * + * @param pauseAtEndOfMediaItems Whether to pause playback at the end of each media item. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) { + Assertions.checkState(!buildCalled); + this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems; + return this; + } + /** * Sets whether the player should throw when it detects it's stuck buffering. * @@ -326,7 +471,7 @@ public class SimpleExoPlayer extends BasePlayer /** * Builds a {@link SimpleExoPlayer} instance. * - * @throws IllegalStateException If {@link #build()} has already been called. + * @throws IllegalStateException If this method has already been called. */ public SimpleExoPlayer build() { Assertions.checkState(!buildCalled); @@ -416,6 +561,10 @@ public class SimpleExoPlayer extends BasePlayer protected SimpleExoPlayer(Builder builder) { bandwidthMeter = builder.bandwidthMeter; analyticsCollector = builder.analyticsCollector; + priorityTaskManager = builder.priorityTaskManager; + audioAttributes = builder.audioAttributes; + videoScalingMode = builder.videoScalingMode; + skipSilenceEnabled = builder.skipSilenceEnabled; componentListener = new ComponentListener(); videoListeners = new CopyOnWriteArraySet<>(); audioListeners = new CopyOnWriteArraySet<>(); @@ -436,8 +585,6 @@ public class SimpleExoPlayer extends BasePlayer // Set initial values. audioVolume = 1; audioSessionId = C.AUDIO_SESSION_ID_UNSET; - audioAttributes = AudioAttributes.DEFAULT; - videoScalingMode = Renderer.VIDEO_SCALING_MODE_DEFAULT; currentCues = Collections.emptyList(); // Build the player and associated objects. @@ -450,6 +597,8 @@ public class SimpleExoPlayer extends BasePlayer bandwidthMeter, analyticsCollector, builder.useLazyPreparation, + builder.seekParameters, + builder.pauseAtEndOfMediaItems, builder.clock, builder.looper); analyticsCollector.setPlayer(player); @@ -461,16 +610,27 @@ public class SimpleExoPlayer extends BasePlayer audioListeners.add(analyticsCollector); addMetadataOutput(analyticsCollector); bandwidthMeter.addEventListener(eventHandler, analyticsCollector); + audioBecomingNoisyManager = new AudioBecomingNoisyManager(builder.context, eventHandler, componentListener); + audioBecomingNoisyManager.setEnabled(builder.handleAudioBecomingNoisy); audioFocusManager = new AudioFocusManager(builder.context, eventHandler, componentListener); + audioFocusManager.setAudioAttributes(builder.handleAudioFocus ? audioAttributes : null); streamVolumeManager = new StreamVolumeManager(builder.context, eventHandler, componentListener); + streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage)); wakeLockManager = new WakeLockManager(builder.context); + wakeLockManager.setEnabled(builder.wakeMode != C.WAKE_MODE_NONE); wifiLockManager = new WifiLockManager(builder.context); + wifiLockManager.setEnabled(builder.wakeMode == C.WAKE_MODE_NETWORK); deviceInfo = createDeviceInfo(streamVolumeManager); if (builder.throwWhenStuckBuffering) { player.experimental_throwWhenStuckBuffering(); } + + sendRendererMessage(C.TRACK_TYPE_AUDIO, Renderer.MSG_SET_AUDIO_ATTRIBUTES, audioAttributes); + sendRendererMessage(C.TRACK_TYPE_VIDEO, Renderer.MSG_SET_SCALING_MODE, videoScalingMode); + sendRendererMessage( + C.TRACK_TYPE_AUDIO, Renderer.MSG_SET_SKIP_SILENCE_ENABLED, skipSilenceEnabled); } @Override @@ -1686,6 +1846,7 @@ public class SimpleExoPlayer extends BasePlayer * @param wakeMode The {@link C.WakeMode} option to keep the device awake during playback. */ public void setWakeMode(@C.WakeMode int wakeMode) { + verifyApplicationThread(); switch (wakeMode) { case C.WAKE_MODE_NONE: wakeLockManager.setEnabled(false);