Fix some crashes / issues after player refactor
This commit is contained in:
parent
76ced59b62
commit
b3f99645a3
8 changed files with 192 additions and 119 deletions
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue