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 920435a7e..41705ffb2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -55,6 +55,7 @@ import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.media.AudioManager; +import android.support.v4.media.session.MediaSessionCompat; import android.util.Log; import android.view.LayoutInflater; @@ -71,6 +72,7 @@ import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player.PositionInfo; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Tracks; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; @@ -269,7 +271,16 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ //region Constructor - public Player(@NonNull final PlayerService service) { + /** + * @param service the service this player resides in + * @param mediaSession used to build the {@link MediaSessionPlayerUi}, lives in the service and + * could possibly be reused with multiple player instances + * @param sessionConnector used to build the {@link MediaSessionPlayerUi}, lives in the service + * and could possibly be reused with multiple player instances + */ + public Player(@NonNull final PlayerService service, + @NonNull final MediaSessionCompat mediaSession, + @NonNull final MediaSessionConnector sessionConnector) { this.service = service; context = service; prefs = PreferenceManager.getDefaultSharedPreferences(context); @@ -302,7 +313,7 @@ public final class Player implements PlaybackListener, Listener { // 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 MediaSessionPlayerUi(this, mediaSession, sessionConnector), new NotificationPlayerUi(this) ); } 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 7b9b76cfb..ee8585c9c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -27,12 +27,15 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.support.v4.media.MediaBrowserCompat; +import android.support.v4.media.session.MediaSessionCompat; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.media.MediaBrowserServiceCompat; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; + import org.schabi.newpipe.ktx.BundleKt; import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.player.notification.NotificationPlayerUi; @@ -52,6 +55,12 @@ public final class PlayerService extends MediaBrowserServiceCompat { public static final String SHOULD_START_FOREGROUND_EXTRA = "should_start_foreground_extra"; public static final String BIND_PLAYER_HOLDER_ACTION = "bind_player_holder_action"; + // these are instantiated in onCreate() as per + // https://developer.android.com/training/cars/media#browser_workflow + private MediaSessionCompat mediaSession; + private MediaSessionConnector sessionConnector; + + @Nullable private Player player; private final IBinder mBinder = new PlayerService.LocalBinder(this); @@ -71,6 +80,12 @@ public final class PlayerService extends MediaBrowserServiceCompat { assureCorrectAppLanguage(this); ThemeHelper.setTheme(this); + // see https://developer.android.com/training/cars/media#browser_workflow + mediaSession = new MediaSessionCompat(this, "MediaSessionPlayerServ"); + setSessionToken(mediaSession.getSessionToken()); + sessionConnector = new MediaSessionConnector(mediaSession); + sessionConnector.setMetadataDeduplicationEnabled(true); + // Note: you might be tempted to create the player instance and call startForeground here, // but be aware that the Android system might start the service just to perform media // queries. In those cases creating a player instance is a waste of resources, and calling @@ -94,7 +109,7 @@ public final class PlayerService extends MediaBrowserServiceCompat { if (intent.getBooleanExtra(SHOULD_START_FOREGROUND_EXTRA, false)) { if (player == null) { // make sure the player exists, in case the service was resumed - player = new Player(this); + player = new Player(this, mediaSession, sessionConnector); } // Be sure that the player notification is set and the service is started in foreground, @@ -159,7 +174,11 @@ public final class PlayerService extends MediaBrowserServiceCompat { Log.d(TAG, "destroy() called"); } super.onDestroy(); + cleanup(); + + mediaSession.setActive(false); + mediaSession.release(); } private void cleanup() { 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 c673e688c..fe884834b 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 @@ -38,10 +38,10 @@ public class MediaSessionPlayerUi extends PlayerUi implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = "MediaSessUi"; - @Nullable - private MediaSessionCompat mediaSession; - @Nullable - private MediaSessionConnector sessionConnector; + @NonNull + private final MediaSessionCompat mediaSession; + @NonNull + private final MediaSessionConnector sessionConnector; private final String ignoreHardwareMediaButtonsKey; private boolean shouldIgnoreHardwareMediaButtons = false; @@ -50,9 +50,13 @@ public class MediaSessionPlayerUi extends PlayerUi private List prevNotificationActions = List.of(); - public MediaSessionPlayerUi(@NonNull final Player player) { + public MediaSessionPlayerUi(@NonNull final Player player, + @NonNull final MediaSessionCompat mediaSession, + @NonNull final MediaSessionConnector sessionConnector) { super(player); - ignoreHardwareMediaButtonsKey = + this.mediaSession = mediaSession; + this.sessionConnector = sessionConnector; + this.ignoreHardwareMediaButtonsKey = context.getString(R.string.ignore_hardware_media_buttons_key); } @@ -61,10 +65,8 @@ public class MediaSessionPlayerUi extends PlayerUi super.initPlayer(); 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()); @@ -89,27 +91,18 @@ public class MediaSessionPlayerUi extends PlayerUi public void destroyPlayer() { super.destroyPlayer(); player.getPrefs().unregisterOnSharedPreferenceChangeListener(this); - if (sessionConnector != null) { - sessionConnector.setMediaButtonEventHandler(null); - sessionConnector.setPlayer(null); - sessionConnector.setQueueNavigator(null); - sessionConnector = null; - } - if (mediaSession != null) { - mediaSession.setActive(false); - mediaSession.release(); - mediaSession = null; - } + sessionConnector.setMediaButtonEventHandler(null); + sessionConnector.setPlayer(null); + sessionConnector.setQueueNavigator(null); + mediaSession.setActive(false); prevNotificationActions = List.of(); } @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); - if (sessionConnector != null) { - // the thumbnail is now loaded: invalidate the metadata to trigger a metadata update - sessionConnector.invalidateMediaSessionMetadata(); - } + // the thumbnail is now loaded: invalidate the metadata to trigger a metadata update + sessionConnector.invalidateMediaSessionMetadata(); } @@ -200,8 +193,8 @@ public class MediaSessionPlayerUi extends PlayerUi return; } - if (sessionConnector == null) { - // sessionConnector will be null after destroyPlayer is called + if (!mediaSession.isActive()) { + // mediaSession will be inactive after destroyPlayer is called return; }