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)
This commit is contained in:
Avently 2020-09-29 06:22:53 +03:00
parent 160a04c3c7
commit c1d5a5cd98
4 changed files with 84 additions and 23 deletions

View File

@ -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);

View File

@ -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(

View File

@ -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();

View File

@ -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,