1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2024-12-23 08:30:44 +00:00

Create MediaSessionPlayerUi

This commit is contained in:
Stypox 2022-07-21 18:00:43 +02:00 committed by litetex
parent ce6f3ca5df
commit c054ea0737
7 changed files with 113 additions and 44 deletions

View File

@ -99,14 +99,13 @@ import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.event.PlayerServiceEventListener;
import org.schabi.newpipe.player.helper.AudioReactor; import org.schabi.newpipe.player.helper.AudioReactor;
import org.schabi.newpipe.player.helper.LoadController; 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.PlayerDataSource;
import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.mediaitem.MediaItemTag; 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.notification.NotificationPlayerUi;
import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.MediaSourceManager;
import org.schabi.newpipe.player.playback.PlaybackListener; 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.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
@ -196,7 +195,6 @@ public final class Player implements PlaybackListener, Listener {
private ExoPlayer simpleExoPlayer; private ExoPlayer simpleExoPlayer;
private AudioReactor audioReactor; private AudioReactor audioReactor;
private MediaSessionManager mediaSessionManager;
@NonNull private final DefaultTrackSelector trackSelector; @NonNull private final DefaultTrackSelector trackSelector;
@NonNull private final LoadController loadController; @NonNull private final LoadController loadController;
@ -225,7 +223,7 @@ public final class Player implements PlaybackListener, Listener {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@SuppressWarnings("MemberName") // keep the unusual member name @SuppressWarnings("MemberName") // keep the unusual member name
private final PlayerUiList UIs = new PlayerUiList(); private final PlayerUiList UIs;
private BroadcastReceiver broadcastReceiver; private BroadcastReceiver broadcastReceiver;
private IntentFilter intentFilter; private IntentFilter intentFilter;
@ -265,6 +263,15 @@ public final class Player implements PlaybackListener, Listener {
videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
audioResolver = new AudioPlaybackResolver(context, dataSource); 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() { private VideoPlaybackResolver.QualityResolver getQualityResolver() {
@ -431,11 +438,6 @@ public final class Player implements PlaybackListener, Listener {
} }
private void initUIsForCurrentPlayerType() { 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) if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN)
|| (UIs.get(PopupPlayerUi.class).isPresent() && playerType == PlayerType.POPUP)) { || (UIs.get(PopupPlayerUi.class).isPresent() && playerType == PlayerType.POPUP)) {
// correct UI already in place // correct UI already in place
@ -506,8 +508,6 @@ public final class Player implements PlaybackListener, Listener {
simpleExoPlayer.setHandleAudioBecomingNoisy(true); simpleExoPlayer.setHandleAudioBecomingNoisy(true);
audioReactor = new AudioReactor(context, simpleExoPlayer); audioReactor = new AudioReactor(context, simpleExoPlayer);
mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer,
new PlayerMediaSession(this));
registerBroadcastReceiver(); registerBroadcastReceiver();
@ -558,9 +558,6 @@ public final class Player implements PlaybackListener, Listener {
if (playQueueManager != null) { if (playQueueManager != null) {
playQueueManager.dispose(); playQueueManager.dispose();
} }
if (mediaSessionManager != null) {
mediaSessionManager.dispose();
}
} }
public void destroy() { public void destroy() {
@ -723,11 +720,6 @@ public final class Player implements PlaybackListener, Listener {
Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received"); Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received");
} }
break; 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)); UIs.call(playerUi -> playerUi.onBroadcastReceived(intent));
@ -1738,15 +1730,6 @@ public final class Player implements PlaybackListener, Listener {
initThumbnail(info.getThumbnailUrl()); initThumbnail(info.getThumbnailUrl());
registerStreamViewed(); 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(); notifyMetadataUpdateToListeners();
UIs.call(playerUi -> playerUi.onMetadataChanged(info)); UIs.call(playerUi -> playerUi.onMetadataChanged(info));
} }
@ -2194,10 +2177,6 @@ public final class Player implements PlaybackListener, Listener {
return prefs; return prefs;
} }
public MediaSessionManager getMediaSessionManager() {
return mediaSessionManager;
}
public PlayerType getPlayerType() { public PlayerType getPlayerType() {
return playerType; return playerType;

View File

@ -28,6 +28,7 @@ import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import android.util.Log; import android.util.Log;
import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
@ -73,9 +74,8 @@ public final class PlayerService extends Service {
} }
player.handleIntent(intent); player.handleIntent(intent);
if (player.getMediaSessionManager() != null) { player.UIs().get(MediaSessionPlayerUi.class)
player.getMediaSessionManager().handleMediaButtonIntent(intent); .ifPresent(ui -> ui.handleMediaButtonIntent(intent));
}
return START_NOT_STICKY; return START_NOT_STICKY;
} }

View File

@ -1,4 +1,4 @@
package org.schabi.newpipe.player.helper; package org.schabi.newpipe.player.mediasession;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -18,8 +18,6 @@ import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.player.mediasession.MediaSessionCallback;
import org.schabi.newpipe.player.mediasession.PlayQueueNavigator;
import java.util.Optional; import java.util.Optional;

View File

@ -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<MediaSessionCompat.Token> getSessionToken() {
return Optional.ofNullable(mediaSessionManager).map(MediaSessionManager::getSessionToken);
}
}

View File

@ -19,11 +19,13 @@ import androidx.core.content.ContextCompat;
import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import java.util.List; import java.util.List;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; 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_ALL;
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE; import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE;
@ -101,9 +103,11 @@ public final class NotificationUtil {
player.getContext(), player.getPrefs(), nonNothingSlotCount); player.getContext(), player.getPrefs(), nonNothingSlotCount);
final int[] compactSlots = compactSlotList.stream().mapToInt(Integer::intValue).toArray(); final int[] compactSlots = compactSlotList.stream().mapToInt(Integer::intValue).toArray();
builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle() final MediaStyle mediaStyle = new MediaStyle().setShowActionsInCompactView(compactSlots);
.setMediaSession(player.getMediaSessionManager().getSessionToken()) player.UIs().get(MediaSessionPlayerUi.class).flatMap(MediaSessionPlayerUi::getSessionToken)
.setShowActionsInCompactView(compactSlots)) .ifPresent(mediaStyle::setMediaSession);
builder.setStyle(mediaStyle)
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setCategory(NotificationCompat.CATEGORY_TRANSPORT) .setCategory(NotificationCompat.CATEGORY_TRANSPORT)

View File

@ -29,7 +29,8 @@ public abstract class PlayerUi {
@NonNull protected final Player player; @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) { protected PlayerUi(@NonNull final Player player) {
this.context = player.getContext(); this.context = player.getContext();

View File

@ -8,6 +8,19 @@ import java.util.function.Consumer;
public final class PlayerUiList { public final class PlayerUiList {
final List<PlayerUi> playerUis = new ArrayList<>(); final List<PlayerUi> 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 * 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 * 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 * @param consumer the consumer to call with player UIs
*/ */
public void call(final Consumer<PlayerUi> consumer) { public void call(final Consumer<PlayerUi> consumer) {
//noinspection SimplifyStreamApiCallChains //noinspection SimplifyStreamApiCallChains
playerUis.stream().forEach(consumer); playerUis.stream().forEachOrdered(consumer);
} }
} }