|
@@ -70,14 +70,15 @@ import com.google.android.exoplayer2.Player;
|
|
|
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
|
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
|
|
import com.google.android.exoplayer2.Timeline;
|
|
import com.google.android.exoplayer2.Timeline;
|
|
|
import com.google.android.exoplayer2.Timeline.Period;
|
|
import com.google.android.exoplayer2.Timeline.Period;
|
|
|
-import com.google.android.exoplayer2.TracksInfo;
|
|
|
|
|
-import com.google.android.exoplayer2.text.Cue;
|
|
|
|
|
|
|
+import com.google.android.exoplayer2.Tracks;
|
|
|
|
|
+import com.google.android.exoplayer2.text.CueGroup;
|
|
|
import com.google.android.exoplayer2.ui.AdOverlayInfo;
|
|
import com.google.android.exoplayer2.ui.AdOverlayInfo;
|
|
|
import com.google.android.exoplayer2.ui.AdViewProvider;
|
|
import com.google.android.exoplayer2.ui.AdViewProvider;
|
|
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
|
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode;
|
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode;
|
|
|
import com.google.android.exoplayer2.ui.DefaultTimeBar;
|
|
import com.google.android.exoplayer2.ui.DefaultTimeBar;
|
|
|
import com.google.android.exoplayer2.ui.PlayerControlView;
|
|
import com.google.android.exoplayer2.ui.PlayerControlView;
|
|
|
|
|
+import com.google.android.exoplayer2.ui.R;
|
|
|
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
|
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
|
|
import com.google.android.exoplayer2.ui.SubtitleView;
|
|
import com.google.android.exoplayer2.ui.SubtitleView;
|
|
|
import com.google.android.exoplayer2.util.Assertions;
|
|
import com.google.android.exoplayer2.util.Assertions;
|
|
@@ -289,64 +290,62 @@ import static java.lang.annotation.ElementType.TYPE_USE;
|
|
|
@Deprecated
|
|
@Deprecated
|
|
|
public class ZoomableExoPlayerView extends FrameLayout implements AdViewProvider {
|
|
public class ZoomableExoPlayerView extends FrameLayout implements AdViewProvider {
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * Determines when the buffering view is shown. One of {@link #SHOW_BUFFERING_NEVER}, {@link
|
|
|
|
|
- * #SHOW_BUFFERING_WHEN_PLAYING} or {@link #SHOW_BUFFERING_ALWAYS}.
|
|
|
|
|
- */
|
|
|
|
|
- @Documented
|
|
|
|
|
- @Retention(RetentionPolicy.SOURCE)
|
|
|
|
|
- @Target(TYPE_USE)
|
|
|
|
|
- @IntDef({SHOW_BUFFERING_NEVER, SHOW_BUFFERING_WHEN_PLAYING, SHOW_BUFFERING_ALWAYS})
|
|
|
|
|
- public @interface ShowBuffering {}
|
|
|
|
|
- /** The buffering view is never shown. */
|
|
|
|
|
- public static final int SHOW_BUFFERING_NEVER = 0;
|
|
|
|
|
- /**
|
|
|
|
|
- * The buffering view is shown when the player is in the {@link Player#STATE_BUFFERING buffering}
|
|
|
|
|
- * state and {@link Player#getPlayWhenReady() playWhenReady} is {@code true}.
|
|
|
|
|
- */
|
|
|
|
|
- public static final int SHOW_BUFFERING_WHEN_PLAYING = 1;
|
|
|
|
|
- /**
|
|
|
|
|
- * The buffering view is always shown when the player is in the {@link Player#STATE_BUFFERING
|
|
|
|
|
- * buffering} state.
|
|
|
|
|
- */
|
|
|
|
|
- public static final int SHOW_BUFFERING_ALWAYS = 2;
|
|
|
|
|
-
|
|
|
|
|
- private static final int SURFACE_TYPE_NONE = 0;
|
|
|
|
|
- private static final int SURFACE_TYPE_SURFACE_VIEW = 1;
|
|
|
|
|
- private static final int SURFACE_TYPE_TEXTURE_VIEW = 2;
|
|
|
|
|
- private static final int SURFACE_TYPE_SPHERICAL_GL_SURFACE_VIEW = 3;
|
|
|
|
|
- private static final int SURFACE_TYPE_VIDEO_DECODER_GL_SURFACE_VIEW = 4;
|
|
|
|
|
-
|
|
|
|
|
- private final ComponentListener componentListener;
|
|
|
|
|
- @Nullable private final AspectRatioFrameLayout contentFrame;
|
|
|
|
|
- @Nullable private final View shutterView;
|
|
|
|
|
- @Nullable private final View surfaceView;
|
|
|
|
|
- private final boolean surfaceViewIgnoresVideoAspectRatio;
|
|
|
|
|
- @Nullable private final ImageView artworkView;
|
|
|
|
|
- @Nullable private final SubtitleView subtitleView;
|
|
|
|
|
- @Nullable private final View bufferingView;
|
|
|
|
|
- @Nullable private final TextView errorMessageView;
|
|
|
|
|
- @Nullable private final PlayerControlView controller;
|
|
|
|
|
- @Nullable private final FrameLayout adOverlayFrameLayout;
|
|
|
|
|
- @Nullable private final FrameLayout overlayFrameLayout;
|
|
|
|
|
-
|
|
|
|
|
- @Nullable private Player player;
|
|
|
|
|
- private boolean useController;
|
|
|
|
|
- @Nullable private PlayerControlView.VisibilityListener controllerVisibilityListener;
|
|
|
|
|
- private boolean useArtwork;
|
|
|
|
|
- @Nullable private Drawable defaultArtwork;
|
|
|
|
|
- private @ShowBuffering int showBuffering;
|
|
|
|
|
- private boolean keepContentOnPlayerReset;
|
|
|
|
|
- @Nullable private ErrorMessageProvider<? super PlaybackException> errorMessageProvider;
|
|
|
|
|
- @Nullable private CharSequence customErrorMessage;
|
|
|
|
|
- private int controllerShowTimeoutMs;
|
|
|
|
|
- private boolean controllerAutoShow;
|
|
|
|
|
- private boolean controllerHideDuringAds;
|
|
|
|
|
- private boolean controllerHideOnTouch;
|
|
|
|
|
- private int textureViewRotation;
|
|
|
|
|
- private boolean isTouching;
|
|
|
|
|
- private static final int PICTURE_TYPE_FRONT_COVER = 3;
|
|
|
|
|
- private static final int PICTURE_TYPE_NOT_SET = -1;
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Determines when the buffering view is shown. One of {@link #SHOW_BUFFERING_NEVER}, {@link
|
|
|
|
|
+ * #SHOW_BUFFERING_WHEN_PLAYING} or {@link #SHOW_BUFFERING_ALWAYS}.
|
|
|
|
|
+ */
|
|
|
|
|
+ @Documented
|
|
|
|
|
+ @Retention(RetentionPolicy.SOURCE)
|
|
|
|
|
+ @Target(TYPE_USE)
|
|
|
|
|
+ @IntDef({SHOW_BUFFERING_NEVER, SHOW_BUFFERING_WHEN_PLAYING, SHOW_BUFFERING_ALWAYS})
|
|
|
|
|
+ public @interface ShowBuffering {}
|
|
|
|
|
+ /** The buffering view is never shown. */
|
|
|
|
|
+ public static final int SHOW_BUFFERING_NEVER = 0;
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The buffering view is shown when the player is in the {@link Player#STATE_BUFFERING buffering}
|
|
|
|
|
+ * state and {@link Player#getPlayWhenReady() playWhenReady} is {@code true}.
|
|
|
|
|
+ */
|
|
|
|
|
+ public static final int SHOW_BUFFERING_WHEN_PLAYING = 1;
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The buffering view is always shown when the player is in the {@link Player#STATE_BUFFERING
|
|
|
|
|
+ * buffering} state.
|
|
|
|
|
+ */
|
|
|
|
|
+ public static final int SHOW_BUFFERING_ALWAYS = 2;
|
|
|
|
|
+
|
|
|
|
|
+ private static final int SURFACE_TYPE_NONE = 0;
|
|
|
|
|
+ private static final int SURFACE_TYPE_SURFACE_VIEW = 1;
|
|
|
|
|
+ private static final int SURFACE_TYPE_TEXTURE_VIEW = 2;
|
|
|
|
|
+ private static final int SURFACE_TYPE_SPHERICAL_GL_SURFACE_VIEW = 3;
|
|
|
|
|
+ private static final int SURFACE_TYPE_VIDEO_DECODER_GL_SURFACE_VIEW = 4;
|
|
|
|
|
+
|
|
|
|
|
+ private final ComponentListener componentListener;
|
|
|
|
|
+ @Nullable private final AspectRatioFrameLayout contentFrame;
|
|
|
|
|
+ @Nullable private final View shutterView;
|
|
|
|
|
+ @Nullable private final View surfaceView;
|
|
|
|
|
+ private final boolean surfaceViewIgnoresVideoAspectRatio;
|
|
|
|
|
+ @Nullable private final ImageView artworkView;
|
|
|
|
|
+ @Nullable private final SubtitleView subtitleView;
|
|
|
|
|
+ @Nullable private final View bufferingView;
|
|
|
|
|
+ @Nullable private final TextView errorMessageView;
|
|
|
|
|
+ @Nullable private final PlayerControlView controller;
|
|
|
|
|
+ @Nullable private final FrameLayout adOverlayFrameLayout;
|
|
|
|
|
+ @Nullable private final FrameLayout overlayFrameLayout;
|
|
|
|
|
+
|
|
|
|
|
+ @Nullable private Player player;
|
|
|
|
|
+ private boolean useController;
|
|
|
|
|
+ @Nullable private PlayerControlView.VisibilityListener controllerVisibilityListener;
|
|
|
|
|
+ private boolean useArtwork;
|
|
|
|
|
+ @Nullable private Drawable defaultArtwork;
|
|
|
|
|
+ private @ShowBuffering int showBuffering;
|
|
|
|
|
+ private boolean keepContentOnPlayerReset;
|
|
|
|
|
+ @Nullable private ErrorMessageProvider<? super PlaybackException> errorMessageProvider;
|
|
|
|
|
+ @Nullable private CharSequence customErrorMessage;
|
|
|
|
|
+ private int controllerShowTimeoutMs;
|
|
|
|
|
+ private boolean controllerAutoShow;
|
|
|
|
|
+ private boolean controllerHideDuringAds;
|
|
|
|
|
+ private boolean controllerHideOnTouch;
|
|
|
|
|
+ private int textureViewRotation;
|
|
|
|
|
+ private boolean isTouching;
|
|
|
|
|
|
|
|
public ZoomableExoPlayerView(Context context) {
|
|
public ZoomableExoPlayerView(Context context) {
|
|
|
this(context, /* attrs= */ null);
|
|
this(context, /* attrs= */ null);
|
|
@@ -360,1245 +359,1253 @@ public class ZoomableExoPlayerView extends FrameLayout implements AdViewProvider
|
|
|
public ZoomableExoPlayerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
|
public ZoomableExoPlayerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
|
|
super(context, attrs, defStyleAttr);
|
|
super(context, attrs, defStyleAttr);
|
|
|
|
|
|
|
|
- componentListener = new ComponentListener();
|
|
|
|
|
-
|
|
|
|
|
- if (isInEditMode()) {
|
|
|
|
|
- contentFrame = null;
|
|
|
|
|
- shutterView = null;
|
|
|
|
|
- surfaceView = null;
|
|
|
|
|
- surfaceViewIgnoresVideoAspectRatio = false;
|
|
|
|
|
- artworkView = null;
|
|
|
|
|
- subtitleView = null;
|
|
|
|
|
- bufferingView = null;
|
|
|
|
|
- errorMessageView = null;
|
|
|
|
|
- controller = null;
|
|
|
|
|
- adOverlayFrameLayout = null;
|
|
|
|
|
- overlayFrameLayout = null;
|
|
|
|
|
- ImageView logo = new ImageView(context);
|
|
|
|
|
- if (Util.SDK_INT >= 23) {
|
|
|
|
|
- configureEditModeLogoV23(getResources(), logo);
|
|
|
|
|
- } else {
|
|
|
|
|
- configureEditModeLogo(getResources(), logo);
|
|
|
|
|
- }
|
|
|
|
|
- addView(logo);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- boolean shutterColorSet = false;
|
|
|
|
|
- int shutterColor = 0;
|
|
|
|
|
- int playerLayoutId = com.google.android.exoplayer2.ui.R.layout.exo_player_view;
|
|
|
|
|
- boolean useArtwork = true;
|
|
|
|
|
- int defaultArtworkId = 0;
|
|
|
|
|
- boolean useController = true;
|
|
|
|
|
- int surfaceType = SURFACE_TYPE_SURFACE_VIEW;
|
|
|
|
|
- int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
|
|
|
|
|
- int controllerShowTimeoutMs = PlayerControlView.DEFAULT_SHOW_TIMEOUT_MS;
|
|
|
|
|
- boolean controllerHideOnTouch = true;
|
|
|
|
|
- boolean controllerAutoShow = true;
|
|
|
|
|
- boolean controllerHideDuringAds = true;
|
|
|
|
|
- int showBuffering = SHOW_BUFFERING_NEVER;
|
|
|
|
|
- if (attrs != null) {
|
|
|
|
|
- TypedArray a =
|
|
|
|
|
- context
|
|
|
|
|
- .getTheme()
|
|
|
|
|
- .obtainStyledAttributes(
|
|
|
|
|
- attrs, com.google.android.exoplayer2.ui.R.styleable.PlayerView, defStyleAttr, /* defStyleRes= */ 0);
|
|
|
|
|
- try {
|
|
|
|
|
- shutterColorSet = a.hasValue(com.google.android.exoplayer2.ui.R.styleable.PlayerView_shutter_background_color);
|
|
|
|
|
- shutterColor = a.getColor(com.google.android.exoplayer2.ui.R.styleable.PlayerView_shutter_background_color, shutterColor);
|
|
|
|
|
- playerLayoutId = a.getResourceId(com.google.android.exoplayer2.ui.R.styleable.PlayerView_player_layout_id, playerLayoutId);
|
|
|
|
|
- useArtwork = a.getBoolean(com.google.android.exoplayer2.ui.R.styleable.PlayerView_use_artwork, useArtwork);
|
|
|
|
|
- defaultArtworkId =
|
|
|
|
|
- a.getResourceId(com.google.android.exoplayer2.ui.R.styleable.PlayerView_default_artwork, defaultArtworkId);
|
|
|
|
|
- useController = a.getBoolean(com.google.android.exoplayer2.ui.R.styleable.PlayerView_use_controller, useController);
|
|
|
|
|
- surfaceType = a.getInt(com.google.android.exoplayer2.ui.R.styleable.PlayerView_surface_type, surfaceType);
|
|
|
|
|
- resizeMode = a.getInt(com.google.android.exoplayer2.ui.R.styleable.PlayerView_resize_mode, resizeMode);
|
|
|
|
|
- controllerShowTimeoutMs =
|
|
|
|
|
- a.getInt(com.google.android.exoplayer2.ui.R.styleable.PlayerView_show_timeout, controllerShowTimeoutMs);
|
|
|
|
|
- controllerHideOnTouch =
|
|
|
|
|
- a.getBoolean(com.google.android.exoplayer2.ui.R.styleable.PlayerView_hide_on_touch, controllerHideOnTouch);
|
|
|
|
|
- controllerAutoShow = a.getBoolean(com.google.android.exoplayer2.ui.R.styleable.PlayerView_auto_show, controllerAutoShow);
|
|
|
|
|
- showBuffering = a.getInteger(com.google.android.exoplayer2.ui.R.styleable.PlayerView_show_buffering, showBuffering);
|
|
|
|
|
- keepContentOnPlayerReset =
|
|
|
|
|
- a.getBoolean(
|
|
|
|
|
- com.google.android.exoplayer2.ui.R.styleable.PlayerView_keep_content_on_player_reset, keepContentOnPlayerReset);
|
|
|
|
|
- controllerHideDuringAds =
|
|
|
|
|
- a.getBoolean(com.google.android.exoplayer2.ui.R.styleable.PlayerView_hide_during_ads, controllerHideDuringAds);
|
|
|
|
|
- } finally {
|
|
|
|
|
- a.recycle();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- LayoutInflater.from(context).inflate(playerLayoutId, this);
|
|
|
|
|
- setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
|
|
|
|
|
-
|
|
|
|
|
- // Content frame.
|
|
|
|
|
- contentFrame = findViewById(com.google.android.exoplayer2.ui.R.id.exo_content_frame);
|
|
|
|
|
- if (contentFrame != null) {
|
|
|
|
|
- setResizeModeRaw(contentFrame, resizeMode);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Shutter view.
|
|
|
|
|
- shutterView = findViewById(com.google.android.exoplayer2.ui.R.id.exo_shutter);
|
|
|
|
|
- if (shutterView != null && shutterColorSet) {
|
|
|
|
|
- shutterView.setBackgroundColor(shutterColor);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Create a surface view and insert it into the content frame, if there is one.
|
|
|
|
|
- boolean surfaceViewIgnoresVideoAspectRatio = false;
|
|
|
|
|
- if (contentFrame != null && surfaceType != SURFACE_TYPE_NONE) {
|
|
|
|
|
- ViewGroup.LayoutParams params =
|
|
|
|
|
- new ViewGroup.LayoutParams(
|
|
|
|
|
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
|
|
|
|
- switch (surfaceType) {
|
|
|
|
|
- case SURFACE_TYPE_TEXTURE_VIEW:
|
|
|
|
|
|
|
+ componentListener = new ComponentListener();
|
|
|
|
|
+
|
|
|
|
|
+ if (isInEditMode()) {
|
|
|
|
|
+ contentFrame = null;
|
|
|
|
|
+ shutterView = null;
|
|
|
|
|
+ surfaceView = null;
|
|
|
|
|
+ surfaceViewIgnoresVideoAspectRatio = false;
|
|
|
|
|
+ artworkView = null;
|
|
|
|
|
+ subtitleView = null;
|
|
|
|
|
+ bufferingView = null;
|
|
|
|
|
+ errorMessageView = null;
|
|
|
|
|
+ controller = null;
|
|
|
|
|
+ adOverlayFrameLayout = null;
|
|
|
|
|
+ overlayFrameLayout = null;
|
|
|
|
|
+ ImageView logo = new ImageView(context);
|
|
|
|
|
+ if (Util.SDK_INT >= 23) {
|
|
|
|
|
+ configureEditModeLogoV23(getResources(), logo);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ configureEditModeLogo(getResources(), logo);
|
|
|
|
|
+ }
|
|
|
|
|
+ addView(logo);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ boolean shutterColorSet = false;
|
|
|
|
|
+ int shutterColor = 0;
|
|
|
|
|
+ int playerLayoutId = R.layout.exo_player_view;
|
|
|
|
|
+ boolean useArtwork = true;
|
|
|
|
|
+ int defaultArtworkId = 0;
|
|
|
|
|
+ boolean useController = true;
|
|
|
|
|
+ int surfaceType = SURFACE_TYPE_SURFACE_VIEW;
|
|
|
|
|
+ int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
|
|
|
|
|
+ int controllerShowTimeoutMs = PlayerControlView.DEFAULT_SHOW_TIMEOUT_MS;
|
|
|
|
|
+ boolean controllerHideOnTouch = true;
|
|
|
|
|
+ boolean controllerAutoShow = true;
|
|
|
|
|
+ boolean controllerHideDuringAds = true;
|
|
|
|
|
+ int showBuffering = SHOW_BUFFERING_NEVER;
|
|
|
|
|
+ if (attrs != null) {
|
|
|
|
|
+ TypedArray a =
|
|
|
|
|
+ context
|
|
|
|
|
+ .getTheme()
|
|
|
|
|
+ .obtainStyledAttributes(
|
|
|
|
|
+ attrs, R.styleable.PlayerView, defStyleAttr, /* defStyleRes= */ 0);
|
|
|
|
|
+ try {
|
|
|
|
|
+ shutterColorSet = a.hasValue(R.styleable.PlayerView_shutter_background_color);
|
|
|
|
|
+ shutterColor = a.getColor(R.styleable.PlayerView_shutter_background_color, shutterColor);
|
|
|
|
|
+ playerLayoutId = a.getResourceId(R.styleable.PlayerView_player_layout_id, playerLayoutId);
|
|
|
|
|
+ useArtwork = a.getBoolean(R.styleable.PlayerView_use_artwork, useArtwork);
|
|
|
|
|
+ defaultArtworkId =
|
|
|
|
|
+ a.getResourceId(R.styleable.PlayerView_default_artwork, defaultArtworkId);
|
|
|
|
|
+ useController = a.getBoolean(R.styleable.PlayerView_use_controller, useController);
|
|
|
|
|
+ surfaceType = a.getInt(R.styleable.PlayerView_surface_type, surfaceType);
|
|
|
|
|
+ resizeMode = a.getInt(R.styleable.PlayerView_resize_mode, resizeMode);
|
|
|
|
|
+ controllerShowTimeoutMs =
|
|
|
|
|
+ a.getInt(R.styleable.PlayerView_show_timeout, controllerShowTimeoutMs);
|
|
|
|
|
+ controllerHideOnTouch =
|
|
|
|
|
+ a.getBoolean(R.styleable.PlayerView_hide_on_touch, controllerHideOnTouch);
|
|
|
|
|
+ controllerAutoShow = a.getBoolean(R.styleable.PlayerView_auto_show, controllerAutoShow);
|
|
|
|
|
+ showBuffering = a.getInteger(R.styleable.PlayerView_show_buffering, showBuffering);
|
|
|
|
|
+ keepContentOnPlayerReset =
|
|
|
|
|
+ a.getBoolean(
|
|
|
|
|
+ R.styleable.PlayerView_keep_content_on_player_reset, keepContentOnPlayerReset);
|
|
|
|
|
+ controllerHideDuringAds =
|
|
|
|
|
+ a.getBoolean(R.styleable.PlayerView_hide_during_ads, controllerHideDuringAds);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ a.recycle();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ LayoutInflater.from(context).inflate(playerLayoutId, this);
|
|
|
|
|
+ setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
|
|
|
|
|
+
|
|
|
|
|
+ // Content frame.
|
|
|
|
|
+ contentFrame = findViewById(R.id.exo_content_frame);
|
|
|
|
|
+ if (contentFrame != null) {
|
|
|
|
|
+ setResizeModeRaw(contentFrame, resizeMode);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Shutter view.
|
|
|
|
|
+ shutterView = findViewById(R.id.exo_shutter);
|
|
|
|
|
+ if (shutterView != null && shutterColorSet) {
|
|
|
|
|
+ shutterView.setBackgroundColor(shutterColor);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Create a surface view and insert it into the content frame, if there is one.
|
|
|
|
|
+ boolean surfaceViewIgnoresVideoAspectRatio = false;
|
|
|
|
|
+ if (contentFrame != null && surfaceType != SURFACE_TYPE_NONE) {
|
|
|
|
|
+ ViewGroup.LayoutParams params =
|
|
|
|
|
+ new ViewGroup.LayoutParams(
|
|
|
|
|
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
|
|
|
|
+ switch (surfaceType) {
|
|
|
|
|
+ case SURFACE_TYPE_TEXTURE_VIEW:
|
|
|
// THREEMA
|
|
// THREEMA
|
|
|
surfaceView = new ZoomableTextureView(context);
|
|
surfaceView = new ZoomableTextureView(context);
|
|
|
surfaceView.setOnClickListener(v -> performClick());
|
|
surfaceView.setOnClickListener(v -> performClick());
|
|
|
- break;
|
|
|
|
|
- case SURFACE_TYPE_SPHERICAL_GL_SURFACE_VIEW:
|
|
|
|
|
- try {
|
|
|
|
|
- Class<?> clazz =
|
|
|
|
|
- Class.forName(
|
|
|
|
|
- "com.google.android.exoplayer2.video.spherical.SphericalGLSurfaceView");
|
|
|
|
|
- surfaceView = (View) clazz.getConstructor(Context.class).newInstance(context);
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- throw new IllegalStateException(
|
|
|
|
|
- "spherical_gl_surface_view requires an ExoPlayer dependency", e);
|
|
|
|
|
- }
|
|
|
|
|
- surfaceViewIgnoresVideoAspectRatio = true;
|
|
|
|
|
- break;
|
|
|
|
|
- case SURFACE_TYPE_VIDEO_DECODER_GL_SURFACE_VIEW:
|
|
|
|
|
- try {
|
|
|
|
|
- Class<?> clazz =
|
|
|
|
|
- Class.forName("com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView");
|
|
|
|
|
- surfaceView = (View) clazz.getConstructor(Context.class).newInstance(context);
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- throw new IllegalStateException(
|
|
|
|
|
- "video_decoder_gl_surface_view requires an ExoPlayer dependency", e);
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
- default:
|
|
|
|
|
- surfaceView = new SurfaceView(context);
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- surfaceView.setLayoutParams(params);
|
|
|
|
|
- // We don't want surfaceView to be clickable separately to the PlayerView itself, but we
|
|
|
|
|
- // do want to register as an OnClickListener so that surfaceView implementations can propagate
|
|
|
|
|
- // click events up to the PlayerView by calling their own performClick method.
|
|
|
|
|
- surfaceView.setOnClickListener(componentListener);
|
|
|
|
|
- surfaceView.setClickable(false);
|
|
|
|
|
- contentFrame.addView(surfaceView, 0);
|
|
|
|
|
- } else {
|
|
|
|
|
- surfaceView = null;
|
|
|
|
|
- }
|
|
|
|
|
- this.surfaceViewIgnoresVideoAspectRatio = surfaceViewIgnoresVideoAspectRatio;
|
|
|
|
|
-
|
|
|
|
|
- // Ad overlay frame layout.
|
|
|
|
|
- adOverlayFrameLayout = findViewById(com.google.android.exoplayer2.ui.R.id.exo_ad_overlay);
|
|
|
|
|
-
|
|
|
|
|
- // Overlay frame layout.
|
|
|
|
|
- overlayFrameLayout = findViewById(com.google.android.exoplayer2.ui.R.id.exo_overlay);
|
|
|
|
|
-
|
|
|
|
|
- // Artwork view.
|
|
|
|
|
- artworkView = findViewById(com.google.android.exoplayer2.ui.R.id.exo_artwork);
|
|
|
|
|
- this.useArtwork = useArtwork && artworkView != null;
|
|
|
|
|
- if (defaultArtworkId != 0) {
|
|
|
|
|
- defaultArtwork = ContextCompat.getDrawable(getContext(), defaultArtworkId);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Subtitle view.
|
|
|
|
|
- subtitleView = findViewById(com.google.android.exoplayer2.ui.R.id.exo_subtitles);
|
|
|
|
|
- if (subtitleView != null) {
|
|
|
|
|
- subtitleView.setUserDefaultStyle();
|
|
|
|
|
- subtitleView.setUserDefaultTextSize();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Buffering view.
|
|
|
|
|
- bufferingView = findViewById(com.google.android.exoplayer2.ui.R.id.exo_buffering);
|
|
|
|
|
- if (bufferingView != null) {
|
|
|
|
|
- bufferingView.setVisibility(View.GONE);
|
|
|
|
|
- }
|
|
|
|
|
- this.showBuffering = showBuffering;
|
|
|
|
|
-
|
|
|
|
|
- // Error message view.
|
|
|
|
|
- errorMessageView = findViewById(com.google.android.exoplayer2.ui.R.id.exo_error_message);
|
|
|
|
|
- if (errorMessageView != null) {
|
|
|
|
|
- errorMessageView.setVisibility(View.GONE);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Playback control view.
|
|
|
|
|
- PlayerControlView customController = findViewById(com.google.android.exoplayer2.ui.R.id.exo_controller);
|
|
|
|
|
- View controllerPlaceholder = findViewById(com.google.android.exoplayer2.ui.R.id.exo_controller_placeholder);
|
|
|
|
|
- if (customController != null) {
|
|
|
|
|
- this.controller = customController;
|
|
|
|
|
- } else if (controllerPlaceholder != null) {
|
|
|
|
|
- // Propagate attrs as playbackAttrs so that PlayerControlView's custom attributes are
|
|
|
|
|
- // transferred, but standard attributes (e.g. background) are not.
|
|
|
|
|
- this.controller = new PlayerControlView(context, null, 0, attrs);
|
|
|
|
|
- controller.setId(com.google.android.exoplayer2.ui.R.id.exo_controller);
|
|
|
|
|
- controller.setLayoutParams(controllerPlaceholder.getLayoutParams());
|
|
|
|
|
- ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent());
|
|
|
|
|
- int controllerIndex = parent.indexOfChild(controllerPlaceholder);
|
|
|
|
|
- parent.removeView(controllerPlaceholder);
|
|
|
|
|
- parent.addView(controller, controllerIndex);
|
|
|
|
|
- } else {
|
|
|
|
|
- this.controller = null;
|
|
|
|
|
- }
|
|
|
|
|
- this.controllerShowTimeoutMs = controller != null ? controllerShowTimeoutMs : 0;
|
|
|
|
|
- this.controllerHideOnTouch = controllerHideOnTouch;
|
|
|
|
|
- this.controllerAutoShow = controllerAutoShow;
|
|
|
|
|
- this.controllerHideDuringAds = controllerHideDuringAds;
|
|
|
|
|
- this.useController = useController && controller != null;
|
|
|
|
|
- hideController();
|
|
|
|
|
- updateContentDescription();
|
|
|
|
|
- if (controller != null) {
|
|
|
|
|
- controller.addVisibilityListener(/* listener= */ componentListener);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Switches the view targeted by a given {@link Player}.
|
|
|
|
|
- *
|
|
|
|
|
- * @param player The player whose target view is being switched.
|
|
|
|
|
- * @param oldPlayerView The old view to detach from the player.
|
|
|
|
|
- * @param newPlayerView The new view to attach to the player.
|
|
|
|
|
- */
|
|
|
|
|
- public static void switchTargetView(
|
|
|
|
|
- Player player, @Nullable ZoomableExoPlayerView oldPlayerView, @Nullable ZoomableExoPlayerView newPlayerView) {
|
|
|
|
|
- if (oldPlayerView == newPlayerView) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- // We attach the new view before detaching the old one because this ordering allows the player
|
|
|
|
|
- // to swap directly from one surface to another, without transitioning through a state where no
|
|
|
|
|
- // surface is attached. This is significantly more efficient and achieves a more seamless
|
|
|
|
|
- // transition when using platform provided video decoders.
|
|
|
|
|
- if (newPlayerView != null) {
|
|
|
|
|
- newPlayerView.setPlayer(player);
|
|
|
|
|
- }
|
|
|
|
|
- if (oldPlayerView != null) {
|
|
|
|
|
- oldPlayerView.setPlayer(null);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /** Returns the player currently set on this view, or null if no player is set. */
|
|
|
|
|
- @Nullable
|
|
|
|
|
- public Player getPlayer() {
|
|
|
|
|
- return player;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets the {@link Player} to use.
|
|
|
|
|
- *
|
|
|
|
|
- * <p>To transition a {@link Player} from targeting one view to another, it's recommended to use
|
|
|
|
|
- * {@link #switchTargetView(Player, ZoomableExoPlayerView, ZoomableExoPlayerView)} rather than this method. If you do
|
|
|
|
|
- * wish to use this method directly, be sure to attach the player to the new view <em>before</em>
|
|
|
|
|
- * calling {@code setPlayer(null)} to detach it from the old one. This ordering is significantly
|
|
|
|
|
- * more efficient and may allow for more seamless transitions.
|
|
|
|
|
- *
|
|
|
|
|
- * @param player The {@link Player} to use, or {@code null} to detach the current player. Only
|
|
|
|
|
- * players which are accessed on the main thread are supported ({@code
|
|
|
|
|
- * player.getApplicationLooper() == Looper.getMainLooper()}).
|
|
|
|
|
- */
|
|
|
|
|
- public void setPlayer(@Nullable Player player) {
|
|
|
|
|
- Assertions.checkState(Looper.myLooper() == Looper.getMainLooper());
|
|
|
|
|
- Assertions.checkArgument(
|
|
|
|
|
- player == null || player.getApplicationLooper() == Looper.getMainLooper());
|
|
|
|
|
- if (this.player == player) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- @Nullable Player oldPlayer = this.player;
|
|
|
|
|
- if (oldPlayer != null) {
|
|
|
|
|
- oldPlayer.removeListener(componentListener);
|
|
|
|
|
- if (oldPlayer.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)) {
|
|
|
|
|
- if (surfaceView instanceof TextureView) {
|
|
|
|
|
- oldPlayer.clearVideoTextureView((TextureView) surfaceView);
|
|
|
|
|
- } else if (surfaceView instanceof SurfaceView) {
|
|
|
|
|
- oldPlayer.clearVideoSurfaceView((SurfaceView) surfaceView);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if (subtitleView != null) {
|
|
|
|
|
- subtitleView.setCues(null);
|
|
|
|
|
- }
|
|
|
|
|
- this.player = player;
|
|
|
|
|
- if (useController()) {
|
|
|
|
|
- controller.setPlayer(player);
|
|
|
|
|
- }
|
|
|
|
|
- updateBuffering();
|
|
|
|
|
- updateErrorMessage();
|
|
|
|
|
- updateForCurrentTrackSelections(/* isNewPlayer= */ true);
|
|
|
|
|
- if (player != null) {
|
|
|
|
|
- if (player.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)) {
|
|
|
|
|
- if (surfaceView instanceof TextureView) {
|
|
|
|
|
- player.setVideoTextureView((TextureView) surfaceView);
|
|
|
|
|
- } else if (surfaceView instanceof SurfaceView) {
|
|
|
|
|
- player.setVideoSurfaceView((SurfaceView) surfaceView);
|
|
|
|
|
- }
|
|
|
|
|
- updateAspectRatio();
|
|
|
|
|
- }
|
|
|
|
|
- if (subtitleView != null && player.isCommandAvailable(COMMAND_GET_TEXT)) {
|
|
|
|
|
- subtitleView.setCues(player.getCurrentCues());
|
|
|
|
|
- }
|
|
|
|
|
- player.addListener(componentListener);
|
|
|
|
|
- maybeShowController(false);
|
|
|
|
|
- } else {
|
|
|
|
|
- hideController();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public void setVisibility(int visibility) {
|
|
|
|
|
- super.setVisibility(visibility);
|
|
|
|
|
- if (surfaceView instanceof SurfaceView) {
|
|
|
|
|
- // Work around https://github.com/google/ExoPlayer/issues/3160.
|
|
|
|
|
- surfaceView.setVisibility(visibility);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets the {@link ResizeMode}.
|
|
|
|
|
- *
|
|
|
|
|
- * @param resizeMode The {@link ResizeMode}.
|
|
|
|
|
- */
|
|
|
|
|
- public void setResizeMode(@ResizeMode int resizeMode) {
|
|
|
|
|
- Assertions.checkStateNotNull(contentFrame);
|
|
|
|
|
- contentFrame.setResizeMode(resizeMode);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /** Returns the {@link ResizeMode}. */
|
|
|
|
|
- public @ResizeMode int getResizeMode() {
|
|
|
|
|
- Assertions.checkStateNotNull(contentFrame);
|
|
|
|
|
- return contentFrame.getResizeMode();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /** Returns whether artwork is displayed if present in the media. */
|
|
|
|
|
- public boolean getUseArtwork() {
|
|
|
|
|
- return useArtwork;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets whether artwork is displayed if present in the media.
|
|
|
|
|
- *
|
|
|
|
|
- * @param useArtwork Whether artwork is displayed.
|
|
|
|
|
- */
|
|
|
|
|
- public void setUseArtwork(boolean useArtwork) {
|
|
|
|
|
- Assertions.checkState(!useArtwork || artworkView != null);
|
|
|
|
|
- if (this.useArtwork != useArtwork) {
|
|
|
|
|
- this.useArtwork = useArtwork;
|
|
|
|
|
- updateForCurrentTrackSelections(/* isNewPlayer= */ false);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /** Returns the default artwork to display. */
|
|
|
|
|
- @Nullable
|
|
|
|
|
- public Drawable getDefaultArtwork() {
|
|
|
|
|
- return defaultArtwork;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets the default artwork to display if {@code useArtwork} is {@code true} and no artwork is
|
|
|
|
|
- * present in the media.
|
|
|
|
|
- *
|
|
|
|
|
- * @param defaultArtwork the default artwork to display
|
|
|
|
|
- */
|
|
|
|
|
- public void setDefaultArtwork(@Nullable Drawable defaultArtwork) {
|
|
|
|
|
- if (this.defaultArtwork != defaultArtwork) {
|
|
|
|
|
- this.defaultArtwork = defaultArtwork;
|
|
|
|
|
- updateForCurrentTrackSelections(/* isNewPlayer= */ false);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /** Returns whether the playback controls can be shown. */
|
|
|
|
|
- public boolean getUseController() {
|
|
|
|
|
- return useController;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets whether the playback controls can be shown. If set to {@code false} the playback controls
|
|
|
|
|
- * are never visible and are disconnected from the player.
|
|
|
|
|
- *
|
|
|
|
|
- * @param useController Whether the playback controls can be shown.
|
|
|
|
|
- */
|
|
|
|
|
- public void setUseController(boolean useController) {
|
|
|
|
|
- Assertions.checkState(!useController || controller != null);
|
|
|
|
|
- if (this.useController == useController) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- this.useController = useController;
|
|
|
|
|
- if (useController()) {
|
|
|
|
|
- controller.setPlayer(player);
|
|
|
|
|
- } else if (controller != null) {
|
|
|
|
|
- controller.hide();
|
|
|
|
|
- controller.setPlayer(/* player= */ null);
|
|
|
|
|
- }
|
|
|
|
|
- updateContentDescription();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets the background color of the {@code exo_shutter} view.
|
|
|
|
|
- *
|
|
|
|
|
- * @param color The background color.
|
|
|
|
|
- */
|
|
|
|
|
- public void setShutterBackgroundColor(int color) {
|
|
|
|
|
- if (shutterView != null) {
|
|
|
|
|
- shutterView.setBackgroundColor(color);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets whether the currently displayed video frame or media artwork is kept visible when the
|
|
|
|
|
- * player is reset. A player reset is defined to mean the player being re-prepared with different
|
|
|
|
|
- * media, the player transitioning to unprepared media or an empty list of media items, or the
|
|
|
|
|
- * player being replaced or cleared by calling {@link #setPlayer(Player)}.
|
|
|
|
|
- *
|
|
|
|
|
- * <p>If enabled, the currently displayed video frame or media artwork will be kept visible until
|
|
|
|
|
- * the player set on the view has been successfully prepared with new media and loaded enough of
|
|
|
|
|
- * it to have determined the available tracks. Hence enabling this option allows transitioning
|
|
|
|
|
- * from playing one piece of media to another, or from using one player instance to another,
|
|
|
|
|
- * without clearing the view's content.
|
|
|
|
|
- *
|
|
|
|
|
- * <p>If disabled, the currently displayed video frame or media artwork will be hidden as soon as
|
|
|
|
|
- * the player is reset. Note that the video frame is hidden by making {@code exo_shutter} visible.
|
|
|
|
|
- * Hence the video frame will not be hidden if using a custom layout that omits this view.
|
|
|
|
|
- *
|
|
|
|
|
- * @param keepContentOnPlayerReset Whether the currently displayed video frame or media artwork is
|
|
|
|
|
- * kept visible when the player is reset.
|
|
|
|
|
- */
|
|
|
|
|
- public void setKeepContentOnPlayerReset(boolean keepContentOnPlayerReset) {
|
|
|
|
|
- if (this.keepContentOnPlayerReset != keepContentOnPlayerReset) {
|
|
|
|
|
- this.keepContentOnPlayerReset = keepContentOnPlayerReset;
|
|
|
|
|
- updateForCurrentTrackSelections(/* isNewPlayer= */ false);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets whether a buffering spinner is displayed when the player is in the buffering state. The
|
|
|
|
|
- * buffering spinner is not displayed by default.
|
|
|
|
|
- *
|
|
|
|
|
- * @param showBuffering The mode that defines when the buffering spinner is displayed. One of
|
|
|
|
|
- * {@link #SHOW_BUFFERING_NEVER}, {@link #SHOW_BUFFERING_WHEN_PLAYING} and {@link
|
|
|
|
|
- * #SHOW_BUFFERING_ALWAYS}.
|
|
|
|
|
- */
|
|
|
|
|
- public void setShowBuffering(@ShowBuffering int showBuffering) {
|
|
|
|
|
- if (this.showBuffering != showBuffering) {
|
|
|
|
|
- this.showBuffering = showBuffering;
|
|
|
|
|
- updateBuffering();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets the optional {@link ErrorMessageProvider}.
|
|
|
|
|
- *
|
|
|
|
|
- * @param errorMessageProvider The error message provider.
|
|
|
|
|
- */
|
|
|
|
|
- public void setErrorMessageProvider(
|
|
|
|
|
- @Nullable ErrorMessageProvider<? super PlaybackException> errorMessageProvider) {
|
|
|
|
|
- if (this.errorMessageProvider != errorMessageProvider) {
|
|
|
|
|
- this.errorMessageProvider = errorMessageProvider;
|
|
|
|
|
- updateErrorMessage();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets a custom error message to be displayed by the view. The error message will be displayed
|
|
|
|
|
- * permanently, unless it is cleared by passing {@code null} to this method.
|
|
|
|
|
- *
|
|
|
|
|
- * @param message The message to display, or {@code null} to clear a previously set message.
|
|
|
|
|
- */
|
|
|
|
|
- public void setCustomErrorMessage(@Nullable CharSequence message) {
|
|
|
|
|
- Assertions.checkState(errorMessageView != null);
|
|
|
|
|
- customErrorMessage = message;
|
|
|
|
|
- updateErrorMessage();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public boolean dispatchKeyEvent(KeyEvent event) {
|
|
|
|
|
- if (player != null && player.isPlayingAd()) {
|
|
|
|
|
- return super.dispatchKeyEvent(event);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- boolean isDpadKey = isDpadKey(event.getKeyCode());
|
|
|
|
|
- boolean handled = false;
|
|
|
|
|
- if (isDpadKey && useController() && !controller.isVisible()) {
|
|
|
|
|
- // Handle the key event by showing the controller.
|
|
|
|
|
- maybeShowController(true);
|
|
|
|
|
- handled = true;
|
|
|
|
|
- } else if (dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event)) {
|
|
|
|
|
- // The key event was handled as a media key or by the super class. We should also show the
|
|
|
|
|
- // controller, or extend its show timeout if already visible.
|
|
|
|
|
- maybeShowController(true);
|
|
|
|
|
- handled = true;
|
|
|
|
|
- } else if (isDpadKey && useController()) {
|
|
|
|
|
- // The key event wasn't handled, but we should extend the controller's show timeout.
|
|
|
|
|
- maybeShowController(true);
|
|
|
|
|
- }
|
|
|
|
|
- return handled;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Called to process media key events. Any {@link KeyEvent} can be passed but only media key
|
|
|
|
|
- * events will be handled. Does nothing if playback controls are disabled.
|
|
|
|
|
- *
|
|
|
|
|
- * @param event A key event.
|
|
|
|
|
- * @return Whether the key event was handled.
|
|
|
|
|
- */
|
|
|
|
|
- public boolean dispatchMediaKeyEvent(KeyEvent event) {
|
|
|
|
|
- return useController() && controller.dispatchMediaKeyEvent(event);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /** Returns whether the controller is currently visible. */
|
|
|
|
|
- public boolean isControllerVisible() {
|
|
|
|
|
- return controller != null && controller.isVisible();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Shows the playback controls. Does nothing if playback controls are disabled.
|
|
|
|
|
- *
|
|
|
|
|
- * <p>The playback controls are automatically hidden during playback after {{@link
|
|
|
|
|
- * #getControllerShowTimeoutMs()}}. They are shown indefinitely when playback has not started yet,
|
|
|
|
|
- * is paused, has ended or failed.
|
|
|
|
|
- */
|
|
|
|
|
- public void showController() {
|
|
|
|
|
- showController(shouldShowControllerIndefinitely());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /** Hides the playback controls. Does nothing if playback controls are disabled. */
|
|
|
|
|
- public void hideController() {
|
|
|
|
|
- if (controller != null) {
|
|
|
|
|
- controller.hide();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Returns the playback controls timeout. The playback controls are automatically hidden after
|
|
|
|
|
- * this duration of time has elapsed without user input and with playback or buffering in
|
|
|
|
|
- * progress.
|
|
|
|
|
- *
|
|
|
|
|
- * @return The timeout in milliseconds. A non-positive value will cause the controller to remain
|
|
|
|
|
- * visible indefinitely.
|
|
|
|
|
- */
|
|
|
|
|
- public int getControllerShowTimeoutMs() {
|
|
|
|
|
- return controllerShowTimeoutMs;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets the playback controls timeout. The playback controls are automatically hidden after this
|
|
|
|
|
- * duration of time has elapsed without user input and with playback or buffering in progress.
|
|
|
|
|
- *
|
|
|
|
|
- * @param controllerShowTimeoutMs The timeout in milliseconds. A non-positive value will cause the
|
|
|
|
|
- * controller to remain visible indefinitely.
|
|
|
|
|
- */
|
|
|
|
|
- public void setControllerShowTimeoutMs(int controllerShowTimeoutMs) {
|
|
|
|
|
- Assertions.checkStateNotNull(controller);
|
|
|
|
|
- this.controllerShowTimeoutMs = controllerShowTimeoutMs;
|
|
|
|
|
- if (controller.isVisible()) {
|
|
|
|
|
- // Update the controller's timeout if necessary.
|
|
|
|
|
- showController();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /** Returns whether the playback controls are hidden by touch events. */
|
|
|
|
|
- public boolean getControllerHideOnTouch() {
|
|
|
|
|
- return controllerHideOnTouch;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets whether the playback controls are hidden by touch events.
|
|
|
|
|
- *
|
|
|
|
|
- * @param controllerHideOnTouch Whether the playback controls are hidden by touch events.
|
|
|
|
|
- */
|
|
|
|
|
- public void setControllerHideOnTouch(boolean controllerHideOnTouch) {
|
|
|
|
|
- Assertions.checkStateNotNull(controller);
|
|
|
|
|
- this.controllerHideOnTouch = controllerHideOnTouch;
|
|
|
|
|
- updateContentDescription();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Returns whether the playback controls are automatically shown when playback starts, pauses,
|
|
|
|
|
- * ends, or fails. If set to false, the playback controls can be manually operated with {@link
|
|
|
|
|
- * #showController()} and {@link #hideController()}.
|
|
|
|
|
- */
|
|
|
|
|
- public boolean getControllerAutoShow() {
|
|
|
|
|
- return controllerAutoShow;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets whether the playback controls are automatically shown when playback starts, pauses, ends,
|
|
|
|
|
- * or fails. If set to false, the playback controls can be manually operated with {@link
|
|
|
|
|
- * #showController()} and {@link #hideController()}.
|
|
|
|
|
- *
|
|
|
|
|
- * @param controllerAutoShow Whether the playback controls are allowed to show automatically.
|
|
|
|
|
- */
|
|
|
|
|
- public void setControllerAutoShow(boolean controllerAutoShow) {
|
|
|
|
|
- this.controllerAutoShow = controllerAutoShow;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets whether the playback controls are hidden when ads are playing. Controls are always shown
|
|
|
|
|
- * during ads if they are enabled and the player is paused.
|
|
|
|
|
- *
|
|
|
|
|
- * @param controllerHideDuringAds Whether the playback controls are hidden when ads are playing.
|
|
|
|
|
- */
|
|
|
|
|
- public void setControllerHideDuringAds(boolean controllerHideDuringAds) {
|
|
|
|
|
- this.controllerHideDuringAds = controllerHideDuringAds;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets the {@link PlayerControlView.VisibilityListener}.
|
|
|
|
|
- *
|
|
|
|
|
- * @param listener The listener to be notified about visibility changes, or null to remove the
|
|
|
|
|
- * current listener.
|
|
|
|
|
- */
|
|
|
|
|
- public void setControllerVisibilityListener(
|
|
|
|
|
- @Nullable PlayerControlView.VisibilityListener listener) {
|
|
|
|
|
- Assertions.checkStateNotNull(controller);
|
|
|
|
|
- if (this.controllerVisibilityListener == listener) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- if (this.controllerVisibilityListener != null) {
|
|
|
|
|
- controller.removeVisibilityListener(this.controllerVisibilityListener);
|
|
|
|
|
- }
|
|
|
|
|
- this.controllerVisibilityListener = listener;
|
|
|
|
|
- if (listener != null) {
|
|
|
|
|
- controller.addVisibilityListener(listener);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets whether the rewind button is shown.
|
|
|
|
|
- *
|
|
|
|
|
- * @param showRewindButton Whether the rewind button is shown.
|
|
|
|
|
- */
|
|
|
|
|
- public void setShowRewindButton(boolean showRewindButton) {
|
|
|
|
|
- Assertions.checkStateNotNull(controller);
|
|
|
|
|
- controller.setShowRewindButton(showRewindButton);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets whether the fast forward button is shown.
|
|
|
|
|
- *
|
|
|
|
|
- * @param showFastForwardButton Whether the fast forward button is shown.
|
|
|
|
|
- */
|
|
|
|
|
- public void setShowFastForwardButton(boolean showFastForwardButton) {
|
|
|
|
|
- Assertions.checkStateNotNull(controller);
|
|
|
|
|
- controller.setShowFastForwardButton(showFastForwardButton);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets whether the previous button is shown.
|
|
|
|
|
- *
|
|
|
|
|
- * @param showPreviousButton Whether the previous button is shown.
|
|
|
|
|
- */
|
|
|
|
|
- public void setShowPreviousButton(boolean showPreviousButton) {
|
|
|
|
|
- Assertions.checkStateNotNull(controller);
|
|
|
|
|
- controller.setShowPreviousButton(showPreviousButton);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets whether the next button is shown.
|
|
|
|
|
- *
|
|
|
|
|
- * @param showNextButton Whether the next button is shown.
|
|
|
|
|
- */
|
|
|
|
|
- public void setShowNextButton(boolean showNextButton) {
|
|
|
|
|
- Assertions.checkStateNotNull(controller);
|
|
|
|
|
- controller.setShowNextButton(showNextButton);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets which repeat toggle modes are enabled.
|
|
|
|
|
- *
|
|
|
|
|
- * @param repeatToggleModes A set of {@link RepeatModeUtil.RepeatToggleModes}.
|
|
|
|
|
- */
|
|
|
|
|
- public void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) {
|
|
|
|
|
- Assertions.checkStateNotNull(controller);
|
|
|
|
|
- controller.setRepeatToggleModes(repeatToggleModes);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets whether the shuffle button is shown.
|
|
|
|
|
- *
|
|
|
|
|
- * @param showShuffleButton Whether the shuffle button is shown.
|
|
|
|
|
- */
|
|
|
|
|
- public void setShowShuffleButton(boolean showShuffleButton) {
|
|
|
|
|
- Assertions.checkStateNotNull(controller);
|
|
|
|
|
- controller.setShowShuffleButton(showShuffleButton);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets whether the time bar should show all windows, as opposed to just the current one.
|
|
|
|
|
- *
|
|
|
|
|
- * @param showMultiWindowTimeBar Whether to show all windows.
|
|
|
|
|
- */
|
|
|
|
|
- public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) {
|
|
|
|
|
- Assertions.checkStateNotNull(controller);
|
|
|
|
|
- controller.setShowMultiWindowTimeBar(showMultiWindowTimeBar);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets the millisecond positions of extra ad markers relative to the start of the window (or
|
|
|
|
|
- * timeline, if in multi-window mode) and whether each extra ad has been played or not. The
|
|
|
|
|
- * markers are shown in addition to any ad markers for ads in the player's timeline.
|
|
|
|
|
- *
|
|
|
|
|
- * @param extraAdGroupTimesMs The millisecond timestamps of the extra ad markers to show, or
|
|
|
|
|
- * {@code null} to show no extra ad markers.
|
|
|
|
|
- * @param extraPlayedAdGroups Whether each ad has been played, or {@code null} to show no extra ad
|
|
|
|
|
- * markers.
|
|
|
|
|
- */
|
|
|
|
|
- public void setExtraAdGroupMarkers(
|
|
|
|
|
- @Nullable long[] extraAdGroupTimesMs, @Nullable boolean[] extraPlayedAdGroups) {
|
|
|
|
|
- Assertions.checkStateNotNull(controller);
|
|
|
|
|
- controller.setExtraAdGroupMarkers(extraAdGroupTimesMs, extraPlayedAdGroups);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Sets the {@link AspectRatioFrameLayout.AspectRatioListener}.
|
|
|
|
|
- *
|
|
|
|
|
- * @param listener The listener to be notified about aspect ratios changes of the video content or
|
|
|
|
|
- * the content frame.
|
|
|
|
|
- */
|
|
|
|
|
- public void setAspectRatioListener(
|
|
|
|
|
- @Nullable AspectRatioFrameLayout.AspectRatioListener listener) {
|
|
|
|
|
- Assertions.checkStateNotNull(contentFrame);
|
|
|
|
|
- contentFrame.setAspectRatioListener(listener);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Gets the view onto which video is rendered. This is a:
|
|
|
|
|
- *
|
|
|
|
|
- * <ul>
|
|
|
|
|
- * <li>{@link SurfaceView} by default, or if the {@code surface_type} attribute is set to {@code
|
|
|
|
|
- * surface_view}.
|
|
|
|
|
- * <li>{@link TextureView} if {@code surface_type} is {@code texture_view}.
|
|
|
|
|
- * <li>{@code SphericalGLSurfaceView} if {@code surface_type} is {@code
|
|
|
|
|
- * spherical_gl_surface_view}.
|
|
|
|
|
- * <li>{@code VideoDecoderGLSurfaceView} if {@code surface_type} is {@code
|
|
|
|
|
- * video_decoder_gl_surface_view}.
|
|
|
|
|
- * <li>{@code null} if {@code surface_type} is {@code none}.
|
|
|
|
|
- * </ul>
|
|
|
|
|
- *
|
|
|
|
|
- * @return The {@link SurfaceView}, {@link TextureView}, {@code SphericalGLSurfaceView}, {@code
|
|
|
|
|
- * VideoDecoderGLSurfaceView} or {@code null}.
|
|
|
|
|
- */
|
|
|
|
|
- @Nullable
|
|
|
|
|
- public View getVideoSurfaceView() {
|
|
|
|
|
- return surfaceView;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Gets the overlay {@link FrameLayout}, which can be populated with UI elements to show on top of
|
|
|
|
|
- * the player.
|
|
|
|
|
- *
|
|
|
|
|
- * @return The overlay {@link FrameLayout}, or {@code null} if the layout has been customized and
|
|
|
|
|
- * the overlay is not present.
|
|
|
|
|
- */
|
|
|
|
|
- @Nullable
|
|
|
|
|
- public FrameLayout getOverlayFrameLayout() {
|
|
|
|
|
- return overlayFrameLayout;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Gets the {@link SubtitleView}.
|
|
|
|
|
- *
|
|
|
|
|
- * @return The {@link SubtitleView}, or {@code null} if the layout has been customized and the
|
|
|
|
|
- * subtitle view is not present.
|
|
|
|
|
- */
|
|
|
|
|
- @Nullable
|
|
|
|
|
- public SubtitleView getSubtitleView() {
|
|
|
|
|
- return subtitleView;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public boolean onTouchEvent(MotionEvent event) {
|
|
|
|
|
- if (!useController() || player == null) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- switch (event.getAction()) {
|
|
|
|
|
- case MotionEvent.ACTION_DOWN:
|
|
|
|
|
- isTouching = true;
|
|
|
|
|
- return true;
|
|
|
|
|
- case MotionEvent.ACTION_UP:
|
|
|
|
|
- if (isTouching) {
|
|
|
|
|
- isTouching = false;
|
|
|
|
|
- performClick();
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
- return false;
|
|
|
|
|
- default:
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public boolean performClick() {
|
|
|
|
|
- super.performClick();
|
|
|
|
|
- return toggleControllerVisibility();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public boolean onTrackballEvent(MotionEvent ev) {
|
|
|
|
|
- if (!useController() || player == null) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- maybeShowController(true);
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Should be called when the player is visible to the user, if the {@code surface_type} extends
|
|
|
|
|
- * {@link GLSurfaceView}. It is the counterpart to {@link #onPause()}.
|
|
|
|
|
- *
|
|
|
|
|
- * <p>This method should typically be called in {@code Activity.onStart()}, or {@code
|
|
|
|
|
- * Activity.onResume()} for API versions <= 23.
|
|
|
|
|
- */
|
|
|
|
|
- public void onResume() {
|
|
|
|
|
- if (surfaceView instanceof GLSurfaceView) {
|
|
|
|
|
- ((GLSurfaceView) surfaceView).onResume();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Should be called when the player is no longer visible to the user, if the {@code surface_type}
|
|
|
|
|
- * extends {@link GLSurfaceView}. It is the counterpart to {@link #onResume()}.
|
|
|
|
|
- *
|
|
|
|
|
- * <p>This method should typically be called in {@code Activity.onStop()}, or {@code
|
|
|
|
|
- * Activity.onPause()} for API versions <= 23.
|
|
|
|
|
- */
|
|
|
|
|
- public void onPause() {
|
|
|
|
|
- if (surfaceView instanceof GLSurfaceView) {
|
|
|
|
|
- ((GLSurfaceView) surfaceView).onPause();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Called when there's a change in the desired aspect ratio of the content frame. The default
|
|
|
|
|
- * implementation sets the aspect ratio of the content frame to the specified value.
|
|
|
|
|
- *
|
|
|
|
|
- * @param contentFrame The content frame, or {@code null}.
|
|
|
|
|
- * @param aspectRatio The aspect ratio to apply.
|
|
|
|
|
- */
|
|
|
|
|
- protected void onContentAspectRatioChanged(
|
|
|
|
|
- @Nullable AspectRatioFrameLayout contentFrame, float aspectRatio) {
|
|
|
|
|
- if (contentFrame != null) {
|
|
|
|
|
- contentFrame.setAspectRatio(aspectRatio);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // AdsLoader.AdViewProvider implementation.
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public ViewGroup getAdViewGroup() {
|
|
|
|
|
- return Assertions.checkStateNotNull(
|
|
|
|
|
- adOverlayFrameLayout, "exo_ad_overlay must be present for ad playback");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public List<AdOverlayInfo> getAdOverlayInfos() {
|
|
|
|
|
- List<AdOverlayInfo> overlayViews = new ArrayList<>();
|
|
|
|
|
- if (overlayFrameLayout != null) {
|
|
|
|
|
- overlayViews.add(
|
|
|
|
|
- new AdOverlayInfo(
|
|
|
|
|
- overlayFrameLayout,
|
|
|
|
|
- AdOverlayInfo.PURPOSE_NOT_VISIBLE,
|
|
|
|
|
- /* detailedReason= */ "Transparent overlay does not impact viewability"));
|
|
|
|
|
- }
|
|
|
|
|
- if (controller != null) {
|
|
|
|
|
- overlayViews.add(new AdOverlayInfo(controller, AdOverlayInfo.PURPOSE_CONTROLS));
|
|
|
|
|
- }
|
|
|
|
|
- return ImmutableList.copyOf(overlayViews);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Internal methods.
|
|
|
|
|
- private boolean useController() {
|
|
|
|
|
- if (useController) {
|
|
|
|
|
- Assertions.checkStateNotNull(controller);
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private boolean useArtwork() {
|
|
|
|
|
- if (useArtwork) {
|
|
|
|
|
- Assertions.checkStateNotNull(artworkView);
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private boolean toggleControllerVisibility() {
|
|
|
|
|
- if (!useController() || player == null) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- if (!controller.isVisible()) {
|
|
|
|
|
- maybeShowController(true);
|
|
|
|
|
- } else if (controllerHideOnTouch) {
|
|
|
|
|
- controller.hide();
|
|
|
|
|
- }
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /** Shows the playback controls, but only if forced or shown indefinitely. */
|
|
|
|
|
- private void maybeShowController(boolean isForced) {
|
|
|
|
|
- if (isPlayingAd() && controllerHideDuringAds) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- if (useController()) {
|
|
|
|
|
- boolean wasShowingIndefinitely = controller.isVisible() && controller.getShowTimeoutMs() <= 0;
|
|
|
|
|
- boolean shouldShowIndefinitely = shouldShowControllerIndefinitely();
|
|
|
|
|
- if (isForced || wasShowingIndefinitely || shouldShowIndefinitely) {
|
|
|
|
|
- showController(shouldShowIndefinitely);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private boolean shouldShowControllerIndefinitely() {
|
|
|
|
|
- if (player == null) {
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
- int playbackState = player.getPlaybackState();
|
|
|
|
|
- return controllerAutoShow
|
|
|
|
|
- && (playbackState == Player.STATE_IDLE
|
|
|
|
|
- || playbackState == Player.STATE_ENDED
|
|
|
|
|
- || !player.getPlayWhenReady());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void showController(boolean showIndefinitely) {
|
|
|
|
|
- if (!useController()) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- controller.setShowTimeoutMs(showIndefinitely ? 0 : controllerShowTimeoutMs);
|
|
|
|
|
- controller.show();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private boolean isPlayingAd() {
|
|
|
|
|
- return player != null && player.isPlayingAd() && player.getPlayWhenReady();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void updateForCurrentTrackSelections(boolean isNewPlayer) {
|
|
|
|
|
- @Nullable Player player = this.player;
|
|
|
|
|
- if (player == null
|
|
|
|
|
- || !player.isCommandAvailable(Player.COMMAND_GET_TRACK_INFOS)
|
|
|
|
|
- || player.getCurrentTracksInfo().getTrackGroupInfos().isEmpty()) {
|
|
|
|
|
- if (!keepContentOnPlayerReset) {
|
|
|
|
|
- hideArtwork();
|
|
|
|
|
- closeShutter();
|
|
|
|
|
- }
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (isNewPlayer && !keepContentOnPlayerReset) {
|
|
|
|
|
- // Hide any video from the previous player.
|
|
|
|
|
- closeShutter();
|
|
|
|
|
- }
|
|
|
|
|
- if (player.getCurrentTracksInfo().isTypeSelected(C.TRACK_TYPE_VIDEO)) {
|
|
|
|
|
- // Video enabled, so artwork must be hidden. If the shutter is closed, it will be opened
|
|
|
|
|
- // in onRenderedFirstFrame().
|
|
|
|
|
- hideArtwork();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Video disabled so the shutter must be closed.
|
|
|
|
|
- closeShutter();
|
|
|
|
|
- // Display artwork if enabled and available, else hide it.
|
|
|
|
|
- if (useArtwork()) {
|
|
|
|
|
- if (setArtworkFromMediaMetadata(player.getMediaMetadata())) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- if (setDrawableArtwork(defaultArtwork)) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- // Artwork disabled or unavailable.
|
|
|
|
|
- hideArtwork();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void updateAspectRatio() {
|
|
|
|
|
- VideoSize videoSize = player != null ? player.getVideoSize() : VideoSize.UNKNOWN;
|
|
|
|
|
- int width = videoSize.width;
|
|
|
|
|
- int height = videoSize.height;
|
|
|
|
|
- int unappliedRotationDegrees = videoSize.unappliedRotationDegrees;
|
|
|
|
|
- float videoAspectRatio =
|
|
|
|
|
- (height == 0 || width == 0) ? 0 : (width * videoSize.pixelWidthHeightRatio) / height;
|
|
|
|
|
-
|
|
|
|
|
- if (surfaceView instanceof TextureView) {
|
|
|
|
|
- // Try to apply rotation transformation when our surface is a TextureView.
|
|
|
|
|
- if (videoAspectRatio > 0
|
|
|
|
|
- && (unappliedRotationDegrees == 90 || unappliedRotationDegrees == 270)) {
|
|
|
|
|
- // We will apply a rotation 90/270 degree to the output texture of the TextureView.
|
|
|
|
|
- // In this case, the output video's width and height will be swapped.
|
|
|
|
|
- videoAspectRatio = 1 / videoAspectRatio;
|
|
|
|
|
- }
|
|
|
|
|
- if (textureViewRotation != 0) {
|
|
|
|
|
- surfaceView.removeOnLayoutChangeListener(componentListener);
|
|
|
|
|
- }
|
|
|
|
|
- textureViewRotation = unappliedRotationDegrees;
|
|
|
|
|
- if (textureViewRotation != 0) {
|
|
|
|
|
- // The texture view's dimensions might be changed after layout step.
|
|
|
|
|
- // So add an OnLayoutChangeListener to apply rotation after layout step.
|
|
|
|
|
- surfaceView.addOnLayoutChangeListener(componentListener);
|
|
|
|
|
- }
|
|
|
|
|
- applyTextureViewRotation((TextureView) surfaceView, textureViewRotation);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- onContentAspectRatioChanged(
|
|
|
|
|
- contentFrame, surfaceViewIgnoresVideoAspectRatio ? 0 : videoAspectRatio);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private boolean setArtworkFromMediaMetadata(MediaMetadata mediaMetadata) {
|
|
|
|
|
- if (mediaMetadata.artworkData == null) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- Bitmap bitmap =
|
|
|
|
|
- BitmapFactory.decodeByteArray(
|
|
|
|
|
- mediaMetadata.artworkData, /* offset= */ 0, mediaMetadata.artworkData.length);
|
|
|
|
|
- return setDrawableArtwork(new BitmapDrawable(getResources(), bitmap));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private boolean setDrawableArtwork(@Nullable Drawable drawable) {
|
|
|
|
|
- if (drawable != null) {
|
|
|
|
|
- int drawableWidth = drawable.getIntrinsicWidth();
|
|
|
|
|
- int drawableHeight = drawable.getIntrinsicHeight();
|
|
|
|
|
- if (drawableWidth > 0 && drawableHeight > 0) {
|
|
|
|
|
- float artworkAspectRatio = (float) drawableWidth / drawableHeight;
|
|
|
|
|
- onContentAspectRatioChanged(contentFrame, artworkAspectRatio);
|
|
|
|
|
- artworkView.setImageDrawable(drawable);
|
|
|
|
|
- artworkView.setVisibility(VISIBLE);
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void hideArtwork() {
|
|
|
|
|
- if (artworkView != null) {
|
|
|
|
|
- artworkView.setImageResource(android.R.color.transparent); // Clears any bitmap reference.
|
|
|
|
|
- artworkView.setVisibility(INVISIBLE);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void closeShutter() {
|
|
|
|
|
- if (shutterView != null) {
|
|
|
|
|
- shutterView.setVisibility(View.VISIBLE);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void updateBuffering() {
|
|
|
|
|
- if (bufferingView != null) {
|
|
|
|
|
- boolean showBufferingSpinner =
|
|
|
|
|
- player != null
|
|
|
|
|
- && player.getPlaybackState() == Player.STATE_BUFFERING
|
|
|
|
|
- && (showBuffering == SHOW_BUFFERING_ALWAYS
|
|
|
|
|
- || (showBuffering == SHOW_BUFFERING_WHEN_PLAYING && player.getPlayWhenReady()));
|
|
|
|
|
- bufferingView.setVisibility(showBufferingSpinner ? View.VISIBLE : View.GONE);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void updateErrorMessage() {
|
|
|
|
|
- if (errorMessageView != null) {
|
|
|
|
|
- if (customErrorMessage != null) {
|
|
|
|
|
- errorMessageView.setText(customErrorMessage);
|
|
|
|
|
- errorMessageView.setVisibility(View.VISIBLE);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- @Nullable PlaybackException error = player != null ? player.getPlayerError() : null;
|
|
|
|
|
- if (error != null && errorMessageProvider != null) {
|
|
|
|
|
- CharSequence errorMessage = errorMessageProvider.getErrorMessage(error).second;
|
|
|
|
|
- errorMessageView.setText(errorMessage);
|
|
|
|
|
- errorMessageView.setVisibility(View.VISIBLE);
|
|
|
|
|
- } else {
|
|
|
|
|
- errorMessageView.setVisibility(View.GONE);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void updateContentDescription() {
|
|
|
|
|
- if (controller == null || !useController) {
|
|
|
|
|
- setContentDescription(/* contentDescription= */ null);
|
|
|
|
|
- } else if (controller.getVisibility() == View.VISIBLE) {
|
|
|
|
|
- setContentDescription(
|
|
|
|
|
- /* contentDescription= */ controllerHideOnTouch
|
|
|
|
|
- ? getResources().getString(com.google.android.exoplayer2.ui.R.string.exo_controls_hide)
|
|
|
|
|
- : null);
|
|
|
|
|
- } else {
|
|
|
|
|
- setContentDescription(
|
|
|
|
|
- /* contentDescription= */ getResources().getString(com.google.android.exoplayer2.ui.R.string.exo_controls_show));
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private void updateControllerVisibility() {
|
|
|
|
|
- if (isPlayingAd() && controllerHideDuringAds) {
|
|
|
|
|
- hideController();
|
|
|
|
|
- } else {
|
|
|
|
|
- maybeShowController(false);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @RequiresApi(23)
|
|
|
|
|
- private static void configureEditModeLogoV23(Resources resources, ImageView logo) {
|
|
|
|
|
- logo.setImageDrawable(resources.getDrawable(com.google.android.exoplayer2.ui.R.drawable.exo_edit_mode_logo, null));
|
|
|
|
|
- logo.setBackgroundColor(resources.getColor(com.google.android.exoplayer2.ui.R.color.exo_edit_mode_background_color, null));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private static void configureEditModeLogo(Resources resources, ImageView logo) {
|
|
|
|
|
- logo.setImageDrawable(resources.getDrawable(com.google.android.exoplayer2.ui.R.drawable.exo_edit_mode_logo));
|
|
|
|
|
- logo.setBackgroundColor(resources.getColor(com.google.android.exoplayer2.ui.R.color.exo_edit_mode_background_color));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @SuppressWarnings("ResourceType")
|
|
|
|
|
- private static void setResizeModeRaw(AspectRatioFrameLayout aspectRatioFrame, int resizeMode) {
|
|
|
|
|
- aspectRatioFrame.setResizeMode(resizeMode);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /** Applies a texture rotation to a {@link TextureView}. */
|
|
|
|
|
- private static void applyTextureViewRotation(TextureView textureView, int textureViewRotation) {
|
|
|
|
|
- Matrix transformMatrix = new Matrix();
|
|
|
|
|
- float textureViewWidth = textureView.getWidth();
|
|
|
|
|
- float textureViewHeight = textureView.getHeight();
|
|
|
|
|
- if (textureViewWidth != 0 && textureViewHeight != 0 && textureViewRotation != 0) {
|
|
|
|
|
- float pivotX = textureViewWidth / 2;
|
|
|
|
|
- float pivotY = textureViewHeight / 2;
|
|
|
|
|
- transformMatrix.postRotate(textureViewRotation, pivotX, pivotY);
|
|
|
|
|
-
|
|
|
|
|
- // After rotation, scale the rotated texture to fit the TextureView size.
|
|
|
|
|
- RectF originalTextureRect = new RectF(0, 0, textureViewWidth, textureViewHeight);
|
|
|
|
|
- RectF rotatedTextureRect = new RectF();
|
|
|
|
|
- transformMatrix.mapRect(rotatedTextureRect, originalTextureRect);
|
|
|
|
|
- transformMatrix.postScale(
|
|
|
|
|
- textureViewWidth / rotatedTextureRect.width(),
|
|
|
|
|
- textureViewHeight / rotatedTextureRect.height(),
|
|
|
|
|
- pivotX,
|
|
|
|
|
- pivotY);
|
|
|
|
|
- }
|
|
|
|
|
- textureView.setTransform(transformMatrix);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @SuppressLint("InlinedApi")
|
|
|
|
|
- private boolean isDpadKey(int keyCode) {
|
|
|
|
|
- return keyCode == KeyEvent.KEYCODE_DPAD_UP
|
|
|
|
|
- || keyCode == KeyEvent.KEYCODE_DPAD_UP_RIGHT
|
|
|
|
|
- || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
|
|
|
|
|
- || keyCode == KeyEvent.KEYCODE_DPAD_DOWN_RIGHT
|
|
|
|
|
- || keyCode == KeyEvent.KEYCODE_DPAD_DOWN
|
|
|
|
|
- || keyCode == KeyEvent.KEYCODE_DPAD_DOWN_LEFT
|
|
|
|
|
- || keyCode == KeyEvent.KEYCODE_DPAD_LEFT
|
|
|
|
|
- || keyCode == KeyEvent.KEYCODE_DPAD_UP_LEFT
|
|
|
|
|
- || keyCode == KeyEvent.KEYCODE_DPAD_CENTER;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private final class ComponentListener
|
|
|
|
|
- implements Player.Listener,
|
|
|
|
|
- OnLayoutChangeListener,
|
|
|
|
|
- OnClickListener,
|
|
|
|
|
- PlayerControlView.VisibilityListener {
|
|
|
|
|
-
|
|
|
|
|
- private final Period period;
|
|
|
|
|
- private @Nullable Object lastPeriodUidWithTracks;
|
|
|
|
|
-
|
|
|
|
|
- public ComponentListener() {
|
|
|
|
|
- period = new Period();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Player.Listener implementation
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public void onCues(List<Cue> cues) {
|
|
|
|
|
- if (subtitleView != null) {
|
|
|
|
|
- subtitleView.setCues(cues);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public void onVideoSizeChanged(VideoSize videoSize) {
|
|
|
|
|
- updateAspectRatio();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public void onRenderedFirstFrame() {
|
|
|
|
|
- if (shutterView != null) {
|
|
|
|
|
- shutterView.setVisibility(INVISIBLE);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
|
|
|
|
- // Suppress the update if transitioning to an unprepared period within the same window. This
|
|
|
|
|
- // is necessary to avoid closing the shutter when such a transition occurs. See:
|
|
|
|
|
- // https://github.com/google/ExoPlayer/issues/5507.
|
|
|
|
|
- Player player = Assertions.checkNotNull(ZoomableExoPlayerView.this.player);
|
|
|
|
|
- Timeline timeline = player.getCurrentTimeline();
|
|
|
|
|
- if (timeline.isEmpty()) {
|
|
|
|
|
- lastPeriodUidWithTracks = null;
|
|
|
|
|
- } else if (!player.getCurrentTracksInfo().getTrackGroupInfos().isEmpty()) {
|
|
|
|
|
- lastPeriodUidWithTracks =
|
|
|
|
|
- timeline.getPeriod(player.getCurrentPeriodIndex(), period, /* setIds= */ true).uid;
|
|
|
|
|
- } else if (lastPeriodUidWithTracks != null) {
|
|
|
|
|
- int lastPeriodIndexWithTracks = timeline.getIndexOfPeriod(lastPeriodUidWithTracks);
|
|
|
|
|
- if (lastPeriodIndexWithTracks != C.INDEX_UNSET) {
|
|
|
|
|
- int lastWindowIndexWithTracks =
|
|
|
|
|
- timeline.getPeriod(lastPeriodIndexWithTracks, period).windowIndex;
|
|
|
|
|
- if (player.getCurrentMediaItemIndex() == lastWindowIndexWithTracks) {
|
|
|
|
|
- // We're in the same media item. Suppress the update.
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- lastPeriodUidWithTracks = null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- updateForCurrentTrackSelections(/* isNewPlayer= */ false);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public void onPlaybackStateChanged(@Player.State int playbackState) {
|
|
|
|
|
- updateBuffering();
|
|
|
|
|
- updateErrorMessage();
|
|
|
|
|
- updateControllerVisibility();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public void onPlayWhenReadyChanged(
|
|
|
|
|
- boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
|
|
|
|
|
- updateBuffering();
|
|
|
|
|
- updateControllerVisibility();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public void onPositionDiscontinuity(
|
|
|
|
|
- Player.PositionInfo oldPosition,
|
|
|
|
|
- Player.PositionInfo newPosition,
|
|
|
|
|
- @DiscontinuityReason int reason) {
|
|
|
|
|
- if (isPlayingAd() && controllerHideDuringAds) {
|
|
|
|
|
- hideController();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // OnLayoutChangeListener implementation
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public void onLayoutChange(
|
|
|
|
|
- View view,
|
|
|
|
|
- int left,
|
|
|
|
|
- int top,
|
|
|
|
|
- int right,
|
|
|
|
|
- int bottom,
|
|
|
|
|
- int oldLeft,
|
|
|
|
|
- int oldTop,
|
|
|
|
|
- int oldRight,
|
|
|
|
|
- int oldBottom) {
|
|
|
|
|
- applyTextureViewRotation((TextureView) view, textureViewRotation);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // OnClickListener implementation
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public void onClick(View view) {
|
|
|
|
|
- toggleControllerVisibility();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // PlayerControlView.VisibilityListener implementation
|
|
|
|
|
-
|
|
|
|
|
- @Override
|
|
|
|
|
- public void onVisibilityChange(int visibility) {
|
|
|
|
|
- updateContentDescription();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ break;
|
|
|
|
|
+ case SURFACE_TYPE_SPHERICAL_GL_SURFACE_VIEW:
|
|
|
|
|
+ try {
|
|
|
|
|
+ Class<?> clazz =
|
|
|
|
|
+ Class.forName(
|
|
|
|
|
+ "com.google.android.exoplayer2.video.spherical.SphericalGLSurfaceView");
|
|
|
|
|
+ surfaceView = (View) clazz.getConstructor(Context.class).newInstance(context);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ throw new IllegalStateException(
|
|
|
|
|
+ "spherical_gl_surface_view requires an ExoPlayer dependency", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ surfaceViewIgnoresVideoAspectRatio = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case SURFACE_TYPE_VIDEO_DECODER_GL_SURFACE_VIEW:
|
|
|
|
|
+ try {
|
|
|
|
|
+ Class<?> clazz =
|
|
|
|
|
+ Class.forName("com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView");
|
|
|
|
|
+ surfaceView = (View) clazz.getConstructor(Context.class).newInstance(context);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ throw new IllegalStateException(
|
|
|
|
|
+ "video_decoder_gl_surface_view requires an ExoPlayer dependency", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ surfaceView = new SurfaceView(context);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ surfaceView.setLayoutParams(params);
|
|
|
|
|
+ // We don't want surfaceView to be clickable separately to the PlayerView itself, but we
|
|
|
|
|
+ // do want to register as an OnClickListener so that surfaceView implementations can propagate
|
|
|
|
|
+ // click events up to the PlayerView by calling their own performClick method.
|
|
|
|
|
+ surfaceView.setOnClickListener(componentListener);
|
|
|
|
|
+ surfaceView.setClickable(false);
|
|
|
|
|
+ contentFrame.addView(surfaceView, 0);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ surfaceView = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.surfaceViewIgnoresVideoAspectRatio = surfaceViewIgnoresVideoAspectRatio;
|
|
|
|
|
+
|
|
|
|
|
+ // Ad overlay frame layout.
|
|
|
|
|
+ adOverlayFrameLayout = findViewById(R.id.exo_ad_overlay);
|
|
|
|
|
+
|
|
|
|
|
+ // Overlay frame layout.
|
|
|
|
|
+ overlayFrameLayout = findViewById(R.id.exo_overlay);
|
|
|
|
|
+
|
|
|
|
|
+ // Artwork view.
|
|
|
|
|
+ artworkView = findViewById(R.id.exo_artwork);
|
|
|
|
|
+ this.useArtwork = useArtwork && artworkView != null;
|
|
|
|
|
+ if (defaultArtworkId != 0) {
|
|
|
|
|
+ defaultArtwork = ContextCompat.getDrawable(getContext(), defaultArtworkId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Subtitle view.
|
|
|
|
|
+ subtitleView = findViewById(R.id.exo_subtitles);
|
|
|
|
|
+ if (subtitleView != null) {
|
|
|
|
|
+ subtitleView.setUserDefaultStyle();
|
|
|
|
|
+ subtitleView.setUserDefaultTextSize();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Buffering view.
|
|
|
|
|
+ bufferingView = findViewById(R.id.exo_buffering);
|
|
|
|
|
+ if (bufferingView != null) {
|
|
|
|
|
+ bufferingView.setVisibility(View.GONE);
|
|
|
|
|
+ }
|
|
|
|
|
+ this.showBuffering = showBuffering;
|
|
|
|
|
+
|
|
|
|
|
+ // Error message view.
|
|
|
|
|
+ errorMessageView = findViewById(R.id.exo_error_message);
|
|
|
|
|
+ if (errorMessageView != null) {
|
|
|
|
|
+ errorMessageView.setVisibility(View.GONE);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Playback control view.
|
|
|
|
|
+ PlayerControlView customController = findViewById(R.id.exo_controller);
|
|
|
|
|
+ View controllerPlaceholder = findViewById(R.id.exo_controller_placeholder);
|
|
|
|
|
+ if (customController != null) {
|
|
|
|
|
+ this.controller = customController;
|
|
|
|
|
+ } else if (controllerPlaceholder != null) {
|
|
|
|
|
+ // Propagate attrs as playbackAttrs so that PlayerControlView's custom attributes are
|
|
|
|
|
+ // transferred, but standard attributes (e.g. background) are not.
|
|
|
|
|
+ this.controller = new PlayerControlView(context, null, 0, attrs);
|
|
|
|
|
+ controller.setId(R.id.exo_controller);
|
|
|
|
|
+ controller.setLayoutParams(controllerPlaceholder.getLayoutParams());
|
|
|
|
|
+ ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent());
|
|
|
|
|
+ int controllerIndex = parent.indexOfChild(controllerPlaceholder);
|
|
|
|
|
+ parent.removeView(controllerPlaceholder);
|
|
|
|
|
+ parent.addView(controller, controllerIndex);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.controller = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.controllerShowTimeoutMs = controller != null ? controllerShowTimeoutMs : 0;
|
|
|
|
|
+ this.controllerHideOnTouch = controllerHideOnTouch;
|
|
|
|
|
+ this.controllerAutoShow = controllerAutoShow;
|
|
|
|
|
+ this.controllerHideDuringAds = controllerHideDuringAds;
|
|
|
|
|
+ this.useController = useController && controller != null;
|
|
|
|
|
+ if (controller != null) {
|
|
|
|
|
+ controller.hide();
|
|
|
|
|
+ controller.addVisibilityListener(/* listener= */ componentListener);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (useController) {
|
|
|
|
|
+ setClickable(true);
|
|
|
|
|
+ }
|
|
|
|
|
+ updateContentDescription();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Switches the view targeted by a given {@link Player}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param player The player whose target view is being switched.
|
|
|
|
|
+ * @param oldPlayerView The old view to detach from the player.
|
|
|
|
|
+ * @param newPlayerView The new view to attach to the player.
|
|
|
|
|
+ */
|
|
|
|
|
+ public static void switchTargetView(
|
|
|
|
|
+ Player player, @Nullable ZoomableExoPlayerView oldPlayerView, @Nullable ZoomableExoPlayerView newPlayerView) {
|
|
|
|
|
+ if (oldPlayerView == newPlayerView) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ // We attach the new view before detaching the old one because this ordering allows the player
|
|
|
|
|
+ // to swap directly from one surface to another, without transitioning through a state where no
|
|
|
|
|
+ // surface is attached. This is significantly more efficient and achieves a more seamless
|
|
|
|
|
+ // transition when using platform provided video decoders.
|
|
|
|
|
+ if (newPlayerView != null) {
|
|
|
|
|
+ newPlayerView.setPlayer(player);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (oldPlayerView != null) {
|
|
|
|
|
+ oldPlayerView.setPlayer(null);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Returns the player currently set on this view, or null if no player is set. */
|
|
|
|
|
+ @Nullable
|
|
|
|
|
+ public Player getPlayer() {
|
|
|
|
|
+ return player;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets the {@link Player} to use.
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>To transition a {@link Player} from targeting one view to another, it's recommended to use
|
|
|
|
|
+ * {@link #switchTargetView(Player, ZoomableExoPlayerView, ZoomableExoPlayerView)} rather than this method. If you do
|
|
|
|
|
+ * wish to use this method directly, be sure to attach the player to the new view <em>before</em>
|
|
|
|
|
+ * calling {@code setPlayer(null)} to detach it from the old one. This ordering is significantly
|
|
|
|
|
+ * more efficient and may allow for more seamless transitions.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param player The {@link Player} to use, or {@code null} to detach the current player. Only
|
|
|
|
|
+ * players which are accessed on the main thread are supported ({@code
|
|
|
|
|
+ * player.getApplicationLooper() == Looper.getMainLooper()}).
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setPlayer(@Nullable Player player) {
|
|
|
|
|
+ Assertions.checkState(Looper.myLooper() == Looper.getMainLooper());
|
|
|
|
|
+ Assertions.checkArgument(
|
|
|
|
|
+ player == null || player.getApplicationLooper() == Looper.getMainLooper());
|
|
|
|
|
+ if (this.player == player) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ @Nullable Player oldPlayer = this.player;
|
|
|
|
|
+ if (oldPlayer != null) {
|
|
|
|
|
+ oldPlayer.removeListener(componentListener);
|
|
|
|
|
+ if (oldPlayer.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)) {
|
|
|
|
|
+ if (surfaceView instanceof TextureView) {
|
|
|
|
|
+ oldPlayer.clearVideoTextureView((TextureView) surfaceView);
|
|
|
|
|
+ } else if (surfaceView instanceof SurfaceView) {
|
|
|
|
|
+ oldPlayer.clearVideoSurfaceView((SurfaceView) surfaceView);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (subtitleView != null) {
|
|
|
|
|
+ subtitleView.setCues(null);
|
|
|
|
|
+ }
|
|
|
|
|
+ this.player = player;
|
|
|
|
|
+ if (useController()) {
|
|
|
|
|
+ controller.setPlayer(player);
|
|
|
|
|
+ }
|
|
|
|
|
+ updateBuffering();
|
|
|
|
|
+ updateErrorMessage();
|
|
|
|
|
+ updateForCurrentTrackSelections(/* isNewPlayer= */ true);
|
|
|
|
|
+ if (player != null) {
|
|
|
|
|
+ if (player.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)) {
|
|
|
|
|
+ if (surfaceView instanceof TextureView) {
|
|
|
|
|
+ player.setVideoTextureView((TextureView) surfaceView);
|
|
|
|
|
+ } else if (surfaceView instanceof SurfaceView) {
|
|
|
|
|
+ player.setVideoSurfaceView((SurfaceView) surfaceView);
|
|
|
|
|
+ }
|
|
|
|
|
+ updateAspectRatio();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (subtitleView != null && player.isCommandAvailable(COMMAND_GET_TEXT)) {
|
|
|
|
|
+ subtitleView.setCues(player.getCurrentCues().cues);
|
|
|
|
|
+ }
|
|
|
|
|
+ player.addListener(componentListener);
|
|
|
|
|
+ maybeShowController(false);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ hideController();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void setVisibility(int visibility) {
|
|
|
|
|
+ super.setVisibility(visibility);
|
|
|
|
|
+ if (surfaceView instanceof SurfaceView) {
|
|
|
|
|
+ // Work around https://github.com/google/ExoPlayer/issues/3160.
|
|
|
|
|
+ surfaceView.setVisibility(visibility);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets the {@link ResizeMode}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param resizeMode The {@link ResizeMode}.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setResizeMode(@ResizeMode int resizeMode) {
|
|
|
|
|
+ Assertions.checkStateNotNull(contentFrame);
|
|
|
|
|
+ contentFrame.setResizeMode(resizeMode);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Returns the {@link ResizeMode}. */
|
|
|
|
|
+ public @ResizeMode int getResizeMode() {
|
|
|
|
|
+ Assertions.checkStateNotNull(contentFrame);
|
|
|
|
|
+ return contentFrame.getResizeMode();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Returns whether artwork is displayed if present in the media. */
|
|
|
|
|
+ public boolean getUseArtwork() {
|
|
|
|
|
+ return useArtwork;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets whether artwork is displayed if present in the media.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param useArtwork Whether artwork is displayed.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setUseArtwork(boolean useArtwork) {
|
|
|
|
|
+ Assertions.checkState(!useArtwork || artworkView != null);
|
|
|
|
|
+ if (this.useArtwork != useArtwork) {
|
|
|
|
|
+ this.useArtwork = useArtwork;
|
|
|
|
|
+ updateForCurrentTrackSelections(/* isNewPlayer= */ false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Returns the default artwork to display. */
|
|
|
|
|
+ @Nullable
|
|
|
|
|
+ public Drawable getDefaultArtwork() {
|
|
|
|
|
+ return defaultArtwork;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets the default artwork to display if {@code useArtwork} is {@code true} and no artwork is
|
|
|
|
|
+ * present in the media.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param defaultArtwork the default artwork to display
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setDefaultArtwork(@Nullable Drawable defaultArtwork) {
|
|
|
|
|
+ if (this.defaultArtwork != defaultArtwork) {
|
|
|
|
|
+ this.defaultArtwork = defaultArtwork;
|
|
|
|
|
+ updateForCurrentTrackSelections(/* isNewPlayer= */ false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Returns whether the playback controls can be shown. */
|
|
|
|
|
+ public boolean getUseController() {
|
|
|
|
|
+ return useController;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets whether the playback controls can be shown. If set to {@code false} the playback controls
|
|
|
|
|
+ * are never visible and are disconnected from the player.
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>This call will update whether the view is clickable. After the call, the view will be
|
|
|
|
|
+ * clickable if playback controls can be shown or if the view has a registered click listener.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param useController Whether the playback controls can be shown.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setUseController(boolean useController) {
|
|
|
|
|
+ Assertions.checkState(!useController || controller != null);
|
|
|
|
|
+ setClickable(useController || hasOnClickListeners());
|
|
|
|
|
+ if (this.useController == useController) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.useController = useController;
|
|
|
|
|
+ if (useController()) {
|
|
|
|
|
+ controller.setPlayer(player);
|
|
|
|
|
+ } else if (controller != null) {
|
|
|
|
|
+ controller.hide();
|
|
|
|
|
+ controller.setPlayer(/* player= */ null);
|
|
|
|
|
+ }
|
|
|
|
|
+ updateContentDescription();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets the background color of the {@code exo_shutter} view.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param color The background color.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setShutterBackgroundColor(int color) {
|
|
|
|
|
+ if (shutterView != null) {
|
|
|
|
|
+ shutterView.setBackgroundColor(color);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets whether the currently displayed video frame or media artwork is kept visible when the
|
|
|
|
|
+ * player is reset. A player reset is defined to mean the player being re-prepared with different
|
|
|
|
|
+ * media, the player transitioning to unprepared media or an empty list of media items, or the
|
|
|
|
|
+ * player being replaced or cleared by calling {@link #setPlayer(Player)}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>If enabled, the currently displayed video frame or media artwork will be kept visible until
|
|
|
|
|
+ * the player set on the view has been successfully prepared with new media and loaded enough of
|
|
|
|
|
+ * it to have determined the available tracks. Hence enabling this option allows transitioning
|
|
|
|
|
+ * from playing one piece of media to another, or from using one player instance to another,
|
|
|
|
|
+ * without clearing the view's content.
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>If disabled, the currently displayed video frame or media artwork will be hidden as soon as
|
|
|
|
|
+ * the player is reset. Note that the video frame is hidden by making {@code exo_shutter} visible.
|
|
|
|
|
+ * Hence the video frame will not be hidden if using a custom layout that omits this view.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param keepContentOnPlayerReset Whether the currently displayed video frame or media artwork is
|
|
|
|
|
+ * kept visible when the player is reset.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setKeepContentOnPlayerReset(boolean keepContentOnPlayerReset) {
|
|
|
|
|
+ if (this.keepContentOnPlayerReset != keepContentOnPlayerReset) {
|
|
|
|
|
+ this.keepContentOnPlayerReset = keepContentOnPlayerReset;
|
|
|
|
|
+ updateForCurrentTrackSelections(/* isNewPlayer= */ false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets whether a buffering spinner is displayed when the player is in the buffering state. The
|
|
|
|
|
+ * buffering spinner is not displayed by default.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param showBuffering The mode that defines when the buffering spinner is displayed. One of
|
|
|
|
|
+ * {@link #SHOW_BUFFERING_NEVER}, {@link #SHOW_BUFFERING_WHEN_PLAYING} and {@link
|
|
|
|
|
+ * #SHOW_BUFFERING_ALWAYS}.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setShowBuffering(@ShowBuffering int showBuffering) {
|
|
|
|
|
+ if (this.showBuffering != showBuffering) {
|
|
|
|
|
+ this.showBuffering = showBuffering;
|
|
|
|
|
+ updateBuffering();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets the optional {@link ErrorMessageProvider}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param errorMessageProvider The error message provider.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setErrorMessageProvider(
|
|
|
|
|
+ @Nullable ErrorMessageProvider<? super PlaybackException> errorMessageProvider) {
|
|
|
|
|
+ if (this.errorMessageProvider != errorMessageProvider) {
|
|
|
|
|
+ this.errorMessageProvider = errorMessageProvider;
|
|
|
|
|
+ updateErrorMessage();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets a custom error message to be displayed by the view. The error message will be displayed
|
|
|
|
|
+ * permanently, unless it is cleared by passing {@code null} to this method.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param message The message to display, or {@code null} to clear a previously set message.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setCustomErrorMessage(@Nullable CharSequence message) {
|
|
|
|
|
+ Assertions.checkState(errorMessageView != null);
|
|
|
|
|
+ customErrorMessage = message;
|
|
|
|
|
+ updateErrorMessage();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public boolean dispatchKeyEvent(KeyEvent event) {
|
|
|
|
|
+ if (player != null && player.isPlayingAd()) {
|
|
|
|
|
+ return super.dispatchKeyEvent(event);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ boolean isDpadKey = isDpadKey(event.getKeyCode());
|
|
|
|
|
+ boolean handled = false;
|
|
|
|
|
+ if (isDpadKey && useController() && !controller.isVisible()) {
|
|
|
|
|
+ // Handle the key event by showing the controller.
|
|
|
|
|
+ maybeShowController(true);
|
|
|
|
|
+ handled = true;
|
|
|
|
|
+ } else if (dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event)) {
|
|
|
|
|
+ // The key event was handled as a media key or by the super class. We should also show the
|
|
|
|
|
+ // controller, or extend its show timeout if already visible.
|
|
|
|
|
+ maybeShowController(true);
|
|
|
|
|
+ handled = true;
|
|
|
|
|
+ } else if (isDpadKey && useController()) {
|
|
|
|
|
+ // The key event wasn't handled, but we should extend the controller's show timeout.
|
|
|
|
|
+ maybeShowController(true);
|
|
|
|
|
+ }
|
|
|
|
|
+ return handled;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Called to process media key events. Any {@link KeyEvent} can be passed but only media key
|
|
|
|
|
+ * events will be handled. Does nothing if playback controls are disabled.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param event A key event.
|
|
|
|
|
+ * @return Whether the key event was handled.
|
|
|
|
|
+ */
|
|
|
|
|
+ public boolean dispatchMediaKeyEvent(KeyEvent event) {
|
|
|
|
|
+ return useController() && controller.dispatchMediaKeyEvent(event);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Returns whether the controller is currently visible. */
|
|
|
|
|
+ public boolean isControllerVisible() {
|
|
|
|
|
+ return controller != null && controller.isVisible();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Shows the playback controls. Does nothing if playback controls are disabled.
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>The playback controls are automatically hidden during playback after {{@link
|
|
|
|
|
+ * #getControllerShowTimeoutMs()}}. They are shown indefinitely when playback has not started yet,
|
|
|
|
|
+ * is paused, has ended or failed.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void showController() {
|
|
|
|
|
+ showController(shouldShowControllerIndefinitely());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Hides the playback controls. Does nothing if playback controls are disabled. */
|
|
|
|
|
+ public void hideController() {
|
|
|
|
|
+ if (controller != null) {
|
|
|
|
|
+ controller.hide();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Returns the playback controls timeout. The playback controls are automatically hidden after
|
|
|
|
|
+ * this duration of time has elapsed without user input and with playback or buffering in
|
|
|
|
|
+ * progress.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return The timeout in milliseconds. A non-positive value will cause the controller to remain
|
|
|
|
|
+ * visible indefinitely.
|
|
|
|
|
+ */
|
|
|
|
|
+ public int getControllerShowTimeoutMs() {
|
|
|
|
|
+ return controllerShowTimeoutMs;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets the playback controls timeout. The playback controls are automatically hidden after this
|
|
|
|
|
+ * duration of time has elapsed without user input and with playback or buffering in progress.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param controllerShowTimeoutMs The timeout in milliseconds. A non-positive value will cause the
|
|
|
|
|
+ * controller to remain visible indefinitely.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setControllerShowTimeoutMs(int controllerShowTimeoutMs) {
|
|
|
|
|
+ Assertions.checkStateNotNull(controller);
|
|
|
|
|
+ this.controllerShowTimeoutMs = controllerShowTimeoutMs;
|
|
|
|
|
+ if (controller.isVisible()) {
|
|
|
|
|
+ // Update the controller's timeout if necessary.
|
|
|
|
|
+ showController();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Returns whether the playback controls are hidden by touch events. */
|
|
|
|
|
+ public boolean getControllerHideOnTouch() {
|
|
|
|
|
+ return controllerHideOnTouch;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets whether the playback controls are hidden by touch events.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param controllerHideOnTouch Whether the playback controls are hidden by touch events.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setControllerHideOnTouch(boolean controllerHideOnTouch) {
|
|
|
|
|
+ Assertions.checkStateNotNull(controller);
|
|
|
|
|
+ this.controllerHideOnTouch = controllerHideOnTouch;
|
|
|
|
|
+ updateContentDescription();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Returns whether the playback controls are automatically shown when playback starts, pauses,
|
|
|
|
|
+ * ends, or fails. If set to false, the playback controls can be manually operated with {@link
|
|
|
|
|
+ * #showController()} and {@link #hideController()}.
|
|
|
|
|
+ */
|
|
|
|
|
+ public boolean getControllerAutoShow() {
|
|
|
|
|
+ return controllerAutoShow;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets whether the playback controls are automatically shown when playback starts, pauses, ends,
|
|
|
|
|
+ * or fails. If set to false, the playback controls can be manually operated with {@link
|
|
|
|
|
+ * #showController()} and {@link #hideController()}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param controllerAutoShow Whether the playback controls are allowed to show automatically.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setControllerAutoShow(boolean controllerAutoShow) {
|
|
|
|
|
+ this.controllerAutoShow = controllerAutoShow;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets whether the playback controls are hidden when ads are playing. Controls are always shown
|
|
|
|
|
+ * during ads if they are enabled and the player is paused.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param controllerHideDuringAds Whether the playback controls are hidden when ads are playing.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setControllerHideDuringAds(boolean controllerHideDuringAds) {
|
|
|
|
|
+ this.controllerHideDuringAds = controllerHideDuringAds;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets the {@link PlayerControlView.VisibilityListener}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param listener The listener to be notified about visibility changes, or null to remove the
|
|
|
|
|
+ * current listener.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setControllerVisibilityListener(
|
|
|
|
|
+ @Nullable PlayerControlView.VisibilityListener listener) {
|
|
|
|
|
+ Assertions.checkStateNotNull(controller);
|
|
|
|
|
+ if (this.controllerVisibilityListener == listener) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.controllerVisibilityListener != null) {
|
|
|
|
|
+ controller.removeVisibilityListener(this.controllerVisibilityListener);
|
|
|
|
|
+ }
|
|
|
|
|
+ this.controllerVisibilityListener = listener;
|
|
|
|
|
+ if (listener != null) {
|
|
|
|
|
+ controller.addVisibilityListener(listener);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets whether the rewind button is shown.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param showRewindButton Whether the rewind button is shown.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setShowRewindButton(boolean showRewindButton) {
|
|
|
|
|
+ Assertions.checkStateNotNull(controller);
|
|
|
|
|
+ controller.setShowRewindButton(showRewindButton);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets whether the fast forward button is shown.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param showFastForwardButton Whether the fast forward button is shown.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setShowFastForwardButton(boolean showFastForwardButton) {
|
|
|
|
|
+ Assertions.checkStateNotNull(controller);
|
|
|
|
|
+ controller.setShowFastForwardButton(showFastForwardButton);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets whether the previous button is shown.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param showPreviousButton Whether the previous button is shown.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setShowPreviousButton(boolean showPreviousButton) {
|
|
|
|
|
+ Assertions.checkStateNotNull(controller);
|
|
|
|
|
+ controller.setShowPreviousButton(showPreviousButton);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets whether the next button is shown.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param showNextButton Whether the next button is shown.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setShowNextButton(boolean showNextButton) {
|
|
|
|
|
+ Assertions.checkStateNotNull(controller);
|
|
|
|
|
+ controller.setShowNextButton(showNextButton);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets which repeat toggle modes are enabled.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param repeatToggleModes A set of {@link RepeatModeUtil.RepeatToggleModes}.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) {
|
|
|
|
|
+ Assertions.checkStateNotNull(controller);
|
|
|
|
|
+ controller.setRepeatToggleModes(repeatToggleModes);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets whether the shuffle button is shown.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param showShuffleButton Whether the shuffle button is shown.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setShowShuffleButton(boolean showShuffleButton) {
|
|
|
|
|
+ Assertions.checkStateNotNull(controller);
|
|
|
|
|
+ controller.setShowShuffleButton(showShuffleButton);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets whether the time bar should show all windows, as opposed to just the current one.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param showMultiWindowTimeBar Whether to show all windows.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) {
|
|
|
|
|
+ Assertions.checkStateNotNull(controller);
|
|
|
|
|
+ controller.setShowMultiWindowTimeBar(showMultiWindowTimeBar);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets the millisecond positions of extra ad markers relative to the start of the window (or
|
|
|
|
|
+ * timeline, if in multi-window mode) and whether each extra ad has been played or not. The
|
|
|
|
|
+ * markers are shown in addition to any ad markers for ads in the player's timeline.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param extraAdGroupTimesMs The millisecond timestamps of the extra ad markers to show, or
|
|
|
|
|
+ * {@code null} to show no extra ad markers.
|
|
|
|
|
+ * @param extraPlayedAdGroups Whether each ad has been played, or {@code null} to show no extra ad
|
|
|
|
|
+ * markers.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setExtraAdGroupMarkers(
|
|
|
|
|
+ @Nullable long[] extraAdGroupTimesMs, @Nullable boolean[] extraPlayedAdGroups) {
|
|
|
|
|
+ Assertions.checkStateNotNull(controller);
|
|
|
|
|
+ controller.setExtraAdGroupMarkers(extraAdGroupTimesMs, extraPlayedAdGroups);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets the {@link AspectRatioFrameLayout.AspectRatioListener}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param listener The listener to be notified about aspect ratios changes of the video content or
|
|
|
|
|
+ * the content frame.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setAspectRatioListener(
|
|
|
|
|
+ @Nullable AspectRatioFrameLayout.AspectRatioListener listener) {
|
|
|
|
|
+ Assertions.checkStateNotNull(contentFrame);
|
|
|
|
|
+ contentFrame.setAspectRatioListener(listener);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Gets the view onto which video is rendered. This is a:
|
|
|
|
|
+ *
|
|
|
|
|
+ * <ul>
|
|
|
|
|
+ * <li>{@link SurfaceView} by default, or if the {@code surface_type} attribute is set to {@code
|
|
|
|
|
+ * surface_view}.
|
|
|
|
|
+ * <li>{@link TextureView} if {@code surface_type} is {@code texture_view}.
|
|
|
|
|
+ * <li>{@code SphericalGLSurfaceView} if {@code surface_type} is {@code
|
|
|
|
|
+ * spherical_gl_surface_view}.
|
|
|
|
|
+ * <li>{@code VideoDecoderGLSurfaceView} if {@code surface_type} is {@code
|
|
|
|
|
+ * video_decoder_gl_surface_view}.
|
|
|
|
|
+ * <li>{@code null} if {@code surface_type} is {@code none}.
|
|
|
|
|
+ * </ul>
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return The {@link SurfaceView}, {@link TextureView}, {@code SphericalGLSurfaceView}, {@code
|
|
|
|
|
+ * VideoDecoderGLSurfaceView} or {@code null}.
|
|
|
|
|
+ */
|
|
|
|
|
+ @Nullable
|
|
|
|
|
+ public View getVideoSurfaceView() {
|
|
|
|
|
+ return surfaceView;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Gets the overlay {@link FrameLayout}, which can be populated with UI elements to show on top of
|
|
|
|
|
+ * the player.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return The overlay {@link FrameLayout}, or {@code null} if the layout has been customized and
|
|
|
|
|
+ * the overlay is not present.
|
|
|
|
|
+ */
|
|
|
|
|
+ @Nullable
|
|
|
|
|
+ public FrameLayout getOverlayFrameLayout() {
|
|
|
|
|
+ return overlayFrameLayout;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Gets the {@link SubtitleView}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return The {@link SubtitleView}, or {@code null} if the layout has been customized and the
|
|
|
|
|
+ * subtitle view is not present.
|
|
|
|
|
+ */
|
|
|
|
|
+ @Nullable
|
|
|
|
|
+ public SubtitleView getSubtitleView() {
|
|
|
|
|
+ return subtitleView;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public boolean performClick() {
|
|
|
|
|
+ toggleControllerVisibility();
|
|
|
|
|
+ return super.performClick();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public boolean onTrackballEvent(MotionEvent ev) {
|
|
|
|
|
+ if (!useController() || player == null) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ maybeShowController(true);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Should be called when the player is visible to the user, if the {@code surface_type} extends
|
|
|
|
|
+ * {@link GLSurfaceView}. It is the counterpart to {@link #onPause()}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>This method should typically be called in {@code Activity.onStart()}, or {@code
|
|
|
|
|
+ * Activity.onResume()} for API versions <= 23.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void onResume() {
|
|
|
|
|
+ if (surfaceView instanceof GLSurfaceView) {
|
|
|
|
|
+ ((GLSurfaceView) surfaceView).onResume();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Should be called when the player is no longer visible to the user, if the {@code surface_type}
|
|
|
|
|
+ * extends {@link GLSurfaceView}. It is the counterpart to {@link #onResume()}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>This method should typically be called in {@code Activity.onStop()}, or {@code
|
|
|
|
|
+ * Activity.onPause()} for API versions <= 23.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void onPause() {
|
|
|
|
|
+ if (surfaceView instanceof GLSurfaceView) {
|
|
|
|
|
+ ((GLSurfaceView) surfaceView).onPause();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Called when there's a change in the desired aspect ratio of the content frame. The default
|
|
|
|
|
+ * implementation sets the aspect ratio of the content frame to the specified value.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param contentFrame The content frame, or {@code null}.
|
|
|
|
|
+ * @param aspectRatio The aspect ratio to apply.
|
|
|
|
|
+ */
|
|
|
|
|
+ protected void onContentAspectRatioChanged(
|
|
|
|
|
+ @Nullable AspectRatioFrameLayout contentFrame, float aspectRatio) {
|
|
|
|
|
+ if (contentFrame != null) {
|
|
|
|
|
+ contentFrame.setAspectRatio(aspectRatio);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // AdsLoader.AdViewProvider implementation.
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public ViewGroup getAdViewGroup() {
|
|
|
|
|
+ return Assertions.checkStateNotNull(
|
|
|
|
|
+ adOverlayFrameLayout, "exo_ad_overlay must be present for ad playback");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<AdOverlayInfo> getAdOverlayInfos() {
|
|
|
|
|
+ List<AdOverlayInfo> overlayViews = new ArrayList<>();
|
|
|
|
|
+ if (overlayFrameLayout != null) {
|
|
|
|
|
+ overlayViews.add(
|
|
|
|
|
+ new AdOverlayInfo(
|
|
|
|
|
+ overlayFrameLayout,
|
|
|
|
|
+ AdOverlayInfo.PURPOSE_NOT_VISIBLE,
|
|
|
|
|
+ /* detailedReason= */ "Transparent overlay does not impact viewability"));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (controller != null) {
|
|
|
|
|
+ overlayViews.add(new AdOverlayInfo(controller, AdOverlayInfo.PURPOSE_CONTROLS));
|
|
|
|
|
+ }
|
|
|
|
|
+ return ImmutableList.copyOf(overlayViews);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Internal methods.
|
|
|
|
|
+ private boolean useController() {
|
|
|
|
|
+ if (useController) {
|
|
|
|
|
+ Assertions.checkStateNotNull(controller);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean useArtwork() {
|
|
|
|
|
+ if (useArtwork) {
|
|
|
|
|
+ Assertions.checkStateNotNull(artworkView);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void toggleControllerVisibility() {
|
|
|
|
|
+ if (!useController() || player == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!controller.isVisible()) {
|
|
|
|
|
+ maybeShowController(true);
|
|
|
|
|
+ } else if (controllerHideOnTouch) {
|
|
|
|
|
+ controller.hide();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Shows the playback controls, but only if forced or shown indefinitely. */
|
|
|
|
|
+ private void maybeShowController(boolean isForced) {
|
|
|
|
|
+ if (isPlayingAd() && controllerHideDuringAds) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (useController()) {
|
|
|
|
|
+ boolean wasShowingIndefinitely = controller.isVisible() && controller.getShowTimeoutMs() <= 0;
|
|
|
|
|
+ boolean shouldShowIndefinitely = shouldShowControllerIndefinitely();
|
|
|
|
|
+ if (isForced || wasShowingIndefinitely || shouldShowIndefinitely) {
|
|
|
|
|
+ showController(shouldShowIndefinitely);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean shouldShowControllerIndefinitely() {
|
|
|
|
|
+ if (player == null) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ int playbackState = player.getPlaybackState();
|
|
|
|
|
+ return controllerAutoShow
|
|
|
|
|
+ && (playbackState == Player.STATE_IDLE
|
|
|
|
|
+ || playbackState == Player.STATE_ENDED
|
|
|
|
|
+ || !player.getPlayWhenReady());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void showController(boolean showIndefinitely) {
|
|
|
|
|
+ if (!useController()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ controller.setShowTimeoutMs(showIndefinitely ? 0 : controllerShowTimeoutMs);
|
|
|
|
|
+ controller.show();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean isPlayingAd() {
|
|
|
|
|
+ return player != null && player.isPlayingAd() && player.getPlayWhenReady();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void updateForCurrentTrackSelections(boolean isNewPlayer) {
|
|
|
|
|
+ @Nullable Player player = this.player;
|
|
|
|
|
+ if (player == null
|
|
|
|
|
+ || !player.isCommandAvailable(Player.COMMAND_GET_TRACKS)
|
|
|
|
|
+ || player.getCurrentTracks().isEmpty()) {
|
|
|
|
|
+ if (!keepContentOnPlayerReset) {
|
|
|
|
|
+ hideArtwork();
|
|
|
|
|
+ closeShutter();
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (isNewPlayer && !keepContentOnPlayerReset) {
|
|
|
|
|
+ // Hide any video from the previous player.
|
|
|
|
|
+ closeShutter();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (player.getCurrentTracks().isTypeSelected(C.TRACK_TYPE_VIDEO)) {
|
|
|
|
|
+ // Video enabled, so artwork must be hidden. If the shutter is closed, it will be opened
|
|
|
|
|
+ // in onRenderedFirstFrame().
|
|
|
|
|
+ hideArtwork();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Video disabled so the shutter must be closed.
|
|
|
|
|
+ closeShutter();
|
|
|
|
|
+ // Display artwork if enabled and available, else hide it.
|
|
|
|
|
+ if (useArtwork()) {
|
|
|
|
|
+ if (setArtworkFromMediaMetadata(player.getMediaMetadata())) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (setDrawableArtwork(defaultArtwork)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // Artwork disabled or unavailable.
|
|
|
|
|
+ hideArtwork();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void updateAspectRatio() {
|
|
|
|
|
+ VideoSize videoSize = player != null ? player.getVideoSize() : VideoSize.UNKNOWN;
|
|
|
|
|
+ int width = videoSize.width;
|
|
|
|
|
+ int height = videoSize.height;
|
|
|
|
|
+ int unappliedRotationDegrees = videoSize.unappliedRotationDegrees;
|
|
|
|
|
+ float videoAspectRatio =
|
|
|
|
|
+ (height == 0 || width == 0) ? 0 : (width * videoSize.pixelWidthHeightRatio) / height;
|
|
|
|
|
+
|
|
|
|
|
+ if (surfaceView instanceof TextureView) {
|
|
|
|
|
+ // Try to apply rotation transformation when our surface is a TextureView.
|
|
|
|
|
+ if (videoAspectRatio > 0
|
|
|
|
|
+ && (unappliedRotationDegrees == 90 || unappliedRotationDegrees == 270)) {
|
|
|
|
|
+ // We will apply a rotation 90/270 degree to the output texture of the TextureView.
|
|
|
|
|
+ // In this case, the output video's width and height will be swapped.
|
|
|
|
|
+ videoAspectRatio = 1 / videoAspectRatio;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (textureViewRotation != 0) {
|
|
|
|
|
+ surfaceView.removeOnLayoutChangeListener(componentListener);
|
|
|
|
|
+ }
|
|
|
|
|
+ textureViewRotation = unappliedRotationDegrees;
|
|
|
|
|
+ if (textureViewRotation != 0) {
|
|
|
|
|
+ // The texture view's dimensions might be changed after layout step.
|
|
|
|
|
+ // So add an OnLayoutChangeListener to apply rotation after layout step.
|
|
|
|
|
+ surfaceView.addOnLayoutChangeListener(componentListener);
|
|
|
|
|
+ }
|
|
|
|
|
+ applyTextureViewRotation((TextureView) surfaceView, textureViewRotation);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ onContentAspectRatioChanged(
|
|
|
|
|
+ contentFrame, surfaceViewIgnoresVideoAspectRatio ? 0 : videoAspectRatio);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean setArtworkFromMediaMetadata(MediaMetadata mediaMetadata) {
|
|
|
|
|
+ if (mediaMetadata.artworkData == null) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ Bitmap bitmap =
|
|
|
|
|
+ BitmapFactory.decodeByteArray(
|
|
|
|
|
+ mediaMetadata.artworkData, /* offset= */ 0, mediaMetadata.artworkData.length);
|
|
|
|
|
+ return setDrawableArtwork(new BitmapDrawable(getResources(), bitmap));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean setDrawableArtwork(@Nullable Drawable drawable) {
|
|
|
|
|
+ if (drawable != null) {
|
|
|
|
|
+ int drawableWidth = drawable.getIntrinsicWidth();
|
|
|
|
|
+ int drawableHeight = drawable.getIntrinsicHeight();
|
|
|
|
|
+ if (drawableWidth > 0 && drawableHeight > 0) {
|
|
|
|
|
+ float artworkAspectRatio = (float) drawableWidth / drawableHeight;
|
|
|
|
|
+ onContentAspectRatioChanged(contentFrame, artworkAspectRatio);
|
|
|
|
|
+ artworkView.setImageDrawable(drawable);
|
|
|
|
|
+ artworkView.setVisibility(VISIBLE);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void hideArtwork() {
|
|
|
|
|
+ if (artworkView != null) {
|
|
|
|
|
+ artworkView.setImageResource(android.R.color.transparent); // Clears any bitmap reference.
|
|
|
|
|
+ artworkView.setVisibility(INVISIBLE);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void closeShutter() {
|
|
|
|
|
+ if (shutterView != null) {
|
|
|
|
|
+ shutterView.setVisibility(View.VISIBLE);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void updateBuffering() {
|
|
|
|
|
+ if (bufferingView != null) {
|
|
|
|
|
+ boolean showBufferingSpinner =
|
|
|
|
|
+ player != null
|
|
|
|
|
+ && player.getPlaybackState() == Player.STATE_BUFFERING
|
|
|
|
|
+ && (showBuffering == SHOW_BUFFERING_ALWAYS
|
|
|
|
|
+ || (showBuffering == SHOW_BUFFERING_WHEN_PLAYING && player.getPlayWhenReady()));
|
|
|
|
|
+ bufferingView.setVisibility(showBufferingSpinner ? View.VISIBLE : View.GONE);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void updateErrorMessage() {
|
|
|
|
|
+ if (errorMessageView != null) {
|
|
|
|
|
+ if (customErrorMessage != null) {
|
|
|
|
|
+ errorMessageView.setText(customErrorMessage);
|
|
|
|
|
+ errorMessageView.setVisibility(View.VISIBLE);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ @Nullable PlaybackException error = player != null ? player.getPlayerError() : null;
|
|
|
|
|
+ if (error != null && errorMessageProvider != null) {
|
|
|
|
|
+ CharSequence errorMessage = errorMessageProvider.getErrorMessage(error).second;
|
|
|
|
|
+ errorMessageView.setText(errorMessage);
|
|
|
|
|
+ errorMessageView.setVisibility(View.VISIBLE);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ errorMessageView.setVisibility(View.GONE);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void updateContentDescription() {
|
|
|
|
|
+ if (controller == null || !useController) {
|
|
|
|
|
+ setContentDescription(/* contentDescription= */ null);
|
|
|
|
|
+ } else if (controller.getVisibility() == View.VISIBLE) {
|
|
|
|
|
+ setContentDescription(
|
|
|
|
|
+ /* contentDescription= */ controllerHideOnTouch
|
|
|
|
|
+ ? getResources().getString(R.string.exo_controls_hide)
|
|
|
|
|
+ : null);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setContentDescription(
|
|
|
|
|
+ /* contentDescription= */ getResources().getString(R.string.exo_controls_show));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void updateControllerVisibility() {
|
|
|
|
|
+ if (isPlayingAd() && controllerHideDuringAds) {
|
|
|
|
|
+ hideController();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ maybeShowController(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @RequiresApi(23)
|
|
|
|
|
+ private static void configureEditModeLogoV23(Resources resources, ImageView logo) {
|
|
|
|
|
+ logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo, null));
|
|
|
|
|
+ logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color, null));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void configureEditModeLogo(Resources resources, ImageView logo) {
|
|
|
|
|
+ logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo));
|
|
|
|
|
+ logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @SuppressWarnings("ResourceType")
|
|
|
|
|
+ private static void setResizeModeRaw(AspectRatioFrameLayout aspectRatioFrame, int resizeMode) {
|
|
|
|
|
+ aspectRatioFrame.setResizeMode(resizeMode);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Applies a texture rotation to a {@link TextureView}. */
|
|
|
|
|
+ private static void applyTextureViewRotation(TextureView textureView, int textureViewRotation) {
|
|
|
|
|
+ Matrix transformMatrix = new Matrix();
|
|
|
|
|
+ float textureViewWidth = textureView.getWidth();
|
|
|
|
|
+ float textureViewHeight = textureView.getHeight();
|
|
|
|
|
+ if (textureViewWidth != 0 && textureViewHeight != 0 && textureViewRotation != 0) {
|
|
|
|
|
+ float pivotX = textureViewWidth / 2;
|
|
|
|
|
+ float pivotY = textureViewHeight / 2;
|
|
|
|
|
+ transformMatrix.postRotate(textureViewRotation, pivotX, pivotY);
|
|
|
|
|
+
|
|
|
|
|
+ // After rotation, scale the rotated texture to fit the TextureView size.
|
|
|
|
|
+ RectF originalTextureRect = new RectF(0, 0, textureViewWidth, textureViewHeight);
|
|
|
|
|
+ RectF rotatedTextureRect = new RectF();
|
|
|
|
|
+ transformMatrix.mapRect(rotatedTextureRect, originalTextureRect);
|
|
|
|
|
+ transformMatrix.postScale(
|
|
|
|
|
+ textureViewWidth / rotatedTextureRect.width(),
|
|
|
|
|
+ textureViewHeight / rotatedTextureRect.height(),
|
|
|
|
|
+ pivotX,
|
|
|
|
|
+ pivotY);
|
|
|
|
|
+ }
|
|
|
|
|
+ textureView.setTransform(transformMatrix);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @SuppressLint("InlinedApi")
|
|
|
|
|
+ private boolean isDpadKey(int keyCode) {
|
|
|
|
|
+ return keyCode == KeyEvent.KEYCODE_DPAD_UP
|
|
|
|
|
+ || keyCode == KeyEvent.KEYCODE_DPAD_UP_RIGHT
|
|
|
|
|
+ || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
|
|
|
|
|
+ || keyCode == KeyEvent.KEYCODE_DPAD_DOWN_RIGHT
|
|
|
|
|
+ || keyCode == KeyEvent.KEYCODE_DPAD_DOWN
|
|
|
|
|
+ || keyCode == KeyEvent.KEYCODE_DPAD_DOWN_LEFT
|
|
|
|
|
+ || keyCode == KeyEvent.KEYCODE_DPAD_LEFT
|
|
|
|
|
+ || keyCode == KeyEvent.KEYCODE_DPAD_UP_LEFT
|
|
|
|
|
+ || keyCode == KeyEvent.KEYCODE_DPAD_CENTER;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private final class ComponentListener
|
|
|
|
|
+ implements Player.Listener,
|
|
|
|
|
+ OnLayoutChangeListener,
|
|
|
|
|
+ OnClickListener,
|
|
|
|
|
+ PlayerControlView.VisibilityListener {
|
|
|
|
|
+
|
|
|
|
|
+ private final Period period;
|
|
|
|
|
+ private @Nullable Object lastPeriodUidWithTracks;
|
|
|
|
|
+
|
|
|
|
|
+ public ComponentListener() {
|
|
|
|
|
+ period = new Period();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Player.Listener implementation
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onCues(CueGroup cueGroup) {
|
|
|
|
|
+ if (subtitleView != null) {
|
|
|
|
|
+ subtitleView.setCues(cueGroup.cues);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onVideoSizeChanged(VideoSize videoSize) {
|
|
|
|
|
+ updateAspectRatio();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onRenderedFirstFrame() {
|
|
|
|
|
+ if (shutterView != null) {
|
|
|
|
|
+ shutterView.setVisibility(INVISIBLE);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onTracksChanged(Tracks tracks) {
|
|
|
|
|
+ // Suppress the update if transitioning to an unprepared period within the same window. This
|
|
|
|
|
+ // is necessary to avoid closing the shutter when such a transition occurs. See:
|
|
|
|
|
+ // https://github.com/google/ExoPlayer/issues/5507.
|
|
|
|
|
+ Player player = Assertions.checkNotNull(ZoomableExoPlayerView.this.player);
|
|
|
|
|
+ Timeline timeline = player.getCurrentTimeline();
|
|
|
|
|
+ if (timeline.isEmpty()) {
|
|
|
|
|
+ lastPeriodUidWithTracks = null;
|
|
|
|
|
+ } else if (!player.getCurrentTracks().isEmpty()) {
|
|
|
|
|
+ lastPeriodUidWithTracks =
|
|
|
|
|
+ timeline.getPeriod(player.getCurrentPeriodIndex(), period, /* setIds= */ true).uid;
|
|
|
|
|
+ } else if (lastPeriodUidWithTracks != null) {
|
|
|
|
|
+ int lastPeriodIndexWithTracks = timeline.getIndexOfPeriod(lastPeriodUidWithTracks);
|
|
|
|
|
+ if (lastPeriodIndexWithTracks != C.INDEX_UNSET) {
|
|
|
|
|
+ int lastWindowIndexWithTracks =
|
|
|
|
|
+ timeline.getPeriod(lastPeriodIndexWithTracks, period).windowIndex;
|
|
|
|
|
+ if (player.getCurrentMediaItemIndex() == lastWindowIndexWithTracks) {
|
|
|
|
|
+ // We're in the same media item. Suppress the update.
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ lastPeriodUidWithTracks = null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ updateForCurrentTrackSelections(/* isNewPlayer= */ false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onPlaybackStateChanged(@Player.State int playbackState) {
|
|
|
|
|
+ updateBuffering();
|
|
|
|
|
+ updateErrorMessage();
|
|
|
|
|
+ updateControllerVisibility();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onPlayWhenReadyChanged(
|
|
|
|
|
+ boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
|
|
|
|
|
+ updateBuffering();
|
|
|
|
|
+ updateControllerVisibility();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onPositionDiscontinuity(
|
|
|
|
|
+ Player.PositionInfo oldPosition,
|
|
|
|
|
+ Player.PositionInfo newPosition,
|
|
|
|
|
+ @DiscontinuityReason int reason) {
|
|
|
|
|
+ if (isPlayingAd() && controllerHideDuringAds) {
|
|
|
|
|
+ hideController();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // OnLayoutChangeListener implementation
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onLayoutChange(
|
|
|
|
|
+ View view,
|
|
|
|
|
+ int left,
|
|
|
|
|
+ int top,
|
|
|
|
|
+ int right,
|
|
|
|
|
+ int bottom,
|
|
|
|
|
+ int oldLeft,
|
|
|
|
|
+ int oldTop,
|
|
|
|
|
+ int oldRight,
|
|
|
|
|
+ int oldBottom) {
|
|
|
|
|
+ applyTextureViewRotation((TextureView) view, textureViewRotation);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // OnClickListener implementation
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onClick(View view) {
|
|
|
|
|
+ toggleControllerVisibility();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // PlayerControlView.VisibilityListener implementation
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onVisibilityChange(int visibility) {
|
|
|
|
|
+ updateContentDescription();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // THREEMA
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public boolean onTouchEvent(MotionEvent event) {
|
|
|
|
|
+ if (!useController() || player == null) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ switch (event.getAction()) {
|
|
|
|
|
+ case MotionEvent.ACTION_DOWN:
|
|
|
|
|
+ isTouching = true;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ case MotionEvent.ACTION_UP:
|
|
|
|
|
+ if (isTouching) {
|
|
|
|
|
+ isTouching = false;
|
|
|
|
|
+ performClick();
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ default:
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|