Refactor player: separate UIs and more

This commit is contained in:
Stypox 2022-04-08 09:35:14 +02:00
parent bc3731265e
commit 76ced59b62
No known key found for this signature in database
GPG key ID: 4BDF1B40A49FDD23
38 changed files with 4242 additions and 3564 deletions

View file

@ -43,6 +43,7 @@ import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import androidx.viewbinding.ViewBinding;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.PlaybackParameters;
@ -77,8 +78,8 @@ import org.schabi.newpipe.fragments.list.videos.RelatedItemsFragment;
import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.MainPlayer.PlayerType;
import org.schabi.newpipe.player.PlayerService;
import org.schabi.newpipe.player.PlayerService.PlayerType;
import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.event.OnKeyDownListener;
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener;
@ -87,6 +88,8 @@ import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.player.ui.MainPlayerUi;
import org.schabi.newpipe.player.ui.VideoPlayerUi;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ExtractorHelper;
@ -106,6 +109,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import icepick.State;
@ -202,7 +206,7 @@ public final class VideoDetailFragment
private ContentObserver settingsContentObserver;
@Nullable
private MainPlayer playerService;
private PlayerService playerService;
private Player player;
private final PlayerHolder playerHolder = PlayerHolder.getInstance();
@ -211,7 +215,7 @@ public final class VideoDetailFragment
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onServiceConnected(final Player connectedPlayer,
final MainPlayer connectedPlayerService,
final PlayerService connectedPlayerService,
final boolean playAfterConnect) {
player = connectedPlayer;
playerService = connectedPlayerService;
@ -219,6 +223,7 @@ public final class VideoDetailFragment
// It will do nothing if the player is not in fullscreen mode
hideSystemUiIfNeeded();
final Optional<MainPlayerUi> playerUi = player.UIs().get(MainPlayerUi.class);
if (!player.videoPlayerSelected() && !playAfterConnect) {
return;
}
@ -227,22 +232,23 @@ public final class VideoDetailFragment
// If the video is playing but orientation changed
// let's make the video in fullscreen again
checkLandscape();
} else if (player.isFullscreen() && !player.isVerticalVideo()
} else if (playerUi.map(ui -> ui.isFullscreen() && !ui.isVerticalVideo()).orElse(false)
// Tablet UI has orientation-independent fullscreen
&& !DeviceUtils.isTablet(activity)) {
// Device is in portrait orientation after rotation but UI is in fullscreen.
// Return back to non-fullscreen state
player.toggleFullscreen();
playerUi.ifPresent(MainPlayerUi::toggleFullscreen);
}
if (playerIsNotStopped() && player.videoPlayerSelected()) {
addVideoPlayerView();
}
//noinspection SimplifyOptionalCallChains
if (playAfterConnect
|| (currentInfo != null
&& isAutoplayEnabled()
&& player.getParentActivity() == null)) {
&& !playerUi.isPresent())) {
autoPlayEnabled = true; // forcefully start playing
openVideoPlayerAutoFullscreen();
}
@ -518,7 +524,7 @@ public final class VideoDetailFragment
case R.id.overlay_play_pause_button:
if (playerIsNotStopped()) {
player.playPause();
player.hideControls(0, 0);
player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0));
showSystemUi();
} else {
autoPlayEnabled = true; // forcefully start playing
@ -583,12 +589,12 @@ public final class VideoDetailFragment
if (binding.detailSecondaryControlPanel.getVisibility() == View.GONE) {
binding.detailVideoTitleView.setMaxLines(10);
animateRotation(binding.detailToggleSecondaryControlsView,
Player.DEFAULT_CONTROLS_DURATION, 180);
VideoPlayerUi.DEFAULT_CONTROLS_DURATION, 180);
binding.detailSecondaryControlPanel.setVisibility(View.VISIBLE);
} else {
binding.detailVideoTitleView.setMaxLines(1);
animateRotation(binding.detailToggleSecondaryControlsView,
Player.DEFAULT_CONTROLS_DURATION, 0);
VideoPlayerUi.DEFAULT_CONTROLS_DURATION, 0);
binding.detailSecondaryControlPanel.setVisibility(View.GONE);
}
// view pager height has changed, update the tab layout
@ -746,7 +752,9 @@ public final class VideoDetailFragment
@Override
public boolean onKeyDown(final int keyCode) {
return isPlayerAvailable() && player.onKeyDown(keyCode);
return isPlayerAvailable()
&& player.UIs().get(VideoPlayerUi.class)
.map(playerUi -> playerUi.onKeyDown(keyCode)).orElse(false);
}
@Override
@ -756,7 +764,7 @@ public final class VideoDetailFragment
}
// If we are in fullscreen mode just exit from it via first back press
if (isPlayerAvailable() && player.isFullscreen()) {
if (isFullscreen()) {
if (!DeviceUtils.isTablet(activity)) {
player.pause();
}
@ -1006,8 +1014,7 @@ public final class VideoDetailFragment
getChildFragmentManager().beginTransaction()
.replace(R.id.relatedItemsLayout, RelatedItemsFragment.getInstance(info))
.commitAllowingStateLoss();
binding.relatedItemsLayout.setVisibility(
isPlayerAvailable() && player.isFullscreen() ? View.GONE : View.VISIBLE);
binding.relatedItemsLayout.setVisibility(isFullscreen() ? View.GONE : View.VISIBLE);
}
}
@ -1087,8 +1094,12 @@ public final class VideoDetailFragment
private void toggleFullscreenIfInFullscreenMode() {
// If a user watched video inside fullscreen mode and than chose another player
// return to non-fullscreen mode
if (isPlayerAvailable() && player.isFullscreen()) {
player.toggleFullscreen();
if (isPlayerAvailable()) {
player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> {
if (playerUi.isFullscreen()) {
playerUi.toggleFullscreen();
}
});
}
}
@ -1214,16 +1225,10 @@ public final class VideoDetailFragment
}
final PlayQueue queue = setupPlayQueueForIntent(false);
// Video view can have elements visible from popup,
// We hide it here but once it ready the view will be shown in handleIntent()
if (playerService.getView() != null) {
playerService.getView().setVisibility(View.GONE);
}
addVideoPlayerView();
final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(),
MainPlayer.class, queue, true, autoPlayEnabled);
PlayerService.class, queue, true, autoPlayEnabled);
ContextCompat.startForegroundService(activity, playerIntent);
}
@ -1235,8 +1240,8 @@ public final class VideoDetailFragment
* be reused in a few milliseconds and the flickering would be annoying.
*/
private void hideMainPlayerOnLoadingNewStream() {
if (!isPlayerServiceAvailable()
|| playerService.getView() == null
//noinspection SimplifyOptionalCallChains
if (!isPlayerServiceAvailable() || !getRoot().isPresent()
|| !player.videoPlayerSelected()) {
return;
}
@ -1244,7 +1249,7 @@ public final class VideoDetailFragment
removeVideoPlayerView();
if (isAutoplayEnabled()) {
playerService.stopForImmediateReusing();
playerService.getView().setVisibility(View.GONE);
getRoot().ifPresent(view -> view.setVisibility(View.GONE));
} else {
playerHolder.stopService();
}
@ -1302,26 +1307,33 @@ public final class VideoDetailFragment
}
private void addVideoPlayerView() {
if (!isPlayerAvailable() || getView() == null) {
if (!isPlayerAvailable()) {
return;
}
// Check if viewHolder already contains a child
if (player.getRootView().getParent() != binding.playerPlaceholder) {
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 (player.getRootView().getParent() == null) {
binding.playerPlaceholder.addView(player.getRootView());
if (root.isPresent() && root.get().getParent() == null) {
binding.playerPlaceholder.addView(root.get());
}
}
private void removeVideoPlayerView() {
makeDefaultHeightForVideoPlaceholder();
playerService.removeViewFromParent();
if (player != null) {
player.UIs().get(VideoPlayerUi.class).ifPresent(VideoPlayerUi::removeViewFromParent);
}
}
private void makeDefaultHeightForVideoPlaceholder() {
@ -1362,7 +1374,7 @@ public final class VideoDetailFragment
final boolean isPortrait = metrics.heightPixels > metrics.widthPixels;
requireView().getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
if (isPlayerAvailable() && player.isFullscreen()) {
if (isFullscreen()) {
final int height = (DeviceUtils.isInMultiWindow(activity)
? requireView()
: activity.getWindow().getDecorView()).getHeight();
@ -1387,8 +1399,9 @@ public final class VideoDetailFragment
binding.detailThumbnailImageView.setMinimumHeight(newHeight);
if (isPlayerAvailable()) {
final int maxHeight = (int) (metrics.heightPixels * MAX_PLAYER_HEIGHT);
player.getSurfaceView()
.setHeights(newHeight, player.isFullscreen() ? newHeight : maxHeight);
player.UIs().get(VideoPlayerUi.class).ifPresent(ui ->
ui.getBinding().surfaceView.setHeights(newHeight,
ui.isFullscreen() ? newHeight : maxHeight));
}
}
@ -1517,7 +1530,7 @@ public final class VideoDetailFragment
if (binding.relatedItemsLayout != null) {
if (showRelatedItems) {
binding.relatedItemsLayout.setVisibility(
isPlayerAvailable() && player.isFullscreen() ? View.GONE : View.INVISIBLE);
isFullscreen() ? View.GONE : View.INVISIBLE);
} else {
binding.relatedItemsLayout.setVisibility(View.GONE);
}
@ -1778,6 +1791,14 @@ public final class VideoDetailFragment
// Player event listener
//////////////////////////////////////////////////////////////////////////*/
@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();
}
@Override
public void onQueueUpdate(final PlayQueue queue) {
playQueue = queue;
@ -1898,15 +1919,10 @@ public final class VideoDetailFragment
@Override
public void onFullscreenStateChanged(final boolean fullscreen) {
setupBrightness();
//noinspection SimplifyOptionalCallChains
if (!isPlayerAndPlayerServiceAvailable()
|| playerService.getView() == null
|| player.getParentActivity() == null) {
return;
}
final View view = playerService.getView();
final ViewGroup parent = (ViewGroup) view.getParent();
if (parent == null) {
|| !player.UIs().get(MainPlayerUi.class).isPresent()
|| getRoot().map(View::getParent).orElse(null) == null) {
return;
}
@ -1934,7 +1950,7 @@ public final class VideoDetailFragment
final boolean isLandscape = DeviceUtils.isLandscape(requireContext());
if (DeviceUtils.isTablet(activity)
&& (!globalScreenOrientationLocked(activity) || isLandscape)) {
player.toggleFullscreen();
player.UIs().get(MainPlayerUi.class).ifPresent(MainPlayerUi::toggleFullscreen);
return;
}
@ -2017,7 +2033,7 @@ public final class VideoDetailFragment
}
activity.getWindow().getDecorView().setSystemUiVisibility(visibility);
if (isInMultiWindow || (isPlayerAvailable() && player.isFullscreen())) {
if (isInMultiWindow || isFullscreen()) {
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
activity.getWindow().setNavigationBarColor(Color.TRANSPARENT);
}
@ -2026,13 +2042,17 @@ public final class VideoDetailFragment
// Listener implementation
public void hideSystemUiIfNeeded() {
if (isPlayerAvailable()
&& player.isFullscreen()
if (isFullscreen()
&& bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
hideSystemUi();
}
}
private boolean isFullscreen() {
return isPlayerAvailable() && player.UIs().get(VideoPlayerUi.class)
.map(VideoPlayerUi::isFullscreen).orElse(false);
}
private boolean playerIsNotStopped() {
return isPlayerAvailable() && !player.isStopped();
}
@ -2055,10 +2075,7 @@ public final class VideoDetailFragment
}
final WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
if (!isPlayerAvailable()
|| !player.videoPlayerSelected()
|| !player.isFullscreen()
|| bottomSheetState != BottomSheetBehavior.STATE_EXPANDED) {
if (!isFullscreen() || bottomSheetState != BottomSheetBehavior.STATE_EXPANDED) {
// Apply system brightness when the player is not in fullscreen
restoreDefaultBrightness();
} else {
@ -2082,7 +2099,7 @@ public final class VideoDetailFragment
setAutoPlay(true);
}
player.checkLandscape();
player.UIs().get(MainPlayerUi.class).ifPresent(MainPlayerUi::checkLandscape);
// Let's give a user time to look at video information page if video is not playing
if (globalScreenOrientationLocked(activity) && !player.isPlaying()) {
player.play();
@ -2309,10 +2326,10 @@ public final class VideoDetailFragment
if (DeviceUtils.isLandscape(requireContext())
&& isPlayerAvailable()
&& player.isPlaying()
&& !player.isFullscreen()
&& !DeviceUtils.isTablet(activity)
&& player.videoPlayerSelected()) {
player.toggleFullscreen();
&& !isFullscreen()
&& !DeviceUtils.isTablet(activity)) {
player.UIs().get(MainPlayerUi.class)
.ifPresent(MainPlayerUi::toggleFullscreen);
}
setOverlayLook(binding.appBarLayout, behavior, 1);
break;
@ -2325,17 +2342,22 @@ public final class VideoDetailFragment
// Re-enable clicks
setOverlayElementsClickable(true);
if (isPlayerAvailable()) {
player.closeItemsList();
player.UIs().get(MainPlayerUi.class)
.ifPresent(MainPlayerUi::closeItemsList);
}
setOverlayLook(binding.appBarLayout, behavior, 0);
break;
case BottomSheetBehavior.STATE_DRAGGING:
case BottomSheetBehavior.STATE_SETTLING:
if (isPlayerAvailable() && player.isFullscreen()) {
if (isFullscreen()) {
showSystemUi();
}
if (isPlayerAvailable() && player.isControlsVisible()) {
player.hideControls(0, 0);
if (isPlayerAvailable()) {
player.UIs().get(MainPlayerUi.class).ifPresent(ui -> {
if (ui.isControlsVisible()) {
ui.hideControls(0, 0);
}
});
}
break;
}
@ -2409,4 +2431,13 @@ public final class VideoDetailFragment
boolean isPlayerAndPlayerServiceAvailable() {
return (player != null && playerService != null);
}
public Optional<View> getRoot() {
if (player == null) {
return Optional.empty();
}
return player.UIs().get(VideoPlayerUi.class)
.map(playerUi -> playerUi.getBinding().getRoot());
}
}

View file

@ -43,7 +43,7 @@ import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.local.feed.notifications.NotificationHelper;
import org.schabi.newpipe.player.MainPlayer.PlayerType;
import org.schabi.newpipe.player.PlayerService.PlayerType;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.util.ExtractorHelper;

View file

@ -43,7 +43,7 @@ import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry;
import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
import org.schabi.newpipe.player.MainPlayer.PlayerType;
import org.schabi.newpipe.player.PlayerService.PlayerType;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
import org.schabi.newpipe.util.ExtractorHelper;