Skip to content

Commit 1d8850d

Browse files
authored
Merge pull request #10712 from Stypox/notification-actions-api-33-2
[Android 13+] Restore support of custom notification actions
2 parents 8345f34 + f985486 commit 1d8850d

9 files changed

Lines changed: 684 additions & 374 deletions

File tree

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

Lines changed: 124 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,15 +16,23 @@
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.List;
32+
import java.util.Objects;
2533
import java.util.Optional;
34+
import java.util.stream.Collectors;
35+
import java.util.stream.IntStream;
2636

2737
public class MediaSessionPlayerUi extends PlayerUi
2838
implements SharedPreferences.OnSharedPreferenceChangeListener {
@@ -34,6 +44,10 @@ public class MediaSessionPlayerUi extends PlayerUi
3444
private final String ignoreHardwareMediaButtonsKey;
3545
private boolean shouldIgnoreHardwareMediaButtons = false;
3646

47+
// used to check whether any notification action changed, before sending costly updates
48+
private List<NotificationActionData> prevNotificationActions = List.of();
49+
50+
3751
public MediaSessionPlayerUi(@NonNull final Player player) {
3852
super(player);
3953
ignoreHardwareMediaButtonsKey =
@@ -63,6 +77,10 @@ public void initPlayer() {
6377

6478
sessionConnector.setMetadataDeduplicationEnabled(true);
6579
sessionConnector.setMediaMetadataProvider(exoPlayer -> buildMediaMetadata());
80+
81+
// force updating media session actions by resetting the previous ones
82+
prevNotificationActions = List.of();
83+
updateMediaSessionActions();
6684
}
6785

6886
@Override
@@ -80,6 +98,7 @@ public void destroyPlayer() {
8098
mediaSession.release();
8199
mediaSession = null;
82100
}
101+
prevNotificationActions = List.of();
83102
}
84103

85104
@Override
@@ -163,4 +182,109 @@ private MediaMetadataCompat buildMediaMetadata() {
163182

164183
return builder.build();
165184
}
185+
186+
187+
private void updateMediaSessionActions() {
188+
// On Android 13+ (or Android T or API 33+) the actions in the player notification can't be
189+
// controlled directly anymore, but are instead derived from custom media session actions.
190+
// However the system allows customizing only two of these actions, since the other three
191+
// are fixed to play-pause-buffering, previous, next.
192+
193+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
194+
// Although setting media session actions on older android versions doesn't seem to
195+
// cause any trouble, it also doesn't seem to do anything, so we don't do anything to
196+
// save battery. Check out NotificationUtil.updateActions() to see what happens on
197+
// older android versions.
198+
return;
199+
}
200+
201+
// only use the fourth and fifth actions (the settings page also shows only the last 2 on
202+
// Android 13+)
203+
final List<NotificationActionData> newNotificationActions = IntStream.of(3, 4)
204+
.map(i -> player.getPrefs().getInt(
205+
player.getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
206+
NotificationConstants.SLOT_DEFAULTS[i]))
207+
.mapToObj(action -> NotificationActionData
208+
.fromNotificationActionEnum(player, action))
209+
.filter(Objects::nonNull)
210+
.collect(Collectors.toList());
211+
212+
// avoid costly notification actions update, if nothing changed from last time
213+
if (!newNotificationActions.equals(prevNotificationActions)) {
214+
prevNotificationActions = newNotificationActions;
215+
sessionConnector.setCustomActionProviders(
216+
newNotificationActions.stream()
217+
.map(data -> new SessionConnectorActionProvider(data, context))
218+
.toArray(SessionConnectorActionProvider[]::new));
219+
}
220+
}
221+
222+
@Override
223+
public void onBlocked() {
224+
super.onBlocked();
225+
updateMediaSessionActions();
226+
}
227+
228+
@Override
229+
public void onPlaying() {
230+
super.onPlaying();
231+
updateMediaSessionActions();
232+
}
233+
234+
@Override
235+
public void onBuffering() {
236+
super.onBuffering();
237+
updateMediaSessionActions();
238+
}
239+
240+
@Override
241+
public void onPaused() {
242+
super.onPaused();
243+
updateMediaSessionActions();
244+
}
245+
246+
@Override
247+
public void onPausedSeek() {
248+
super.onPausedSeek();
249+
updateMediaSessionActions();
250+
}
251+
252+
@Override
253+
public void onCompleted() {
254+
super.onCompleted();
255+
updateMediaSessionActions();
256+
}
257+
258+
@Override
259+
public void onRepeatModeChanged(@RepeatMode final int repeatMode) {
260+
super.onRepeatModeChanged(repeatMode);
261+
updateMediaSessionActions();
262+
}
263+
264+
@Override
265+
public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) {
266+
super.onShuffleModeEnabledChanged(shuffleModeEnabled);
267+
updateMediaSessionActions();
268+
}
269+
270+
@Override
271+
public void onBroadcastReceived(final Intent intent) {
272+
super.onBroadcastReceived(intent);
273+
if (ACTION_RECREATE_NOTIFICATION.equals(intent.getAction())) {
274+
// the notification actions changed
275+
updateMediaSessionActions();
276+
}
277+
}
278+
279+
@Override
280+
public void onMetadataChanged(@NonNull final StreamInfo info) {
281+
super.onMetadataChanged(info);
282+
updateMediaSessionActions();
283+
}
284+
285+
@Override
286+
public void onPlayQueueEdited() {
287+
super.onPlayQueueEdited();
288+
updateMediaSessionActions();
289+
}
166290
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
return new PlaybackStateCompat.CustomAction.Builder(
44+
data.action(), data.name(), data.icon()
45+
).build();
46+
}
47+
}

0 commit comments

Comments
 (0)