diff --git a/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java b/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java index 8a207bea8f..f5ef8b2ca4 100644 --- a/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java +++ b/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.leanback; import android.content.Context; import android.os.Handler; +import android.support.annotation.Nullable; import android.support.v17.leanback.R; import android.support.v17.leanback.media.PlaybackGlueHost; import android.support.v17.leanback.media.PlayerAdapter; @@ -25,6 +26,8 @@ import android.util.Pair; import android.view.Surface; import android.view.SurfaceHolder; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ControlDispatcher; +import com.google.android.exoplayer2.DefaultControlDispatcher; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.PlaybackParameters; @@ -48,13 +51,13 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter { private final SimpleExoPlayer player; private final Handler handler; private final ComponentListener componentListener; - private final Runnable updatePlayerRunnable; + private final Runnable updateProgressRunnable; + private ControlDispatcher controlDispatcher; private ErrorMessageProvider super ExoPlaybackException> errorMessageProvider; private SurfaceHolderGlueHost surfaceHolderGlueHost; - private boolean initialized; private boolean hasSurface; - private boolean isBuffering; + private boolean lastNotifiedPreparedState; /** * Builds an instance. Note that the {@code PlayerAdapter} does not manage the lifecycle of the @@ -70,7 +73,8 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter { this.player = player; handler = new Handler(); componentListener = new ComponentListener(); - updatePlayerRunnable = new Runnable() { + controlDispatcher = new DefaultControlDispatcher(); + updateProgressRunnable = new Runnable() { @Override public void run() { Callback callback = getCallback(); @@ -81,34 +85,15 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter { }; } - @Override - public void onAttachedToHost(PlaybackGlueHost host) { - if (host instanceof SurfaceHolderGlueHost) { - surfaceHolderGlueHost = ((SurfaceHolderGlueHost) host); - surfaceHolderGlueHost.setSurfaceHolderCallback(componentListener); - } - notifyListeners(); - player.addListener(componentListener); - player.addVideoListener(componentListener); - } - - private void notifyListeners() { - boolean oldIsPrepared = isPrepared(); - int playbackState = player.getPlaybackState(); - boolean isInitialized = playbackState != Player.STATE_IDLE; - isBuffering = playbackState == Player.STATE_BUFFERING; - boolean hasEnded = playbackState == Player.STATE_ENDED; - - initialized = isInitialized; - Callback callback = getCallback(); - if (oldIsPrepared != isPrepared()) { - callback.onPreparedStateChanged(this); - } - callback.onPlayStateChanged(this); - callback.onBufferingStateChanged(this, isBuffering || !initialized); - if (hasEnded) { - callback.onPlayCompleted(this); - } + /** + * Sets the {@link ControlDispatcher}. + * + * @param controlDispatcher The {@link ControlDispatcher}, or null to use + * {@link DefaultControlDispatcher}. + */ + public void setControlDispatcher(@Nullable ControlDispatcher controlDispatcher) { + this.controlDispatcher = controlDispatcher == null ? new DefaultControlDispatcher() + : controlDispatcher; } /** @@ -121,6 +106,19 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter { this.errorMessageProvider = errorMessageProvider; } + // PlayerAdapter implementation. + + @Override + public void onAttachedToHost(PlaybackGlueHost host) { + if (host instanceof SurfaceHolderGlueHost) { + surfaceHolderGlueHost = ((SurfaceHolderGlueHost) host); + surfaceHolderGlueHost.setSurfaceHolderCallback(componentListener); + } + notifyStateChanged(); + player.addListener(componentListener); + player.addVideoListener(componentListener); + } + @Override public void onDetachedFromHost() { player.removeListener(componentListener); @@ -129,56 +127,59 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter { surfaceHolderGlueHost.setSurfaceHolderCallback(null); surfaceHolderGlueHost = null; } - initialized = false; hasSurface = false; Callback callback = getCallback(); callback.onBufferingStateChanged(this, false); callback.onPlayStateChanged(this); - callback.onPreparedStateChanged(this); + maybeNotifyPreparedStateChanged(callback); } @Override - public void setProgressUpdatingEnabled(final boolean enabled) { - handler.removeCallbacks(updatePlayerRunnable); + public void setProgressUpdatingEnabled(boolean enabled) { + handler.removeCallbacks(updateProgressRunnable); if (enabled) { - handler.post(updatePlayerRunnable); + handler.post(updateProgressRunnable); } } @Override public boolean isPlaying() { - return initialized && player.getPlayWhenReady(); + int playbackState = player.getPlaybackState(); + return playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED + && player.getPlayWhenReady(); } @Override public long getDuration() { long durationMs = player.getDuration(); - return durationMs != C.TIME_UNSET ? durationMs : -1; + return durationMs == C.TIME_UNSET ? -1 : durationMs; } @Override public long getCurrentPosition() { - return initialized ? player.getCurrentPosition() : -1; + return player.getPlaybackState() == Player.STATE_IDLE ? -1 : player.getCurrentPosition(); } @Override public void play() { if (player.getPlaybackState() == Player.STATE_ENDED) { - player.seekToDefaultPosition(); + controlDispatcher.dispatchSeekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET); + } + if (controlDispatcher.dispatchSetPlayWhenReady(player, true)) { + getCallback().onPlayStateChanged(this); } - player.setPlayWhenReady(true); - getCallback().onPlayStateChanged(this); } @Override public void pause() { - player.setPlayWhenReady(false); - getCallback().onPlayStateChanged(this); + if (controlDispatcher.dispatchSetPlayWhenReady(player, false)) { + getCallback().onPlayStateChanged(this); + } } @Override public void seekTo(long positionMs) { - player.seekTo(positionMs); + controlDispatcher.dispatchSeekTo(player, player.getCurrentWindowIndex(), positionMs); } @Override @@ -188,13 +189,35 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter { @Override public boolean isPrepared() { - return initialized && (surfaceHolderGlueHost == null || hasSurface); + return player.getPlaybackState() != Player.STATE_IDLE + && (surfaceHolderGlueHost == null || hasSurface); } - private void setVideoSurface(Surface surface) { + // Internal methods. + + /* package */ void setVideoSurface(Surface surface) { hasSurface = surface != null; player.setVideoSurface(surface); - getCallback().onPreparedStateChanged(this); + maybeNotifyPreparedStateChanged(getCallback()); + } + + /* package */ void notifyStateChanged() { + int playbackState = player.getPlaybackState(); + Callback callback = getCallback(); + maybeNotifyPreparedStateChanged(callback); + callback.onPlayStateChanged(this); + callback.onBufferingStateChanged(this, playbackState == Player.STATE_BUFFERING); + if (playbackState == Player.STATE_ENDED) { + callback.onPlayCompleted(this); + } + } + + private void maybeNotifyPreparedStateChanged(Callback callback) { + boolean isPrepared = isPrepared(); + if (lastNotifiedPreparedState != isPrepared) { + lastNotifiedPreparedState = isPrepared; + callback.onPreparedStateChanged(this); + } } private final class ComponentListener implements Player.EventListener, @@ -208,7 +231,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter { } @Override - public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { + public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { // Do nothing. } @@ -221,7 +244,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter { @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - notifyListeners(); + notifyStateChanged(); } @Override @@ -292,4 +315,3 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter { } } - diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java b/library/core/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java new file mode 100644 index 0000000000..21c596e6d4 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 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.exoplayer2; + +import com.google.android.exoplayer2.Player.RepeatMode; + +/** + * Dispatches operations to the {@link Player}. + *
+ * Implementations may choose to suppress (e.g. prevent playback from resuming if audio focus is + * denied) or modify (e.g. change the seek position to prevent a user from seeking past a + * non-skippable advert) operations. + */ +public interface ControlDispatcher { + + /** + * Dispatches a {@link Player#setPlayWhenReady(boolean)} operation. + * + * @param player The {@link Player} to which the operation should be dispatched. + * @param playWhenReady Whether playback should proceed when ready. + * @return True if the operation was dispatched. False if suppressed. + */ + boolean dispatchSetPlayWhenReady(Player player, boolean playWhenReady); + + /** + * Dispatches a {@link Player#seekTo(int, long)} operation. + * + * @param player The {@link Player} to which the operation should be dispatched. + * @param windowIndex The index of the window. + * @param positionMs The seek position in the specified window, or {@link C#TIME_UNSET} to seek to + * the window's default position. + * @return True if the operation was dispatched. False if suppressed. + */ + boolean dispatchSeekTo(Player player, int windowIndex, long positionMs); + + /** + * Dispatches a {@link Player#setRepeatMode(int)} operation. + * + * @param player The {@link Player} to which the operation should be dispatched. + * @param repeatMode The repeat mode. + * @return True if the operation was dispatched. False if suppressed. + */ + boolean dispatchSetRepeatMode(Player player, @RepeatMode int repeatMode); + + /** + * Dispatches a {@link Player#setShuffleModeEnabled(boolean)} operation. + * + * @param player The {@link Player} to which the operation should be dispatched. + * @param shuffleModeEnabled Whether shuffling is enabled. + * @return True if the operation was dispatched. False if suppressed. + */ + boolean dispatchSetShuffleModeEnabled(Player player, boolean shuffleModeEnabled); + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java new file mode 100644 index 0000000000..84711d752a --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 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.exoplayer2; + +import com.google.android.exoplayer2.Player.RepeatMode; + +/** + * Default {@link ControlDispatcher} that dispatches all operations to the player without + * modification. + */ +public class DefaultControlDispatcher implements ControlDispatcher { + + @Override + public boolean dispatchSetPlayWhenReady(Player player, boolean playWhenReady) { + player.setPlayWhenReady(playWhenReady); + return true; + } + + @Override + public boolean dispatchSeekTo(Player player, int windowIndex, long positionMs) { + player.seekTo(windowIndex, positionMs); + return true; + } + + @Override + public boolean dispatchSetRepeatMode(Player player, @RepeatMode int repeatMode) { + player.setRepeatMode(repeatMode); + return true; + } + + @Override + public boolean dispatchSetShuffleModeEnabled(Player player, boolean shuffleModeEnabled) { + player.setShuffleModeEnabled(shuffleModeEnabled); + return true; + } + +} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index f83bab5770..c89feaebf5 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -34,7 +34,6 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.Player.RepeatMode; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; @@ -180,6 +179,12 @@ public class PlaybackControlView extends FrameLayout { ExoPlayerLibraryInfo.registerModule("goog.exo.ui"); } + /** + * @deprecated Use {@link com.google.android.exoplayer2.ControlDispatcher}. + */ + @Deprecated + public interface ControlDispatcher extends com.google.android.exoplayer2.ControlDispatcher {} + /** * Listener to be notified about changes of the visibility of the UI control. */ @@ -194,86 +199,13 @@ public class PlaybackControlView extends FrameLayout { } + private static final class DefaultControlDispatcher + extends com.google.android.exoplayer2.DefaultControlDispatcher implements ControlDispatcher {} /** - * Dispatches operations to the {@link Player}. - *
- * Implementations may choose to suppress (e.g. prevent playback from resuming if audio focus is - * denied) or modify (e.g. change the seek position to prevent a user from seeking past a - * non-skippable advert) operations. + * @deprecated Use {@link com.google.android.exoplayer2.DefaultControlDispatcher}. */ - public interface ControlDispatcher { - - /** - * Dispatches a {@link Player#setPlayWhenReady(boolean)} operation. - * - * @param player The {@link Player} to which the operation should be dispatched. - * @param playWhenReady Whether playback should proceed when ready. - * @return True if the operation was dispatched. False if suppressed. - */ - boolean dispatchSetPlayWhenReady(Player player, boolean playWhenReady); - - /** - * Dispatches a {@link Player#seekTo(int, long)} operation. - * - * @param player The {@link Player} to which the operation should be dispatched. - * @param windowIndex The index of the window. - * @param positionMs The seek position in the specified window, or {@link C#TIME_UNSET} to seek - * to the window's default position. - * @return True if the operation was dispatched. False if suppressed. - */ - boolean dispatchSeekTo(Player player, int windowIndex, long positionMs); - - /** - * Dispatches a {@link Player#setRepeatMode(int)} operation. - * - * @param player The {@link Player} to which the operation should be dispatched. - * @param repeatMode The repeat mode. - * @return True if the operation was dispatched. False if suppressed. - */ - boolean dispatchSetRepeatMode(Player player, @RepeatMode int repeatMode); - - /** - * Dispatches a {@link Player#setShuffleModeEnabled(boolean)} operation. - * - * @param player The {@link Player} to which the operation should be dispatched. - * @param shuffleModeEnabled Whether shuffling is enabled. - * @return True if the operation was dispatched. False if suppressed. - */ - boolean dispatchSetShuffleModeEnabled(Player player, boolean shuffleModeEnabled); - - } - - /** - * Default {@link ControlDispatcher} that dispatches operations to the player without - * modification. - */ - public static final ControlDispatcher DEFAULT_CONTROL_DISPATCHER = new ControlDispatcher() { - - @Override - public boolean dispatchSetPlayWhenReady(Player player, boolean playWhenReady) { - player.setPlayWhenReady(playWhenReady); - return true; - } - - @Override - public boolean dispatchSeekTo(Player player, int windowIndex, long positionMs) { - player.seekTo(windowIndex, positionMs); - return true; - } - - @Override - public boolean dispatchSetRepeatMode(Player player, @RepeatMode int repeatMode) { - player.setRepeatMode(repeatMode); - return true; - } - - @Override - public boolean dispatchSetShuffleModeEnabled(Player player, boolean shuffleModeEnabled) { - player.setShuffleModeEnabled(shuffleModeEnabled); - return true; - } - - }; + @Deprecated + public static final ControlDispatcher DEFAULT_CONTROL_DISPATCHER = new DefaultControlDispatcher(); /** * The default fast forward increment, in milliseconds. @@ -325,7 +257,7 @@ public class PlaybackControlView extends FrameLayout { private final String repeatAllButtonContentDescription; private Player player; - private ControlDispatcher controlDispatcher; + private com.google.android.exoplayer2.ControlDispatcher controlDispatcher; private VisibilityListener visibilityListener; private boolean isAttachedToWindow; @@ -400,7 +332,7 @@ public class PlaybackControlView extends FrameLayout { extraAdGroupTimesMs = new long[0]; extraPlayedAdGroups = new boolean[0]; componentListener = new ComponentListener(); - controlDispatcher = DEFAULT_CONTROL_DISPATCHER; + controlDispatcher = new com.google.android.exoplayer2.DefaultControlDispatcher(); LayoutInflater.from(context).inflate(controllerLayoutId, this); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); @@ -534,14 +466,15 @@ public class PlaybackControlView extends FrameLayout { } /** - * Sets the {@link ControlDispatcher}. + * Sets the {@link com.google.android.exoplayer2.ControlDispatcher}. * - * @param controlDispatcher The {@link ControlDispatcher}, or null to use - * {@link #DEFAULT_CONTROL_DISPATCHER}. + * @param controlDispatcher The {@link com.google.android.exoplayer2.ControlDispatcher}, or null + * to use {@link com.google.android.exoplayer2.DefaultControlDispatcher}. */ - public void setControlDispatcher(ControlDispatcher controlDispatcher) { - this.controlDispatcher = controlDispatcher == null ? DEFAULT_CONTROL_DISPATCHER - : controlDispatcher; + public void setControlDispatcher( + @Nullable com.google.android.exoplayer2.ControlDispatcher controlDispatcher) { + this.controlDispatcher = controlDispatcher == null + ? new com.google.android.exoplayer2.DefaultControlDispatcher() : controlDispatcher; } /** diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java index a8926f9ecc..5b6e11c5e4 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -34,6 +34,8 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ControlDispatcher; +import com.google.android.exoplayer2.DefaultControlDispatcher; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; @@ -47,7 +49,6 @@ import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode; -import com.google.android.exoplayer2.ui.PlaybackControlView.ControlDispatcher; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.Util; @@ -616,9 +617,9 @@ public final class SimpleExoPlayerView extends FrameLayout { * Sets the {@link ControlDispatcher}. * * @param controlDispatcher The {@link ControlDispatcher}, or null to use - * {@link PlaybackControlView#DEFAULT_CONTROL_DISPATCHER}. + * {@link DefaultControlDispatcher}. */ - public void setControlDispatcher(ControlDispatcher controlDispatcher) { + public void setControlDispatcher(@Nullable ControlDispatcher controlDispatcher) { Assertions.checkState(controller != null); controller.setControlDispatcher(controlDispatcher); }