From c054ea07372ab33049047cbcfcf5ae5584c05dea Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 18:00:43 +0200 Subject: [PATCH 01/11] Create MediaSessionPlayerUi --- .../org/schabi/newpipe/player/Player.java | 43 +++-------- .../schabi/newpipe/player/PlayerService.java | 6 +- .../MediaSessionManager.java | 4 +- .../mediasession/MediaSessionPlayerUi.java | 74 +++++++++++++++++++ .../player/notification/NotificationUtil.java | 10 ++- .../schabi/newpipe/player/ui/PlayerUi.java | 3 +- .../newpipe/player/ui/PlayerUiList.java | 17 ++++- 7 files changed, 113 insertions(+), 44 deletions(-) rename app/src/main/java/org/schabi/newpipe/player/{helper => mediasession}/MediaSessionManager.java (97%) create mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java 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 159d361c1..e1bc2f061 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -99,14 +99,13 @@ import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.helper.AudioReactor; import org.schabi.newpipe.player.helper.LoadController; -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.mediasession.MediaSessionPlayerUi; 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; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; @@ -196,7 +195,6 @@ public final class Player implements PlaybackListener, Listener { private ExoPlayer simpleExoPlayer; private AudioReactor audioReactor; - private MediaSessionManager mediaSessionManager; @NonNull private final DefaultTrackSelector trackSelector; @NonNull private final LoadController loadController; @@ -225,7 +223,7 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ @SuppressWarnings("MemberName") // keep the unusual member name - private final PlayerUiList UIs = new PlayerUiList(); + private final PlayerUiList UIs; private BroadcastReceiver broadcastReceiver; private IntentFilter intentFilter; @@ -265,6 +263,15 @@ public final class Player implements PlaybackListener, Listener { videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); + + // The UIs added here should always be present. They will be initialized when the player + // reaches the initialization step. Make sure the media session ui is before the + // notification ui in the UIs list, since the notification depends on the media session in + // PlayerUi#initPlayer(), and UIs.call() guarantees UI order is preserved. + UIs = new PlayerUiList( + new MediaSessionPlayerUi(this), + new NotificationPlayerUi(this) + ); } private VideoPlaybackResolver.QualityResolver getQualityResolver() { @@ -431,11 +438,6 @@ public final class Player implements PlaybackListener, Listener { } private void initUIsForCurrentPlayerType() { - //noinspection SimplifyOptionalCallChains - if (!UIs.get(NotificationPlayerUi.class).isPresent()) { - UIs.addAndPrepare(new NotificationPlayerUi(this)); - } - if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN) || (UIs.get(PopupPlayerUi.class).isPresent() && playerType == PlayerType.POPUP)) { // correct UI already in place @@ -506,8 +508,6 @@ public final class Player implements PlaybackListener, Listener { simpleExoPlayer.setHandleAudioBecomingNoisy(true); audioReactor = new AudioReactor(context, simpleExoPlayer); - mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer, - new PlayerMediaSession(this)); registerBroadcastReceiver(); @@ -558,9 +558,6 @@ public final class Player implements PlaybackListener, Listener { if (playQueueManager != null) { playQueueManager.dispose(); } - if (mediaSessionManager != null) { - mediaSessionManager.dispose(); - } } public void destroy() { @@ -723,11 +720,6 @@ public final class Player implements PlaybackListener, Listener { Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received"); } break; - case Intent.ACTION_HEADSET_PLUG: //FIXME - /*notificationManager.cancel(NOTIFICATION_ID); - mediaSessionManager.dispose(); - mediaSessionManager.enable(getBaseContext(), basePlayerImpl.simpleExoPlayer);*/ - break; } UIs.call(playerUi -> playerUi.onBroadcastReceived(intent)); @@ -1738,15 +1730,6 @@ public final class Player implements PlaybackListener, Listener { initThumbnail(info.getThumbnailUrl()); registerStreamViewed(); - final boolean showThumbnail = prefs.getBoolean( - context.getString(R.string.show_thumbnail_key), true); - mediaSessionManager.setMetadata( - getVideoTitle(), - getUploaderName(), - showThumbnail ? Optional.ofNullable(getThumbnail()) : Optional.empty(), - StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() - ); - notifyMetadataUpdateToListeners(); UIs.call(playerUi -> playerUi.onMetadataChanged(info)); } @@ -2194,10 +2177,6 @@ public final class Player implements PlaybackListener, Listener { return prefs; } - public MediaSessionManager getMediaSessionManager() { - return mediaSessionManager; - } - public PlayerType getPlayerType() { return playerType; 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 8d982617a..33b024e3d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -28,6 +28,7 @@ import android.os.Binder; import android.os.IBinder; import android.util.Log; +import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.util.ThemeHelper; @@ -73,9 +74,8 @@ public final class PlayerService extends Service { } player.handleIntent(intent); - if (player.getMediaSessionManager() != null) { - player.getMediaSessionManager().handleMediaButtonIntent(intent); - } + player.UIs().get(MediaSessionPlayerUi.class) + .ifPresent(ui -> ui.handleMediaButtonIntent(intent)); return START_NOT_STICKY; } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java similarity index 97% rename from app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java rename to app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index a8735dc08..61bc9e2e7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player.helper; +package org.schabi.newpipe.player.mediasession; import android.content.Context; import android.content.Intent; @@ -18,8 +18,6 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import org.schabi.newpipe.MainActivity; -import org.schabi.newpipe.player.mediasession.MediaSessionCallback; -import org.schabi.newpipe.player.mediasession.PlayQueueNavigator; import java.util.Optional; diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java new file mode 100644 index 000000000..d2de11ccf --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -0,0 +1,74 @@ +package org.schabi.newpipe.player.mediasession; + +import android.content.Intent; +import android.support.v4.media.session.MediaSessionCompat; + +import androidx.annotation.NonNull; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.playback.PlayerMediaSession; +import org.schabi.newpipe.player.ui.PlayerUi; +import org.schabi.newpipe.util.StreamTypeUtil; + +import java.util.Optional; + +public class MediaSessionPlayerUi extends PlayerUi { + + private MediaSessionManager mediaSessionManager; + + public MediaSessionPlayerUi(@NonNull final Player player) { + super(player); + } + + @Override + public void initPlayer() { + super.initPlayer(); + if (mediaSessionManager != null) { + mediaSessionManager.dispose(); + } + mediaSessionManager = new MediaSessionManager(context, player.getExoPlayer(), + new PlayerMediaSession(player)); + } + + @Override + public void destroyPlayer() { + super.destroyPlayer(); + if (mediaSessionManager != null) { + mediaSessionManager.dispose(); + mediaSessionManager = null; + } + } + + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + // TODO decide whether to handle ACTION_HEADSET_PLUG or not + } + + @Override + public void onMetadataChanged(@NonNull final StreamInfo info) { + super.onMetadataChanged(info); + + final boolean showThumbnail = player.getPrefs().getBoolean( + context.getString(R.string.show_thumbnail_key), true); + + mediaSessionManager.setMetadata( + player.getVideoTitle(), + player.getUploaderName(), + showThumbnail ? Optional.ofNullable(player.getThumbnail()) : Optional.empty(), + StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() + ); + } + + public void handleMediaButtonIntent(final Intent intent) { + if (mediaSessionManager != null) { + mediaSessionManager.handleMediaButtonIntent(intent); + } + } + + public Optional getSessionToken() { + return Optional.ofNullable(mediaSessionManager).map(MediaSessionManager::getSessionToken); + } +} 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 1a91bc66d..29ec7a981 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 @@ -19,11 +19,13 @@ import androidx.core.content.ContextCompat; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.util.NavigationHelper; import java.util.List; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; +import static androidx.media.app.NotificationCompat.MediaStyle; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE; @@ -101,9 +103,11 @@ public final class NotificationUtil { player.getContext(), player.getPrefs(), nonNothingSlotCount); final int[] compactSlots = compactSlotList.stream().mapToInt(Integer::intValue).toArray(); - builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle() - .setMediaSession(player.getMediaSessionManager().getSessionToken()) - .setShowActionsInCompactView(compactSlots)) + final MediaStyle mediaStyle = new MediaStyle().setShowActionsInCompactView(compactSlots); + player.UIs().get(MediaSessionPlayerUi.class).flatMap(MediaSessionPlayerUi::getSessionToken) + .ifPresent(mediaStyle::setMediaSession); + + builder.setStyle(mediaStyle) .setPriority(NotificationCompat.PRIORITY_HIGH) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setCategory(NotificationCompat.CATEGORY_TRANSPORT) 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 9ce04bfd5..57e2ec2a2 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 @@ -29,7 +29,8 @@ public abstract class PlayerUi { @NonNull protected final Player player; /** - * @param player the player instance that will be usable throughout the lifetime of this UI + * @param player the player instance that will be usable throughout the lifetime of this UI; its + * context should already have been initialized */ protected PlayerUi(@NonNull final Player player) { this.context = player.getContext(); diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java index 05c0ed5b3..24fec3b8a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java @@ -8,6 +8,19 @@ import java.util.function.Consumer; public final class PlayerUiList { final List playerUis = new ArrayList<>(); + /** + * Creates a {@link PlayerUiList} starting with the provided player uis. The provided player uis + * will not be prepared like those passed to {@link #addAndPrepare(PlayerUi)}, because when + * the {@link PlayerUiList} constructor is called, the player is still not running and it + * wouldn't make sense to initialize uis then. Instead the player will initialize them by doing + * proper calls to {@link #call(Consumer)}. + * + * @param initialPlayerUis the player uis this list should start with; the order will be kept + */ + public PlayerUiList(final PlayerUi... initialPlayerUis) { + playerUis.addAll(List.of(initialPlayerUis)); + } + /** * Adds the provided player ui to the list and calls on it the initialization functions that * apply based on the current player state. The preparation step needs to be done since when UIs @@ -67,11 +80,11 @@ public final class PlayerUiList { } /** - * Calls the provided consumer on all player UIs in the list. + * Calls the provided consumer on all player UIs in the list, in order of addition. * @param consumer the consumer to call with player UIs */ public void call(final Consumer consumer) { //noinspection SimplifyStreamApiCallChains - playerUis.stream().forEach(consumer); + playerUis.stream().forEachOrdered(consumer); } } From bc33322d4be00a0c34db81248e938074555874b7 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 22:40:21 +0200 Subject: [PATCH 02/11] Remove useless MediaSessionCallback The player is now passed directly, it made no sense to wrap around it in a callback that was not really a callback but rather, actually, a wrapper. --- .../mediasession/MediaSessionCallback.java | 21 ---- .../mediasession/MediaSessionManager.java | 16 +-- .../mediasession/MediaSessionPlayerUi.java | 4 +- .../mediasession/PlayQueueNavigator.java | 100 +++++++++++++----- .../player/playback/PlayerMediaSession.java | 99 ----------------- 5 files changed, 83 insertions(+), 157 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java delete mode 100644 app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java deleted file mode 100644 index c4b02d985..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.schabi.newpipe.player.mediasession; - -import android.support.v4.media.MediaDescriptionCompat; - -public interface MediaSessionCallback { - void playPrevious(); - - void playNext(); - - void playItemAtIndex(int index); - - int getCurrentPlayingIndex(); - - int getQueueSize(); - - MediaDescriptionCompat getQueueMetadata(int index); - - void play(); - - void pause(); -} diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index 61bc9e2e7..69f7d38fe 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -14,10 +14,11 @@ import androidx.annotation.Nullable; import androidx.media.session.MediaButtonReceiver; import com.google.android.exoplayer2.ForwardingPlayer; -import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import org.schabi.newpipe.MainActivity; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import java.util.Optional; @@ -36,8 +37,7 @@ public class MediaSessionManager { private int lastAlbumArtHashCode; public MediaSessionManager(@NonNull final Context context, - @NonNull final Player player, - @NonNull final MediaSessionCallback callback) { + @NonNull final Player player) { mediaSession = new MediaSessionCompat(context, TAG); mediaSession.setActive(true); @@ -53,16 +53,18 @@ public class MediaSessionManager { .build()); sessionConnector = new MediaSessionConnector(mediaSession); - sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback)); - sessionConnector.setPlayer(new ForwardingPlayer(player) { + sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); + sessionConnector.setPlayer(new ForwardingPlayer(player.getExoPlayer()) { @Override public void play() { - callback.play(); + player.play(); + // hide the player controls even if the play command came from the media session + player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); } @Override public void pause() { - callback.pause(); + player.pause(); } }); } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java index d2de11ccf..a2eca575f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -8,7 +8,6 @@ import androidx.annotation.NonNull; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.playback.PlayerMediaSession; import org.schabi.newpipe.player.ui.PlayerUi; import org.schabi.newpipe.util.StreamTypeUtil; @@ -28,8 +27,7 @@ public class MediaSessionPlayerUi extends PlayerUi { if (mediaSessionManager != null) { mediaSessionManager.dispose(); } - mediaSessionManager = new MediaSessionManager(context, player.getExoPlayer(), - new PlayerMediaSession(player)); + mediaSessionManager = new MediaSessionManager(context, player); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java index 92cd425c5..7bd27bfdc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -1,106 +1,152 @@ package org.schabi.newpipe.player.mediasession; +import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; +import android.support.v4.media.MediaDescriptionCompat; +import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; + public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator { - public static final int DEFAULT_MAX_QUEUE_SIZE = 10; + private static final int MAX_QUEUE_SIZE = 10; private final MediaSessionCompat mediaSession; - private final MediaSessionCallback callback; - private final int maxQueueSize; + private final Player player; private long activeQueueItemId; public PlayQueueNavigator(@NonNull final MediaSessionCompat mediaSession, - @NonNull final MediaSessionCallback callback) { + @NonNull final Player player) { this.mediaSession = mediaSession; - this.callback = callback; - this.maxQueueSize = DEFAULT_MAX_QUEUE_SIZE; + this.player = player; this.activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; } @Override - public long getSupportedQueueNavigatorActions(@Nullable final Player player) { + public long getSupportedQueueNavigatorActions( + @Nullable final com.google.android.exoplayer2.Player exoPlayer) { return ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS | ACTION_SKIP_TO_QUEUE_ITEM; } @Override - public void onTimelineChanged(@NonNull final Player player) { + public void onTimelineChanged(@NonNull final com.google.android.exoplayer2.Player exoPlayer) { publishFloatingQueueWindow(); } @Override - public void onCurrentMediaItemIndexChanged(@NonNull final Player player) { + public void onCurrentMediaItemIndexChanged( + @NonNull final com.google.android.exoplayer2.Player exoPlayer) { if (activeQueueItemId == MediaSessionCompat.QueueItem.UNKNOWN_ID - || player.getCurrentTimeline().getWindowCount() > maxQueueSize) { + || exoPlayer.getCurrentTimeline().getWindowCount() > MAX_QUEUE_SIZE) { publishFloatingQueueWindow(); - } else if (!player.getCurrentTimeline().isEmpty()) { - activeQueueItemId = player.getCurrentMediaItemIndex(); + } else if (!exoPlayer.getCurrentTimeline().isEmpty()) { + activeQueueItemId = exoPlayer.getCurrentMediaItemIndex(); } } @Override - public long getActiveQueueItemId(@Nullable final Player player) { - return callback.getCurrentPlayingIndex(); + public long getActiveQueueItemId( + @Nullable final com.google.android.exoplayer2.Player exoPlayer) { + return Optional.ofNullable(player.getPlayQueue()).map(PlayQueue::getIndex).orElse(-1); } @Override - public void onSkipToPrevious(@NonNull final Player player) { - callback.playPrevious(); + public void onSkipToPrevious(@NonNull final com.google.android.exoplayer2.Player exoPlayer) { + player.playPrevious(); } @Override - public void onSkipToQueueItem(@NonNull final Player player, final long id) { - callback.playItemAtIndex((int) id); + public void onSkipToQueueItem(@NonNull final com.google.android.exoplayer2.Player exoPlayer, + final long id) { + if (player.getPlayQueue() != null) { + player.selectQueueItem(player.getPlayQueue().getItem((int) id)); + } } @Override - public void onSkipToNext(@NonNull final Player player) { - callback.playNext(); + public void onSkipToNext(@NonNull final com.google.android.exoplayer2.Player exoPlayer) { + player.playNext(); } private void publishFloatingQueueWindow() { - if (callback.getQueueSize() == 0) { + final int windowCount = Optional.ofNullable(player.getPlayQueue()) + .map(PlayQueue::size) + .orElse(0); + if (windowCount == 0) { mediaSession.setQueue(Collections.emptyList()); activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; return; } // Yes this is almost a copypasta, got a problem with that? =\ - final int windowCount = callback.getQueueSize(); - final int currentWindowIndex = callback.getCurrentPlayingIndex(); - final int queueSize = Math.min(maxQueueSize, windowCount); + final int currentWindowIndex = player.getPlayQueue().getIndex(); + final int queueSize = Math.min(MAX_QUEUE_SIZE, windowCount); final int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0, windowCount - queueSize); final List queue = new ArrayList<>(); for (int i = startIndex; i < startIndex + queueSize; i++) { - queue.add(new MediaSessionCompat.QueueItem(callback.getQueueMetadata(i), i)); + queue.add(new MediaSessionCompat.QueueItem(getQueueMetadata(i), i)); } mediaSession.setQueue(queue); activeQueueItemId = currentWindowIndex; } + public MediaDescriptionCompat getQueueMetadata(final int index) { + if (player.getPlayQueue() == null) { + return null; + } + final PlayQueueItem item = player.getPlayQueue().getItem(index); + if (item == null) { + return null; + } + + final MediaDescriptionCompat.Builder descBuilder = new MediaDescriptionCompat.Builder() + .setMediaId(String.valueOf(index)) + .setTitle(item.getTitle()) + .setSubtitle(item.getUploader()); + + // set additional metadata for A2DP/AVRCP + final Bundle additionalMetadata = new Bundle(); + additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle()); + additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); + additionalMetadata + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000); + additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); + additionalMetadata + .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); + descBuilder.setExtras(additionalMetadata); + + final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl()); + if (thumbnailUri != null) { + descBuilder.setIconUri(thumbnailUri); + } + + return descBuilder.build(); + } + @Override - public boolean onCommand(@NonNull final Player player, + public boolean onCommand(@NonNull final com.google.android.exoplayer2.Player exoPlayer, @NonNull final String command, @Nullable final Bundle extras, @Nullable final ResultReceiver cb) { diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java deleted file mode 100644 index 3c41acc75..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.schabi.newpipe.player.playback; - -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.media.MediaDescriptionCompat; -import android.support.v4.media.MediaMetadataCompat; - -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.mediasession.MediaSessionCallback; -import org.schabi.newpipe.player.playqueue.PlayQueueItem; -import org.schabi.newpipe.player.ui.VideoPlayerUi; - -public class PlayerMediaSession implements MediaSessionCallback { - private final Player player; - - public PlayerMediaSession(final Player player) { - this.player = player; - } - - @Override - public void playPrevious() { - player.playPrevious(); - } - - @Override - public void playNext() { - player.playNext(); - } - - @Override - public void playItemAtIndex(final int index) { - if (player.getPlayQueue() == null) { - return; - } - player.selectQueueItem(player.getPlayQueue().getItem(index)); - } - - @Override - public int getCurrentPlayingIndex() { - if (player.getPlayQueue() == null) { - return -1; - } - return player.getPlayQueue().getIndex(); - } - - @Override - public int getQueueSize() { - if (player.getPlayQueue() == null) { - return -1; - } - return player.getPlayQueue().size(); - } - - @Override - public MediaDescriptionCompat getQueueMetadata(final int index) { - if (player.getPlayQueue() == null) { - return null; - } - final PlayQueueItem item = player.getPlayQueue().getItem(index); - if (item == null) { - return null; - } - - final MediaDescriptionCompat.Builder descBuilder = new MediaDescriptionCompat.Builder() - .setMediaId(String.valueOf(index)) - .setTitle(item.getTitle()) - .setSubtitle(item.getUploader()); - - // set additional metadata for A2DP/AVRCP - final Bundle additionalMetadata = new Bundle(); - additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle()); - additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); - additionalMetadata - .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000); - additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); - additionalMetadata - .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); - descBuilder.setExtras(additionalMetadata); - - final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl()); - if (thumbnailUri != null) { - descBuilder.setIconUri(thumbnailUri); - } - - return descBuilder.build(); - } - - @Override - public void play() { - player.play(); - // hide the player controls even if the play command came from the media session - player.UIs().get(VideoPlayerUi.class).ifPresent(playerUi -> playerUi.hideControls(0, 0)); - } - - @Override - public void pause() { - player.pause(); - } -} From 3cc43e9fb9eb32063deebcfaf29531aa4588fc3d Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 22:44:59 +0200 Subject: [PATCH 03/11] Fix thumbnail sometimes not set to media session metadata The thumbnail was not being updated in the media session metadata after it was loaded, since there was no metadata update in that case, only a notification update. --- .../newpipe/player/mediasession/MediaSessionPlayerUi.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java index a2eca575f..e0343820e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -1,9 +1,11 @@ package org.schabi.newpipe.player.mediasession; import android.content.Intent; +import android.graphics.Bitmap; import android.support.v4.media.session.MediaSessionCompat; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; @@ -60,6 +62,12 @@ public class MediaSessionPlayerUi extends PlayerUi { ); } + @Override + public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + super.onThumbnailLoaded(bitmap); + player.getCurrentStreamInfo().ifPresent(this::onMetadataChanged); + } + public void handleMediaButtonIntent(final Intent intent) { if (mediaSessionManager != null) { mediaSessionManager.handleMediaButtonIntent(intent); From f3a9b81b670c0e999a9d2fcc8b657d3e85e182f0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 23:03:42 +0200 Subject: [PATCH 04/11] Fix sometimes seeing outdated thumbnail in notification Before the thumbnail finishes loading for the new video the player is now playing, the old thumbnail was being used, leading to wrong thumbnails set in the media session and the notification. --- .../java/org/schabi/newpipe/player/Player.java | 14 +++++++++----- .../org/schabi/newpipe/util/PicassoHelper.java | 7 +++++++ 2 files changed, 16 insertions(+), 5 deletions(-) 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 e1bc2f061..13dd1d938 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -748,11 +748,15 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ //region Thumbnail loading - private void initThumbnail(final String url) { + private void loadCurrentThumbnail(final String url) { if (DEBUG) { - Log.d(TAG, "Thumbnail - initThumbnail() called with url = [" + Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with url = [" + (url == null ? "null" : url) + "]"); } + + // Unset currentThumbnail, since it is now outdated. This ensures it is not used in media + // session metadata while the new thumbnail is being loaded by Picasso. + currentThumbnail = null; if (isNullOrEmpty(url)) { return; } @@ -762,8 +766,8 @@ public final class Player implements PlaybackListener, Listener { @Override public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { if (DEBUG) { - Log.d(TAG, "Thumbnail - onLoadingComplete() called with: url = [" + url - + "], " + "loadedImage = [" + bitmap + " -> " + bitmap.getWidth() + "x" + Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: url = [" + url + + "], " + "bitmap = [" + bitmap + " -> " + bitmap.getWidth() + "x" + bitmap.getHeight() + "], from = [" + from + "]"); } @@ -1727,7 +1731,7 @@ public final class Player implements PlaybackListener, Listener { maybeAutoQueueNextStream(info); - initThumbnail(info.getThumbnailUrl()); + loadCurrentThumbnail(info.getThumbnailUrl()); registerStreamViewed(); notifyMetadataUpdateToListeners(); diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index fc7600d4b..5739b930b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -1,10 +1,12 @@ package org.schabi.newpipe.util; +import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.extractor.utils.Utils.isBlank; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; +import android.util.Log; import androidx.annotation.Nullable; @@ -24,6 +26,7 @@ import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; public final class PicassoHelper { + private static final String TAG = PicassoHelper.class.getSimpleName(); public static final String PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY = "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; @@ -129,6 +132,10 @@ public final class PicassoHelper { .transform(new Transformation() { @Override public Bitmap transform(final Bitmap source) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - transform() called"); + } + final float notificationThumbnailWidth = Math.min( context.getResources() .getDimension(R.dimen.player_notification_thumbnail_width), From d73ca41cfe6dd3e39bf3f7d751d5870f307a336b Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 23:44:17 +0200 Subject: [PATCH 05/11] Even when thumbnails should not be shown, set it to null in notification This makes sure the thumbnail is removed from the notification if the user disables thumbnails --- .../player/notification/NotificationUtil.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) 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 29ec7a981..84e9cc3bc 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 @@ -9,6 +9,7 @@ import android.os.Build; import android.util.Log; import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; @@ -137,12 +138,9 @@ public final class NotificationUtil { notificationBuilder.setContentTitle(player.getVideoTitle()); notificationBuilder.setContentText(player.getUploaderName()); notificationBuilder.setTicker(player.getVideoTitle()); + updateActions(notificationBuilder); - final boolean showThumbnail = player.getPrefs().getBoolean( - player.getContext().getString(R.string.show_thumbnail_key), true); - if (showThumbnail) { - setLargeIcon(notificationBuilder); - } + setLargeIcon(notificationBuilder); } @@ -344,17 +342,26 @@ public final class NotificationUtil { ///////////////////////////////////////////////////// private void setLargeIcon(final NotificationCompat.Builder builder) { + final boolean showThumbnail = player.getPrefs().getBoolean( + player.getContext().getString(R.string.show_thumbnail_key), true); + final Bitmap thumbnail = player.getThumbnail(); + if (thumbnail == null || !showThumbnail) { + // since the builder is reused, make sure the thumbnail is unset if there is not one + builder.setLargeIcon(null); + return; + } + final boolean scaleImageToSquareAspectRatio = player.getPrefs().getBoolean( player.getContext().getString(R.string.scale_to_square_image_in_notifications_key), false); if (scaleImageToSquareAspectRatio) { - builder.setLargeIcon(getBitmapWithSquareAspectRatio(player.getThumbnail())); + builder.setLargeIcon(getBitmapWithSquareAspectRatio(thumbnail)); } else { - builder.setLargeIcon(player.getThumbnail()); + builder.setLargeIcon(thumbnail); } } - private Bitmap getBitmapWithSquareAspectRatio(final Bitmap bitmap) { + private Bitmap getBitmapWithSquareAspectRatio(@NonNull final Bitmap bitmap) { // Find the smaller dimension and then take a center portion of the image that // has that size. final int w = bitmap.getWidth(); From 8bff445ec3d314a35d3243d4fff2711184265533 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 10:17:22 +0200 Subject: [PATCH 06/11] Remove useless checks before updating metadata A while ago NewPipe called the metadata update function very often, so checks were needed to ensure not wasting time updating metadata if it were already up to date. Now, instead, the metadata update function is called exactly when needed, i.e. when metadata changes, so such checks are not needed anymore (and were probably also a little resource-heavy). --- .../org/schabi/newpipe/player/Player.java | 5 - .../mediasession/MediaSessionManager.java | 123 ++---------------- .../mediasession/MediaSessionPlayerUi.java | 2 +- 3 files changed, 11 insertions(+), 119 deletions(-) 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 13dd1d938..22d46bcbe 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -54,7 +54,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.util.Log; @@ -1773,10 +1772,6 @@ public final class Player implements PlaybackListener, Listener { @Nullable public Bitmap getThumbnail() { - if (currentThumbnail == null) { - currentThumbnail = BitmapFactory.decodeResource( - context.getResources(), R.drawable.placeholder_thumbnail_video); - } return currentThumbnail; } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index 69f7d38fe..98b6d1b32 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -20,8 +20,6 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.VideoPlayerUi; -import java.util.Optional; - public class MediaSessionManager { private static final String TAG = MediaSessionManager.class.getSimpleName(); public static final boolean DEBUG = MainActivity.DEBUG; @@ -31,11 +29,6 @@ public class MediaSessionManager { @NonNull private final MediaSessionConnector sessionConnector; - private int lastTitleHashCode; - private int lastArtistHashCode; - private long lastDuration; - private int lastAlbumArtHashCode; - public MediaSessionManager(@NonNull final Context context, @NonNull final Player player) { mediaSession = new MediaSessionCompat(context, TAG); @@ -84,134 +77,38 @@ public class MediaSessionManager { * * @param title {@link MediaMetadataCompat#METADATA_KEY_TITLE} * @param artist {@link MediaMetadataCompat#METADATA_KEY_ARTIST} - * @param optAlbumArt {@link MediaMetadataCompat#METADATA_KEY_ALBUM_ART} + * @param albumArt {@link MediaMetadataCompat#METADATA_KEY_ALBUM_ART}, if not null * @param duration {@link MediaMetadataCompat#METADATA_KEY_DURATION} * - should be a negative value for unknown durations, e.g. for livestreams */ public void setMetadata(@NonNull final String title, @NonNull final String artist, - @NonNull final Optional optAlbumArt, - final long duration - ) { + @Nullable final Bitmap albumArt, + final long duration) { if (DEBUG) { - Log.d(TAG, "setMetadata called:" - + " t: " + title - + " a: " + artist - + " thumb: " + ( - optAlbumArt.isPresent() - ? optAlbumArt.get().hashCode() - : "") - + " d: " + duration); + Log.d(TAG, "setMetadata called with: title = [" + title + "], artist = [" + artist + + "], albumArt = [" + (albumArt == null ? "null" : albumArt.hashCode()) + + "], duration = [" + duration + "]"); } if (!mediaSession.isActive()) { if (DEBUG) { - Log.d(TAG, "setMetadata: mediaSession not active - exiting"); + Log.d(TAG, "setMetadata: media session not active, exiting"); } return; } - if (!checkIfMetadataShouldBeSet(title, artist, optAlbumArt, duration)) { - if (DEBUG) { - Log.d(TAG, "setMetadata: No update required - exiting"); - } - return; - } - - if (DEBUG) { - Log.d(TAG, "setMetadata: N_Metadata update:" - + " t: " + title - + " a: " + artist - + " thumb: " + ( - optAlbumArt.isPresent() - ? optAlbumArt.get().hashCode() - : "") - + " d: " + duration); - } - final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist) .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); - if (optAlbumArt.isPresent()) { - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, optAlbumArt.get()); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, optAlbumArt.get()); + if (albumArt != null) { + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt); + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, albumArt); } mediaSession.setMetadata(builder.build()); - - lastTitleHashCode = title.hashCode(); - lastArtistHashCode = artist.hashCode(); - lastDuration = duration; - optAlbumArt.ifPresent(bitmap -> lastAlbumArtHashCode = bitmap.hashCode()); - } - - private boolean checkIfMetadataShouldBeSet( - @NonNull final String title, - @NonNull final String artist, - @NonNull final Optional optAlbumArt, - final long duration - ) { - // Check if the values have changed since the last time - if (title.hashCode() != lastTitleHashCode - || artist.hashCode() != lastArtistHashCode - || duration != lastDuration - || (optAlbumArt.isPresent() && optAlbumArt.get().hashCode() != lastAlbumArtHashCode) - ) { - if (DEBUG) { - Log.d(TAG, - "checkIfMetadataShouldBeSet: true - reason: changed values since last"); - } - return true; - } - - // Check if the currently set metadata is valid - if (getMetadataTitle() == null - || getMetadataArtist() == null - // Note that the duration can be <= 0 for live streams - ) { - if (DEBUG) { - if (getMetadataTitle() == null) { - Log.d(TAG, - "N_getMetadataTitle: title == null"); - } else if (getMetadataArtist() == null) { - Log.d(TAG, - "N_getMetadataArtist: artist == null"); - } - } - return true; - } - - // If we got an album art check if the current set AlbumArt is null - if (optAlbumArt.isPresent() && getMetadataAlbumArt() == null) { - if (DEBUG) { - Log.d(TAG, "N_getMetadataAlbumArt: thumb == null"); - } - return true; - } - - // Default - no update required - return false; - } - - - @Nullable - private Bitmap getMetadataAlbumArt() { - return mediaSession.getController().getMetadata() - .getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART); - } - - @Nullable - private String getMetadataTitle() { - return mediaSession.getController().getMetadata() - .getString(MediaMetadataCompat.METADATA_KEY_TITLE); - } - - @Nullable - private String getMetadataArtist() { - return mediaSession.getController().getMetadata() - .getString(MediaMetadataCompat.METADATA_KEY_ARTIST); } /** diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java index e0343820e..2140be26d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -57,7 +57,7 @@ public class MediaSessionPlayerUi extends PlayerUi { mediaSessionManager.setMetadata( player.getVideoTitle(), player.getUploaderName(), - showThumbnail ? Optional.ofNullable(player.getThumbnail()) : Optional.empty(), + showThumbnail ? player.getThumbnail() : null, StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() ); } From f80d1dc48dcc0539ac7faa20b085b0adc2c5fe64 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 11:31:49 +0200 Subject: [PATCH 07/11] Let exoplayer decide when to update metadata Though still make sure metadata is updated after the thumbnail is loaded. This fixes the wrong seekbar properties (duration and current position) being shown in the notification sometimes. --- .../mediasession/MediaSessionManager.java | 87 ++++++++----------- .../mediasession/MediaSessionPlayerUi.java | 22 +---- 2 files changed, 40 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index 98b6d1b32..c6766fbcb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -2,10 +2,8 @@ package org.schabi.newpipe.player.mediasession; import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; -import android.support.v4.media.session.PlaybackStateCompat; import android.util.Log; import android.view.KeyEvent; @@ -17,8 +15,12 @@ import com.google.android.exoplayer2.ForwardingPlayer; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import org.schabi.newpipe.MainActivity; +import org.schabi.newpipe.R; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.VideoPlayerUi; +import org.schabi.newpipe.util.StreamTypeUtil; + +import java.util.Optional; public class MediaSessionManager { private static final String TAG = MediaSessionManager.class.getSimpleName(); @@ -34,17 +36,6 @@ public class MediaSessionManager { mediaSession = new MediaSessionCompat(context, TAG); mediaSession.setActive(true); - mediaSession.setPlaybackState(new PlaybackStateCompat.Builder() - .setState(PlaybackStateCompat.STATE_NONE, -1, 1) - .setActions(PlaybackStateCompat.ACTION_SEEK_TO - | PlaybackStateCompat.ACTION_PLAY - | PlaybackStateCompat.ACTION_PAUSE // was play and pause now play/pause - | PlaybackStateCompat.ACTION_SKIP_TO_NEXT - | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS - | PlaybackStateCompat.ACTION_SET_REPEAT_MODE - | PlaybackStateCompat.ACTION_STOP) - .build()); - sessionConnector = new MediaSessionConnector(mediaSession); sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); sessionConnector.setPlayer(new ForwardingPlayer(player.getExoPlayer()) { @@ -60,6 +51,37 @@ public class MediaSessionManager { player.pause(); } }); + + sessionConnector.setMetadataDeduplicationEnabled(true); + sessionConnector.setMediaMetadataProvider(exoPlayer -> { + if (DEBUG) { + Log.d(TAG, "MediaMetadataProvider#getMetadata called"); + } + + // set title and artist + final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, player.getVideoTitle()) + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, player.getUploaderName()); + + // set duration (-1 for livestreams, since they don't have a duration) + final long duration = player.getCurrentStreamInfo() + .filter(info -> !StreamTypeUtil.isLiveStream(info.getStreamType())) + .map(info -> info.getDuration() * 1000L) + .orElse(-1L); + builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); + + // set album art, unless the user asked not to, or there is no thumbnail available + final boolean showThumbnail = player.getPrefs().getBoolean( + context.getString(R.string.show_thumbnail_key), true); + Optional.ofNullable(player.getThumbnail()) + .filter(bitmap -> showThumbnail) + .ifPresent(bitmap -> { + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap); + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap); + }); + + return builder.build(); + }); } @Nullable @@ -72,43 +94,8 @@ public class MediaSessionManager { return mediaSession.getSessionToken(); } - /** - * sets the Metadata - if required. - * - * @param title {@link MediaMetadataCompat#METADATA_KEY_TITLE} - * @param artist {@link MediaMetadataCompat#METADATA_KEY_ARTIST} - * @param albumArt {@link MediaMetadataCompat#METADATA_KEY_ALBUM_ART}, if not null - * @param duration {@link MediaMetadataCompat#METADATA_KEY_DURATION} - * - should be a negative value for unknown durations, e.g. for livestreams - */ - public void setMetadata(@NonNull final String title, - @NonNull final String artist, - @Nullable final Bitmap albumArt, - final long duration) { - if (DEBUG) { - Log.d(TAG, "setMetadata called with: title = [" + title + "], artist = [" + artist - + "], albumArt = [" + (albumArt == null ? "null" : albumArt.hashCode()) - + "], duration = [" + duration + "]"); - } - - if (!mediaSession.isActive()) { - if (DEBUG) { - Log.d(TAG, "setMetadata: media session not active, exiting"); - } - return; - } - - final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist) - .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); - - if (albumArt != null) { - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, albumArt); - } - - mediaSession.setMetadata(builder.build()); + void triggerMetadataUpdate() { + sessionConnector.invalidateMediaSessionMetadata(); } /** diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java index 2140be26d..c78a3a6b1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -7,11 +7,8 @@ import android.support.v4.media.session.MediaSessionCompat; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.PlayerUi; -import org.schabi.newpipe.util.StreamTypeUtil; import java.util.Optional; @@ -47,25 +44,12 @@ public class MediaSessionPlayerUi extends PlayerUi { // TODO decide whether to handle ACTION_HEADSET_PLUG or not } - @Override - public void onMetadataChanged(@NonNull final StreamInfo info) { - super.onMetadataChanged(info); - - final boolean showThumbnail = player.getPrefs().getBoolean( - context.getString(R.string.show_thumbnail_key), true); - - mediaSessionManager.setMetadata( - player.getVideoTitle(), - player.getUploaderName(), - showThumbnail ? player.getThumbnail() : null, - StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() - ); - } - @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); - player.getCurrentStreamInfo().ifPresent(this::onMetadataChanged); + if (mediaSessionManager != null) { + mediaSessionManager.triggerMetadataUpdate(); + } } public void handleMediaButtonIntent(final Intent intent) { From 11bd2369e5e8c0e5352fe76dada02089664f36c0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 12:00:32 +0200 Subject: [PATCH 08/11] Merge MediaSessionManager into MediaSessionPlayerUi --- .../mediasession/MediaSessionManager.java | 110 ------------------ .../mediasession/MediaSessionPlayerUi.java | 104 ++++++++++++++--- 2 files changed, 90 insertions(+), 124 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java deleted file mode 100644 index c6766fbcb..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.schabi.newpipe.player.mediasession; - -import android.content.Context; -import android.content.Intent; -import android.support.v4.media.MediaMetadataCompat; -import android.support.v4.media.session.MediaSessionCompat; -import android.util.Log; -import android.view.KeyEvent; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.media.session.MediaButtonReceiver; - -import com.google.android.exoplayer2.ForwardingPlayer; -import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; - -import org.schabi.newpipe.MainActivity; -import org.schabi.newpipe.R; -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.ui.VideoPlayerUi; -import org.schabi.newpipe.util.StreamTypeUtil; - -import java.util.Optional; - -public class MediaSessionManager { - private static final String TAG = MediaSessionManager.class.getSimpleName(); - public static final boolean DEBUG = MainActivity.DEBUG; - - @NonNull - private final MediaSessionCompat mediaSession; - @NonNull - private final MediaSessionConnector sessionConnector; - - public MediaSessionManager(@NonNull final Context context, - @NonNull final Player player) { - mediaSession = new MediaSessionCompat(context, TAG); - mediaSession.setActive(true); - - sessionConnector = new MediaSessionConnector(mediaSession); - sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); - sessionConnector.setPlayer(new ForwardingPlayer(player.getExoPlayer()) { - @Override - public void play() { - player.play(); - // hide the player controls even if the play command came from the media session - player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); - } - - @Override - public void pause() { - player.pause(); - } - }); - - sessionConnector.setMetadataDeduplicationEnabled(true); - sessionConnector.setMediaMetadataProvider(exoPlayer -> { - if (DEBUG) { - Log.d(TAG, "MediaMetadataProvider#getMetadata called"); - } - - // set title and artist - final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, player.getVideoTitle()) - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, player.getUploaderName()); - - // set duration (-1 for livestreams, since they don't have a duration) - final long duration = player.getCurrentStreamInfo() - .filter(info -> !StreamTypeUtil.isLiveStream(info.getStreamType())) - .map(info -> info.getDuration() * 1000L) - .orElse(-1L); - builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); - - // set album art, unless the user asked not to, or there is no thumbnail available - final boolean showThumbnail = player.getPrefs().getBoolean( - context.getString(R.string.show_thumbnail_key), true); - Optional.ofNullable(player.getThumbnail()) - .filter(bitmap -> showThumbnail) - .ifPresent(bitmap -> { - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap); - }); - - return builder.build(); - }); - } - - @Nullable - @SuppressWarnings("UnusedReturnValue") - public KeyEvent handleMediaButtonIntent(final Intent intent) { - return MediaButtonReceiver.handleIntent(mediaSession, intent); - } - - public MediaSessionCompat.Token getSessionToken() { - return mediaSession.getSessionToken(); - } - - void triggerMetadataUpdate() { - sessionConnector.invalidateMediaSessionMetadata(); - } - - /** - * Should be called on player destruction to prevent leakage. - */ - public void dispose() { - sessionConnector.setPlayer(null); - sessionConnector.setQueueNavigator(null); - mediaSession.setActive(false); - mediaSession.release(); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java index c78a3a6b1..92a137900 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -1,20 +1,33 @@ package org.schabi.newpipe.player.mediasession; +import static org.schabi.newpipe.MainActivity.DEBUG; + import android.content.Intent; import android.graphics.Bitmap; +import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.media.session.MediaButtonReceiver; +import com.google.android.exoplayer2.ForwardingPlayer; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; + +import org.schabi.newpipe.R; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.PlayerUi; +import org.schabi.newpipe.player.ui.VideoPlayerUi; +import org.schabi.newpipe.util.StreamTypeUtil; import java.util.Optional; public class MediaSessionPlayerUi extends PlayerUi { + private static final String TAG = "MediaSessUi"; - private MediaSessionManager mediaSessionManager; + private MediaSessionCompat mediaSession; + private MediaSessionConnector sessionConnector; public MediaSessionPlayerUi(@NonNull final Player player) { super(player); @@ -23,18 +36,31 @@ public class MediaSessionPlayerUi extends PlayerUi { @Override public void initPlayer() { super.initPlayer(); - if (mediaSessionManager != null) { - mediaSessionManager.dispose(); - } - mediaSessionManager = new MediaSessionManager(context, player); + destroyPlayer(); // release previously used resources + + mediaSession = new MediaSessionCompat(context, TAG); + mediaSession.setActive(true); + + sessionConnector = new MediaSessionConnector(mediaSession); + sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); + sessionConnector.setPlayer(getForwardingPlayer()); + + sessionConnector.setMetadataDeduplicationEnabled(true); + sessionConnector.setMediaMetadataProvider(exoPlayer -> buildMediaMetadata()); } @Override public void destroyPlayer() { super.destroyPlayer(); - if (mediaSessionManager != null) { - mediaSessionManager.dispose(); - mediaSessionManager = null; + if (sessionConnector != null) { + sessionConnector.setPlayer(null); + sessionConnector.setQueueNavigator(null); + sessionConnector = null; + } + if (mediaSession != null) { + mediaSession.setActive(false); + mediaSession.release(); + mediaSession = null; } } @@ -47,18 +73,68 @@ public class MediaSessionPlayerUi extends PlayerUi { @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); - if (mediaSessionManager != null) { - mediaSessionManager.triggerMetadataUpdate(); + if (sessionConnector != null) { + // the thumbnail is now loaded: invalidate the metadata to trigger a metadata update + sessionConnector.invalidateMediaSessionMetadata(); } } + public void handleMediaButtonIntent(final Intent intent) { - if (mediaSessionManager != null) { - mediaSessionManager.handleMediaButtonIntent(intent); - } + MediaButtonReceiver.handleIntent(mediaSession, intent); } public Optional getSessionToken() { - return Optional.ofNullable(mediaSessionManager).map(MediaSessionManager::getSessionToken); + return Optional.ofNullable(mediaSession).map(MediaSessionCompat::getSessionToken); + } + + + private ForwardingPlayer getForwardingPlayer() { + // ForwardingPlayer means that all media session actions called on this player are + // forwarded directly to the connected exoplayer, except for the overridden methods. So + // override play and pause since our player adds more functionality to them over exoplayer. + return new ForwardingPlayer(player.getExoPlayer()) { + @Override + public void play() { + player.play(); + // hide the player controls even if the play command came from the media session + player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); + } + + @Override + public void pause() { + player.pause(); + } + }; + } + + private MediaMetadataCompat buildMediaMetadata() { + if (DEBUG) { + Log.d(TAG, "buildMediaMetadata called"); + } + + // set title and artist + final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, player.getVideoTitle()) + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, player.getUploaderName()); + + // set duration (-1 for livestreams or if unknown, see the METADATA_KEY_DURATION docs) + final long duration = player.getCurrentStreamInfo() + .filter(info -> !StreamTypeUtil.isLiveStream(info.getStreamType())) + .map(info -> info.getDuration() * 1000L) + .orElse(-1L); + builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); + + // set album art, unless the user asked not to, or there is no thumbnail available + final boolean showThumbnail = player.getPrefs().getBoolean( + context.getString(R.string.show_thumbnail_key), true); + Optional.ofNullable(player.getThumbnail()) + .filter(bitmap -> showThumbnail) + .ifPresent(bitmap -> { + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap); + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap); + }); + + return builder.build(); } } From 510efaae976c1fe15a85c3f81c4a49573760097f Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 14:39:25 +0200 Subject: [PATCH 09/11] Keep strong reference to Picasso thumbnail loading target Before the Target would sometimes be garbage collected before being called with the loaded thumbnail, since Picasso holds weak references to targets --- .../org/schabi/newpipe/player/Player.java | 79 ++++++++++++------- .../schabi/newpipe/util/PicassoHelper.java | 2 - 2 files changed, 51 insertions(+), 30 deletions(-) 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 22d46bcbe..45b9b0fde 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -174,6 +174,7 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ public static final int RENDERER_UNAVAILABLE = -1; + private static final String PICASSO_PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; /*////////////////////////////////////////////////////////////////////////// // Playback @@ -232,6 +233,11 @@ public final class Player implements PlaybackListener, Listener { @NonNull private final SerialDisposable progressUpdateDisposable = new SerialDisposable(); @NonNull private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable(); + // This is the only listener we need for thumbnail loading, since there is always at most only + // one thumbnail being loaded at a time. This field is also here to maintain a strong reference, + // which would otherwise be garbage collected since Picasso holds weak references to targets. + @NonNull private final Target currentThumbnailTarget; + /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ @@ -263,6 +269,8 @@ public final class Player implements PlaybackListener, Listener { videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); + currentThumbnailTarget = getCurrentThumbnailTarget(); + // The UIs added here should always be present. They will be initialized when the player // reaches the initialization step. Make sure the media session ui is before the // notification ui in the UIs list, since the notification depends on the media session in @@ -573,7 +581,7 @@ public final class Player implements PlaybackListener, Listener { databaseUpdateDisposable.clear(); progressUpdateDisposable.set(null); - PicassoHelper.cancelTag(PicassoHelper.PLAYER_THUMBNAIL_TAG); // cancel thumbnail loading + cancelLoadingCurrentThumbnail(); UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object } @@ -747,12 +755,47 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ //region Thumbnail loading + private Target getCurrentThumbnailTarget() { + // a Picasso target is just a listener for thumbnail loading events + return new Target() { + @Override + public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: bitmap = [" + bitmap + + " -> " + bitmap.getWidth() + "x" + bitmap.getHeight() + "], from = [" + + from + "]"); + } + currentThumbnail = bitmap; + // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. + UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); + } + + @Override + public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { + Log.e(TAG, "Thumbnail - onBitmapFailed() called", e); + currentThumbnail = null; + // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. + UIs.call(playerUi -> playerUi.onThumbnailLoaded(null)); + } + + @Override + public void onPrepareLoad(final Drawable placeHolderDrawable) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onPrepareLoad() called"); + } + } + }; + } + private void loadCurrentThumbnail(final String url) { if (DEBUG) { Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with url = [" + (url == null ? "null" : url) + "]"); } + // first cancel any previous loading + cancelLoadingCurrentThumbnail(); + // Unset currentThumbnail, since it is now outdated. This ensures it is not used in media // session metadata while the new thumbnail is being loaded by Picasso. currentThumbnail = null; @@ -761,34 +804,14 @@ public final class Player implements PlaybackListener, Listener { } // scale down the notification thumbnail for performance - PicassoHelper.loadScaledDownThumbnail(context, url).into(new Target() { - @Override - public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { - if (DEBUG) { - Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: url = [" + url - + "], " + "bitmap = [" + bitmap + " -> " + bitmap.getWidth() + "x" - + bitmap.getHeight() + "], from = [" + from + "]"); - } + PicassoHelper.loadScaledDownThumbnail(context, url) + .tag(PICASSO_PLAYER_THUMBNAIL_TAG) + .into(currentThumbnailTarget); + } - currentThumbnail = bitmap; - // there is a new thumbnail, so changed the end screen thumbnail, too. - UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); - } - - @Override - public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { - Log.e(TAG, "Thumbnail - onBitmapFailed() called: url = [" + url + "]", e); - currentThumbnail = null; - UIs.call(playerUi -> playerUi.onThumbnailLoaded(null)); - } - - @Override - public void onPrepareLoad(final Drawable placeHolderDrawable) { - if (DEBUG) { - Log.d(TAG, "Thumbnail - onPrepareLoad() called: url = [" + url + "]"); - } - } - }); + private void cancelLoadingCurrentThumbnail() { + // cancel the Picasso job associated with the player thumbnail, if any + PicassoHelper.cancelTag(PICASSO_PLAYER_THUMBNAIL_TAG); } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 5739b930b..2e781631e 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -27,7 +27,6 @@ import okhttp3.OkHttpClient; public final class PicassoHelper { private static final String TAG = PicassoHelper.class.getSimpleName(); - public static final String PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY = "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; @@ -128,7 +127,6 @@ public final class PicassoHelper { public static RequestCreator loadScaledDownThumbnail(final Context context, final String url) { // scale down the notification thumbnail for performance return PicassoHelper.loadThumbnail(url) - .tag(PLAYER_THUMBNAIL_TAG) .transform(new Transformation() { @Override public Bitmap transform(final Bitmap source) { From 973a9660115cc809a99ae571af45f0b67a9c7c03 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 26 Jul 2022 16:35:57 +0200 Subject: [PATCH 10/11] Review suggestions --- .../newpipe/player/mediasession/PlayQueueNavigator.java | 2 +- .../newpipe/player/notification/NotificationUtil.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java index 7bd27bfdc..e84c0837b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -126,7 +126,7 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator .setTitle(item.getTitle()) .setSubtitle(item.getUploader()); - // set additional metadata for A2DP/AVRCP + // set additional metadata for A2DP/AVRCP (Audio/Video Bluetooth profiles) final Bundle additionalMetadata = new Bundle(); additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle()); additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); 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 84e9cc3bc..2c3199a28 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 @@ -105,8 +105,10 @@ public final class NotificationUtil { final int[] compactSlots = compactSlotList.stream().mapToInt(Integer::intValue).toArray(); final MediaStyle mediaStyle = new MediaStyle().setShowActionsInCompactView(compactSlots); - player.UIs().get(MediaSessionPlayerUi.class).flatMap(MediaSessionPlayerUi::getSessionToken) - .ifPresent(mediaStyle::setMediaSession); + player.UIs() + .get(MediaSessionPlayerUi.class) + .flatMap(MediaSessionPlayerUi::getSessionToken) + .ifPresent(mediaStyle::setMediaSession); builder.setStyle(mediaStyle) .setPriority(NotificationCompat.PRIORITY_HIGH) From 59d1ded94e876bd040a7c23a7a2e3ae18987df07 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Thu, 25 Aug 2022 16:59:46 +0200 Subject: [PATCH 11/11] Fixed sonar detected problems + Automatically fixed code style (imports) --- .../java/org/schabi/newpipe/player/Player.java | 2 +- .../mediasession/MediaSessionPlayerUi.java | 6 ------ .../mediasession/PlayQueueNavigator.java | 18 +++++++++--------- 3 files changed, 10 insertions(+), 16 deletions(-) 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 45b9b0fde..319c163e8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -222,7 +222,7 @@ public final class Player implements PlaybackListener, Listener { // UIs, listeners and disposables //////////////////////////////////////////////////////////////////////////*/ - @SuppressWarnings("MemberName") // keep the unusual member name + @SuppressWarnings({"MemberName", "java:S116"}) // keep the unusual member name private final PlayerUiList UIs; private BroadcastReceiver broadcastReceiver; diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java index 92a137900..e9541ab06 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -64,12 +64,6 @@ public class MediaSessionPlayerUi extends PlayerUi { } } - @Override - public void onBroadcastReceived(final Intent intent) { - super.onBroadcastReceived(intent); - // TODO decide whether to handle ACTION_HEADSET_PLUG or not - } - @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java index e84c0837b..2e54b1129 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -1,5 +1,9 @@ package org.schabi.newpipe.player.mediasession; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; + import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; @@ -13,19 +17,15 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.util.Util; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; + import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; -import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT; -import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; -import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; - -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.playqueue.PlayQueue; -import org.schabi.newpipe.player.playqueue.PlayQueueItem; - public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator { private static final int MAX_QUEUE_SIZE = 10; @@ -132,7 +132,7 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); additionalMetadata .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000); - additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); + additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1L); additionalMetadata .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); descBuilder.setExtras(additionalMetadata);