diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java index f5caf2c79..e88defe7f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java @@ -45,22 +45,16 @@ public final class NotificationUtil { private static final boolean DEBUG = Player.DEBUG; private static final int NOTIFICATION_ID = 123789; - @Nullable private static NotificationUtil instance = null; - @NotificationConstants.Action private final int[] notificationSlots = NotificationConstants.SLOT_DEFAULTS.clone(); private NotificationManagerCompat notificationManager; private NotificationCompat.Builder notificationBuilder; - private NotificationUtil() { - } + private Player player; - public static NotificationUtil getInstance() { - if (instance == null) { - instance = new NotificationUtil(); - } - return instance; + public NotificationUtil(final Player player) { + this.player = player; } @@ -71,20 +65,18 @@ public final class NotificationUtil { /** * Creates the notification if it does not exist already and recreates it if forceRecreate is * true. Updates the notification with the data in the player. - * @param player the player currently open, to take data from * @param forceRecreate whether to force the recreation of the notification even if it already * exists */ - synchronized void createNotificationIfNeededAndUpdate(final Player player, - final boolean forceRecreate) { + public synchronized void createNotificationIfNeededAndUpdate(final boolean forceRecreate) { if (forceRecreate || notificationBuilder == null) { - notificationBuilder = createNotification(player); + notificationBuilder = createNotification(); } - updateNotification(player); + updateNotification(); notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); } - private synchronized NotificationCompat.Builder createNotification(final Player player) { + private synchronized NotificationCompat.Builder createNotification() { if (DEBUG) { Log.d(TAG, "createNotification()"); } @@ -93,7 +85,7 @@ public final class NotificationUtil { new NotificationCompat.Builder(player.getContext(), player.getContext().getString(R.string.notification_channel_id)); - initializeNotificationSlots(player); + initializeNotificationSlots(); // count the number of real slots, to make sure compact slots indices are not out of bound int nonNothingSlotCount = 5; @@ -132,9 +124,8 @@ public final class NotificationUtil { /** * Updates the notification builder and the button icons depending on the playback state. - * @param player the player currently open, to take data from */ - private synchronized void updateNotification(final Player player) { + private synchronized void updateNotification() { if (DEBUG) { Log.d(TAG, "updateNotification()"); } @@ -145,17 +136,17 @@ public final class NotificationUtil { notificationBuilder.setContentTitle(player.getVideoTitle()); notificationBuilder.setContentText(player.getUploaderName()); notificationBuilder.setTicker(player.getVideoTitle()); - updateActions(notificationBuilder, player); + updateActions(notificationBuilder); final boolean showThumbnail = player.getPrefs().getBoolean( player.getContext().getString(R.string.show_thumbnail_key), true); if (showThumbnail) { - setLargeIcon(notificationBuilder, player); + setLargeIcon(notificationBuilder); } } @SuppressLint("RestrictedApi") - boolean shouldUpdateBufferingSlot() { + public boolean shouldUpdateBufferingSlot() { if (notificationBuilder == null) { // if there is no notification active, there is no point in updating it return false; @@ -173,22 +164,22 @@ public final class NotificationUtil { } - public void createNotificationAndStartForeground(final Player player, final Service service) { + public void createNotificationAndStartForeground() { if (notificationBuilder == null) { - notificationBuilder = createNotification(player); + notificationBuilder = createNotification(); } - updateNotification(player); + updateNotification(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - service.startForeground(NOTIFICATION_ID, notificationBuilder.build(), + player.getService().startForeground(NOTIFICATION_ID, notificationBuilder.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK); } else { - service.startForeground(NOTIFICATION_ID, notificationBuilder.build()); + player.getService().startForeground(NOTIFICATION_ID, notificationBuilder.build()); } } - void cancelNotificationAndStopForeground(final Service service) { - ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_REMOVE); + public void cancelNotificationAndStopForeground() { + ServiceCompat.stopForeground(player.getService(), ServiceCompat.STOP_FOREGROUND_REMOVE); if (notificationManager != null) { notificationManager.cancel(NOTIFICATION_ID); @@ -202,7 +193,7 @@ public final class NotificationUtil { // ACTIONS ///////////////////////////////////////////////////// - private void initializeNotificationSlots(final Player player) { + private void initializeNotificationSlots() { for (int i = 0; i < 5; ++i) { notificationSlots[i] = player.getPrefs().getInt( player.getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]), @@ -211,7 +202,7 @@ public final class NotificationUtil { } @SuppressLint("RestrictedApi") - private void updateActions(final NotificationCompat.Builder builder, final Player player) { + private void updateActions(final NotificationCompat.Builder builder) { builder.mActions.clear(); for (int i = 0; i < 5; ++i) { addAction(builder, player, notificationSlots[i]); @@ -221,7 +212,7 @@ public final class NotificationUtil { private void addAction(final NotificationCompat.Builder builder, final Player player, @NotificationConstants.Action final int slot) { - final NotificationCompat.Action action = getAction(player, slot); + final NotificationCompat.Action action = getAction(slot); if (action != null) { builder.addAction(action); } @@ -229,41 +220,40 @@ public final class NotificationUtil { @Nullable private NotificationCompat.Action getAction( - final Player player, @NotificationConstants.Action final int selectedAction) { final int baseActionIcon = NotificationConstants.ACTION_ICONS[selectedAction]; switch (selectedAction) { case NotificationConstants.PREVIOUS: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS); case NotificationConstants.NEXT: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_next_description, ACTION_PLAY_NEXT); case NotificationConstants.REWIND: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_rewind_description, ACTION_FAST_REWIND); case NotificationConstants.FORWARD: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD); case NotificationConstants.SMART_REWIND_PREVIOUS: if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) { - return getAction(player, R.drawable.exo_notification_previous, + return getAction(R.drawable.exo_notification_previous, R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS); } else { - return getAction(player, R.drawable.exo_controls_rewind, + return getAction(R.drawable.exo_controls_rewind, R.string.exo_controls_rewind_description, ACTION_FAST_REWIND); } case NotificationConstants.SMART_FORWARD_NEXT: if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) { - return getAction(player, R.drawable.exo_notification_next, + return getAction(R.drawable.exo_notification_next, R.string.exo_controls_next_description, ACTION_PLAY_NEXT); } else { - return getAction(player, R.drawable.exo_controls_fastforward, + return getAction(R.drawable.exo_controls_fastforward, R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD); } @@ -279,42 +269,42 @@ public final class NotificationUtil { case NotificationConstants.PLAY_PAUSE: if (player.getCurrentState() == Player.STATE_COMPLETED) { - return getAction(player, R.drawable.ic_replay, + return getAction(R.drawable.ic_replay, R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE); } else if (player.isPlaying() || player.getCurrentState() == Player.STATE_PREFLIGHT || player.getCurrentState() == Player.STATE_BLOCKED || player.getCurrentState() == Player.STATE_BUFFERING) { - return getAction(player, R.drawable.exo_notification_pause, + return getAction(R.drawable.exo_notification_pause, R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE); } else { - return getAction(player, R.drawable.exo_notification_play, + return getAction(R.drawable.exo_notification_play, R.string.exo_controls_play_description, ACTION_PLAY_PAUSE); } case NotificationConstants.REPEAT: if (player.getRepeatMode() == REPEAT_MODE_ALL) { - return getAction(player, R.drawable.exo_media_action_repeat_all, + return getAction(R.drawable.exo_media_action_repeat_all, R.string.exo_controls_repeat_all_description, ACTION_REPEAT); } else if (player.getRepeatMode() == REPEAT_MODE_ONE) { - return getAction(player, R.drawable.exo_media_action_repeat_one, + return getAction(R.drawable.exo_media_action_repeat_one, R.string.exo_controls_repeat_one_description, ACTION_REPEAT); } else /* player.getRepeatMode() == REPEAT_MODE_OFF */ { - return getAction(player, R.drawable.exo_media_action_repeat_off, + return getAction(R.drawable.exo_media_action_repeat_off, R.string.exo_controls_repeat_off_description, ACTION_REPEAT); } case NotificationConstants.SHUFFLE: if (player.getPlayQueue() != null && player.getPlayQueue().isShuffled()) { - return getAction(player, R.drawable.exo_controls_shuffle_on, + return getAction(R.drawable.exo_controls_shuffle_on, R.string.exo_controls_shuffle_on_description, ACTION_SHUFFLE); } else { - return getAction(player, R.drawable.exo_controls_shuffle_off, + return getAction(R.drawable.exo_controls_shuffle_off, R.string.exo_controls_shuffle_off_description, ACTION_SHUFFLE); } case NotificationConstants.CLOSE: - return getAction(player, R.drawable.ic_close, + return getAction(R.drawable.ic_close, R.string.close, ACTION_CLOSE); case NotificationConstants.NOTHING: @@ -324,8 +314,7 @@ public final class NotificationUtil { } } - private NotificationCompat.Action getAction(final Player player, - @DrawableRes final int drawable, + private NotificationCompat.Action getAction(@DrawableRes final int drawable, @StringRes final int title, final String intentAction) { return new NotificationCompat.Action(drawable, player.getContext().getString(title), @@ -353,7 +342,7 @@ public final class NotificationUtil { // BITMAP ///////////////////////////////////////////////////// - private void setLargeIcon(final NotificationCompat.Builder builder, final Player player) { + private void setLargeIcon(final NotificationCompat.Builder builder) { final boolean scaleImageToSquareAspectRatio = player.getPrefs().getBoolean( player.getContext().getString(R.string.scale_to_square_image_in_notifications_key), false); 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 78e93970c..e2732f4d0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -38,7 +38,6 @@ import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; -import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; @@ -620,7 +619,7 @@ public final class Player implements PlaybackListener, Listener { } private void setRecovery(final int queuePos, final long windowPos) { - if (playQueue.size() <= queuePos) { + if (playQueue == null || playQueue.size() <= queuePos) { return; } @@ -735,9 +734,6 @@ public final class Player implements PlaybackListener, Listener { case ACTION_SHUFFLE: toggleShuffleModeEnabled(); break; - case ACTION_RECREATE_NOTIFICATION: - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true); - break; case Intent.ACTION_CONFIGURATION_CHANGED: assureCorrectAppLanguage(service); if (DEBUG) { @@ -797,8 +793,6 @@ public final class Player implements PlaybackListener, Listener { } currentThumbnail = bitmap; - NotificationUtil.getInstance() - .createNotificationIfNeededAndUpdate(Player.this, false); // there is a new thumbnail, so changed the end screen thumbnail, too. UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); } @@ -807,8 +801,7 @@ public final class Player implements PlaybackListener, Listener { public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { Log.e(TAG, "Thumbnail - onBitmapFailed() called: url = [" + url + "]", e); currentThumbnail = null; - NotificationUtil.getInstance() - .createNotificationIfNeededAndUpdate(Player.this, false); + UIs.call(playerUi -> playerUi.onThumbnailLoaded(null)); } @Override @@ -1082,8 +1075,6 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onBlocked); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void onPlaying() { @@ -1095,8 +1086,6 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onPlaying); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void onBuffering() { @@ -1105,10 +1094,6 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onBuffering); - - if (NotificationUtil.getInstance().shouldUpdateBufferingSlot()) { - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } } private void onPaused() { @@ -1121,24 +1106,13 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onPaused); - - // Remove running notification when user does not want minimization to background or popup - if (PlayerHelper.getMinimizeOnExitAction(context) == MINIMIZE_ON_EXIT_MODE_NONE - && videoPlayerSelected()) { - NotificationUtil.getInstance().cancelNotificationAndStopForeground(service); - } else { - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } } private void onPausedSeek() { if (DEBUG) { Log.d(TAG, "onPausedSeek() called"); } - UIs.call(PlayerUi::onPausedSeek); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void onCompleted() { @@ -1150,7 +1124,6 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onCompleted); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); if (playQueue.getIndex() < playQueue.size() - 1) { playQueue.offsetIndex(+1); @@ -1190,7 +1163,7 @@ public final class Player implements PlaybackListener, Listener { + "repeatMode = [" + repeatMode + "]"); } UIs.call(playerUi -> playerUi.onRepeatModeChanged(repeatMode)); - onShuffleOrRepeatModeChanged(); + notifyPlaybackUpdateToListeners(); } @Override @@ -1209,7 +1182,7 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(playerUi -> playerUi.onShuffleModeEnabledChanged(shuffleModeEnabled)); - onShuffleOrRepeatModeChanged(); + notifyPlaybackUpdateToListeners(); } public void toggleShuffleModeEnabled() { @@ -1217,11 +1190,6 @@ public final class Player implements PlaybackListener, Listener { simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled()); } } - - private void onShuffleOrRepeatModeChanged() { - notifyPlaybackUpdateToListeners(); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } //endregion @@ -1806,12 +1774,15 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ //region Metadata - private void onMetadataChanged(@NonNull final StreamInfo info) { + private void updateMetadataWith(@NonNull final StreamInfo info) { if (DEBUG) { Log.d(TAG, "Playback - onMetadataChanged() called, playing: " + info.getName()); } + if (exoPlayerIsNull()) { + return; + } - UIs.call(playerUi -> playerUi.onMetadataChanged(info)); + maybeAutoQueueNextStream(info); initThumbnail(info.getThumbnailUrl()); registerStreamViewed(); @@ -1826,17 +1797,7 @@ public final class Player implements PlaybackListener, Listener { ); notifyMetadataUpdateToListeners(); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } - - private void updateMetadataWith(@NonNull final StreamInfo streamInfo) { - if (exoPlayerIsNull()) { - return; - } - - maybeAutoQueueNextStream(streamInfo); - onMetadataChanged(streamInfo); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true); + UIs.call(playerUi -> playerUi.onMetadataChanged(info)); } @NonNull @@ -1925,7 +1886,6 @@ public final class Player implements PlaybackListener, Listener { public void onPlayQueueEdited() { notifyPlaybackUpdateToListeners(); UIs.call(PlayerUi::onPlayQueueEdited); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } @Override // own playback listener 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 cf83dc5c2..7bf918c73 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -88,9 +88,6 @@ public final class PlayerService extends Service { ThemeHelper.setTheme(this); player = new Player(this); - /*final MainPlayerUi mainPlayerUi = new MainPlayerUi(player, - PlayerBinding.inflate(LayoutInflater.from(this))); - player.UIs().add(mainPlayerUi);*/ } @Override @@ -159,7 +156,6 @@ public final class PlayerService extends Service { } public void stopService() { - NotificationUtil.getInstance().cancelNotificationAndStopForeground(this); cleanup(); stopSelf(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java index 40c83c6c7..5736eca3b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java @@ -1,26 +1,125 @@ package org.schabi.newpipe.player.ui; -import androidx.annotation.NonNull; +import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; +import android.content.Intent; +import android.graphics.Bitmap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.Player.RepeatMode; + +import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.NotificationUtil; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.helper.PlayerHelper; public final class NotificationPlayerUi extends PlayerUi { - boolean foregroundNotificationAlreadyCreated = false; + private boolean foregroundNotificationAlreadyCreated = false; + private final NotificationUtil notificationUtil; public NotificationPlayerUi(@NonNull final Player player) { super(player); + notificationUtil = new NotificationUtil(player); } @Override public void initPlayer() { super.initPlayer(); if (!foregroundNotificationAlreadyCreated) { - NotificationUtil.getInstance() - .createNotificationAndStartForeground(player, player.getService()); + notificationUtil.createNotificationAndStartForeground(); foregroundNotificationAlreadyCreated = true; } } - // TODO TODO on destroy remove foreground + @Override + public void destroy() { + super.destroy(); + notificationUtil.cancelNotificationAndStopForeground(); + } + + @Override + public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + super.onThumbnailLoaded(bitmap); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onBlocked() { + super.onBlocked(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onPlaying() { + super.onPlaying(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onBuffering() { + super.onBuffering(); + if (notificationUtil.shouldUpdateBufferingSlot()) { + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + } + + @Override + public void onPaused() { + super.onPaused(); + + // Remove running notification when user does not want minimization to background or popup + if (PlayerHelper.getMinimizeOnExitAction(context) == MINIMIZE_ON_EXIT_MODE_NONE + && player.videoPlayerSelected()) { + notificationUtil.cancelNotificationAndStopForeground(); + } else { + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + } + + @Override + public void onPausedSeek() { + super.onPausedSeek(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onCompleted() { + super.onCompleted(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onRepeatModeChanged(@RepeatMode final int repeatMode) { + super.onRepeatModeChanged(repeatMode); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { + super.onShuffleModeEnabledChanged(shuffleModeEnabled); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + if (ACTION_RECREATE_NOTIFICATION.equals(intent.getAction())) { + notificationUtil.createNotificationIfNeededAndUpdate(true); + } + } + + @Override + public void onMetadataChanged(@NonNull final StreamInfo info) { + super.onMetadataChanged(info); + notificationUtil.createNotificationIfNeededAndUpdate(true); + } + + @Override + public void onPlayQueueEdited() { + super.onPlayQueueEdited(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } }