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