diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java index f568ef81a..dec8b05b2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java @@ -9,15 +9,20 @@ import android.view.Window; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentManager; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; +import org.schabi.newpipe.player.Player; import org.schabi.newpipe.util.StateSaver; import java.util.List; +import java.util.Objects; import java.util.Queue; import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.disposables.Disposable; @@ -131,13 +136,13 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave * @param context context used for accessing the database * @param streamEntities used for crating the dialog * @param onExec execution that should occur after a dialog got created, e.g. showing it - * @return Disposable + * @return the disposable that was created */ public static Disposable createCorrespondingDialog( final Context context, final List streamEntities, - final Consumer onExec - ) { + final Consumer onExec) { + return new LocalPlaylistManager(NewPipeDatabase.getInstance(context)) .hasPlaylists() .observeOn(AndroidSchedulers.mainThread()) @@ -147,4 +152,30 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave : PlaylistCreationDialog.newInstance(streamEntities)) ); } + + /** + * Creates a {@link PlaylistAppendDialog} when playlists exists, + * otherwise a {@link PlaylistCreationDialog}. If the player's play queue is null or empty, no + * dialog will be created. + * + * @param player the player from which to extract the context and the play queue + * @param fragmentManager the fragment manager to use to show the dialog + * @return the disposable that was created + */ + public static Disposable showForPlayQueue( + final Player player, + @NonNull final FragmentManager fragmentManager) { + + final List streamEntities = Stream.of(player.getPlayQueue()) + .filter(Objects::nonNull) + .flatMap(playQueue -> Objects.requireNonNull(playQueue).getStreams().stream()) + .map(StreamEntity::new) + .collect(Collectors.toList()); + if (streamEntities.isEmpty()) { + return Disposable.empty(); + } + + return PlaylistDialog.createCorrespondingDialog(player.getContext(), streamEntities, + dialog -> dialog.show(fragmentManager, "PlaylistDialog")); + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java index cdba900f9..c18a7f487 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -29,6 +29,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ActivityPlayerQueueControlBinding; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; +import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.helper.PlaybackParameterDialog; import org.schabi.newpipe.player.playqueue.PlayQueue; @@ -53,8 +54,6 @@ public final class PlayQueueActivity extends AppCompatActivity private Player player; - private PlayQueueAdapter adapter = null; - private boolean serviceBound; private ServiceConnection serviceConnection; @@ -128,7 +127,7 @@ public final class PlayQueueActivity extends AppCompatActivity NavigationHelper.openSettings(this); return true; case R.id.action_append_playlist: - player.onAddToPlaylistClicked(getSupportFragmentManager()); + PlaylistDialog.showForPlayQueue(player, getSupportFragmentManager()); return true; case R.id.action_playback_speed: openPlaybackParameterDialog(); @@ -441,10 +440,9 @@ public final class PlayQueueActivity extends AppCompatActivity @Override public void onQueueUpdate(@Nullable final PlayQueue queue) { if (queue == null) { - adapter = null; queueControlBinding.playQueue.setAdapter(null); } else { - adapter = new PlayQueueAdapter(this, queue); + final PlayQueueAdapter adapter = new PlayQueueAdapter(this, queue); adapter.setSelectedListener(getOnSelectedListener()); queueControlBinding.playQueue.setAdapter(adapter); } diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 0755f9b4d..2d44c6449 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -63,16 +63,6 @@ import android.view.LayoutInflater; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.appcompat.view.ContextThemeWrapper; -import androidx.appcompat.widget.AppCompatImageButton; -import androidx.appcompat.widget.PopupMenu; -import androidx.core.content.ContextCompat; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; -import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; import com.google.android.exoplayer2.C; @@ -96,7 +86,6 @@ import com.squareup.picasso.Target; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; -import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.databinding.PlayerBinding; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; @@ -105,7 +94,6 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; -import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.event.PlayerEventListener; @@ -116,6 +104,7 @@ import org.schabi.newpipe.player.helper.MediaSessionManager; import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.mediaitem.MediaItemTag; +import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.player.playback.PlayerMediaSession; @@ -125,7 +114,6 @@ import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType; import org.schabi.newpipe.player.ui.MainPlayerUi; -import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.ui.PlayerUi; import org.schabi.newpipe.player.ui.PlayerUiList; import org.schabi.newpipe.player.ui.PopupPlayerUi; @@ -137,10 +125,8 @@ import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.SerializedCache; import org.schabi.newpipe.util.StreamTypeUtil; -import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import java.util.stream.IntStream; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -1192,32 +1178,6 @@ public final class Player implements PlaybackListener, Listener { - /*////////////////////////////////////////////////////////////////////////// - // Playlist append TODO this does not make sense here - //////////////////////////////////////////////////////////////////////////*/ - //region Playlist append - - public void onAddToPlaylistClicked(@NonNull final FragmentManager fragmentManager) { - if (DEBUG) { - Log.d(TAG, "onAddToPlaylistClicked() called"); - } - - if (getPlayQueue() != null) { - PlaylistDialog.createCorrespondingDialog( - getContext(), - getPlayQueue() - .getStreams() - .stream() - .map(StreamEntity::new) - .collect(Collectors.toList()), - dialog -> dialog.show(fragmentManager, TAG) - ); - } - } - //endregion - - - /*////////////////////////////////////////////////////////////////////////// // Mute / Unmute //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index b5014eeed..326b01590 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -71,16 +71,17 @@ public final class PlayerService extends Service { Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]"); } - if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) - && player.getPlayQueue() == null) { - // Player is not working, no need to process media button's action - return START_NOT_STICKY; + + if (!Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) + || player.getPlayQueue() != null) { + // ^ no need to process media button's action if player is not working + + player.handleIntent(intent); + if (player.getMediaSessionManager() != null) { + player.getMediaSessionManager().handleMediaButtonIntent(intent); + } } - player.handleIntent(intent); - if (player.getMediaSessionManager() != null) { - player.getMediaSessionManager().handleMediaButtonIntent(intent); - } return START_NOT_STICKY; } diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt index bd5d6f1c5..b006e73aa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt @@ -92,7 +92,10 @@ abstract class BasePlayerGestureListener( return true } - return if (onDownNotDoubleTapping(e)) super.onDown(e) else true + if (onDownNotDoubleTapping(e)) { + return super.onDown(e) + } + return true } /** diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 28c3b3655..2ba754500 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -266,6 +266,7 @@ public final class NotificationUtil { null); } + // fallthrough case NotificationConstants.PLAY_PAUSE: if (player.getCurrentState() == Player.STATE_COMPLETED) { return getAction(R.drawable.ic_replay, diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 3bdda0029..eebcc81c4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -51,6 +51,7 @@ import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.info_list.StreamSegmentAdapter; import org.schabi.newpipe.ktx.AnimationType; +import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.gesture.BasePlayerGestureListener; @@ -147,7 +148,8 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh binding.addToPlaylistButton.setOnClickListener(v -> getParentActivity().map(FragmentActivity::getSupportFragmentManager) - .ifPresent(player::onAddToPlaylistClicked)); + .ifPresent(fragmentManager -> + PlaylistDialog.showForPlayQueue(player, fragmentManager))); settingsContentObserver = new ContentObserver(new Handler()) { @Override @@ -401,6 +403,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh //////////////////////////////////////////////////////////////////////////*/ //region Controls showing / hiding + @Override protected void showOrHideButtons() { super.showOrHideButtons(); @Nullable final PlayQueue playQueue = player.getPlayQueue(); @@ -667,12 +670,11 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } animate(binding.itemsListPanel, false, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA, 0, () -> { + AnimationType.SLIDE_AND_ALPHA, 0, () -> // Even when queueLayout is GONE it receives touch events // and ruins normal behavior of the app. This line fixes it binding.itemsListPanel.setTranslationY( - -binding.itemsListPanel.getHeight() * 5); - }); + -binding.itemsListPanel.getHeight() * 5.0f)); // clear focus, otherwise a white rectangle remains on top of the player binding.itemsListClose.clearFocus(); @@ -845,8 +847,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(), - player.getPlaybackSkipSilence(), (speed, pitch, skipSilence) - -> player.setPlaybackParameters(speed, pitch, skipSilence)) + player.getPlaybackSkipSilence(), player::setPlaybackParameters) .show(activity.getSupportFragmentManager(), null); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index 499800690..9ce04bfd5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -31,7 +31,7 @@ public abstract class PlayerUi { /** * @param player the player instance that will be usable throughout the lifetime of this UI */ - public PlayerUi(@NonNull final Player player) { + protected PlayerUi(@NonNull final Player player) { this.context = player.getContext(); this.player = player; } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java index 43440b873..8283437f8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.player.ui; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; @@ -140,8 +141,7 @@ public final class PopupPlayerUi extends VideoPlayerUi { binding.segmentsButton.setVisibility(View.GONE); binding.moreOptionsButton.setVisibility(View.GONE); binding.topControls.setOrientation(LinearLayout.HORIZONTAL); - binding.primaryControls.getLayoutParams().width - = LinearLayout.LayoutParams.WRAP_CONTENT; + binding.primaryControls.getLayoutParams().width = WRAP_CONTENT; binding.secondaryControls.setAlpha(1.0f); binding.secondaryControls.setVisibility(View.VISIBLE); binding.secondaryControls.setTranslationY(0); @@ -193,14 +193,12 @@ public final class PopupPlayerUi extends VideoPlayerUi { updateScreenSize(); changePopupSize(popupLayoutParams.width); checkPopupPositionBounds(); - } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { - // Use only audio source when screen turns off while popup player is playing - if (player.isPlaying() || player.isLoading()) { + } else if (player.isPlaying() || player.isLoading()) { + if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + // Use only audio source when screen turns off while popup player is playing player.useVideoSource(false); - } - } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { - // Restore video source when screen turns on and user is watching video in popup player - if (player.isPlaying() || player.isLoading()) { + } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { + // Restore video source when screen turns on and user was watching video in popup player.useVideoSource(true); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 393bf141b..5b0be6f64 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -41,7 +41,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.view.ContextThemeWrapper; -import androidx.appcompat.widget.AppCompatImageButton; import androidx.appcompat.widget.PopupMenu; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; @@ -142,7 +141,7 @@ public abstract class VideoPlayerUi extends PlayerUi //////////////////////////////////////////////////////////////////////////*/ //region Constructor, setup, destroy - public VideoPlayerUi(@NonNull final Player player, + protected VideoPlayerUi(@NonNull final Player player, @NonNull final PlayerBinding playerBinding) { super(player); binding = playerBinding; @@ -912,7 +911,20 @@ public abstract class VideoPlayerUi extends PlayerUi @Override public void onRepeatModeChanged(@RepeatMode final int repeatMode) { super.onRepeatModeChanged(repeatMode); - setRepeatModeButton(binding.repeatButton, repeatMode); + + switch (repeatMode) { + case REPEAT_MODE_OFF: + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_off); + break; + case REPEAT_MODE_ONE: + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_one); + break; + case REPEAT_MODE_ALL: + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_all); + break; + default: + break; // unreachable + } } @Override @@ -927,21 +939,6 @@ public abstract class VideoPlayerUi extends PlayerUi setMuteButton(isMuted); } - private void setRepeatModeButton(final AppCompatImageButton imageButton, - @RepeatMode final int repeatMode) { - switch (repeatMode) { - case REPEAT_MODE_OFF: - imageButton.setImageResource(R.drawable.exo_controls_repeat_off); - break; - case REPEAT_MODE_ONE: - imageButton.setImageResource(R.drawable.exo_controls_repeat_one); - break; - case REPEAT_MODE_ALL: - imageButton.setImageResource(R.drawable.exo_controls_repeat_all); - break; - } - } - private void setMuteButton(final boolean isMuted) { binding.switchMute.setImageDrawable(AppCompatResources.getDrawable(context, isMuted ? R.drawable.ic_volume_off : R.drawable.ic_volume_up)); @@ -1037,6 +1034,7 @@ public abstract class VideoPlayerUi extends PlayerUi binding.qualityTextView.setVisibility(View.VISIBLE); binding.surfaceView.setVisibility(View.VISIBLE); + // fallthrough default: binding.endScreen.setVisibility(View.GONE); binding.playbackEndTime.setVisibility(View.VISIBLE); @@ -1426,8 +1424,6 @@ public abstract class VideoPlayerUi extends PlayerUi public boolean onKeyDown(final int keyCode) { switch (keyCode) { - default: - break; case KeyEvent.KEYCODE_BACK: if (DeviceUtils.isTv(context) && isControlsVisible()) { hideControls(0, 0); @@ -1442,7 +1438,7 @@ public abstract class VideoPlayerUi extends PlayerUi if ((binding.getRoot().hasFocus() && !binding.playbackControlRoot.hasFocus()) || isAnyListViewOpen()) { // do not interfere with focus in playlist and play queue etc. - return false; + break; } if (player.getCurrentState() == org.schabi.newpipe.player.Player.STATE_BLOCKED) { @@ -1458,6 +1454,8 @@ public abstract class VideoPlayerUi extends PlayerUi return true; } break; + default: + break; // ignore other keys } return false;