Skip to content

Commit 5edafca

Browse files
committed
Implement notification actions via MediaSessionConnector on Android 13+
1 parent 2c4c283 commit 5edafca

3 files changed

Lines changed: 177 additions & 5 deletions

File tree

app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package org.schabi.newpipe.player.mediasession;
22

33
import static org.schabi.newpipe.MainActivity.DEBUG;
4+
import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_RECREATE_NOTIFICATION;
45

56
import android.content.Intent;
67
import android.content.SharedPreferences;
78
import android.graphics.Bitmap;
9+
import android.os.Build;
810
import android.support.v4.media.MediaMetadataCompat;
911
import android.support.v4.media.session.MediaSessionCompat;
1012
import android.util.Log;
@@ -14,14 +16,20 @@
1416
import androidx.media.session.MediaButtonReceiver;
1517

1618
import com.google.android.exoplayer2.ForwardingPlayer;
19+
import com.google.android.exoplayer2.Player.RepeatMode;
1720
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
1821

1922
import org.schabi.newpipe.R;
23+
import org.schabi.newpipe.extractor.stream.StreamInfo;
2024
import org.schabi.newpipe.player.Player;
25+
import org.schabi.newpipe.player.notification.NotificationActionData;
26+
import org.schabi.newpipe.player.notification.NotificationConstants;
2127
import org.schabi.newpipe.player.ui.PlayerUi;
2228
import org.schabi.newpipe.player.ui.VideoPlayerUi;
2329
import org.schabi.newpipe.util.StreamTypeUtil;
2430

31+
import java.util.ArrayList;
32+
import java.util.List;
2533
import java.util.Optional;
2634

2735
public class MediaSessionPlayerUi extends PlayerUi
@@ -163,4 +171,107 @@ private MediaMetadataCompat buildMediaMetadata() {
163171

164172
return builder.build();
165173
}
174+
175+
176+
private void updateMediaSessionActions() {
177+
// On Android 13+ (or Android T or API 33+) the actions in the player notification can't be
178+
// controlled directly anymore, but are instead derived from custom media session actions.
179+
// However the system allows customizing only two of these actions, since the other three
180+
// are fixed to play-pause-buffering, previous, next.
181+
182+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
183+
// Although setting media session actions on older android versions doesn't seem to
184+
// cause any trouble, it also doesn't seem to do anything, so we don't do anything to
185+
// save battery. Check out NotificationUtil.updateActions() to see what happens on
186+
// older android versions.
187+
return;
188+
}
189+
190+
final List<SessionConnectorActionProvider> actions = new ArrayList<>(2);
191+
for (int i = 3; i < 5; ++i) {
192+
// only use the fourth and fifth actions (the settings page also shows only the last 2)
193+
final int action = player.getPrefs().getInt(
194+
player.getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
195+
NotificationConstants.SLOT_DEFAULTS[i]);
196+
197+
@Nullable final NotificationActionData data =
198+
NotificationActionData.fromNotificationActionEnum(player, action);
199+
200+
if (data != null) {
201+
actions.add(new SessionConnectorActionProvider(data, context));
202+
}
203+
}
204+
205+
sessionConnector.setCustomActionProviders(
206+
actions.toArray(new MediaSessionConnector.CustomActionProvider[0]));
207+
}
208+
209+
@Override
210+
public void onBlocked() {
211+
super.onBlocked();
212+
updateMediaSessionActions();
213+
}
214+
215+
@Override
216+
public void onPlaying() {
217+
super.onPlaying();
218+
updateMediaSessionActions();
219+
}
220+
221+
@Override
222+
public void onBuffering() {
223+
super.onBuffering();
224+
updateMediaSessionActions();
225+
}
226+
227+
@Override
228+
public void onPaused() {
229+
super.onPaused();
230+
updateMediaSessionActions();
231+
}
232+
233+
@Override
234+
public void onPausedSeek() {
235+
super.onPausedSeek();
236+
updateMediaSessionActions();
237+
}
238+
239+
@Override
240+
public void onCompleted() {
241+
super.onCompleted();
242+
updateMediaSessionActions();
243+
}
244+
245+
@Override
246+
public void onRepeatModeChanged(@RepeatMode final int repeatMode) {
247+
super.onRepeatModeChanged(repeatMode);
248+
updateMediaSessionActions();
249+
}
250+
251+
@Override
252+
public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) {
253+
super.onShuffleModeEnabledChanged(shuffleModeEnabled);
254+
updateMediaSessionActions();
255+
}
256+
257+
@Override
258+
public void onBroadcastReceived(final Intent intent) {
259+
super.onBroadcastReceived(intent);
260+
if (ACTION_RECREATE_NOTIFICATION.equals(intent.getAction())) {
261+
// the notification actions changed
262+
updateMediaSessionActions();
263+
}
264+
}
265+
266+
@Override
267+
public void onMetadataChanged(@NonNull final StreamInfo info) {
268+
super.onMetadataChanged(info);
269+
updateMediaSessionActions();
270+
}
271+
272+
@Override
273+
public void onPlayQueueEdited() {
274+
super.onPlayQueueEdited();
275+
updateMediaSessionActions();
276+
}
166277
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.schabi.newpipe.player.mediasession;
2+
3+
import android.content.Context;
4+
import android.content.Intent;
5+
import android.os.Bundle;
6+
import android.support.v4.media.session.PlaybackStateCompat;
7+
8+
import androidx.annotation.NonNull;
9+
import androidx.annotation.Nullable;
10+
11+
import com.google.android.exoplayer2.Player;
12+
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
13+
14+
import org.schabi.newpipe.player.notification.NotificationActionData;
15+
16+
import java.lang.ref.WeakReference;
17+
18+
public class SessionConnectorActionProvider implements MediaSessionConnector.CustomActionProvider {
19+
20+
private final NotificationActionData data;
21+
@NonNull
22+
private final WeakReference<Context> context;
23+
24+
public SessionConnectorActionProvider(final NotificationActionData notificationActionData,
25+
@NonNull final Context context) {
26+
this.data = notificationActionData;
27+
this.context = new WeakReference<>(context);
28+
}
29+
30+
@Override
31+
public void onCustomAction(@NonNull final Player player,
32+
@NonNull final String action,
33+
@Nullable final Bundle extras) {
34+
final Context actualContext = context.get();
35+
if (actualContext != null) {
36+
actualContext.sendBroadcast(new Intent(action));
37+
}
38+
}
39+
40+
@Nullable
41+
@Override
42+
public PlaybackStateCompat.CustomAction getCustomAction(@NonNull final Player player) {
43+
if (data.action() == null) {
44+
return null;
45+
} else {
46+
return new PlaybackStateCompat.CustomAction.Builder(
47+
data.action(), data.name(), data.icon()
48+
).build();
49+
}
50+
}
51+
}

app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,21 @@ private synchronized NotificationCompat.Builder createNotification() {
9292
final NotificationCompat.Builder builder =
9393
new NotificationCompat.Builder(player.getContext(),
9494
player.getContext().getString(R.string.notification_channel_id));
95-
96-
final int[] compactSlots = initializeNotificationSlots();
97-
98-
final MediaStyle mediaStyle = new MediaStyle().setShowActionsInCompactView(compactSlots);
95+
final MediaStyle mediaStyle = new MediaStyle();
96+
97+
// setup media style (compact notification slots and media session)
98+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
99+
// notification actions are ignored on Android 13+, and are replaced by code in
100+
// MediaSessionPlayerUi
101+
final int[] compactSlots = initializeNotificationSlots();
102+
mediaStyle.setShowActionsInCompactView(compactSlots);
103+
}
99104
player.UIs()
100105
.get(MediaSessionPlayerUi.class)
101106
.flatMap(MediaSessionPlayerUi::getSessionToken)
102107
.ifPresent(mediaStyle::setMediaSession);
103108

109+
// setup notification builder
104110
builder.setStyle(mediaStyle)
105111
.setPriority(NotificationCompat.PRIORITY_HIGH)
106112
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
@@ -135,7 +141,11 @@ private synchronized void updateNotification() {
135141
notificationBuilder.setContentText(player.getUploaderName());
136142
notificationBuilder.setTicker(player.getVideoTitle());
137143

138-
updateActions(notificationBuilder);
144+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
145+
// notification actions are ignored on Android 13+, and are replaced by code in
146+
// MediaSessionPlayerUi
147+
updateActions(notificationBuilder);
148+
}
139149
}
140150

141151

0 commit comments

Comments
 (0)