From c1d5a5cd987104b93b8b952bf318cde24e0314bb Mon Sep 17 00:00:00 2001 From: Avently <7953703+avently@users.noreply.github.com> Date: Tue, 29 Sep 2020 06:22:53 +0300 Subject: [PATCH 1/2] Player will be rebound when needed, prev/next/queue buttons, preserving paused state - each time something starts to play in any player VideoDetailFragment will be started (if not yet started) and mini player will show up. It makes possible to see a playing stream in mini player even if the stream was started without using fragment or after player service was closed somehow - play/next/queue buttons will be updated in realtime when stream was added/removed from queue instead of waiting for a onPlay/onPause action to happen - when popup or background players start the stream will start playing only if paused state wasn't requested. Which means, for example, if a user opens popup it will be started when START_PAUSED is false. If, for example, the stream was played in main player and then popup was started the stream will still be playing, but if it was paused it still be paused in popup (or background) in APPEND_ONLY mode (but will be playing on new queue initialization) --- .../java/org/schabi/newpipe/MainActivity.java | 28 +++++++++++-- .../fragments/detail/VideoDetailFragment.java | 39 +++++++++++++++---- .../newpipe/player/VideoPlayerImpl.java | 25 ++++++------ .../schabi/newpipe/util/NavigationHelper.java | 15 ++++++- 4 files changed, 84 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index aedf64231..e3abd32b1 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -20,7 +20,10 @@ package org.schabi.newpipe; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Build; @@ -101,6 +104,8 @@ public class MainActivity extends AppCompatActivity { private boolean servicesShown = false; private ImageView serviceArrow; + private BroadcastReceiver broadcastReceiver; + private static final int ITEM_ID_SUBSCRIPTIONS = -1; private static final int ITEM_ID_FEED = -2; private static final int ITEM_ID_BOOKMARKS = -3; @@ -147,6 +152,7 @@ public class MainActivity extends AppCompatActivity { if (DeviceUtils.isTv(this)) { FocusOverlayView.setupFocusObserver(this); } + setupBroadcastReceiver(); } private void setupDrawer() throws Exception { @@ -454,6 +460,7 @@ public class MainActivity extends AppCompatActivity { if (!isChangingConfigurations()) { StateSaver.clearStateFiles(); } + unregisterReceiver(broadcastReceiver); } @Override @@ -795,9 +802,24 @@ public class MainActivity extends AppCompatActivity { ErrorActivity.reportUiError(this, e); } } - /* - * Utils - * */ + + private void setupBroadcastReceiver() { + broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + if (intent.getAction().equals(VideoDetailFragment.ACTION_PLAYER_STARTED)) { + final Fragment fragmentPlayer = getSupportFragmentManager() + .findFragmentById(R.id.fragment_player_holder); + if (fragmentPlayer == null) { + NavigationHelper.showMiniPlayer(getSupportFragmentManager()); + } + } + } + }; + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(VideoDetailFragment.ACTION_PLAYER_STARTED); + registerReceiver(broadcastReceiver, intentFilter); + } private boolean bottomSheetHiddenOrCollapsed() { final FrameLayout bottomSheetLayout = findViewById(R.id.fragment_player_holder); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 1b62c8ffb..39ee38220 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -146,6 +146,8 @@ public class VideoDetailFragment "org.schabi.newpipe.VideoDetailFragment.ACTION_SHOW_MAIN_PLAYER"; public static final String ACTION_HIDE_MAIN_PLAYER = "org.schabi.newpipe.VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER"; + public static final String ACTION_PLAYER_STARTED = + "org.schabi.newpipe.VideoDetailFragment.ACTION_PLAYER_STARTED"; public static final String ACTION_VIDEO_FRAGMENT_RESUMED = "org.schabi.newpipe.VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED"; public static final String ACTION_VIDEO_FRAGMENT_STOPPED = @@ -302,6 +304,12 @@ public class VideoDetailFragment return instance; } + public static VideoDetailFragment getInstanceCollapsed() { + final VideoDetailFragment instance = new VideoDetailFragment(); + instance.bottomSheetState = BottomSheetBehavior.STATE_COLLAPSED; + return instance; + } + /*////////////////////////////////////////////////////////////////////////// // Fragment's Lifecycle @@ -518,7 +526,7 @@ public class VideoDetailFragment openVideoPlayer(); } - setOverlayPlayPauseImage(); + setOverlayPlayPauseImage(player != null && player.isPlaying()); break; case R.id.overlay_close_button: bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); @@ -1325,12 +1333,22 @@ public class VideoDetailFragment bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); } else if (intent.getAction().equals(ACTION_HIDE_MAIN_PLAYER)) { bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + } else if (intent.getAction().equals(ACTION_PLAYER_STARTED)) { + // If the state is not hidden we don't need to show the mini player + if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN) { + bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + } + // Rebound to the service if it was closed via notification or mini player + if (!PlayerHolder.bound) { + PlayerHolder.startService(App.getApp(), false, VideoDetailFragment.this); + } } } }; final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ACTION_SHOW_MAIN_PLAYER); intentFilter.addAction(ACTION_HIDE_MAIN_PLAYER); + intentFilter.addAction(ACTION_PLAYER_STARTED); activity.registerReceiver(broadcastReceiver, intentFilter); } @@ -1719,8 +1737,12 @@ public class VideoDetailFragment // It will allow to have live instance of PlayQueue with actual information about // deleted/added items inside Channel/Playlist queue and makes possible to have // a history of played items - if (stack.isEmpty() || !stack.peek().getPlayQueue().equals(queue)) { - stack.push(new StackItem(serviceId, url, name, playQueue)); + if ((stack.isEmpty() || !stack.peek().getPlayQueue().equals(queue) + && queue.getItem() != null)) { + stack.push(new StackItem(queue.getItem().getServiceId(), + queue.getItem().getUrl(), + queue.getItem().getTitle(), + queue)); } else { final StackItem stackWithQueue = findQueueInStack(queue); if (stackWithQueue != null) { @@ -1744,7 +1766,7 @@ public class VideoDetailFragment final int repeatMode, final boolean shuffled, final PlaybackParameters parameters) { - setOverlayPlayPauseImage(); + setOverlayPlayPauseImage(player != null && player.isPlaying()); switch (state) { case BasePlayer.STATE_PLAYING: @@ -1818,7 +1840,7 @@ public class VideoDetailFragment @Override public void onServiceStopped() { - setOverlayPlayPauseImage(); + setOverlayPlayPauseImage(false); if (currentInfo != null) { updateOverlayData(currentInfo.getName(), currentInfo.getUploaderName(), @@ -2224,6 +2246,9 @@ public class VideoDetailFragment break; case BottomSheetBehavior.STATE_COLLAPSED: moveFocusToMainFragment(true); + manageSpaceAtTheBottom(false); + + bottomSheetBehavior.setPeekHeight(peekHeight); // Re-enable clicks setOverlayElementsClickable(true); @@ -2270,8 +2295,8 @@ public class VideoDetailFragment } } - private void setOverlayPlayPauseImage() { - final int attr = player != null && player.isPlaying() + private void setOverlayPlayPauseImage(final boolean playerIsPlaying) { + final int attr = playerIsPlaying ? R.attr.ic_pause : R.attr.ic_play_arrow; overlayPlayPauseButton.setImageResource( diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java index f93df4d08..e3eb1c9f7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java @@ -255,9 +255,9 @@ public class VideoPlayerImpl extends VideoPlayer onQueueClosed(); // Android TV: without it focus will frame the whole player playPauseButton.requestFocus(); + onPlay(); } - - onPlay(); + NavigationHelper.sendPlayerStartedEvent(service); } VideoPlayerImpl(final MainPlayer service) { @@ -662,6 +662,7 @@ public class VideoPlayerImpl extends VideoPlayer @Override public void onPlayQueueEdited() { updatePlayback(); + showOrHideButtons(); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } @@ -1455,15 +1456,16 @@ public class VideoPlayerImpl extends VideoPlayer return; } - playPreviousButton.setVisibility(playQueue.getIndex() == 0 - ? View.INVISIBLE - : View.VISIBLE); - playNextButton.setVisibility(playQueue.getIndex() + 1 == playQueue.getStreams().size() - ? View.INVISIBLE - : View.VISIBLE); - queueButton.setVisibility(playQueue.getStreams().size() <= 1 || popupPlayerSelected() - ? View.GONE - : View.VISIBLE); + final boolean showPrev = playQueue.getIndex() != 0; + final boolean showNext = playQueue.getIndex() + 1 != playQueue.getStreams().size(); + final boolean showQueue = playQueue.getStreams().size() > 1 && !popupPlayerSelected(); + + playPreviousButton.setVisibility(showPrev ? View.VISIBLE : View.INVISIBLE); + playPreviousButton.setAlpha(showPrev ? 1.0f : 0.0f); + playNextButton.setVisibility(showNext ? View.VISIBLE : View.INVISIBLE); + playNextButton.setAlpha(showNext ? 1.0f : 0.0f); + queueButton.setVisibility(showQueue ? View.VISIBLE : View.GONE); + queueButton.setAlpha(showQueue ? 1.0f : 0.0f); } private void showSystemUIPartially() { @@ -1936,6 +1938,7 @@ public class VideoPlayerImpl extends VideoPlayer getControlsRoot().setPadding(0, 0, 0, 0); } queueLayout.setPadding(0, 0, 0, 0); + updateQueue(); updateMetadata(); updatePlayback(); triggerProgressUpdate(); diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index fc4a6cacc..ac5e9c449 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -384,8 +384,19 @@ public final class NavigationHelper { } public static void expandMainPlayer(final Context context) { - final Intent intent = new Intent(VideoDetailFragment.ACTION_SHOW_MAIN_PLAYER); - context.sendBroadcast(intent); + context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_SHOW_MAIN_PLAYER)); + } + + public static void sendPlayerStartedEvent(final Context context) { + context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_PLAYER_STARTED)); + } + + public static void showMiniPlayer(final FragmentManager fragmentManager) { + final VideoDetailFragment instance = VideoDetailFragment.getInstanceCollapsed(); + defaultTransaction(fragmentManager) + .replace(R.id.fragment_player_holder, instance) + .runOnCommit(() -> sendPlayerStartedEvent(instance.requireActivity())) + .commit(); } public static void openChannelFragment(final FragmentManager fragmentManager, From 6665d630ec7e104e35514e85a0b79376584cda3a Mon Sep 17 00:00:00 2001 From: Avently <7953703+avently@users.noreply.github.com> Date: Wed, 30 Sep 2020 00:49:34 +0300 Subject: [PATCH 2/2] Added comments and improved the code --- .../java/org/schabi/newpipe/MainActivity.java | 16 +++++++++++++++- .../fragments/detail/VideoDetailFragment.java | 2 +- .../schabi/newpipe/util/NavigationHelper.java | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index e3abd32b1..e72d4609e 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -460,7 +460,9 @@ public class MainActivity extends AppCompatActivity { if (!isChangingConfigurations()) { StateSaver.clearStateFiles(); } - unregisterReceiver(broadcastReceiver); + if (broadcastReceiver != null) { + unregisterReceiver(broadcastReceiver); + } } @Override @@ -811,8 +813,20 @@ public class MainActivity extends AppCompatActivity { final Fragment fragmentPlayer = getSupportFragmentManager() .findFragmentById(R.id.fragment_player_holder); if (fragmentPlayer == null) { + /* + * We still don't have a fragment attached to the activity. + * It can happen when a user started popup or background players + * without opening a stream inside the fragment. + * Adding it in a collapsed state (only mini player will be visible) + * */ NavigationHelper.showMiniPlayer(getSupportFragmentManager()); } + /* + * At this point the player is added 100%, we can unregister. + * Other actions are useless since the fragment will not be removed after that + * */ + unregisterReceiver(broadcastReceiver); + broadcastReceiver = null; } } }; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 39ee38220..cf993ccbb 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -304,7 +304,7 @@ public class VideoDetailFragment return instance; } - public static VideoDetailFragment getInstanceCollapsed() { + public static VideoDetailFragment getInstanceInCollapsedState() { final VideoDetailFragment instance = new VideoDetailFragment(); instance.bottomSheetState = BottomSheetBehavior.STATE_COLLAPSED; return instance; diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index ac5e9c449..eef70c1e5 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -392,7 +392,7 @@ public final class NavigationHelper { } public static void showMiniPlayer(final FragmentManager fragmentManager) { - final VideoDetailFragment instance = VideoDetailFragment.getInstanceCollapsed(); + final VideoDetailFragment instance = VideoDetailFragment.getInstanceInCollapsedState(); defaultTransaction(fragmentManager) .replace(R.id.fragment_player_holder, instance) .runOnCommit(() -> sendPlayerStartedEvent(instance.requireActivity()))