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);
|
playerUi.ifPresent(MainPlayerUi::toggleFullscreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerIsNotStopped() && player.videoPlayerSelected()) {
|
|
||||||
addVideoPlayerView();
|
|
||||||
}
|
|
||||||
|
|
||||||
//noinspection SimplifyOptionalCallChains
|
//noinspection SimplifyOptionalCallChains
|
||||||
if (playAfterConnect
|
if (playAfterConnect
|
||||||
|| (currentInfo != null
|
|| (currentInfo != null
|
||||||
|
@ -335,6 +331,9 @@ public final class VideoDetailFragment
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onResume() called");
|
||||||
|
}
|
||||||
|
|
||||||
activity.sendBroadcast(new Intent(ACTION_VIDEO_FRAGMENT_RESUMED));
|
activity.sendBroadcast(new Intent(ACTION_VIDEO_FRAGMENT_RESUMED));
|
||||||
|
|
||||||
|
@ -1310,22 +1309,14 @@ public final class VideoDetailFragment
|
||||||
if (!isPlayerAvailable()) {
|
if (!isPlayerAvailable()) {
|
||||||
return;
|
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();
|
setHeightThumbnail();
|
||||||
|
|
||||||
// Prevent from re-adding a view multiple times
|
// Prevent from re-adding a view multiple times
|
||||||
if (root.isPresent() && root.get().getParent() == null) {
|
new Handler().post(() -> player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> {
|
||||||
binding.playerPlaceholder.addView(root.get());
|
playerUi.removeViewFromParent();
|
||||||
}
|
binding.playerPlaceholder.addView(playerUi.getBinding().getRoot());
|
||||||
|
playerUi.setupVideoSurfaceIfNeeded();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeVideoPlayerView() {
|
private void removeVideoPlayerView() {
|
||||||
|
@ -1793,9 +1784,6 @@ public final class VideoDetailFragment
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated() {
|
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();
|
addVideoPlayerView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -485,6 +485,10 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
// make sure UIs know whether a service is connected or not
|
// make sure UIs know whether a service is connected or not
|
||||||
UIs.call(PlayerUi::onFragmentListenerSet);
|
UIs.call(PlayerUi::onFragmentListenerSet);
|
||||||
}
|
}
|
||||||
|
if (!exoPlayerIsNull()) {
|
||||||
|
UIs.call(PlayerUi::initPlayer);
|
||||||
|
UIs.call(PlayerUi::initPlayback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initPlayback(@NonNull final PlayQueue queue,
|
private void initPlayback(@NonNull final PlayQueue queue,
|
||||||
|
@ -599,7 +603,7 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
progressUpdateDisposable.set(null);
|
progressUpdateDisposable.set(null);
|
||||||
PicassoHelper.cancelTag(PicassoHelper.PLAYER_THUMBNAIL_TAG); // cancel thumbnail loading
|
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() {
|
public void setRecovery() {
|
||||||
|
@ -737,7 +741,7 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
case Intent.ACTION_CONFIGURATION_CHANGED:
|
case Intent.ACTION_CONFIGURATION_CHANGED:
|
||||||
assureCorrectAppLanguage(service);
|
assureCorrectAppLanguage(service);
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onConfigurationChanged() called");
|
Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Intent.ACTION_HEADSET_PLUG: //FIXME
|
case Intent.ACTION_HEADSET_PLUG: //FIXME
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package org.schabi.newpipe.player.gesture
|
package org.schabi.newpipe.player.gesture
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.OnTouchListener
|
import android.view.View.OnTouchListener
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import org.schabi.newpipe.MainActivity
|
import org.schabi.newpipe.MainActivity
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
|
@ -29,8 +29,6 @@ import kotlin.math.min
|
||||||
class MainPlayerGestureListener(
|
class MainPlayerGestureListener(
|
||||||
private val playerUi: MainPlayerUi
|
private val playerUi: MainPlayerUi
|
||||||
) : BasePlayerGestureListener(playerUi), OnTouchListener {
|
) : BasePlayerGestureListener(playerUi), OnTouchListener {
|
||||||
private val maxVolume: Int = player.audioReactor.maxVolume
|
|
||||||
|
|
||||||
private var isMoving = false
|
private var isMoving = false
|
||||||
|
|
||||||
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
||||||
|
@ -41,11 +39,11 @@ class MainPlayerGestureListener(
|
||||||
}
|
}
|
||||||
return when (event.action) {
|
return when (event.action) {
|
||||||
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
|
||||||
v.parent.requestDisallowInterceptTouchEvent(playerUi.isFullscreen)
|
v.parent?.requestDisallowInterceptTouchEvent(playerUi.isFullscreen)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
MotionEvent.ACTION_UP -> {
|
MotionEvent.ACTION_UP -> {
|
||||||
v.parent.requestDisallowInterceptTouchEvent(false)
|
v.parent?.requestDisallowInterceptTouchEvent(false)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
else -> true
|
else -> true
|
||||||
|
@ -68,14 +66,15 @@ class MainPlayerGestureListener(
|
||||||
private fun onScrollVolume(distanceY: Float) {
|
private fun onScrollVolume(distanceY: Float) {
|
||||||
// If we just started sliding, change the progress bar to match the system volume
|
// If we just started sliding, change the progress bar to match the system volume
|
||||||
if (binding.volumeRelativeLayout.visibility != View.VISIBLE) {
|
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.progress = (volumePercent * MAX_GESTURE_LENGTH).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.volumeProgressBar.incrementProgressBy(distanceY.toInt())
|
binding.volumeProgressBar.incrementProgressBy(distanceY.toInt())
|
||||||
val currentProgressPercent: Float =
|
val currentProgressPercent: Float =
|
||||||
binding.volumeProgressBar.progress.toFloat() / MAX_GESTURE_LENGTH
|
binding.volumeProgressBar.progress.toFloat() / MAX_GESTURE_LENGTH
|
||||||
val currentVolume = (maxVolume * currentProgressPercent).toInt()
|
val currentVolume = (player.audioReactor.maxVolume * currentProgressPercent).toInt()
|
||||||
player.audioReactor.volume = currentVolume
|
player.audioReactor.volume = currentVolume
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onScroll().volumeControl, currentVolume = $currentVolume")
|
Log.d(TAG, "onScroll().volumeControl, currentVolume = $currentVolume")
|
||||||
|
@ -102,7 +101,7 @@ class MainPlayerGestureListener(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onScrollBrightness(distanceY: Float) {
|
private fun onScrollBrightness(distanceY: Float) {
|
||||||
val parent: Activity = playerUi.parentActivity
|
val parent: AppCompatActivity = playerUi.parentActivity.orElse(null) ?: return
|
||||||
val window = parent.window
|
val window = parent.window
|
||||||
val layoutParams = window.attributes
|
val layoutParams = window.attributes
|
||||||
val bar: ProgressBar = binding.brightnessProgressBar
|
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.getTimeString;
|
||||||
import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked;
|
import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
|
@ -28,7 +29,6 @@ import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewParent;
|
import android.view.ViewParent;
|
||||||
import android.view.Window;
|
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
@ -37,6 +37,7 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.content.res.AppCompatResources;
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
@ -68,8 +69,9 @@ import org.schabi.newpipe.util.external_communication.KoreUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
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 static final String TAG = MainPlayerUi.class.getSimpleName();
|
||||||
|
|
||||||
private boolean isFullscreen = false;
|
private boolean isFullscreen = false;
|
||||||
|
@ -113,7 +115,6 @@ public final class MainPlayerUi extends VideoPlayerUi {
|
||||||
|
|
||||||
super.setupAfterIntent();
|
super.setupAfterIntent();
|
||||||
|
|
||||||
binding.getRoot().setVisibility(View.VISIBLE);
|
|
||||||
initVideoPlayer();
|
initVideoPlayer();
|
||||||
// Android TV: without it focus will frame the whole player
|
// Android TV: without it focus will frame the whole player
|
||||||
binding.playPauseButton.requestFocus();
|
binding.playPauseButton.requestFocus();
|
||||||
|
@ -139,7 +140,8 @@ public final class MainPlayerUi extends VideoPlayerUi {
|
||||||
binding.segmentsButton.setOnClickListener(v -> onSegmentsClicked());
|
binding.segmentsButton.setOnClickListener(v -> onSegmentsClicked());
|
||||||
|
|
||||||
binding.addToPlaylistButton.setOnClickListener(v ->
|
binding.addToPlaylistButton.setOnClickListener(v ->
|
||||||
player.onAddToPlaylistClicked(getParentActivity().getSupportFragmentManager()));
|
getParentActivity().map(FragmentActivity::getSupportFragmentManager)
|
||||||
|
.ifPresent(player::onAddToPlaylistClicked));
|
||||||
|
|
||||||
settingsContentObserver = new ContentObserver(new Handler()) {
|
settingsContentObserver = new ContentObserver(new Handler()) {
|
||||||
@Override
|
@Override
|
||||||
|
@ -151,7 +153,20 @@ public final class MainPlayerUi extends VideoPlayerUi {
|
||||||
Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false,
|
Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false,
|
||||||
settingsContentObserver);
|
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
|
@Override
|
||||||
|
@ -178,7 +193,6 @@ public final class MainPlayerUi extends VideoPlayerUi {
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
super.destroy();
|
super.destroy();
|
||||||
context.getContentResolver().unregisterContentObserver(settingsContentObserver);
|
|
||||||
|
|
||||||
// Exit from fullscreen when user closes the player via notification
|
// Exit from fullscreen when user closes the player via notification
|
||||||
if (isFullscreen) {
|
if (isFullscreen) {
|
||||||
|
@ -324,9 +338,10 @@ public final class MainPlayerUi extends VideoPlayerUi {
|
||||||
player.useVideoSource(false);
|
player.useVideoSource(false);
|
||||||
break;
|
break;
|
||||||
case MINIMIZE_ON_EXIT_MODE_POPUP:
|
case MINIMIZE_ON_EXIT_MODE_POPUP:
|
||||||
|
getParentActivity().ifPresent(activity -> {
|
||||||
player.setRecovery();
|
player.setRecovery();
|
||||||
NavigationHelper.playOnPopupPlayer(getParentActivity(),
|
NavigationHelper.playOnPopupPlayer(activity, player.getPlayQueue(), true);
|
||||||
player.getPlayQueue(), true);
|
});
|
||||||
break;
|
break;
|
||||||
case MINIMIZE_ON_EXIT_MODE_NONE: default:
|
case MINIMIZE_ON_EXIT_MODE_NONE: default:
|
||||||
player.pause();
|
player.pause();
|
||||||
|
@ -385,7 +400,7 @@ public final class MainPlayerUi extends VideoPlayerUi {
|
||||||
@Override
|
@Override
|
||||||
public void showSystemUIPartially() {
|
public void showSystemUIPartially() {
|
||||||
if (isFullscreen) {
|
if (isFullscreen) {
|
||||||
final Window window = getParentActivity().getWindow();
|
getParentActivity().map(Activity::getWindow).ifPresent(window -> {
|
||||||
window.setStatusBarColor(Color.TRANSPARENT);
|
window.setStatusBarColor(Color.TRANSPARENT);
|
||||||
window.setNavigationBarColor(Color.TRANSPARENT);
|
window.setNavigationBarColor(Color.TRANSPARENT);
|
||||||
final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
@ -393,6 +408,7 @@ public final class MainPlayerUi extends VideoPlayerUi {
|
||||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
|
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
|
||||||
window.getDecorView().setSystemUiVisibility(visibility);
|
window.getDecorView().setSystemUiVisibility(visibility);
|
||||||
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,7 +492,8 @@ public final class MainPlayerUi extends VideoPlayerUi {
|
||||||
//region Gestures
|
//region Gestures
|
||||||
|
|
||||||
@SuppressWarnings("checkstyle:ParameterNumber")
|
@SuppressWarnings("checkstyle:ParameterNumber")
|
||||||
private void onLayoutChange(final View view, final int l, final int t, final int r, final int b,
|
@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) {
|
final int ol, final int ot, final int or, final int ob) {
|
||||||
if (l != ol || t != ot || r != or || b != ob) {
|
if (l != ol || t != ot || r != or || b != ob) {
|
||||||
// Use smaller value to be consistent between screen orientations
|
// Use smaller value to be consistent between screen orientations
|
||||||
|
@ -501,8 +518,7 @@ public final class MainPlayerUi extends VideoPlayerUi {
|
||||||
|
|
||||||
private void setInitialGestureValues() {
|
private void setInitialGestureValues() {
|
||||||
if (player.getAudioReactor() != null) {
|
if (player.getAudioReactor() != null) {
|
||||||
final float currentVolumeNormalized =
|
final float currentVolumeNormalized = (float) player.getAudioReactor().getVolume()
|
||||||
(float) player.getAudioReactor().getVolume()
|
|
||||||
/ player.getAudioReactor().getMaxVolume();
|
/ player.getAudioReactor().getMaxVolume();
|
||||||
binding.volumeProgressBar.setProgress(
|
binding.volumeProgressBar.setProgress(
|
||||||
(int) (binding.volumeProgressBar.getMax() * currentVolumeNormalized));
|
(int) (binding.volumeProgressBar.getMax() * currentVolumeNormalized));
|
||||||
|
@ -714,7 +730,7 @@ public final class MainPlayerUi extends VideoPlayerUi {
|
||||||
@Override
|
@Override
|
||||||
public void held(final PlayQueueItem item, final View view) {
|
public void held(final PlayQueueItem item, final View view) {
|
||||||
@Nullable final PlayQueue playQueue = player.getPlayQueue();
|
@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) {
|
if (playQueue != null && parentActivity != null && playQueue.indexOf(item) != -1) {
|
||||||
openPopupMenu(player.getPlayQueue(), item, view, true,
|
openPopupMenu(player.getPlayQueue(), item, view, true,
|
||||||
parentActivity.getSupportFragmentManager(), context);
|
parentActivity.getSupportFragmentManager(), context);
|
||||||
|
@ -801,10 +817,15 @@ public final class MainPlayerUi extends VideoPlayerUi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPlaybackSpeedClicked() {
|
protected void onPlaybackSpeedClicked() {
|
||||||
|
final AppCompatActivity activity = getParentActivity().orElse(null);
|
||||||
|
if (activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
|
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
|
||||||
player.getPlaybackSkipSilence(), (speed, pitch, skipSilence)
|
player.getPlaybackSkipSilence(), (speed, pitch, skipSilence)
|
||||||
-> player.setPlaybackParameters(speed, pitch, skipSilence))
|
-> player.setPlaybackParameters(speed, pitch, skipSilence))
|
||||||
.show(getParentActivity().getSupportFragmentManager(), null);
|
.show(activity.getSupportFragmentManager(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -876,15 +897,15 @@ public final class MainPlayerUi extends VideoPlayerUi {
|
||||||
}
|
}
|
||||||
|
|
||||||
isFullscreen = !isFullscreen;
|
isFullscreen = !isFullscreen;
|
||||||
if (!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 {
|
|
||||||
// Android needs tens milliseconds to send new insets but a user is able to see
|
// 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.
|
// how controls changes it's position from `0` to `nav bar height` padding.
|
||||||
// So just hide the controls to hide this visual inconsistency
|
// So just hide the controls to hide this visual inconsistency
|
||||||
hideControls(0, 0);
|
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);
|
fragmentListener.onFullscreenStateChanged(isFullscreen);
|
||||||
|
|
||||||
|
@ -924,14 +945,22 @@ public final class MainPlayerUi extends VideoPlayerUi {
|
||||||
return binding;
|
return binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppCompatActivity getParentActivity() {
|
public Optional<AppCompatActivity> getParentActivity() {
|
||||||
return (AppCompatActivity) ((ViewGroup) binding.getRoot().getParent()).getContext();
|
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() {
|
public boolean isLandscape() {
|
||||||
// DisplayMetrics from activity context knows about MultiWindow feature
|
// DisplayMetrics from activity context knows about MultiWindow feature
|
||||||
// while DisplayMetrics from app context doesn't
|
// while DisplayMetrics from app context doesn't
|
||||||
return DeviceUtils.isLandscape(getParentActivity());
|
return DeviceUtils.isLandscape(
|
||||||
|
getParentActivity().map(Context.class::cast).orElse(player.getService()));
|
||||||
}
|
}
|
||||||
//endregion
|
//endregion
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import org.schabi.newpipe.player.Player;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class PlayerUi {
|
public abstract class PlayerUi {
|
||||||
private static final String TAG = PlayerUi.class.getSimpleName();
|
|
||||||
|
|
||||||
@NonNull protected Context context;
|
@NonNull protected Context context;
|
||||||
@NonNull protected Player player;
|
@NonNull protected Player player;
|
||||||
|
|
|
@ -69,11 +69,9 @@ public final class PopupPlayerUi extends VideoPlayerUi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setupAfterIntent() {
|
public void setupAfterIntent() {
|
||||||
setupElementsVisibility();
|
super.setupAfterIntent();
|
||||||
binding.getRoot().setVisibility(View.VISIBLE);
|
|
||||||
initPopup();
|
initPopup();
|
||||||
initPopupCloseOverlay();
|
initPopupCloseOverlay();
|
||||||
binding.playPauseButton.requestFocus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -103,6 +101,7 @@ public final class PopupPlayerUi extends VideoPlayerUi {
|
||||||
binding.loadingPanel.setMinimumHeight(popupLayoutParams.height);
|
binding.loadingPanel.setMinimumHeight(popupLayoutParams.height);
|
||||||
|
|
||||||
windowManager.addView(binding.getRoot(), popupLayoutParams);
|
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
|
// Popup doesn't have aspectRatio selector, using FIT automatically
|
||||||
setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT);
|
setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT);
|
||||||
|
@ -304,7 +303,6 @@ public final class PopupPlayerUi extends VideoPlayerUi {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePopupFromView() {
|
public void removePopupFromView() {
|
||||||
if (windowManager != null) {
|
|
||||||
// wrap in try-catch since it could sometimes generate errors randomly
|
// wrap in try-catch since it could sometimes generate errors randomly
|
||||||
try {
|
try {
|
||||||
if (popupHasParent()) {
|
if (popupHasParent()) {
|
||||||
|
@ -324,7 +322,6 @@ public final class PopupPlayerUi extends VideoPlayerUi {
|
||||||
Log.w(TAG, "Failed to remove popup overlay from window manager", e);
|
Log.w(TAG, "Failed to remove popup overlay from window manager", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void animatePopupOverlayAndFinishService() {
|
private void animatePopupOverlayAndFinishService() {
|
||||||
final int targetTranslationY =
|
final int targetTranslationY =
|
||||||
|
|
|
@ -32,7 +32,6 @@ import android.view.Gravity;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.Surface;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
|
@ -107,6 +106,7 @@ public abstract class VideoPlayerUi extends PlayerUi
|
||||||
protected PlayerBinding binding;
|
protected PlayerBinding binding;
|
||||||
private final Handler controlsVisibilityHandler = new Handler();
|
private final Handler controlsVisibilityHandler = new Handler();
|
||||||
@Nullable private SurfaceHolderCallback surfaceHolderCallback;
|
@Nullable private SurfaceHolderCallback surfaceHolderCallback;
|
||||||
|
boolean surfaceIsSetup = false;
|
||||||
@Nullable private Bitmap thumbnail = null;
|
@Nullable private Bitmap thumbnail = null;
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,6 +130,7 @@ public abstract class VideoPlayerUi extends PlayerUi
|
||||||
|
|
||||||
private GestureDetector gestureDetector;
|
private GestureDetector gestureDetector;
|
||||||
private BasePlayerGestureListener playerGestureListener;
|
private BasePlayerGestureListener playerGestureListener;
|
||||||
|
@Nullable private View.OnLayoutChangeListener onLayoutChangeListener = null;
|
||||||
|
|
||||||
@NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder =
|
@NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder =
|
||||||
new SeekbarPreviewThumbnailHolder();
|
new SeekbarPreviewThumbnailHolder();
|
||||||
|
@ -138,6 +139,7 @@ public abstract class VideoPlayerUi extends PlayerUi
|
||||||
@NonNull final PlayerBinding playerBinding) {
|
@NonNull final PlayerBinding playerBinding) {
|
||||||
super(player);
|
super(player);
|
||||||
binding = playerBinding;
|
binding = playerBinding;
|
||||||
|
setupFromView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -222,8 +224,8 @@ public abstract class VideoPlayerUi extends PlayerUi
|
||||||
|
|
||||||
// PlaybackControlRoot already consumed window insets but we should pass them to
|
// 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.
|
// player_overlays and fast_seek_overlay too. Without it they will be off-centered.
|
||||||
binding.playbackControlRoot.addOnLayoutChangeListener(
|
onLayoutChangeListener
|
||||||
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
= (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
||||||
binding.playerOverlays.setPadding(
|
binding.playerOverlays.setPadding(
|
||||||
v.getPaddingLeft(),
|
v.getPaddingLeft(),
|
||||||
v.getPaddingTop(),
|
v.getPaddingTop(),
|
||||||
|
@ -240,7 +242,43 @@ public abstract class VideoPlayerUi extends PlayerUi
|
||||||
fastSeekParams.topMargin = -v.getPaddingBottom();
|
fastSeekParams.topMargin = -v.getPaddingBottom();
|
||||||
fastSeekParams.rightMargin = -v.getPaddingLeft();
|
fastSeekParams.rightMargin = -v.getPaddingLeft();
|
||||||
fastSeekParams.bottomMargin = -v.getPaddingTop();
|
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);
|
playerGestureListener.doubleTapControls(binding.fastSeekOverlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deinitPlayerSeekOverlay() {
|
||||||
|
binding.fastSeekOverlay
|
||||||
|
.seekSecondsSupplier(null)
|
||||||
|
.performListener(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setupAfterIntent() {
|
public void setupAfterIntent() {
|
||||||
super.setupAfterIntent();
|
super.setupAfterIntent();
|
||||||
setupElementsVisibility();
|
setupElementsVisibility();
|
||||||
setupElementsSize(context.getResources());
|
setupElementsSize(context.getResources());
|
||||||
|
binding.getRoot().setVisibility(View.VISIBLE);
|
||||||
|
binding.playPauseButton.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initPlayer() {
|
public void initPlayer() {
|
||||||
super.initPlayer();
|
super.initPlayer();
|
||||||
setupVideoSurface();
|
setupVideoSurfaceIfNeeded();
|
||||||
setupFromView();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -331,7 +376,7 @@ public abstract class VideoPlayerUi extends PlayerUi
|
||||||
@Override
|
@Override
|
||||||
public void destroyPlayer() {
|
public void destroyPlayer() {
|
||||||
super.destroyPlayer();
|
super.destroyPlayer();
|
||||||
cleanupVideoSurface();
|
clearVideoSurface();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -340,6 +385,8 @@ public abstract class VideoPlayerUi extends PlayerUi
|
||||||
if (binding != null) {
|
if (binding != null) {
|
||||||
binding.endScreen.setImageBitmap(null);
|
binding.endScreen.setImageBitmap(null);
|
||||||
}
|
}
|
||||||
|
deinitPlayerSeekOverlay();
|
||||||
|
deinitListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setupElementsVisibility() {
|
protected void setupElementsVisibility() {
|
||||||
|
@ -1470,40 +1517,50 @@ public abstract class VideoPlayerUi extends PlayerUi
|
||||||
// SurfaceHolderCallback helpers
|
// SurfaceHolderCallback helpers
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
//region SurfaceHolderCallback helpers
|
//region SurfaceHolderCallback helpers
|
||||||
private void setupVideoSurface() {
|
|
||||||
|
/**
|
||||||
|
* 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
|
// make sure there is nothing left over from previous calls
|
||||||
cleanupVideoSurface();
|
clearVideoSurface();
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23
|
||||||
surfaceHolderCallback = new SurfaceHolderCallback(context, player.getExoPlayer());
|
surfaceHolderCallback = new SurfaceHolderCallback(context, player.getExoPlayer());
|
||||||
binding.surfaceView.getHolder().addCallback(surfaceHolderCallback);
|
binding.surfaceView.getHolder().addCallback(surfaceHolderCallback);
|
||||||
final Surface surface = binding.surfaceView.getHolder().getSurface();
|
|
||||||
|
|
||||||
// ensure player is using an unreleased surface, which the surfaceView might not be
|
// ensure player is using an unreleased surface, which the surfaceView might not be
|
||||||
// when starting playback on background or during player switching
|
// when starting playback on background or during player switching
|
||||||
if (surface.isValid()) {
|
if (binding.surfaceView.getHolder().getSurface().isValid()) {
|
||||||
// initially set the surface manually otherwise
|
// initially set the surface manually otherwise
|
||||||
// onRenderedFirstFrame() will not be called
|
// onRenderedFirstFrame() will not be called
|
||||||
player.getExoPlayer().setVideoSurface(surface);
|
player.getExoPlayer().setVideoSurfaceHolder(binding.surfaceView.getHolder());
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
player.getExoPlayer().setVideoSurfaceView(binding.surfaceView);
|
player.getExoPlayer().setVideoSurfaceView(binding.surfaceView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
surfaceIsSetup = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cleanupVideoSurface() {
|
private void clearVideoSurface() {
|
||||||
final Optional<ExoPlayer> exoPlayer = Optional.ofNullable(player.getExoPlayer());
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M // >=API23
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23
|
&& surfaceHolderCallback != null) {
|
||||||
if (surfaceHolderCallback != null) {
|
|
||||||
binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback);
|
binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback);
|
||||||
surfaceHolderCallback.release();
|
surfaceHolderCallback.release();
|
||||||
surfaceHolderCallback = null;
|
surfaceHolderCallback = null;
|
||||||
}
|
}
|
||||||
exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurface(null));
|
Optional.ofNullable(player.getExoPlayer()).ifPresent(ExoPlayer::clearVideoSurface);
|
||||||
} else {
|
surfaceIsSetup = false;
|
||||||
exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurfaceView(null));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|
|
@ -38,14 +38,14 @@ class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
|
|
||||||
private var performListener: PerformListener? = null
|
private var performListener: PerformListener? = null
|
||||||
|
|
||||||
fun performListener(listener: PerformListener) = apply {
|
fun performListener(listener: PerformListener?) = apply {
|
||||||
performListener = listener
|
performListener = listener
|
||||||
}
|
}
|
||||||
|
|
||||||
private var seekSecondsSupplier: () -> Int = { 0 }
|
private var seekSecondsSupplier: () -> Int = { 0 }
|
||||||
|
|
||||||
fun seekSecondsSupplier(supplier: () -> Int) = apply {
|
fun seekSecondsSupplier(supplier: (() -> Int)?) = apply {
|
||||||
seekSecondsSupplier = supplier
|
seekSecondsSupplier = supplier ?: { 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indicates whether this (double) tap is the first of a series
|
// Indicates whether this (double) tap is the first of a series
|
||||||
|
|
Loading…
Reference in a new issue