feat(exoplayerview): Migrate ExoPlayerView to kotlin (#4038)
This commit is contained in:
parent
d86adc52f3
commit
78f4f0480d
@ -1,334 +0,0 @@
|
||||
package com.brentvatne.exoplayer;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.media3.common.AdViewProvider;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Tracks;
|
||||
import androidx.media3.common.VideoSize;
|
||||
import androidx.media3.common.text.Cue;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.ui.SubtitleView;
|
||||
|
||||
import android.util.TypedValue;
|
||||
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.brentvatne.common.api.ResizeMode;
|
||||
import com.brentvatne.common.api.SubtitleStyle;
|
||||
import com.brentvatne.common.api.ViewType;
|
||||
import com.brentvatne.common.toolbox.DebugLog;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
|
||||
private final static String TAG = "ExoPlayerView";
|
||||
private View surfaceView;
|
||||
private final View shutterView;
|
||||
private final SubtitleView subtitleLayout;
|
||||
private final AspectRatioFrameLayout layout;
|
||||
private final ComponentListener componentListener;
|
||||
private ExoPlayer player;
|
||||
private final Context context;
|
||||
private final ViewGroup.LayoutParams layoutParams;
|
||||
private final FrameLayout adOverlayFrameLayout;
|
||||
|
||||
private @ViewType.ViewType int viewType = ViewType.VIEW_TYPE_SURFACE;
|
||||
private boolean hideShutterView = false;
|
||||
|
||||
private SubtitleStyle localStyle = new SubtitleStyle();
|
||||
|
||||
public ExoPlayerView(Context context) {
|
||||
super(context, null, 0);
|
||||
|
||||
this.context = context;
|
||||
|
||||
layoutParams = new ViewGroup.LayoutParams(
|
||||
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());
|
||||
shutterView.setLayoutParams(layoutParams);
|
||||
shutterView.setBackgroundColor(ContextCompat.getColor(context, android.R.color.black));
|
||||
|
||||
subtitleLayout = new SubtitleView(context);
|
||||
subtitleLayout.setLayoutParams(layoutParams);
|
||||
subtitleLayout.setUserDefaultStyle();
|
||||
subtitleLayout.setUserDefaultTextSize();
|
||||
|
||||
updateSurfaceView(viewType);
|
||||
|
||||
adOverlayFrameLayout = new FrameLayout(context);
|
||||
|
||||
layout.addView(shutterView, 1, layoutParams);
|
||||
if (localStyle.getSubtitlesFollowVideo()) {
|
||||
layout.addView(subtitleLayout, layoutParams);
|
||||
layout.addView(adOverlayFrameLayout, layoutParams);
|
||||
}
|
||||
|
||||
addViewInLayout(layout, 0, aspectRatioParams);
|
||||
if (!localStyle.getSubtitlesFollowVideo()) {
|
||||
addViewInLayout(subtitleLayout, 1, layoutParams);
|
||||
}
|
||||
}
|
||||
|
||||
public void showAds() {
|
||||
adOverlayFrameLayout.setVisibility(View.GONE);
|
||||
}
|
||||
public void hideAds() {
|
||||
adOverlayFrameLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void clearVideoView() {
|
||||
if (surfaceView instanceof TextureView) {
|
||||
player.clearVideoTextureView((TextureView) surfaceView);
|
||||
} else if (surfaceView instanceof SurfaceView) {
|
||||
player.clearVideoSurfaceView((SurfaceView) surfaceView);
|
||||
}
|
||||
}
|
||||
|
||||
private void setVideoView() {
|
||||
if (surfaceView instanceof TextureView) {
|
||||
player.setVideoTextureView((TextureView) surfaceView);
|
||||
} else if (surfaceView instanceof SurfaceView) {
|
||||
player.setVideoSurfaceView((SurfaceView) surfaceView);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
return player != null && player.isPlaying();
|
||||
}
|
||||
|
||||
public void setSubtitleStyle(SubtitleStyle style) {
|
||||
// ensure we reset subtitle 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());
|
||||
if (style.getOpacity() != 0) {
|
||||
subtitleLayout.setAlpha(style.getOpacity());
|
||||
subtitleLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
subtitleLayout.setVisibility(View.GONE);
|
||||
}
|
||||
if (localStyle.getSubtitlesFollowVideo() != style.getSubtitlesFollowVideo()) {
|
||||
// No need to manipulate layout if value didn't change
|
||||
if (style.getSubtitlesFollowVideo()) {
|
||||
removeViewInLayout(subtitleLayout);
|
||||
layout.addView(subtitleLayout, layoutParams);
|
||||
} else {
|
||||
layout.removeViewInLayout(subtitleLayout);
|
||||
addViewInLayout(subtitleLayout, 1, layoutParams, false);
|
||||
}
|
||||
requestLayout();
|
||||
}
|
||||
localStyle = style;
|
||||
}
|
||||
|
||||
public void setShutterColor(Integer color) {
|
||||
shutterView.setBackgroundColor(color);
|
||||
}
|
||||
|
||||
public void updateSurfaceView(@ViewType.ViewType int viewType) {
|
||||
this.viewType = viewType;
|
||||
boolean viewNeedRefresh = false;
|
||||
if (viewType == ViewType.VIEW_TYPE_SURFACE || viewType == ViewType.VIEW_TYPE_SURFACE_SECURE) {
|
||||
if (!(surfaceView instanceof SurfaceView)) {
|
||||
surfaceView = new SurfaceView(context);
|
||||
viewNeedRefresh = true;
|
||||
}
|
||||
((SurfaceView)surfaceView).setSecure(viewType == ViewType.VIEW_TYPE_SURFACE_SECURE);
|
||||
} else if (viewType == ViewType.VIEW_TYPE_TEXTURE) {
|
||||
if (!(surfaceView instanceof TextureView)) {
|
||||
surfaceView = new TextureView(context);
|
||||
viewNeedRefresh = true;
|
||||
}
|
||||
// Support opacity properly:
|
||||
((TextureView) surfaceView).setOpaque(false);
|
||||
} else {
|
||||
DebugLog.wtf(TAG, "wtf is this texture " + viewType);
|
||||
}
|
||||
if (viewNeedRefresh) {
|
||||
surfaceView.setLayoutParams(layoutParams);
|
||||
|
||||
if (layout.getChildAt(0) != null) {
|
||||
layout.removeViewAt(0);
|
||||
}
|
||||
layout.addView(surfaceView, 0, layoutParams);
|
||||
|
||||
if (this.player != null) {
|
||||
setVideoView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void hideShutterView() {
|
||||
shutterView.setVisibility(INVISIBLE);
|
||||
surfaceView.setAlpha(1);
|
||||
}
|
||||
|
||||
private void showShutterView() {
|
||||
shutterView.setVisibility(VISIBLE);
|
||||
surfaceView.setAlpha(0);
|
||||
}
|
||||
|
||||
public void updateShutterViewVisibility() {
|
||||
if (this.hideShutterView) {
|
||||
hideShutterView();
|
||||
} else {
|
||||
showShutterView();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLayout() {
|
||||
super.requestLayout();
|
||||
post(measureAndLayout);
|
||||
}
|
||||
|
||||
// AdsLoader.AdViewProvider implementation.
|
||||
|
||||
@Override
|
||||
public ViewGroup getAdViewGroup() {
|
||||
return Assertions.checkNotNull(adOverlayFrameLayout, "exo_ad_overlay must be present for ad playback");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ExoPlayer} to use. The {@link ExoPlayer#addListener} method of the
|
||||
* player will be called and previous
|
||||
* assignments are overridden.
|
||||
*
|
||||
* @param player The {@link ExoPlayer} to use.
|
||||
*/
|
||||
public void setPlayer(ExoPlayer player) {
|
||||
if (this.player == player) {
|
||||
return;
|
||||
}
|
||||
if (this.player != null) {
|
||||
this.player.removeListener(componentListener);
|
||||
clearVideoView();
|
||||
}
|
||||
this.player = player;
|
||||
|
||||
updateShutterViewVisibility();
|
||||
|
||||
if (player != null) {
|
||||
setVideoView();
|
||||
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 != null && layout.getResizeMode() != resizeMode) {
|
||||
layout.setResizeMode(resizeMode);
|
||||
post(measureAndLayout);
|
||||
}
|
||||
}
|
||||
|
||||
public void setHideShutterView(boolean hideShutterView) {
|
||||
this.hideShutterView = hideShutterView;
|
||||
updateShutterViewVisibility();
|
||||
}
|
||||
|
||||
private final Runnable measureAndLayout = () -> {
|
||||
measure(
|
||||
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
|
||||
layout(getLeft(), getTop(), getRight(), getBottom());
|
||||
};
|
||||
|
||||
private void updateForCurrentTrackSelections(Tracks tracks) {
|
||||
if (tracks == null) {
|
||||
return;
|
||||
}
|
||||
ImmutableList<Tracks.Group> groups = tracks.getGroups();
|
||||
for (Tracks.Group group: groups) {
|
||||
if (group.getType() == C.TRACK_TYPE_VIDEO && group.length > 0) {
|
||||
// get the first track of the group to identify aspect ratio
|
||||
Format format = group.getTrackFormat(0);
|
||||
|
||||
// There are weird cases when video height and width did not change with rotation so we need change aspect ration to fix it
|
||||
switch (format.rotationDegrees) {
|
||||
// update aspect ratio !
|
||||
case 90:
|
||||
case 270:
|
||||
layout.setVideoAspectRatio(format.width == 0 ? 1 : (format.height * format.pixelWidthHeightRatio) / format.width);
|
||||
break;
|
||||
default:
|
||||
layout.setVideoAspectRatio(format.height == 0 ? 1 : (format.width * format.pixelWidthHeightRatio) / format.height);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// no video tracks, in that case refresh shutterView visibility
|
||||
shutterView.setVisibility(this.hideShutterView ? View.INVISIBLE : View.VISIBLE);
|
||||
}
|
||||
|
||||
public void invalidateAspectRatio() {
|
||||
// Resetting aspect ratio will force layout refresh on next video size changed
|
||||
layout.invalidateAspectRatio();
|
||||
}
|
||||
|
||||
private final class ComponentListener implements Player.Listener {
|
||||
|
||||
@Override
|
||||
public void onCues(@NonNull List<Cue> cues) {
|
||||
subtitleLayout.setCues(cues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoSizeChanged(VideoSize videoSize) {
|
||||
boolean isInitialRatio = layout.getVideoAspectRatio() == 0;
|
||||
if (videoSize.height == 0 || videoSize.width == 0) {
|
||||
// When changing video track we receive an ghost state with height / width = 0
|
||||
// No need to resize the view in that case
|
||||
return;
|
||||
}
|
||||
layout.setVideoAspectRatio((videoSize.width * videoSize.pixelWidthHeightRatio) / videoSize.height);
|
||||
|
||||
// React native workaround for measuring and layout on initial load.
|
||||
if (isInitialRatio) {
|
||||
post(measureAndLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRenderedFirstFrame() {
|
||||
hideShutterView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksChanged(@NonNull Tracks tracks) {
|
||||
updateForCurrentTrackSelections(tracks);
|
||||
}
|
||||
}
|
||||
}
|
355
android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.kt
Normal file
355
android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.kt
Normal file
@ -0,0 +1,355 @@
|
||||
package com.brentvatne.exoplayer
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
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 androidx.core.content.ContextCompat
|
||||
import androidx.media3.common.AdViewProvider
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.Tracks
|
||||
import androidx.media3.common.VideoSize
|
||||
import androidx.media3.common.text.Cue
|
||||
import androidx.media3.common.util.Assertions
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.ui.SubtitleView
|
||||
import com.brentvatne.common.api.ResizeMode
|
||||
import com.brentvatne.common.api.SubtitleStyle
|
||||
import com.brentvatne.common.api.ViewType
|
||||
import com.brentvatne.common.toolbox.DebugLog
|
||||
|
||||
@UnstableApi
|
||||
class ExoPlayerView(private val context: Context) :
|
||||
FrameLayout(context, null, 0),
|
||||
AdViewProvider {
|
||||
|
||||
private var surfaceView: View? = null
|
||||
private var shutterView: View
|
||||
private var subtitleLayout: SubtitleView
|
||||
private var layout: AspectRatioFrameLayout
|
||||
private var componentListener: ComponentListener
|
||||
private var player: ExoPlayer? = null
|
||||
private var layoutParams: ViewGroup.LayoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
private var adOverlayFrameLayout: FrameLayout
|
||||
val isPlaying: Boolean
|
||||
get() = player != null && player?.isPlaying == true
|
||||
|
||||
@ViewType.ViewType
|
||||
private var viewType = ViewType.VIEW_TYPE_SURFACE
|
||||
private var hideShutterView = false
|
||||
|
||||
private var localStyle = SubtitleStyle()
|
||||
|
||||
init {
|
||||
componentListener = ComponentListener()
|
||||
|
||||
val aspectRatioParams = LayoutParams(
|
||||
LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.MATCH_PARENT
|
||||
)
|
||||
aspectRatioParams.gravity = Gravity.CENTER
|
||||
layout = AspectRatioFrameLayout(context)
|
||||
layout.layoutParams = aspectRatioParams
|
||||
|
||||
shutterView = View(context)
|
||||
shutterView.layoutParams = layoutParams
|
||||
shutterView.setBackgroundColor(ContextCompat.getColor(context, android.R.color.black))
|
||||
|
||||
subtitleLayout = SubtitleView(context)
|
||||
subtitleLayout.layoutParams = layoutParams
|
||||
subtitleLayout.setUserDefaultStyle()
|
||||
subtitleLayout.setUserDefaultTextSize()
|
||||
|
||||
updateSurfaceView(viewType)
|
||||
|
||||
adOverlayFrameLayout = FrameLayout(context)
|
||||
|
||||
layout.addView(shutterView, 1, layoutParams)
|
||||
if (localStyle.subtitlesFollowVideo) {
|
||||
layout.addView(subtitleLayout, layoutParams)
|
||||
layout.addView(adOverlayFrameLayout, layoutParams)
|
||||
}
|
||||
|
||||
addViewInLayout(layout, 0, aspectRatioParams)
|
||||
if (!localStyle.subtitlesFollowVideo) {
|
||||
addViewInLayout(subtitleLayout, 1, layoutParams)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearVideoView() {
|
||||
when (val view = surfaceView) {
|
||||
is TextureView -> player?.clearVideoTextureView(view)
|
||||
|
||||
is SurfaceView -> player?.clearVideoSurfaceView(view)
|
||||
|
||||
else -> {
|
||||
Log.w(
|
||||
"clearVideoView",
|
||||
"Unexpected surfaceView type: ${surfaceView?.javaClass?.name}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setVideoView() {
|
||||
when (val view = surfaceView) {
|
||||
is TextureView -> player?.setVideoTextureView(view)
|
||||
|
||||
is SurfaceView -> player?.setVideoSurfaceView(view)
|
||||
|
||||
else -> {
|
||||
Log.w(
|
||||
"setVideoView",
|
||||
"Unexpected surfaceView type: ${surfaceView?.javaClass?.name}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleStyle(style: SubtitleStyle) {
|
||||
// ensure we reset subtitle style before reapplying it
|
||||
subtitleLayout.setUserDefaultStyle()
|
||||
subtitleLayout.setUserDefaultTextSize()
|
||||
|
||||
if (style.fontSize > 0) {
|
||||
subtitleLayout.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, style.fontSize.toFloat())
|
||||
}
|
||||
subtitleLayout.setPadding(
|
||||
style.paddingLeft,
|
||||
style.paddingTop,
|
||||
style.paddingTop,
|
||||
style.paddingBottom
|
||||
)
|
||||
if (style.opacity != 0.0f) {
|
||||
subtitleLayout.alpha = style.opacity
|
||||
subtitleLayout.visibility = View.VISIBLE
|
||||
} else {
|
||||
subtitleLayout.visibility = View.GONE
|
||||
}
|
||||
if (localStyle.subtitlesFollowVideo != style.subtitlesFollowVideo) {
|
||||
// No need to manipulate layout if value didn't change
|
||||
if (style.subtitlesFollowVideo) {
|
||||
removeViewInLayout(subtitleLayout)
|
||||
layout.addView(subtitleLayout, layoutParams)
|
||||
} else {
|
||||
layout.removeViewInLayout(subtitleLayout)
|
||||
addViewInLayout(subtitleLayout, 1, layoutParams, false)
|
||||
}
|
||||
requestLayout()
|
||||
}
|
||||
localStyle = style
|
||||
}
|
||||
|
||||
fun setShutterColor(color: Int) {
|
||||
shutterView.setBackgroundColor(color)
|
||||
}
|
||||
|
||||
fun updateSurfaceView(@ViewType.ViewType viewType: Int) {
|
||||
this.viewType = viewType
|
||||
var viewNeedRefresh = false
|
||||
when (viewType) {
|
||||
ViewType.VIEW_TYPE_SURFACE, ViewType.VIEW_TYPE_SURFACE_SECURE -> {
|
||||
if (surfaceView !is SurfaceView) {
|
||||
surfaceView = SurfaceView(context)
|
||||
viewNeedRefresh = true
|
||||
}
|
||||
(surfaceView as SurfaceView).setSecure(viewType == ViewType.VIEW_TYPE_SURFACE_SECURE)
|
||||
}
|
||||
|
||||
ViewType.VIEW_TYPE_TEXTURE -> {
|
||||
if (surfaceView !is TextureView) {
|
||||
surfaceView = TextureView(context)
|
||||
viewNeedRefresh = true
|
||||
}
|
||||
// Support opacity properly:
|
||||
(surfaceView as TextureView).isOpaque = false
|
||||
}
|
||||
|
||||
else -> {
|
||||
DebugLog.wtf(TAG, "Unexpected texture view type: $viewType")
|
||||
}
|
||||
}
|
||||
|
||||
if (viewNeedRefresh) {
|
||||
surfaceView?.layoutParams = layoutParams
|
||||
|
||||
if (layout.getChildAt(0) != null) {
|
||||
layout.removeViewAt(0)
|
||||
}
|
||||
layout.addView(surfaceView, 0, layoutParams)
|
||||
|
||||
if (this.player != null) {
|
||||
setVideoView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideShutterView() {
|
||||
shutterView.setVisibility(INVISIBLE)
|
||||
surfaceView?.setAlpha(1f)
|
||||
}
|
||||
|
||||
private fun showShutterView() {
|
||||
shutterView.setVisibility(VISIBLE)
|
||||
surfaceView?.setAlpha(0f)
|
||||
}
|
||||
|
||||
fun showAds() {
|
||||
adOverlayFrameLayout.setVisibility(View.VISIBLE)
|
||||
}
|
||||
|
||||
fun hideAds() {
|
||||
adOverlayFrameLayout.setVisibility(View.GONE)
|
||||
}
|
||||
|
||||
fun updateShutterViewVisibility() {
|
||||
shutterView.visibility = if (this.hideShutterView) {
|
||||
View.INVISIBLE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
override fun requestLayout() {
|
||||
super.requestLayout()
|
||||
post(measureAndLayout)
|
||||
}
|
||||
|
||||
// AdsLoader.AdViewProvider implementation.
|
||||
override fun getAdViewGroup(): ViewGroup =
|
||||
Assertions.checkNotNull(
|
||||
adOverlayFrameLayout,
|
||||
"exo_ad_overlay must be present for ad playback"
|
||||
)
|
||||
|
||||
/**
|
||||
* Set the {@link ExoPlayer} to use. The {@link ExoPlayer#addListener} method of the
|
||||
* player will be called and previous
|
||||
* assignments are overridden.
|
||||
*
|
||||
* @param player The {@link ExoPlayer} to use.
|
||||
*/
|
||||
fun setPlayer(player: ExoPlayer?) {
|
||||
if (this.player == player) {
|
||||
return
|
||||
}
|
||||
if (this.player != null) {
|
||||
this.player!!.removeListener(componentListener)
|
||||
clearVideoView()
|
||||
}
|
||||
this.player = player
|
||||
|
||||
updateShutterViewVisibility()
|
||||
if (player != null) {
|
||||
setVideoView()
|
||||
player.addListener(componentListener)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the resize mode which can be of value {@link ResizeMode.Mode}
|
||||
*
|
||||
* @param resizeMode The resize mode.
|
||||
*/
|
||||
fun setResizeMode(@ResizeMode.Mode resizeMode: Int) {
|
||||
if (layout.resizeMode != resizeMode) {
|
||||
layout.resizeMode = resizeMode
|
||||
post(measureAndLayout)
|
||||
}
|
||||
}
|
||||
|
||||
fun setHideShutterView(hideShutterView: Boolean) {
|
||||
this.hideShutterView = hideShutterView
|
||||
updateShutterViewVisibility()
|
||||
}
|
||||
|
||||
private val measureAndLayout: Runnable = Runnable {
|
||||
measure(
|
||||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
||||
)
|
||||
layout(left, top, right, bottom)
|
||||
}
|
||||
|
||||
private fun updateForCurrentTrackSelections(tracks: Tracks?) {
|
||||
if (tracks == null) {
|
||||
return
|
||||
}
|
||||
val groups = tracks.groups
|
||||
|
||||
for (group in groups) {
|
||||
if (group.type == C.TRACK_TYPE_VIDEO && group.length > 0) {
|
||||
// get the first track of the group to identify aspect ratio
|
||||
val format = group.getTrackFormat(0)
|
||||
|
||||
// There are weird cases when video height and width did not change with rotation so we need change aspect ration to fix
|
||||
layout.videoAspectRatio = when (format.rotationDegrees) {
|
||||
// update aspect ratio !
|
||||
90, 270 -> if (format.width == 0) {
|
||||
1f
|
||||
} else {
|
||||
(format.height * format.pixelWidthHeightRatio) / format.width
|
||||
}
|
||||
|
||||
else -> if (format.height == 0) {
|
||||
1f
|
||||
} else {
|
||||
(format.width * format.pixelWidthHeightRatio) / format.height
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// no video tracks, in that case refresh shutterView visibility
|
||||
updateShutterViewVisibility()
|
||||
}
|
||||
|
||||
fun invalidateAspectRatio() {
|
||||
// Resetting aspect ratio will force layout refresh on next video size changed
|
||||
layout.invalidateAspectRatio()
|
||||
}
|
||||
|
||||
private inner class ComponentListener : Player.Listener {
|
||||
override fun onCues(cues: List<Cue>) {
|
||||
subtitleLayout.setCues(cues)
|
||||
}
|
||||
|
||||
override fun onVideoSizeChanged(videoSize: VideoSize) {
|
||||
val isInitialRatio = layout.videoAspectRatio == 0f
|
||||
if (videoSize.height == 0 || videoSize.width == 0) {
|
||||
// When changing video track we receive an ghost state with height / width = 0
|
||||
// No need to resize the view in that case
|
||||
return
|
||||
}
|
||||
layout.videoAspectRatio =
|
||||
((videoSize.width * videoSize.pixelWidthHeightRatio) / videoSize.height)
|
||||
|
||||
// React native workaround for measuring and layout on initial load.
|
||||
if (isInitialRatio) {
|
||||
post(measureAndLayout)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRenderedFirstFrame() {
|
||||
shutterView.visibility = INVISIBLE
|
||||
}
|
||||
|
||||
override fun onTracksChanged(tracks: Tracks) {
|
||||
updateForCurrentTrackSelections(tracks)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ExoPlayerView"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user