2017-01-11 05:51:45 -07:00
|
|
|
package com.brentvatne.exoplayer;
|
|
|
|
|
|
|
|
import android.annotation.TargetApi;
|
|
|
|
import android.content.Context;
|
2019-07-28 07:42:32 -06:00
|
|
|
import androidx.core.content.ContextCompat;
|
2017-01-11 05:51:45 -07:00
|
|
|
import android.util.AttributeSet;
|
2017-02-13 19:38:02 -07:00
|
|
|
import android.util.Log;
|
2022-07-05 15:58:30 -06:00
|
|
|
import android.util.TypedValue;
|
2017-01-11 05:51:45 -07:00
|
|
|
import android.view.Gravity;
|
|
|
|
import android.view.SurfaceView;
|
|
|
|
import android.view.TextureView;
|
|
|
|
import android.view.View;
|
|
|
|
import android.view.ViewGroup;
|
|
|
|
import android.widget.FrameLayout;
|
|
|
|
|
|
|
|
import com.google.android.exoplayer2.C;
|
2022-06-15 11:24:55 -06:00
|
|
|
import com.google.android.exoplayer2.PlaybackException;
|
2017-06-13 16:45:12 -06:00
|
|
|
import com.google.android.exoplayer2.PlaybackParameters;
|
2022-06-15 11:24:55 -06:00
|
|
|
import com.google.android.exoplayer2.Player;
|
|
|
|
import com.google.android.exoplayer2.ExoPlayer;
|
2017-01-11 05:51:45 -07:00
|
|
|
import com.google.android.exoplayer2.Timeline;
|
2022-06-15 11:24:55 -06:00
|
|
|
import com.google.android.exoplayer2.TracksInfo;
|
2017-01-11 05:51:45 -07:00
|
|
|
import com.google.android.exoplayer2.text.Cue;
|
|
|
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
|
|
|
import com.google.android.exoplayer2.ui.SubtitleView;
|
2022-06-15 11:24:55 -06:00
|
|
|
import com.google.android.exoplayer2.video.VideoSize;
|
2017-01-11 05:51:45 -07:00
|
|
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
@TargetApi(16)
|
|
|
|
public final class ExoPlayerView extends FrameLayout {
|
|
|
|
|
2018-06-08 01:01:13 -06:00
|
|
|
private View surfaceView;
|
2017-01-11 05:51:45 -07:00
|
|
|
private final View shutterView;
|
|
|
|
private final SubtitleView subtitleLayout;
|
|
|
|
private final AspectRatioFrameLayout layout;
|
|
|
|
private final ComponentListener componentListener;
|
2022-06-15 11:24:55 -06:00
|
|
|
private ExoPlayer player;
|
2018-06-08 01:01:13 -06:00
|
|
|
private Context context;
|
|
|
|
private ViewGroup.LayoutParams layoutParams;
|
|
|
|
|
2018-12-13 11:05:09 -07:00
|
|
|
private boolean useTextureView = true;
|
2022-02-14 18:17:22 -07:00
|
|
|
private boolean useSecureView = false;
|
2018-11-28 05:56:58 -07:00
|
|
|
private boolean hideShutterView = false;
|
2017-01-11 05:51:45 -07:00
|
|
|
|
|
|
|
public ExoPlayerView(Context context) {
|
|
|
|
this(context, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public ExoPlayerView(Context context, AttributeSet attrs) {
|
|
|
|
this(context, attrs, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
public ExoPlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
|
|
super(context, attrs, defStyleAttr);
|
|
|
|
|
2018-06-08 01:01:13 -06:00
|
|
|
this.context = context;
|
2017-01-11 05:51:45 -07:00
|
|
|
|
2018-06-08 01:01:13 -06:00
|
|
|
layoutParams = new ViewGroup.LayoutParams(
|
2017-01-11 05:51:45 -07:00
|
|
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
|
|
ViewGroup.LayoutParams.MATCH_PARENT);
|
|
|
|
|
|
|
|
componentListener = new ComponentListener();
|
|
|
|
|
|
|
|
FrameLayout.LayoutParams aspectRatioParams = new FrameLayout.LayoutParams(
|
|
|
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
|
|
FrameLayout.LayoutParams.MATCH_PARENT);
|
|
|
|
aspectRatioParams.gravity = Gravity.CENTER;
|
|
|
|
layout = new AspectRatioFrameLayout(context);
|
|
|
|
layout.setLayoutParams(aspectRatioParams);
|
|
|
|
|
|
|
|
shutterView = new View(getContext());
|
2018-06-08 01:01:13 -06:00
|
|
|
shutterView.setLayoutParams(layoutParams);
|
2017-01-11 05:51:45 -07:00
|
|
|
shutterView.setBackgroundColor(ContextCompat.getColor(context, android.R.color.black));
|
|
|
|
|
|
|
|
subtitleLayout = new SubtitleView(context);
|
2018-06-08 01:01:13 -06:00
|
|
|
subtitleLayout.setLayoutParams(layoutParams);
|
2017-01-11 05:51:45 -07:00
|
|
|
subtitleLayout.setUserDefaultStyle();
|
|
|
|
subtitleLayout.setUserDefaultTextSize();
|
|
|
|
|
2018-06-08 01:01:13 -06:00
|
|
|
updateSurfaceView();
|
2017-01-11 05:51:45 -07:00
|
|
|
|
2018-06-08 01:01:13 -06:00
|
|
|
layout.addView(shutterView, 1, layoutParams);
|
|
|
|
layout.addView(subtitleLayout, 2, layoutParams);
|
2017-01-11 05:51:45 -07:00
|
|
|
|
|
|
|
addViewInLayout(layout, 0, aspectRatioParams);
|
|
|
|
}
|
|
|
|
|
2021-05-07 02:37:57 -06:00
|
|
|
private void clearVideoView() {
|
|
|
|
if (surfaceView instanceof TextureView) {
|
|
|
|
player.clearVideoTextureView((TextureView) surfaceView);
|
|
|
|
} else if (surfaceView instanceof SurfaceView) {
|
|
|
|
player.clearVideoSurfaceView((SurfaceView) surfaceView);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-08 01:01:13 -06:00
|
|
|
private void setVideoView() {
|
|
|
|
if (surfaceView instanceof TextureView) {
|
|
|
|
player.setVideoTextureView((TextureView) surfaceView);
|
|
|
|
} else if (surfaceView instanceof SurfaceView) {
|
|
|
|
player.setVideoSurfaceView((SurfaceView) surfaceView);
|
|
|
|
}
|
|
|
|
}
|
2022-07-05 15:58:30 -06:00
|
|
|
public void setSubtitleStyle(SubtitleStyle style) {
|
|
|
|
// ensure we reset subtile style before reapplying it
|
|
|
|
subtitleLayout.setUserDefaultStyle();
|
|
|
|
subtitleLayout.setUserDefaultTextSize();
|
|
|
|
|
|
|
|
if (style.getFontSize() > 0) {
|
|
|
|
subtitleLayout.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, style.getFontSize());
|
|
|
|
}
|
|
|
|
subtitleLayout.setPadding(style.getPaddingLeft(), style.getPaddingTop(), style.getPaddingRight(), style.getPaddingBottom());
|
|
|
|
}
|
2018-06-08 01:01:13 -06:00
|
|
|
|
|
|
|
private void updateSurfaceView() {
|
2022-02-14 18:17:22 -07:00
|
|
|
View view;
|
|
|
|
if (!useTextureView || useSecureView) {
|
|
|
|
view = new SurfaceView(context);
|
|
|
|
if (useSecureView) {
|
|
|
|
((SurfaceView)view).setSecure(true);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
view = new TextureView(context);
|
|
|
|
}
|
2018-06-08 01:01:13 -06:00
|
|
|
view.setLayoutParams(layoutParams);
|
|
|
|
|
|
|
|
surfaceView = view;
|
|
|
|
if (layout.getChildAt(0) != null) {
|
|
|
|
layout.removeViewAt(0);
|
|
|
|
}
|
|
|
|
layout.addView(surfaceView, 0, layoutParams);
|
|
|
|
|
|
|
|
if (this.player != null) {
|
|
|
|
setVideoView();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-28 05:56:58 -07:00
|
|
|
private void updateShutterViewVisibility() {
|
|
|
|
shutterView.setVisibility(this.hideShutterView ? View.INVISIBLE : View.VISIBLE);
|
|
|
|
}
|
|
|
|
|
2017-01-11 05:51:45 -07:00
|
|
|
/**
|
2022-06-15 11:24:55 -06:00
|
|
|
* Set the {@link ExoPlayer} to use. The {@link ExoPlayer#addListener} method of the
|
|
|
|
* player will be called and previous
|
2017-01-11 05:51:45 -07:00
|
|
|
* assignments are overridden.
|
|
|
|
*
|
2022-06-15 11:24:55 -06:00
|
|
|
* @param player The {@link ExoPlayer} to use.
|
2017-01-11 05:51:45 -07:00
|
|
|
*/
|
2022-06-15 11:24:55 -06:00
|
|
|
public void setPlayer(ExoPlayer player) {
|
2017-01-11 05:51:45 -07:00
|
|
|
if (this.player == player) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (this.player != null) {
|
|
|
|
this.player.removeListener(componentListener);
|
2021-05-07 02:37:57 -06:00
|
|
|
clearVideoView();
|
2017-01-11 05:51:45 -07:00
|
|
|
}
|
|
|
|
this.player = player;
|
2022-03-03 16:57:21 -07:00
|
|
|
shutterView.setVisibility(this.hideShutterView ? View.INVISIBLE : View.VISIBLE);
|
2017-01-11 05:51:45 -07:00
|
|
|
if (player != null) {
|
2018-06-08 01:01:13 -06:00
|
|
|
setVideoView();
|
2017-01-11 05:51:45 -07:00
|
|
|
player.addListener(componentListener);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the resize mode which can be of value {@link ResizeMode.Mode}
|
|
|
|
*
|
|
|
|
* @param resizeMode The resize mode.
|
|
|
|
*/
|
|
|
|
public void setResizeMode(@ResizeMode.Mode int resizeMode) {
|
|
|
|
if (layout.getResizeMode() != resizeMode) {
|
|
|
|
layout.setResizeMode(resizeMode);
|
|
|
|
post(measureAndLayout);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the view onto which video is rendered. This is either a {@link SurfaceView} (default)
|
|
|
|
* or a {@link TextureView} if the {@code use_texture_view} view attribute has been set to true.
|
|
|
|
*
|
|
|
|
* @return either a {@link SurfaceView} or a {@link TextureView}.
|
|
|
|
*/
|
|
|
|
public View getVideoSurfaceView() {
|
|
|
|
return surfaceView;
|
|
|
|
}
|
|
|
|
|
2018-06-08 01:01:13 -06:00
|
|
|
public void setUseTextureView(boolean useTextureView) {
|
2018-12-13 11:05:09 -07:00
|
|
|
if (useTextureView != this.useTextureView) {
|
|
|
|
this.useTextureView = useTextureView;
|
|
|
|
updateSurfaceView();
|
|
|
|
}
|
2018-06-08 01:01:13 -06:00
|
|
|
}
|
|
|
|
|
2022-02-14 18:17:22 -07:00
|
|
|
public void useSecureView(boolean useSecureView) {
|
|
|
|
if (useSecureView != this.useSecureView) {
|
|
|
|
this.useSecureView = useSecureView;
|
|
|
|
updateSurfaceView();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-28 05:56:58 -07:00
|
|
|
public void setHideShutterView(boolean hideShutterView) {
|
|
|
|
this.hideShutterView = hideShutterView;
|
|
|
|
updateShutterViewVisibility();
|
|
|
|
}
|
|
|
|
|
2017-01-11 05:51:45 -07:00
|
|
|
private final Runnable measureAndLayout = new Runnable() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
measure(
|
|
|
|
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
|
|
|
|
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
|
|
|
|
layout(getLeft(), getTop(), getRight(), getBottom());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private void updateForCurrentTrackSelections() {
|
|
|
|
if (player == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
TrackSelectionArray selections = player.getCurrentTrackSelections();
|
|
|
|
for (int i = 0; i < selections.length; i++) {
|
|
|
|
if (player.getRendererType(i) == C.TRACK_TYPE_VIDEO && selections.get(i) != null) {
|
|
|
|
// Video enabled so artwork must be hidden. If the shutter is closed, it will be opened in
|
|
|
|
// onRenderedFirstFrame().
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Video disabled so the shutter must be closed.
|
2022-03-03 16:57:21 -07:00
|
|
|
shutterView.setVisibility(this.hideShutterView ? View.INVISIBLE : View.VISIBLE);
|
2017-01-11 05:51:45 -07:00
|
|
|
}
|
|
|
|
|
2020-06-30 11:00:16 -06:00
|
|
|
public void invalidateAspectRatio() {
|
|
|
|
// Resetting aspect ratio will force layout refresh on next video size changed
|
|
|
|
layout.invalidateAspectRatio();
|
|
|
|
}
|
|
|
|
|
2022-06-15 11:24:55 -06:00
|
|
|
private final class ComponentListener implements Player.Listener {
|
2017-01-11 05:51:45 -07:00
|
|
|
|
|
|
|
// TextRenderer.Output implementation
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onCues(List<Cue> cues) {
|
|
|
|
subtitleLayout.onCues(cues);
|
|
|
|
}
|
|
|
|
|
2022-06-15 11:24:55 -06:00
|
|
|
// ExoPlayer.VideoListener implementation
|
2017-01-11 05:51:45 -07:00
|
|
|
|
|
|
|
@Override
|
2022-06-15 11:24:55 -06:00
|
|
|
public void onVideoSizeChanged(VideoSize videoSize) {
|
2017-01-11 05:51:45 -07:00
|
|
|
boolean isInitialRatio = layout.getAspectRatio() == 0;
|
2022-06-15 11:24:55 -06:00
|
|
|
layout.setAspectRatio(videoSize.height == 0 ? 1 : (videoSize.width * videoSize.pixelWidthHeightRatio) / videoSize.height);
|
2017-01-11 05:51:45 -07:00
|
|
|
|
|
|
|
// React native workaround for measuring and layout on initial load.
|
|
|
|
if (isInitialRatio) {
|
|
|
|
post(measureAndLayout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onRenderedFirstFrame() {
|
|
|
|
shutterView.setVisibility(INVISIBLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExoPlayer.EventListener implementation
|
|
|
|
|
|
|
|
@Override
|
2022-06-15 11:24:55 -06:00
|
|
|
public void onIsLoadingChanged(boolean isLoading) {
|
2017-01-11 05:51:45 -07:00
|
|
|
// Do nothing.
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-06-15 11:24:55 -06:00
|
|
|
public void onPlaybackStateChanged(int playbackState) {
|
2017-01-11 05:51:45 -07:00
|
|
|
// Do nothing.
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-06-15 11:24:55 -06:00
|
|
|
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
|
2017-01-11 05:51:45 -07:00
|
|
|
// Do nothing.
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-06-15 11:24:55 -06:00
|
|
|
public void onPlayerError(PlaybackException e) {
|
2017-01-11 05:51:45 -07:00
|
|
|
// Do nothing.
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-06-15 11:24:55 -06:00
|
|
|
public void onPositionDiscontinuity(Player.PositionInfo oldPosition, Player.PositionInfo newPosition, int reason) {
|
2017-01-11 05:51:45 -07:00
|
|
|
// Do nothing.
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-06-15 11:24:55 -06:00
|
|
|
public void onTimelineChanged(Timeline timeline, int reason) {
|
|
|
|
// Do nothing.
|
2017-01-11 05:51:45 -07:00
|
|
|
}
|
|
|
|
|
2017-06-13 16:45:12 -06:00
|
|
|
@Override
|
2022-06-15 11:24:55 -06:00
|
|
|
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
|
|
|
updateForCurrentTrackSelections();
|
2017-06-13 16:45:12 -06:00
|
|
|
}
|
|
|
|
|
2018-04-03 11:19:04 -06:00
|
|
|
@Override
|
2022-06-15 11:24:55 -06:00
|
|
|
public void onPlaybackParametersChanged(PlaybackParameters params) {
|
|
|
|
// Do nothing
|
2018-04-03 11:19:04 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
|
|
|
|
// Do nothing.
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onRepeatModeChanged(int repeatMode) {
|
|
|
|
// Do nothing.
|
|
|
|
}
|
2017-01-11 05:51:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|