mirror of
https://github.com/TeamNewPipe/NewPipe.git
synced 2024-11-25 12:32:31 +01:00
Implement notification actions via MediaSessionConnector on Android 13+
This commit is contained in:
parent
2c4c283099
commit
5edafca05a
@ -1,10 +1,12 @@
|
|||||||
package org.schabi.newpipe.player.mediasession;
|
package org.schabi.newpipe.player.mediasession;
|
||||||
|
|
||||||
import static org.schabi.newpipe.MainActivity.DEBUG;
|
import static org.schabi.newpipe.MainActivity.DEBUG;
|
||||||
|
import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_RECREATE_NOTIFICATION;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.Build;
|
||||||
import android.support.v4.media.MediaMetadataCompat;
|
import android.support.v4.media.MediaMetadataCompat;
|
||||||
import android.support.v4.media.session.MediaSessionCompat;
|
import android.support.v4.media.session.MediaSessionCompat;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@ -14,14 +16,20 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.media.session.MediaButtonReceiver;
|
import androidx.media.session.MediaButtonReceiver;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.ForwardingPlayer;
|
import com.google.android.exoplayer2.ForwardingPlayer;
|
||||||
|
import com.google.android.exoplayer2.Player.RepeatMode;
|
||||||
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
|
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.player.Player;
|
import org.schabi.newpipe.player.Player;
|
||||||
|
import org.schabi.newpipe.player.notification.NotificationActionData;
|
||||||
|
import org.schabi.newpipe.player.notification.NotificationConstants;
|
||||||
import org.schabi.newpipe.player.ui.PlayerUi;
|
import org.schabi.newpipe.player.ui.PlayerUi;
|
||||||
import org.schabi.newpipe.player.ui.VideoPlayerUi;
|
import org.schabi.newpipe.player.ui.VideoPlayerUi;
|
||||||
import org.schabi.newpipe.util.StreamTypeUtil;
|
import org.schabi.newpipe.util.StreamTypeUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class MediaSessionPlayerUi extends PlayerUi
|
public class MediaSessionPlayerUi extends PlayerUi
|
||||||
@ -163,4 +171,107 @@ public class MediaSessionPlayerUi extends PlayerUi
|
|||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void updateMediaSessionActions() {
|
||||||
|
// On Android 13+ (or Android T or API 33+) the actions in the player notification can't be
|
||||||
|
// controlled directly anymore, but are instead derived from custom media session actions.
|
||||||
|
// However the system allows customizing only two of these actions, since the other three
|
||||||
|
// are fixed to play-pause-buffering, previous, next.
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
// Although setting media session actions on older android versions doesn't seem to
|
||||||
|
// cause any trouble, it also doesn't seem to do anything, so we don't do anything to
|
||||||
|
// save battery. Check out NotificationUtil.updateActions() to see what happens on
|
||||||
|
// older android versions.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<SessionConnectorActionProvider> actions = new ArrayList<>(2);
|
||||||
|
for (int i = 3; i < 5; ++i) {
|
||||||
|
// only use the fourth and fifth actions (the settings page also shows only the last 2)
|
||||||
|
final int action = player.getPrefs().getInt(
|
||||||
|
player.getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
|
||||||
|
NotificationConstants.SLOT_DEFAULTS[i]);
|
||||||
|
|
||||||
|
@Nullable final NotificationActionData data =
|
||||||
|
NotificationActionData.fromNotificationActionEnum(player, action);
|
||||||
|
|
||||||
|
if (data != null) {
|
||||||
|
actions.add(new SessionConnectorActionProvider(data, context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionConnector.setCustomActionProviders(
|
||||||
|
actions.toArray(new MediaSessionConnector.CustomActionProvider[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBlocked() {
|
||||||
|
super.onBlocked();
|
||||||
|
updateMediaSessionActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaying() {
|
||||||
|
super.onPlaying();
|
||||||
|
updateMediaSessionActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBuffering() {
|
||||||
|
super.onBuffering();
|
||||||
|
updateMediaSessionActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPaused() {
|
||||||
|
super.onPaused();
|
||||||
|
updateMediaSessionActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPausedSeek() {
|
||||||
|
super.onPausedSeek();
|
||||||
|
updateMediaSessionActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCompleted() {
|
||||||
|
super.onCompleted();
|
||||||
|
updateMediaSessionActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRepeatModeChanged(@RepeatMode final int repeatMode) {
|
||||||
|
super.onRepeatModeChanged(repeatMode);
|
||||||
|
updateMediaSessionActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) {
|
||||||
|
super.onShuffleModeEnabledChanged(shuffleModeEnabled);
|
||||||
|
updateMediaSessionActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBroadcastReceived(final Intent intent) {
|
||||||
|
super.onBroadcastReceived(intent);
|
||||||
|
if (ACTION_RECREATE_NOTIFICATION.equals(intent.getAction())) {
|
||||||
|
// the notification actions changed
|
||||||
|
updateMediaSessionActions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMetadataChanged(@NonNull final StreamInfo info) {
|
||||||
|
super.onMetadataChanged(info);
|
||||||
|
updateMediaSessionActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlayQueueEdited() {
|
||||||
|
super.onPlayQueueEdited();
|
||||||
|
updateMediaSessionActions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
package org.schabi.newpipe.player.mediasession;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.media.session.PlaybackStateCompat;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.Player;
|
||||||
|
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.player.notification.NotificationActionData;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
public class SessionConnectorActionProvider implements MediaSessionConnector.CustomActionProvider {
|
||||||
|
|
||||||
|
private final NotificationActionData data;
|
||||||
|
@NonNull
|
||||||
|
private final WeakReference<Context> context;
|
||||||
|
|
||||||
|
public SessionConnectorActionProvider(final NotificationActionData notificationActionData,
|
||||||
|
@NonNull final Context context) {
|
||||||
|
this.data = notificationActionData;
|
||||||
|
this.context = new WeakReference<>(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCustomAction(@NonNull final Player player,
|
||||||
|
@NonNull final String action,
|
||||||
|
@Nullable final Bundle extras) {
|
||||||
|
final Context actualContext = context.get();
|
||||||
|
if (actualContext != null) {
|
||||||
|
actualContext.sendBroadcast(new Intent(action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public PlaybackStateCompat.CustomAction getCustomAction(@NonNull final Player player) {
|
||||||
|
if (data.action() == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return new PlaybackStateCompat.CustomAction.Builder(
|
||||||
|
data.action(), data.name(), data.icon()
|
||||||
|
).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -92,15 +92,21 @@ public final class NotificationUtil {
|
|||||||
final NotificationCompat.Builder builder =
|
final NotificationCompat.Builder builder =
|
||||||
new NotificationCompat.Builder(player.getContext(),
|
new NotificationCompat.Builder(player.getContext(),
|
||||||
player.getContext().getString(R.string.notification_channel_id));
|
player.getContext().getString(R.string.notification_channel_id));
|
||||||
|
final MediaStyle mediaStyle = new MediaStyle();
|
||||||
|
|
||||||
|
// setup media style (compact notification slots and media session)
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
// notification actions are ignored on Android 13+, and are replaced by code in
|
||||||
|
// MediaSessionPlayerUi
|
||||||
final int[] compactSlots = initializeNotificationSlots();
|
final int[] compactSlots = initializeNotificationSlots();
|
||||||
|
mediaStyle.setShowActionsInCompactView(compactSlots);
|
||||||
final MediaStyle mediaStyle = new MediaStyle().setShowActionsInCompactView(compactSlots);
|
}
|
||||||
player.UIs()
|
player.UIs()
|
||||||
.get(MediaSessionPlayerUi.class)
|
.get(MediaSessionPlayerUi.class)
|
||||||
.flatMap(MediaSessionPlayerUi::getSessionToken)
|
.flatMap(MediaSessionPlayerUi::getSessionToken)
|
||||||
.ifPresent(mediaStyle::setMediaSession);
|
.ifPresent(mediaStyle::setMediaSession);
|
||||||
|
|
||||||
|
// setup notification builder
|
||||||
builder.setStyle(mediaStyle)
|
builder.setStyle(mediaStyle)
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
@ -135,8 +141,12 @@ public final class NotificationUtil {
|
|||||||
notificationBuilder.setContentText(player.getUploaderName());
|
notificationBuilder.setContentText(player.getUploaderName());
|
||||||
notificationBuilder.setTicker(player.getVideoTitle());
|
notificationBuilder.setTicker(player.getVideoTitle());
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
// notification actions are ignored on Android 13+, and are replaced by code in
|
||||||
|
// MediaSessionPlayerUi
|
||||||
updateActions(notificationBuilder);
|
updateActions(notificationBuilder);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
|
Loading…
Reference in New Issue
Block a user