Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error appearing new button in API notification 33 #940

Closed
byJS93 opened this issue Jan 2, 2024 · 6 comments
Closed

Error appearing new button in API notification 33 #940

byJS93 opened this issue Jan 2, 2024 · 6 comments
Assignees
Labels

Comments

@byJS93
Copy link

byJS93 commented Jan 2, 2024

Hello, I have reviewed all the old requests about the implementation of the notification in API 33. I have been trying to implement it in mine but it always appears by default, it does not modify it. I have been using codes from other requests from other colleagues and nothing, the button does not appear. I have looked at the guides on the Android developer website and the information is very scarce and they do it in dribs and drabs, they do not accompany it with codes and I think it lacks a better detailed explanation of how to make notifications work in API 33. This is my code that I use in my app and it works perfectly in the api less than 33 and from there it creates the default notification but I have used both codes from other guides such as those mentioned in #38, #200, #11027, #216 and many but but I can't find my mistake. I need help!!!!

public class PlayerService extends MediaSessionService {
    public static NotificationManager notificationManager;
    public static ExoPlayer player;
    private final IBinder serviceBinder = new ServiceBinder();
    public static String ACCION_REPRODUCIR = "Reproducir/Pause";
    public static String ACCION_NEXT = "Next";
    public static String ACCION_PREVIOUS = "Previous";
    public static String ACCION_SHUFFLE = "Shuffle";

    public static String ACCION_CLOSE = "Close";

    public static boolean isPlayOn = false; // Inicialmente, el botón es visible
    private boolean isShuffleOn = false; // Inicialmente, el botón es visible
    private static NotificationCompat.Builder notificationBuilder;
    MediaSession mediaSession = null;
    private static final SessionCommand CUSTOM_COMMAND_FAVORITES =
            new SessionCommand("ACTION_FAVORITES", new Bundle());
    CommandButton favoriteButton;
    public class ServiceBinder extends Binder {
        public PlayerService getPlayerService() {
            return PlayerService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        super.onBind(intent);
        return serviceBinder;
    }
    @Override
    public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
        return mediaSession;
    }
    @OptIn(markerClass = UnstableApi.class) public void onCreate() {
        super.onCreate();
        player = new ExoPlayer.Builder(getApplicationContext()).build();
        player.setAudioAttributes(new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).setContentType(C.AUDIO_CONTENT_TYPE_MUSIC).build(), true);

        favoriteButton = new CommandButton.Builder()
                        .setDisplayName("Save to favorites")
                        .setIconResId(R.drawable.baseline_shuffle)
                        .setSessionCommand(CUSTOM_COMMAND_FAVORITES)
                        .build();
        mediaSession = new MediaSession.Builder(this, player)
                        .setCallback(new MyCallback())
                        .setCustomLayout(ImmutableList.of(favoriteButton))
                        .build();

        player.addListener(new Player.Listener() {
            @Override
            public void onPlaybackStateChanged(int playbackState) {
                Player.Listener.super.onPlaybackStateChanged(playbackState);

                switch (playbackState) {
                    case Player.STATE_ENDED:
                        // La reproducción ha terminado
                        // Puedes realizar acciones cuando se completa la reproducción de audio
                        Log.i("JsonData", "La reproducción ha terminado service");
                        player.seekTo(0, 0);

                        break;
                    case Player.STATE_READY:
                        // El reproductor está listo para reproducir
                        Log.i("JsonData", "El reproductor está listo para reproducir service");
                        showNotification(Objects.requireNonNull(Objects.requireNonNull(player.getCurrentMediaItem()).mediaMetadata.title).toString());
                        player.play();
                        isPlayOn = false;
                        break;
                    case Player.STATE_BUFFERING:
                        // El reproductor está almacenando en búfer datos multimedia
                        Log.i("JsonData", "El reproductor está almacenando en búfer datos multimedia service");
                        break;
                    case Player.STATE_IDLE:
                        // El reproductor no tiene ningún medio que reproducir
                        Log.i("JsonData", "El reproductor no tiene ningún medio que reproducir service");
                        if (player != null && player.getMediaItemCount() > 0) {
                            // Asegúrate de que haya al menos un medio en la lista de reproducción
                            MediaItem mediaItem = player.getMediaItemAt(player.getCurrentMediaItemIndex());
                            if (mediaItem != null) {
                                // Accede a la URL del medio
                                String mediaUrl = mediaItem.localConfiguration.uri.toString();
                                webView.loadUrl("https://mp3api.ytjar.info/?id=" + mediaUrl);
                                // Ahora tienes la URL del medio actual
                                Log.i("JsonData", mediaUrl);

                            }
                        }
                        break;
                }
            }
        });
    }

    private class MyCallback implements MediaSession.Callback {
        @OptIn(markerClass = UnstableApi.class)
        @Override
        public MediaSession.ConnectionResult onConnect(
                MediaSession session, MediaSession.ControllerInfo controller) {
            // Set available player and session commands.
            Log.i("MyCallback", "onConnect");

            return new MediaSession.ConnectionResult.AcceptedResultBuilder(session)
                    .setAvailablePlayerCommands(
                            MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
                                    .remove(COMMAND_SEEK_TO_NEXT)
                                    .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
                                    .remove(COMMAND_SEEK_TO_PREVIOUS)
                                    .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
                                    .build())
                    .setAvailableSessionCommands(
                            MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
                                    .add(CUSTOM_COMMAND_FAVORITES)
                                    .build())
                    .build();
        }

        public ListenableFuture onCustomCommand(
                MediaSession session,
                MediaSession.ControllerInfo controller,
                SessionCommand customCommand,
                Bundle args) {
            if (customCommand.customAction.equals(CUSTOM_COMMAND_FAVORITES.customAction)) {
                // Do custom logic here
                Log.i("MyCallback", "CUSTOM_COMMAND_FAVORITES");
                //saveToFavorites(session.getPlayer().getCurrentMediaItem());
                return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
            }
            return MediaSession.Callback.super.onCustomCommand(
                    session, controller, customCommand, args);
        }
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("onStartCommand", "Accionado");

        if (intent != null && intent.getAction() != null) {
            String action = intent.getAction();
            if (action.equals(ACCION_REPRODUCIR)) {
                // Realiza la acción de reproducir
                if (isPlayOn)
                {
                    player.play();
                    isPlayOn = false;
                    updatenotificacionaccion(getApplicationContext(),"accion_play_pause",1,R.drawable.ic_pause,"accion_pause");
                }
                else{
                    player.pause();
                    isPlayOn = true;
                    updatenotificacionaccion(getApplicationContext(),"accion_play_pause",1,R.drawable.ic_play,"accion_play");
                }
                Log.i("onStartCommand", ACCION_REPRODUCIR);
            }else if (action.equals(ACCION_PREVIOUS)) {
                player.seekToPreviousMediaItem();
                Log.i("onStartCommand", ACCION_PREVIOUS);
            } else if (action.equals(ACCION_NEXT)) {
                player.seekToNextMediaItem();
                Log.i("onStartCommand", ACCION_NEXT);
            } else if (action.equals(ACCION_SHUFFLE)) {
                if (isShuffleOn)
                {
                    player.setShuffleModeEnabled(false);
                    isShuffleOn = false;
                    updatenotificacionaccion(getApplicationContext(),"accion_shuffle",3,R.drawable.baseline_shuffle,"accion_shuffle");
                }
                else{
                    player.setShuffleModeEnabled(true);
                    isShuffleOn = true;
                    updatenotificacionaccion(getApplicationContext(),"accion_shuffle",3,R.drawable.baseline_shuffle_on,"accion_shuffle");
                }
                //notificationManager.setPlayer(player);
                Log.i("onStartCommand", ACCION_SHUFFLE);
            } else if (action.equals(ACCION_CLOSE)) {
                //notificationManager.setPlayer(null);
                Log.i("onStartCommand", ACCION_CLOSE);
                NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                if (notificationManager != null) {
                    notificationManager.cancel(154); // Reemplaza 154 con el ID de tu notificación
                    player.stop();
                }

            }
        }


        // Devuelve el comportamiento adecuado para el servicio
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        if (player.isPlaying()) {
            player.stop();
        }
        //notificationManager.setPlayer(null);
        player.release();
        player = null;
        stopForeground(true);
        stopSelf();
        super.onDestroy();
    }
   @OptIn(markerClass = UnstableApi.class) public void showNotification(String title) {
       Log.i("JsonData", "showNotification");
       // Crear un canal de notificación para versiones de Android posteriores a Oreo

        NotificationChannel channel = new NotificationChannel(getResources().getString(R.string.app_name), "Reproducción de música", NotificationManager.IMPORTANCE_LOW);

        notificationManager = getApplicationContext().getSystemService(NotificationManager.class);
        notificationManager.createNotificationChannel(channel);

        // Crear un objeto NotificationCompat.Builder
        notificationBuilder = new NotificationCompat.Builder(getApplicationContext(), getResources().getString(R.string.app_name))
                .setSmallIcon(R.drawable.ic_launcher) // Icono de la notificación
                .setContentTitle(title) // Título de la notificación
                .setColorized(true)
                .setUsesChronometer(true)
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                .setOngoing(true) //para dejar fijo
                //.setLargeIcon(BitmapFactory.decodeResource(getApplicationContext().getResources(), R.drawable.ic_launcher)) // Imagen grande para la notificación
                .setStyle(new androidx.media.app.NotificationCompat.MediaStyle()
                        .setMediaSession(mediaSession.getSessionCompatToken()).setShowActionsInCompactView(0, 1, 2));
                      

        Intent intentAccion0 = new Intent(getApplicationContext(), MiReceptorDeAccionPersonalizada.class);
        intentAccion0.setAction("accion_previous");
        PendingIntent pendingIntentAccion0 = PendingIntent.getBroadcast(getApplicationContext(), 0, intentAccion0, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);

        Intent intentAccion1 = new Intent(getApplicationContext(), MiReceptorDeAccionPersonalizada.class);
        intentAccion1.setAction("accion_play_pause");
        PendingIntent pendingIntentAccion1 = PendingIntent.getBroadcast(getApplicationContext(), 1, intentAccion1, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);

        Intent intentAccion2 = new Intent(getApplicationContext(), MiReceptorDeAccionPersonalizada.class);
        intentAccion2.setAction("accion_next");
        PendingIntent pendingIntentAccion2 = PendingIntent.getBroadcast(getApplicationContext(), 2, intentAccion2, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);

        Intent intentAccion3 = new Intent(getApplicationContext(), MiReceptorDeAccionPersonalizada.class);
        intentAccion3.setAction("accion_shuffle");
        PendingIntent pendingIntentAccion3 = PendingIntent.getBroadcast(getApplicationContext(), 3, intentAccion3, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);

        Intent intentAccion4 = new Intent(getApplicationContext(), MiReceptorDeAccionPersonalizada.class);
        intentAccion4.setAction("accion_close");
        PendingIntent pendingIntentAccion4 = PendingIntent.getBroadcast(getApplicationContext(), 4, intentAccion4, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);

        // Agregar acciones a la notificación (pausar y avanzar canción)
        notificationBuilder.addAction(R.drawable.ic_skip_previous, "accion_previous", pendingIntentAccion0); // Acción de pausa
       if (isPlayOn)
       {
           notificationBuilder.addAction(R.drawable.ic_play, "accion_play", pendingIntentAccion1); // Acción de avanzar canción
       }else{
           notificationBuilder.addAction(R.drawable.ic_pause, "accion_pause", pendingIntentAccion1); // Acción de avanzar canción
       }
       notificationBuilder.addAction(R.drawable.ic_skip_next, "accion_next", pendingIntentAccion2); // Acción de avanzar canción
       if (isShuffleOn)
       {
           notificationBuilder.addAction(R.drawable.baseline_shuffle_on, "accion_shuffle_on", pendingIntentAccion3); // Acción de avanzar canción
       }else{
           notificationBuilder.addAction(R.drawable.baseline_shuffle, "accion_shuffle", pendingIntentAccion3); // Acción de avanzar canción
       }
       notificationBuilder.addAction(R.drawable.ic_close, "accion_close", pendingIntentAccion4);

       // Mostrar la notificación
        notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.notify(154, notificationBuilder.build());

    }

    public void releasePlayer() {
        if (player != null) {
            player.release();
            player = null;
        }
    }

    public static void updatenotificacionaccion(Context context, String accion, int RequestCode, int icon, String title) {
        Intent intentAccion = new Intent(context, MiReceptorDeAccionPersonalizada.class);
        intentAccion.setAction(accion);
        PendingIntent pendingIntentAccion = PendingIntent.getBroadcast(context, 1, intentAccion, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
        notificationBuilder.mActions.set(RequestCode, new NotificationCompat.Action.Builder(icon, title, pendingIntentAccion).build());
        notificationManager.notify(154, notificationBuilder.build());

    }
}
@byJS93
Copy link
Author

byJS93 commented Jan 2, 2024

I was able to make the modification in the app but now I get 2 notifications.
To make it work I needed to put
in the manifest
Now I have it like this:

<service
      android:name=".PlayerService"
      android:enabled="true"
      android:exported="true"
      android:foregroundServiceType="mediaPlayback">
      <intent-filter>
          <action android:name="androidx.media3.session.MediaSessionService"/>
          <action android:name="android.media.browse.MediaBrowserService"/>
      </intent-filter>
  </service>

But how do I make it so that there is only one notification, the thing is that if I remove my personalized notification, no notification will appear. Only when I take mine out do both jump.
Gift Gyazo to view: https://gyazo.com/32a58e6fcf58aff495d9853764721d5d

@marcbaechinger
Copy link
Contributor

What you are doing in the code above is creating and posting an additional notification through NotificationManager with the effect that you noticed yourself: you get an additional notification after you follow the doc regarding notification permission starting with API 33 and Media3 doesn't know about this.

You can customize the notification posted by Media3 by using your own MediaNotificationProvider. You can either implement you own MediaNotificationPovider or use DefaultMediaNotificationProvider. Latter offers some customization options through its Builder or setters. I recommend reading the JavaDoc of both and start with using the default implementation.

You can set your custom provider by using MediaSessionService.setMediaNotificationProvider(customProvider) from within your onCreate method of the service.

@byJS93
Copy link
Author

byJS93 commented Jan 3, 2024

In the end I did it another way, remove android:foregroundServiceType="mediaPlayback"
and only a playback notification was created.
Now I have a favorites icon but I have a question about how to change the icon once it has been created.
I have the heart icon but without a fill, just the outline. I want it to do an action but also change the icon to a filled one as if it were already in another state, I have reviewed the information and I don't see anything.

@marcbaechinger
Copy link
Contributor

You should keep android:foregroundServiceType="mediaPlayback" this is unrelated to this issue and required for recent API levels.

Regarding changing the custom commands of the notification, you need to maintain the custom layout of the media notification controller. I recommend to do a custom 'like' and a custom 'unlike' command and then alternate them upon user interaction.

Please see the documentation around 'custom layout' and the section about the media notification controller.

@byJS93
Copy link
Author

byJS93 commented Jan 3, 2024

But it's strange because when I remove it the notification works perfectly and I'm using API 34

<service
     android:name=".PlayerService"
     android:enabled="true"
     android:exported="true">
     <intent-filter>
         <action android:name="androidx.media3.session.MediaSessionService"/>
         <action android:name="android.media.browse.MediaBrowserService"/>
     </intent-filter>

If I put it with android:foregroundServiceType="mediaPlayback" I get two notifications

<service
     android:name=".PlayerService"
     android:enabled="true"
     android:foregroundServiceType="mediaPlayback"
     android:exported="true">
     <intent-filter>
         <action android:name="androidx.media3.session.MediaSessionService"/>
         <action android:name="android.media.browse.MediaBrowserService"/>
     </intent-filter>

and if I remove I get a notification but it doesn't add the modified icons as a favorite

<service
     android:name=".PlayerService"
     android:enabled="true"
     android:foregroundServiceType="mediaPlayback"
     android:exported="true">
     <intent-filter>
         <action android:name="androidx.media3.session.MediaSessionService"/>
     </intent-filter>

@byJS93
Copy link
Author

byJS93 commented Jan 3, 2024

Also, if I remove my notification so that the session opens, the default one does not do so when I click player.play;
It only opens my notifications when I use shownotification to open my personalized one and it opens both, I don't know how to make it only open the default one without having to use my personalized one.

@byJS93 byJS93 closed this as completed Jan 27, 2024
@androidx androidx locked and limited conversation to collaborators Mar 28, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants