Fix some crashes / issues after player refactor

This commit is contained in:
Stypox 2022-04-09 10:48:34 +02:00
parent 76ced59b62
commit b3f99645a3
No known key found for this signature in database
GPG key ID: 4BDF1B40A49FDD23
8 changed files with 192 additions and 119 deletions

View file

@ -240,10 +240,6 @@ public final class VideoDetailFragment
playerUi.ifPresent(MainPlayerUi::toggleFullscreen);
}
if (playerIsNotStopped() && player.videoPlayerSelected()) {
addVideoPlayerView();
}
//noinspection SimplifyOptionalCallChains
if (playAfterConnect
|| (currentInfo != null
@ -335,6 +331,9 @@ public final class VideoDetailFragment
@Override
public void onResume() {
super.onResume();
if (DEBUG) {
Log.d(TAG, "onResume() called");
}
activity.sendBroadcast(new Intent(ACTION_VIDEO_FRAGMENT_RESUMED));
@ -1310,22 +1309,14 @@ public final class VideoDetailFragment
if (!isPlayerAvailable()) {
return;
}
final Optional<View> root = player.UIs().get(VideoPlayerUi.class)
.map(VideoPlayerUi::getBinding)
.map(ViewBinding::getRoot);
// Check if viewHolder already contains a child TODO TODO whaat
/*if (playerService != null
&& root.map(View::getParent).orElse(null) != binding.playerPlaceholder) {
playerService.removeViewFromParent();
}*/
setHeightThumbnail();
// Prevent from re-adding a view multiple times
if (root.isPresent() && root.get().getParent() == null) {
binding.playerPlaceholder.addView(root.get());
}
new Handler().post(() -> player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> {
playerUi.removeViewFromParent();
binding.playerPlaceholder.addView(playerUi.getBinding().getRoot());
playerUi.setupVideoSurfaceIfNeeded();
}));
}
private void removeVideoPlayerView() {
@ -1793,9 +1784,6 @@ public final class VideoDetailFragment
@Override
public void onViewCreated() {
// Video view can have elements visible from popup,
// We hide it here but once it ready the view will be shown in handleIntent()
getRoot().ifPresent(view -> view.setVisibility(View.GONE));
addVideoPlayerView();
}

View file

@ -485,6 +485,10 @@ public final class Player implements PlaybackListener, Listener {
// make sure UIs know whether a service is connected or not
UIs.call(PlayerUi::onFragmentListenerSet);
}
if (!exoPlayerIsNull()) {
UIs.call(PlayerUi::initPlayer);
UIs.call(PlayerUi::initPlayback);
}
}
private void initPlayback(@NonNull final PlayQueue queue,
@ -599,7 +603,7 @@ public final class Player implements PlaybackListener, Listener {
progressUpdateDisposable.set(null);
PicassoHelper.cancelTag(PicassoHelper.PLAYER_THUMBNAIL_TAG); // cancel thumbnail loading
UIs.call(PlayerUi::destroy);
UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object
}
public void setRecovery() {
@ -737,7 +741,7 @@ public final class Player implements PlaybackListener, Listener {
case Intent.ACTION_CONFIGURATION_CHANGED:
assureCorrectAppLanguage(service);
if (DEBUG) {
Log.d(TAG, "onConfigurationChanged() called");
Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received");
}
break;
case Intent.ACTION_HEADSET_PLUG: //FIXME

View file

@ -1,12 +1,12 @@
package org.schabi.newpipe.player.gesture
import android.app.Activity
import android.content.Context
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import android.widget.ProgressBar
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import org.schabi.newpipe.MainActivity
import org.schabi.newpipe.R
@ -29,8 +29,6 @@ import kotlin.math.min
class MainPlayerGestureListener(
private val playerUi: MainPlayerUi
) : BasePlayerGestureListener(playerUi), OnTouchListener {
private val maxVolume: Int = player.audioReactor.maxVolume
private var isMoving = false
override fun onTouch(v: View, event: MotionEvent): Boolean {
@ -41,11 +39,11 @@ class MainPlayerGestureListener(
}
return when (event.action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
v.parent.requestDisallowInterceptTouchEvent(playerUi.isFullscreen)
v.parent?.requestDisallowInterceptTouchEvent(playerUi.isFullscreen)
true
}
MotionEvent.ACTION_UP -> {
v.parent.requestDisallowInterceptTouchEvent(false)
v.parent?.requestDisallowInterceptTouchEvent(false)
false
}
else -> true
@ -68,14 +66,15 @@ class MainPlayerGestureListener(
private fun onScrollVolume(distanceY: Float) {
// If we just started sliding, change the progress bar to match the system volume
if (binding.volumeRelativeLayout.visibility != View.VISIBLE) {
val volumePercent: Float = player.audioReactor.volume / maxVolume.toFloat()
val volumePercent: Float =
player.audioReactor.volume / player.audioReactor.maxVolume.toFloat()
binding.volumeProgressBar.progress = (volumePercent * MAX_GESTURE_LENGTH).toInt()
}
binding.volumeProgressBar.incrementProgressBy(distanceY.toInt())
val currentProgressPercent: Float =
binding.volumeProgressBar.progress.toFloat() / MAX_GESTURE_LENGTH
val currentVolume = (maxVolume * currentProgressPercent).toInt()
val currentVolume = (player.audioReactor.maxVolume * currentProgressPercent).toInt()
player.audioReactor.volume = currentVolume
if (DEBUG) {
Log.d(TAG, "onScroll().volumeControl, currentVolume = $currentVolume")
@ -102,7 +101,7 @@ class MainPlayerGestureListener(
}
private fun onScrollBrightness(distanceY: Float) {
val parent: Activity = playerUi.parentActivity
val parent: AppCompatActivity = playerUi.parentActivity.orElse(null) ?: return
val window = parent.window
val layoutParams = window.attributes
val bar: ProgressBar = binding.brightnessProgressBar

View file

@ -13,12 +13,13 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAct
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Build;
import android.os.Handler;
import android.provider.Settings;
import android.util.DisplayMetrics;
@ -28,7 +29,6 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
@ -37,6 +37,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
@ -68,8 +69,9 @@ import org.schabi.newpipe.util.external_communication.KoreUtils;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
public final class MainPlayerUi extends VideoPlayerUi {
public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutChangeListener {
private static final String TAG = MainPlayerUi.class.getSimpleName();
private boolean isFullscreen = false;
@ -113,7 +115,6 @@ public final class MainPlayerUi extends VideoPlayerUi {
super.setupAfterIntent();
binding.getRoot().setVisibility(View.VISIBLE);
initVideoPlayer();
// Android TV: without it focus will frame the whole player
binding.playPauseButton.requestFocus();
@ -139,7 +140,8 @@ public final class MainPlayerUi extends VideoPlayerUi {
binding.segmentsButton.setOnClickListener(v -> onSegmentsClicked());
binding.addToPlaylistButton.setOnClickListener(v ->
player.onAddToPlaylistClicked(getParentActivity().getSupportFragmentManager()));
getParentActivity().map(FragmentActivity::getSupportFragmentManager)
.ifPresent(player::onAddToPlaylistClicked));
settingsContentObserver = new ContentObserver(new Handler()) {
@Override
@ -151,7 +153,20 @@ public final class MainPlayerUi extends VideoPlayerUi {
Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false,
settingsContentObserver);
binding.getRoot().addOnLayoutChangeListener(this::onLayoutChange);
binding.getRoot().addOnLayoutChangeListener(this);
}
@Override
protected void deinitListeners() {
super.deinitListeners();
binding.queueButton.setOnClickListener(null);
binding.segmentsButton.setOnClickListener(null);
binding.addToPlaylistButton.setOnClickListener(null);
context.getContentResolver().unregisterContentObserver(settingsContentObserver);
binding.getRoot().removeOnLayoutChangeListener(this);
}
@Override
@ -178,7 +193,6 @@ public final class MainPlayerUi extends VideoPlayerUi {
@Override
public void destroy() {
super.destroy();
context.getContentResolver().unregisterContentObserver(settingsContentObserver);
// Exit from fullscreen when user closes the player via notification
if (isFullscreen) {
@ -324,9 +338,10 @@ public final class MainPlayerUi extends VideoPlayerUi {
player.useVideoSource(false);
break;
case MINIMIZE_ON_EXIT_MODE_POPUP:
player.setRecovery();
NavigationHelper.playOnPopupPlayer(getParentActivity(),
player.getPlayQueue(), true);
getParentActivity().ifPresent(activity -> {
player.setRecovery();
NavigationHelper.playOnPopupPlayer(activity, player.getPlayQueue(), true);
});
break;
case MINIMIZE_ON_EXIT_MODE_NONE: default:
player.pause();
@ -385,14 +400,15 @@ public final class MainPlayerUi extends VideoPlayerUi {
@Override
public void showSystemUIPartially() {
if (isFullscreen) {
final Window window = getParentActivity().getWindow();
window.setStatusBarColor(Color.TRANSPARENT);
window.setNavigationBarColor(Color.TRANSPARENT);
final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
window.getDecorView().setSystemUiVisibility(visibility);
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
getParentActivity().map(Activity::getWindow).ifPresent(window -> {
window.setStatusBarColor(Color.TRANSPARENT);
window.setNavigationBarColor(Color.TRANSPARENT);
final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
window.getDecorView().setSystemUiVisibility(visibility);
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
});
}
}
@ -476,8 +492,9 @@ public final class MainPlayerUi extends VideoPlayerUi {
//region Gestures
@SuppressWarnings("checkstyle:ParameterNumber")
private void onLayoutChange(final View view, final int l, final int t, final int r, final int b,
final int ol, final int ot, final int or, final int ob) {
@Override
public void onLayoutChange(final View view, final int l, final int t, final int r, final int b,
final int ol, final int ot, final int or, final int ob) {
if (l != ol || t != ot || r != or || b != ob) {
// Use smaller value to be consistent between screen orientations
// (and to make usage easier)
@ -501,9 +518,8 @@ public final class MainPlayerUi extends VideoPlayerUi {
private void setInitialGestureValues() {
if (player.getAudioReactor() != null) {
final float currentVolumeNormalized =
(float) player.getAudioReactor().getVolume()
/ player.getAudioReactor().getMaxVolume();
final float currentVolumeNormalized = (float) player.getAudioReactor().getVolume()
/ player.getAudioReactor().getMaxVolume();
binding.volumeProgressBar.setProgress(
(int) (binding.volumeProgressBar.getMax() * currentVolumeNormalized));
}
@ -714,7 +730,7 @@ public final class MainPlayerUi extends VideoPlayerUi {
@Override
public void held(final PlayQueueItem item, final View view) {
@Nullable final PlayQueue playQueue = player.getPlayQueue();
@Nullable final AppCompatActivity parentActivity = getParentActivity();
@Nullable final AppCompatActivity parentActivity = getParentActivity().orElse(null);
if (playQueue != null && parentActivity != null && playQueue.indexOf(item) != -1) {
openPopupMenu(player.getPlayQueue(), item, view, true,
parentActivity.getSupportFragmentManager(), context);
@ -801,10 +817,15 @@ public final class MainPlayerUi extends VideoPlayerUi {
@Override
protected void onPlaybackSpeedClicked() {
final AppCompatActivity activity = getParentActivity().orElse(null);
if (activity == null) {
return;
}
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
player.getPlaybackSkipSilence(), (speed, pitch, skipSilence)
-> player.setPlaybackParameters(speed, pitch, skipSilence))
.show(getParentActivity().getSupportFragmentManager(), null);
.show(activity.getSupportFragmentManager(), null);
}
@Override
@ -876,15 +897,15 @@ public final class MainPlayerUi extends VideoPlayerUi {
}
isFullscreen = !isFullscreen;
if (!isFullscreen) {
// Apply window insets because Android will not do it when orientation changes
// from landscape to portrait (open vertical video to reproduce)
binding.playbackControlRoot.setPadding(0, 0, 0, 0);
} else {
if (isFullscreen) {
// Android needs tens milliseconds to send new insets but a user is able to see
// how controls changes it's position from `0` to `nav bar height` padding.
// So just hide the controls to hide this visual inconsistency
hideControls(0, 0);
} else {
// Apply window insets because Android will not do it when orientation changes
// from landscape to portrait (open vertical video to reproduce)
binding.playbackControlRoot.setPadding(0, 0, 0, 0);
}
fragmentListener.onFullscreenStateChanged(isFullscreen);
@ -924,14 +945,22 @@ public final class MainPlayerUi extends VideoPlayerUi {
return binding;
}
public AppCompatActivity getParentActivity() {
return (AppCompatActivity) ((ViewGroup) binding.getRoot().getParent()).getContext();
public Optional<AppCompatActivity> getParentActivity() {
final ViewParent rootParent = binding.getRoot().getParent();
if (rootParent instanceof ViewGroup) {
final Context activity = ((ViewGroup) rootParent).getContext();
if (activity instanceof AppCompatActivity) {
return Optional.of((AppCompatActivity) activity);
}
}
return Optional.empty();
}
public boolean isLandscape() {
// DisplayMetrics from activity context knows about MultiWindow feature
// while DisplayMetrics from app context doesn't
return DeviceUtils.isLandscape(getParentActivity());
return DeviceUtils.isLandscape(
getParentActivity().map(Context.class::cast).orElse(player.getService()));
}
//endregion
}

View file

@ -19,7 +19,6 @@ import org.schabi.newpipe.player.Player;
import java.util.List;
public abstract class PlayerUi {
private static final String TAG = PlayerUi.class.getSimpleName();
@NonNull protected Context context;
@NonNull protected Player player;

View file

@ -69,11 +69,9 @@ public final class PopupPlayerUi extends VideoPlayerUi {
@Override
public void setupAfterIntent() {
setupElementsVisibility();
binding.getRoot().setVisibility(View.VISIBLE);
super.setupAfterIntent();
initPopup();
initPopupCloseOverlay();
binding.playPauseButton.requestFocus();
}
@Override
@ -103,6 +101,7 @@ public final class PopupPlayerUi extends VideoPlayerUi {
binding.loadingPanel.setMinimumHeight(popupLayoutParams.height);
windowManager.addView(binding.getRoot(), popupLayoutParams);
setupVideoSurfaceIfNeeded(); // now there is a parent, we can setup video surface
// Popup doesn't have aspectRatio selector, using FIT automatically
setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT);
@ -304,25 +303,23 @@ public final class PopupPlayerUi extends VideoPlayerUi {
}
public void removePopupFromView() {
if (windowManager != null) {
// wrap in try-catch since it could sometimes generate errors randomly
try {
if (popupHasParent()) {
windowManager.removeView(binding.getRoot());
}
} catch (final IllegalArgumentException e) {
Log.w(TAG, "Failed to remove popup from window manager", e);
// wrap in try-catch since it could sometimes generate errors randomly
try {
if (popupHasParent()) {
windowManager.removeView(binding.getRoot());
}
} catch (final IllegalArgumentException e) {
Log.w(TAG, "Failed to remove popup from window manager", e);
}
try {
final boolean closeOverlayHasParent = closeOverlayBinding != null
&& closeOverlayBinding.getRoot().getParent() != null;
if (closeOverlayHasParent) {
windowManager.removeView(closeOverlayBinding.getRoot());
}
} catch (final IllegalArgumentException e) {
Log.w(TAG, "Failed to remove popup overlay from window manager", e);
try {
final boolean closeOverlayHasParent = closeOverlayBinding != null
&& closeOverlayBinding.getRoot().getParent() != null;
if (closeOverlayHasParent) {
windowManager.removeView(closeOverlayBinding.getRoot());
}
} catch (final IllegalArgumentException e) {
Log.w(TAG, "Failed to remove popup overlay from window manager", e);
}
}

View file

@ -32,7 +32,6 @@ import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Surface;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
@ -107,6 +106,7 @@ public abstract class VideoPlayerUi extends PlayerUi
protected PlayerBinding binding;
private final Handler controlsVisibilityHandler = new Handler();
@Nullable private SurfaceHolderCallback surfaceHolderCallback;
boolean surfaceIsSetup = false;
@Nullable private Bitmap thumbnail = null;
@ -130,6 +130,7 @@ public abstract class VideoPlayerUi extends PlayerUi
private GestureDetector gestureDetector;
private BasePlayerGestureListener playerGestureListener;
@Nullable private View.OnLayoutChangeListener onLayoutChangeListener = null;
@NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder =
new SeekbarPreviewThumbnailHolder();
@ -138,6 +139,7 @@ public abstract class VideoPlayerUi extends PlayerUi
@NonNull final PlayerBinding playerBinding) {
super(player);
binding = playerBinding;
setupFromView();
}
@ -222,8 +224,8 @@ public abstract class VideoPlayerUi extends PlayerUi
// PlaybackControlRoot already consumed window insets but we should pass them to
// player_overlays and fast_seek_overlay too. Without it they will be off-centered.
binding.playbackControlRoot.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
onLayoutChangeListener
= (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
binding.playerOverlays.setPadding(
v.getPaddingLeft(),
v.getPaddingTop(),
@ -240,7 +242,43 @@ public abstract class VideoPlayerUi extends PlayerUi
fastSeekParams.topMargin = -v.getPaddingBottom();
fastSeekParams.rightMargin = -v.getPaddingLeft();
fastSeekParams.bottomMargin = -v.getPaddingTop();
});
};
binding.playbackControlRoot.addOnLayoutChangeListener(onLayoutChangeListener);
}
protected void deinitListeners() {
binding.qualityTextView.setOnClickListener(null);
binding.playbackSpeed.setOnClickListener(null);
binding.playbackSeekBar.setOnSeekBarChangeListener(null);
binding.captionTextView.setOnClickListener(null);
binding.resizeTextView.setOnClickListener(null);
binding.playbackLiveSync.setOnClickListener(null);
binding.getRoot().setOnTouchListener(null);
playerGestureListener = null;
gestureDetector = null;
binding.repeatButton.setOnClickListener(null);
binding.shuffleButton.setOnClickListener(null);
binding.playPauseButton.setOnClickListener(null);
binding.playPreviousButton.setOnClickListener(null);
binding.playNextButton.setOnClickListener(null);
binding.moreOptionsButton.setOnClickListener(null);
binding.moreOptionsButton.setOnLongClickListener(null);
binding.share.setOnClickListener(null);
binding.share.setOnLongClickListener(null);
binding.fullScreenButton.setOnClickListener(null);
binding.screenRotationButton.setOnClickListener(null);
binding.playWithKodi.setOnClickListener(null);
binding.openInBrowser.setOnClickListener(null);
binding.playerCloseButton.setOnClickListener(null);
binding.switchMute.setOnClickListener(null);
ViewCompat.setOnApplyWindowInsetsListener(binding.itemsListPanel, null);
binding.playbackControlRoot.removeOnLayoutChangeListener(onLayoutChangeListener);
}
/**
@ -304,18 +342,25 @@ public abstract class VideoPlayerUi extends PlayerUi
playerGestureListener.doubleTapControls(binding.fastSeekOverlay);
}
public void deinitPlayerSeekOverlay() {
binding.fastSeekOverlay
.seekSecondsSupplier(null)
.performListener(null);
}
@Override
public void setupAfterIntent() {
super.setupAfterIntent();
setupElementsVisibility();
setupElementsSize(context.getResources());
binding.getRoot().setVisibility(View.VISIBLE);
binding.playPauseButton.requestFocus();
}
@Override
public void initPlayer() {
super.initPlayer();
setupVideoSurface();
setupFromView();
setupVideoSurfaceIfNeeded();
}
@Override
@ -331,7 +376,7 @@ public abstract class VideoPlayerUi extends PlayerUi
@Override
public void destroyPlayer() {
super.destroyPlayer();
cleanupVideoSurface();
clearVideoSurface();
}
@Override
@ -340,6 +385,8 @@ public abstract class VideoPlayerUi extends PlayerUi
if (binding != null) {
binding.endScreen.setImageBitmap(null);
}
deinitPlayerSeekOverlay();
deinitListeners();
}
protected void setupElementsVisibility() {
@ -1470,40 +1517,50 @@ public abstract class VideoPlayerUi extends PlayerUi
// SurfaceHolderCallback helpers
//////////////////////////////////////////////////////////////////////////*/
//region SurfaceHolderCallback helpers
private void setupVideoSurface() {
// make sure there is nothing left over from previous calls
cleanupVideoSurface();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23
surfaceHolderCallback = new SurfaceHolderCallback(context, player.getExoPlayer());
binding.surfaceView.getHolder().addCallback(surfaceHolderCallback);
final Surface surface = binding.surfaceView.getHolder().getSurface();
/**
* Connects the video surface to the exo player. This can be called anytime without the risk for
* issues to occur, since the player will run just fine when no surface is connected. Therefore
* the video surface will be setup only when all of these conditions are true: it is not already
* setup (this just prevents wasting resources to setup the surface again), there is an exo
* player, the root view is attached to a parent and the surface view is valid/unreleased (the
* latter two conditions prevent "The surface has been released" errors). So this function can
* be called many times and even while the UI is in unready states.
*/
public void setupVideoSurfaceIfNeeded() {
if (!surfaceIsSetup && player.getExoPlayer() != null
&& binding.getRoot().getParent() != null) {
// make sure there is nothing left over from previous calls
clearVideoSurface();
// ensure player is using an unreleased surface, which the surfaceView might not be
// when starting playback on background or during player switching
if (surface.isValid()) {
// initially set the surface manually otherwise
// onRenderedFirstFrame() will not be called
player.getExoPlayer().setVideoSurface(surface);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23
surfaceHolderCallback = new SurfaceHolderCallback(context, player.getExoPlayer());
binding.surfaceView.getHolder().addCallback(surfaceHolderCallback);
// ensure player is using an unreleased surface, which the surfaceView might not be
// when starting playback on background or during player switching
if (binding.surfaceView.getHolder().getSurface().isValid()) {
// initially set the surface manually otherwise
// onRenderedFirstFrame() will not be called
player.getExoPlayer().setVideoSurfaceHolder(binding.surfaceView.getHolder());
}
} else {
player.getExoPlayer().setVideoSurfaceView(binding.surfaceView);
}
} else {
player.getExoPlayer().setVideoSurfaceView(binding.surfaceView);
surfaceIsSetup = true;
}
}
private void cleanupVideoSurface() {
final Optional<ExoPlayer> exoPlayer = Optional.ofNullable(player.getExoPlayer());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23
if (surfaceHolderCallback != null) {
binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback);
surfaceHolderCallback.release();
surfaceHolderCallback = null;
}
exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurface(null));
} else {
exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurfaceView(null));
private void clearVideoSurface() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M // >=API23
&& surfaceHolderCallback != null) {
binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback);
surfaceHolderCallback.release();
surfaceHolderCallback = null;
}
Optional.ofNullable(player.getExoPlayer()).ifPresent(ExoPlayer::clearVideoSurface);
surfaceIsSetup = false;
}
//endregion

View file

@ -38,14 +38,14 @@ class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) :
private var performListener: PerformListener? = null
fun performListener(listener: PerformListener) = apply {
fun performListener(listener: PerformListener?) = apply {
performListener = listener
}
private var seekSecondsSupplier: () -> Int = { 0 }
fun seekSecondsSupplier(supplier: () -> Int) = apply {
seekSecondsSupplier = supplier
fun seekSecondsSupplier(supplier: (() -> Int)?) = apply {
seekSecondsSupplier = supplier ?: { 0 }
}
// Indicates whether this (double) tap is the first of a series