0

I'm creating an audiobook player, and I'm using MediaSessionCompat related classes to handle notifications. My code is heavily inspired by the android-MediaBrowserService samples ( https://github.com/googlearchive/android-MediaBrowserService ), and i'm not quite understanding it all for the moment ( the createContentIntent espicially )

Here is my simple class in charge of building notifications from a bookPlayer providing metadata and playbackstate data

class PlayerNotificationManager(val playerService: PlayerService) {

    val CHANNEL_ID = "pylvain.gamma"
    val NOTIFICATION_ID = 412
    private val REQUEST_CODE = 501

    val notificationManager =
        playerService.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

    private val playAction: NotificationCompat.Action =
        NotificationCompat.Action (
            android.R.drawable.ic_media_pause,
            "PAUSE",
            buildMediaButtonPendingIntent(
                playerService,
                PlaybackStateCompat.ACTION_PAUSE
            )
        )

    fun getNotification(bookPlayer: BookPlayer): Notification  {

        if (isAndroidOOrHigher()) createChannel()

        val style = androidx.media.app.NotificationCompat.MediaStyle()
            .setMediaSession(playerService.sessionToken)
            .setShowCancelButton(true)
            .setShowActionsInCompactView(0)
            .setCancelButtonIntent(
                buildMediaButtonPendingIntent(
                    playerService,
                    PlaybackStateCompat.ACTION_STOP
                )
            )

        val builder = NotificationCompat.Builder(playerService, CHANNEL_ID)
            .addAction(playAction)
            .setStyle(style)
            .setSmallIcon(R.drawable.ic_music_rest_quarter)
            .setContentIntent(createContentIntent())
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

        return builder.build()

    }

    private fun createContentIntent(): PendingIntent { //TODO: Understand that
        Timber.i("Creating Intent")
        val openUI = Intent(playerService, MainActivity::class.java)
        openUI.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
        return PendingIntent.getActivity(
            playerService, 0, openUI, PendingIntent.FLAG_UPDATE_CURRENT
        )
    }

The notification is showing perfectly fine with metadata

Here is my MediaBrowserService handling the media session, where I registered the callbacks. The bookplayer is constructed and injected with Koin. :

class PlayerService : MediaBrowserServiceCompat() {

    private lateinit var playerNotificationManager: PlayerNotificationManager
    lateinit var session: MediaSessionCompat

    private val bookPlayer: BookPlayer by inject()

    override fun onCreate() {
        super.onCreate()

        session = MediaSessionCompat(this, "MusicService")
        session.apply {
            setFlags(
                MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
                        MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
            )
            setPlaybackState(bookPlayer.playbackState)
            setMetadata(bookPlayer.getMetadata())
            setCallback(callbacks)
            setActive(true)
        }

        setSessionToken(session.sessionToken)
        playerNotificationManager = PlayerNotificationManager(this)

        val notification = playerNotificationManager.getNotification(bookPlayer)

        startForeground(playerNotificationManager.NOTIFICATION_ID, notification)

    }

    override fun onTaskRemoved(rootIntent: Intent?) { //TODO
        super.onTaskRemoved(rootIntent)
        stopSelf()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onGetRoot(
        clientPackageName: String,
        clientUid: Int,
        rootHints: Bundle?
    ): BrowserRoot? {
        return BrowserRoot("root", null)
    }

    override fun onLoadChildren(
        parentId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>
    ) {
        result.sendResult(null);
    }
    
    override fun onDestroy() {
        session.release()
    }


    val callbacks = object : MediaSessionCompat.Callback() {

        override fun onCommand(command: String?, extras: Bundle?, cb: ResultReceiver?) {
            Timber.i("Test")
            super.onCommand(command, extras, cb)
        }

        override fun onPrepare() {
            Timber.i("Preparing")
        }

        override fun onPlay() {
            Timber.i("Playing")
            bookPlayer.pause()
        }

        override fun onPause() {
            Timber.i("Pausing")
            bookPlayer.pause()
        }

        override fun onSkipToNext() {}
        override fun onSkipToPrevious() {}
        override fun onStop() {}
        override fun onSeekTo(pos: Long) {}
        override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean = true

    }

}

Then the service is started from the main activity with

startService(Intent(mainContext, PlayerService::class.java))

I also added this to my Android manifest


        <service android:name=".playerservice.PlayerService">
            <intent-filter>
                <action android:name="android.media.browse.MediaBrowserService" />
            </intent-filter>
        </service>


        <receiver android:name="androidx.media.session.MediaButtonReceiver">
            <intent-filter>
                <action android:name="android.intent.action.MEDIA_BUTTON" />
            </intent-filter>
        </receiver>

None of the callbacks are called whenever I push the button. when i do, the app log the following text :

D/MediaBrowserCompat: Connecting to a MediaBrowserService.

and nothing happens ... I've searched the entire internet and I'm completely clueless, but it's surely something simple. Can someone help me ? Thank you very much in advance <3

Pylvain
  • 110
  • 1
  • 7

2 Answers2

2

The callback worked ... Just not the way intended. It turns out that the play action button was calling

override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean = true

I deleted the function, and ... It works ...

Thank you for your attention !

Pylvain
  • 110
  • 1
  • 7
  • 2
    The [documentation for handling media buttons](https://developer.android.com/guide/topics/media-apps/mediabuttons#mediabuttons-and-active-mediasessions) actually specifically calls out "Android automatically dispatches media button events to your active media session by calling onMediaButtonEvent(). **By default this callback translates the KeyEvent into the appropriate media session Callback method that matches the key code.**" - you removed the default implementation, so yeah, you're not going to get the default behavior. – ianhanniballake Jan 20 '21 at 17:39
  • Sorry, I was a little bit salty. It took me hours to find that bug. It wasn't easy to spot – Pylvain Jan 20 '21 at 18:36
  • The docs could indeed be clearer on this topic. While the [javadoc for `onMediaButtonEvent()`](https://developer.android.com/reference/android/support/v4/media/session/MediaSessionCompat.Callback#onMediaButtonEvent(android.content.Intent)) specifically mentions that returning true indicates the event was handled, it could be emphasized better. Let me know if there are areas of the documentation that led you to believe this method needed to be overridden. – Paul Lammertsma Mar 28 '22 at 23:03
0

If you want to get media button, you have to play something. Try to play dummy audio, when your service is started

    // play dummy audio
    AudioTrack at = new AudioTrack(AudioManager.STREAM_MUSIC, 48000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT,
            AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT), AudioTrack.MODE_STREAM);
    at.play();

    // a little sleep 
    at.stop();
    at.release();

https://stackoverflow.com/a/50678833/9891730 - this is my answer before

오효민
  • 181
  • 1
  • 4