Search code examples
androidexoplayerandroid-autoandroid-mediasessionmediabrowserservicecompat

Android Auto: Media item gets stuck "Getting your selection..." instead of showing controls while playing


I've got a PlaybackService.kt class that implements MediaBrowserServiceCompat() since that is required for Android Auto support. The player itself works great in-app. The app is 95% used for HLS streams between a few different channels (5% is audio sample files).

I can successfully get my root view which is a simple list of channels ("Live", "80's", "90's", etc.) and when I click one, audio starts playing, but I get stuck on the "Getting your selection..." screen as shown below. When looking at the logcat, no errors are reported. One thing that may be odd, or perhaps is normal, onGetRoot is called again after selecting a channel.

Android Auto error

Here is my entire PlaybackService.kt file.

package <my-package>.audio.service

import android.app.Notification
import android.app.PendingIntent
import android.content.Intent
import android.content.res.Configuration
import android.net.Uri
import android.os.Binder
import android.os.Bundle
import android.os.IBinder
import android.os.ResultReceiver
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaDescriptionCompat
import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import androidx.annotation.MainThread
import androidx.core.content.ContextCompat
import androidx.media.MediaBrowserServiceCompat
import androidx.media.session.MediaButtonReceiver
import <my-package>.MainActivity
import <my-package>.R
import <my-package>.audio.api.ChannelApi
import <my-package>.audio.api.StreamApi
import <my-package>.audio.models.NetworkChannel
import <my-package>.audio.models.State
import <my-package>.audio.utils.StorageHelper
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.WritableMap
import com.facebook.react.modules.core.DeviceEventManagerModule
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame
import com.google.android.exoplayer2.metadata.id3.UrlLinkFrame
import com.google.android.exoplayer2.ui.PlayerNotificationManager
import com.google.android.exoplayer2.util.MimeTypes
import com.npaw.youbora.lib6.exoplayer2.Exoplayer2Adapter
import com.npaw.youbora.lib6.plugin.Options
import com.npaw.youbora.lib6.plugin.Plugin
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import java.util.*

open class PlaybackService : MediaBrowserServiceCompat() {
    var reactContext: ReactContext? = null
    private val binder = PlaybackBinder()
    private val scope = MainScope()
    private var sourceUrl: String? = null

    private var isForegroundService = false

    private val playerListener = playerListener()

    private val playbackAudioAttributes = AudioAttributes.Builder()
        .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
        .setUsage(C.USAGE_MEDIA)
        .build()

    private val player: ExoPlayer by lazy {
        ExoPlayer.Builder(this).build().apply {
            setAudioAttributes(playbackAudioAttributes, true)
            setHandleAudioBecomingNoisy(true)
            setWakeMode(C.WAKE_MODE_NETWORK)
            addListener(playerListener)
            setPlaybackSpeed(1.0f)
            playWhenReady = true
        }
    }

    private val youboraOptions = Options()
    private lateinit var youboraPlugin: Plugin

    private lateinit var notificationManager: NotificationManager
    private lateinit var mediaSession: MediaSessionCompat
    private lateinit var mediaSessionConnector: MediaSessionConnector

    private var autoStations: List<NetworkChannel>? = null

    private lateinit var appName: String

    // START: Audio Module actions
    fun play(streamName: String){
        Log.d(TAG, "Processing stream: $streamName")
        youboraOptions.contentChannel = streamName
        buildStreamAndPlay(streamName)
    }

    fun playHook(hookUri: String, songTitle: String, artistName: String){
        Log.d(TAG, "Process hook file: $hookUri")
        youboraOptions.contentTitle = "$songTitle-$artistName"
        buildMediaAndPlay(hookUri)
    }

    fun stop(){
        Log.d(TAG, "Stopping stream.")
        player.stop()
        player.clearMediaItems()
    }

    fun getPlaybackState(): Int {
        return player.playbackState
    }
    // END: Audio Module actions

    override fun onCreate() {
        Log.d(TAG, "onCreate")
        super.onCreate()

        appName = applicationContext.getString(R.string.app_name)

        val sessionActivityPendingIntent = PendingIntent.getActivity(
            /* context = */ reactContext ?: applicationContext,
            /* requestCode = */ 0,
            /* intent = */ Intent(reactContext ?: applicationContext, MainActivity::class.java),
            /* flags = */ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )

        mediaSession = MediaSessionCompat(this, TAG).apply {
            setSessionActivity(sessionActivityPendingIntent)
            isActive = true
        }

        sessionToken = mediaSession.sessionToken

        notificationManager = NotificationManager(
            this,
            mediaSession.sessionToken,
            PlayerNotificationListener()
        )

        mediaSessionConnector = MediaSessionConnector(mediaSession)
        mediaSessionConnector.setPlaybackPreparer(AudioServicePreparer())
        mediaSessionConnector.setPlayer(player)

        notificationManager.showNotificationForPlayer(player)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d(TAG, "onStartCommand")
        MediaButtonReceiver.handleIntent(mediaSession, intent)
        return START_STICKY
    }

    override fun onBind(intent: Intent?): IBinder {
        if (intent != null) {
            if(SERVICE_INTERFACE == intent.action){
                return super.onBind(intent)!!
            }
        }
        return binder
    }

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        player.stop()
        player.clearMediaItems()
    }

    override fun onGetRoot(
        clientPackageName: String,
        clientUid: Int,
        rootHints: Bundle?
    ): BrowserRoot? {
        Log.d(TAG, "onGetRoot called")
        return BrowserRoot(MEDIA_ROOT_ID, null)
    }

    override fun onLoadChildren(
        parentId: String,
        result: Result<List<MediaBrowserCompat.MediaItem>>
    ) {
        Log.d(TAG, "OnLoadChildren: parentMediaId = $parentId")

        if(EMPTY_MEDIA_ROOT_ID == parentId){
            result.sendResult(null)
            return
        }

        val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()

        if(MEDIA_ROOT_ID == parentId){
            // Build list of channels
            Log.d(TAG, "Building root items")
            if (autoStations != null) {
                Log.d(TAG, "Channels exist, creating media items...")
                for (i in 0 until autoStations!!.size) {
                    val station = autoStations!![i]
                    val mediaItem = createMediaItem(station)
                    if (mediaItem != null) {
                        mediaItems.add(mediaItem)
                    }
                }
                result.sendResult(mediaItems)
            } else {
                Log.d(TAG, "Making call to get channels...")
                result.detach()
                scope.launch {
                    try {
                        autoStations = ChannelApi.retrofitService.getChannels()

                        if (autoStations!!.isNotEmpty()) {
                            Log.d(TAG, "Stations fetched, creating media items...")
                            for (station in autoStations!!) {
                                val mediaItem = createMediaItem(station)
                                if (mediaItem != null) {
                                    mediaItems.add(mediaItem)
                                }
                            }
                            result.sendResult(mediaItems)
                        }else{
                            useFallbackStation()
                        }
                    } catch (err: Exception) {
                        Log.e(TAG, "Failed to get channels from API. Reason: ${err.message}")
                        result.sendResult(mediaItems)
                    }
                }
            }
        } else{
            result.sendResult(mediaItems)
        }
    }

    private fun createMediaItem(station: NetworkChannel): MediaBrowserCompat.MediaItem? {
        return try {
            val description = MediaDescriptionCompat.Builder()
                .setMediaId(station.streamName)
                .setTitle(station.title)

            val isDarkMode = (reactContext ?: applicationContext).resources.configuration.uiMode == Configuration.UI_MODE_NIGHT_YES

            if (isDarkMode) {
                description.setIconUri(
                    Uri.parse("<my-cdn>" + station.streamName + "/album-md.png")
                )
            } else {
                description.setIconUri(
                    Uri.parse("<my-cdn>" + station.streamName + "/album-md-dark.png")
                )
            }

            MediaBrowserCompat.MediaItem(
                description.build(),
                MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
            )
        } catch (ex: Exception) {
            null
        }
    }

    private fun useFallbackStation() {
        autoStations = List(1) {
            NetworkChannel(
                channelId = 5,
                title = appName,
                streamName = appName.lowercase(Locale.getDefault()),
                slug = "live",
                inactiveOn = null
            )
        }
    }

    override fun onDestroy() {
        scope.cancel()

        mediaSession.run {
            isActive = false
            release()
        }

        player.removeListener(playerListener)
        player.release()

        super.onDestroy()
    }

    private fun playStream(){
        Log.d(TAG, "playStream - sourceUrl: $sourceUrl")
        player.clearMediaItems()

        val mediaItem = MediaItem.Builder()
            .setUri(sourceUrl)
            .setMimeType(MimeTypes.APPLICATION_M3U8)
            .setLiveConfiguration(
                MediaItem.LiveConfiguration.Builder()
                    .setMaxPlaybackSpeed(1.02f)
                    .build()
            )
            .build()

        // Testing adding metadata for AA issue, no change...
        mediaSession.setMetadata(MediaMetadataCompat.Builder()
            .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Test 1")
            .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "Test 2")
            .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, -1)
            .build())

        if (player.isPlaying || player.isLoading) {
            stop()
        }

        player.addMediaItem(mediaItem)
        player.prepare()
    }

    private fun buildStreamAndPlay(streamName: String) {
        scope.launch {
            sourceUrl = StreamApi.retrofitService.getStreamUrl(
                mapOf(
                    "encoding" to "hls",
                    "platform" to "android",
                    "network" to streamName,
                )
            ).data.streamUrl

            Log.d(TAG, "Got stream url: $sourceUrl")

            playStream()
        }
    }

    private fun buildMediaAndPlay(hookUri: String) = scope.launch {
        val userData = StorageHelper.getUserData(reactContext ?: applicationContext)

        if(!::youboraPlugin.isInitialized && reactContext != null){
            youboraPlugin = Plugin(youboraOptions, reactContext)
            youboraPlugin.activity = reactContext?.currentActivity

            Exoplayer2Adapter(player)
                .also { adapter ->
                    youboraPlugin.adapter = adapter
                }
        }

        if (player.isPlaying == true || player.isLoading == true) {
            stop()
        }

        val mediaItem = MediaItem.Builder()
            .setUri(hookUri)
            .build()

        player.addMediaItem(mediaItem)
        player.prepare()
        player.play()
    }

    @MainThread
    private fun playerListener() = object: Player.Listener {
        override fun onPlaybackStateChanged(playbackState: Int) {
            val state: State = when (playbackState){
                ExoPlayer.STATE_IDLE -> State.Idle
                ExoPlayer.STATE_BUFFERING -> State.Buffering
                ExoPlayer.STATE_READY -> State.Ready
                ExoPlayer.STATE_ENDED -> State.Stopped
                else -> State.Stopped
            }

            if(state != State.Buffering){
                notificationManager.hideNotification()
            }

            Log.d(TAG, "PlayerState: $state")
            sendEvent(EVENT_PLAYER_STATE, state.state )
        }

        override fun onIsPlayingChanged(isPlaying: Boolean) {
            if(isPlaying) {
                Log.d(TAG, "PlayerState: Playing")
                notificationManager.showNotificationForPlayer(player)
                sendEvent(EVENT_PLAYER_STATE, State.Playing.state)
            }else{
                Log.d(TAG, "PlayerState: Paused (Stopping and clearing)")
                sendEvent(EVENT_PLAYER_STATE, State.Paused.state)
                player.stop()
                player.clearMediaItems()
            }
        }

        override fun onPlayerError(error: PlaybackException) {
            sendEvent(EVENT_PLAYER_STATE, State.Error.state)
            super.onPlayerError(error)
        }

        override fun onMetadata(metadata: com.google.android.exoplayer2.metadata.Metadata) {
            var handled = false

            var id: String? = null
            var title: String? = null
            var artist: String? = null
            var isBreak = false

            Log.d(TAG, "Raw Meta: $metadata")

            (0 until metadata.length()).forEach { i ->
                when (val entry = metadata[i]) {
                    is TextInformationFrame -> {
                        when (entry.id.uppercase()) {
                            "TIT2", "TT2" -> {
                                if(entry.value.isNotEmpty()){ // Added to due current bug with empty metadata
                                    handled = true
                                    title = entry.value
                                }
                            }
                            "TOPE", "TPE1", "TP1" -> {
                                handled = true
                                artist = entry.value
                            }
                            "TUID" -> {
                                handled = true
                                id = entry.value
                            }
                        }
                    }
                    is UrlLinkFrame -> {
                        when (entry.id.uppercase()) {
                            "WXXX" -> {
                                handled = true
                                if (id == null) {
                                    id = entry.url
                                }
                            }
                        }
                    }
                }
            }

            if (handled){
                try {
                    UUID.fromString(id)
                } catch (ignored: Exception) {
                    Log.d(TAG, "Detected stream break.")
                    id = null
                    isBreak = true
                }

                notificationManager.currentArtist = artist.toString()
                notificationManager.currentSong = title.toString()

                youboraOptions.contentTitle = "$title-$artist"
                youboraOptions.contentId = "$id"

                youboraOptions.contentBitrate = player.audioFormat?.bitrate?.toLong()

                Arguments.createMap().apply {
                    putString("id", id)
                    putString("title", title)
                    putString("artist", artist)
                    putBoolean("isBreak", isBreak)
                    sendEvent(EVENT_METADATA, this)
                }
            }
        }
    }

    private fun sendEvent(eventName: String, params: WritableMap?){
        reactContext?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
            ?.emit(eventName, params)
    }

    private fun sendEvent(eventName: String, eventValue: String){
        reactContext?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
            ?.emit(eventName, eventValue)
    }

    inner class PlaybackBinder: Binder(){
        val service = this@PlaybackService
    }

    /**
     * Listen for notification events.
     */
    private inner class PlayerNotificationListener :
        PlayerNotificationManager.NotificationListener {
        override fun onNotificationPosted(
            notificationId: Int,
            notification: Notification,
            ongoing: Boolean
        ) {
            if (ongoing && !isForegroundService) {
                ContextCompat.startForegroundService(
                    applicationContext,
                    Intent(applicationContext, [email protected])
                )

                startForeground(notificationId, notification)
                isForegroundService = true
            }
        }

        override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {
            stopForeground(true)
            isForegroundService = false
            stopSelf()
        }
    }

    private inner class AudioServicePreparer: MediaSessionConnector.PlaybackPreparer {
        override fun onCommand(
            player: Player,
            command: String,
            extras: Bundle?,
            cb: ResultReceiver?
        ): Boolean  = false

        override fun getSupportedPrepareActions(): Long =
            PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID or
            PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID or
            PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH or
            PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH


        override fun onPrepare(playWhenReady: Boolean) {
            Log.d(TAG, "[AudioServicePreparer] - onPrepare")
            // TODO: Should get last played station
            onPrepareFromMediaId("<default-media-id>", playWhenReady, null)
        }

        override fun onPrepareFromMediaId(
            mediaId: String,
            playWhenReady: Boolean,
            extras: Bundle?
        ) {
            Log.d(TAG, "[AudioServicePreparer] - onPrepareFromMediaId - $mediaId - playWhenReady? $playWhenReady")
            player.playWhenReady = playWhenReady
            play(mediaId)
        }

        override fun onPrepareFromSearch(query: String, playWhenReady: Boolean, extras: Bundle?) {
            Log.d(TAG, "[AudioServicePreparer] - onPrepareFromSearch")
            // TODO: Should search for station
            play("<default-media-id>")
        }

        override fun onPrepareFromUri(uri: Uri, playWhenReady: Boolean, extras: Bundle?) = Unit
    }

    companion object {
        const val TAG = "PlaybackService"

        const val EVENT_PLAYER_STATE = "onPlayerStateChanged"
        const val EVENT_METADATA = "onMetadataReceived"

        private const val MEDIA_ROOT_ID = "my_root"
        private const val EMPTY_MEDIA_ROOT_ID = "my_empty_root"
    }
}

Logcat output when selecting a station in Android Auto:

2022-11-22 17:11:08.293  2264-2264  PlaybackService         <my-package>                        D  [AudioServicePreparer] - onPrepareFromMediaId - <stream-name> - playWhenReady? true
2022-11-22 17:11:08.304  2264-2264  PlaybackService         <my-package>                        D  Processing stream: <stream-name>
2022-11-22 17:11:08.305  2264-2264  PlaybackService         <my-package>                        D  playStream - sourceUrl: <valid-url>
2022-11-22 17:11:08.395  2264-2264  PlaybackService         <my-package>                        D  PlayerState: Buffering
2022-11-22 17:11:08.397  2264-2476  ReactNativeJS           <my-package>                        I  'STATE:', 'buffering'
2022-11-22 17:11:11.102  2264-2521  DMCodecAdapterFactory   <my-package>                        I  Creating an asynchronous MediaCodec adapter for track type audio
2022-11-22 17:11:11.105  2264-9864  CCodec                  <my-package>                        I  state->set(ALLOCATING)
2022-11-22 17:11:11.105  2264-9864  CCodec                  <my-package>                        I  allocate(c2.android.aac.decoder)
2022-11-22 17:11:11.111  2264-9864  CCodec                  <my-package>                        I  setting up 'default' as default (vendor) store
2022-11-22 17:11:11.114  2264-9864  CCodec                  <my-package>                        I  Created component [c2.android.aac.decoder]
2022-11-22 17:11:11.114  2264-9864  CCodec                  <my-package>                        I  state->set(ALLOCATED)
2022-11-22 17:11:11.115  2264-9864  CCodecConfig            <my-package>                        D  read media type: audio/mp4a-latm
2022-11-22 17:11:11.123  2264-9864  CCodecConfig            <my-package>                        I  query failed after returning 19 values (BAD_INDEX)
2022-11-22 17:11:11.124  2264-9864  MediaCodec              <my-package>                        I  MediaCodec will operate in async mode
2022-11-22 17:11:11.125  2264-9864  CCodec                  <my-package>                        D  [c2.android.aac.decoder] buffers are bound to CCodec for this session
2022-11-22 17:11:11.125  2264-9864  CCodec                  <my-package>                        I  appPid(2264) width(0) height(0)
2022-11-22 17:11:11.125  2264-9864  CCodecConfig            <my-package>                        D  no c2 equivalents for log-session-id
2022-11-22 17:11:11.125  2264-9864  CCodecConfig            <my-package>                        D  no c2 equivalents for flags
2022-11-22 17:11:11.126  2264-9864  CCodecConfig            <my-package>                        D  c2 config diff is   c2::u32 raw.channel-count.value = 2
2022-11-22 17:11:11.126  2264-9864  Codec2Client            <my-package>                        W  query -- param skipped: index = 1107298332.
2022-11-22 17:11:11.126  2264-9864  CCodecConfig            <my-package>                        I  query failed after returning 19 values (BAD_INDEX)
2022-11-22 17:11:11.127  2264-2521  MediaCodec              <my-package>                        D  keep callback message for reclaim
2022-11-22 17:11:11.128  2264-9864  CCodec                  <my-package>                        I  state->set(STARTING)
2022-11-22 17:11:11.132  2264-9864  Codec2Client            <my-package>                        W  query -- param skipped: index = 1342179345.
2022-11-22 17:11:11.132  2264-9864  Codec2Client            <my-package>                        W  query -- param skipped: index = 2415921170.
2022-11-22 17:11:11.132  2264-9864  Codec2Client            <my-package>                        W  query -- param skipped: index = 1610614798.
2022-11-22 17:11:11.135  2264-9864  CCodec                  <my-package>                        I  state->set(RUNNING)
2022-11-22 17:11:11.135  2264-9864  CCodecBufferChannel     <my-package>                        I  [c2.android.aac.decoder#96] 4 initial input buffers available
2022-11-22 17:11:11.136  2264-2980  BufferPoolAccessor2.0   <my-package>                        D  bufferpool2 0xb40000701ca21d68 : 0(0 size) total buffers - 0(0 size) used buffers - 3845/3850 (recycle/alloc) - 5/3845 (fetch/transfer)
2022-11-22 17:11:11.136  2264-2980  BufferPoolAccessor2.0   <my-package>                        D  Destruction - bufferpool2 0xb40000701ca21d68 cached: 0/0M, 0/0% in use; allocs: 3850, 100% recycled; transfers: 3845, 100% unfetched
2022-11-22 17:11:11.140  2264-2264  PlaybackService         <my-package>                        D  PlayerState: Ready
2022-11-22 17:11:11.141  2264-2476  ReactNativeJS           <my-package>                        I  'STATE:', 'ready'
2022-11-22 17:11:11.147  2264-2264  PlaybackService         <my-package>                        D  PlayerState: Playing
2022-11-22 17:11:11.148  2264-2264  PlaybackService         <my-package>                        D  Raw Meta: entries=[PRIV: owner=com.apple.streaming.transportStreamTimestamp]
2022-11-22 17:11:11.149  2264-2264  PlaybackService         <my-package>                        D  Raw Meta: entries=[TIT2: description=null: value=]
2022-11-22 17:11:11.154  2264-9864  CCodecConfig            <my-package>                        D  c2 config diff is   c2::u32 raw.channel-mask.value = 12
2022-11-22 17:11:11.163  2264-2264  PlaybackService         <my-package>                        D  onStartCommand
2022-11-22 17:11:11.172  2264-2521  AudioTrack              <my-package>                        D  setVolume(1.000000, 1.000000) pid : 2264
2022-11-22 17:11:11.238  2264-2521  AudioTrack              <my-package>                        D  getTimestamp_l(74): device stall time corrected using current time 2384346281016
2022-11-22 17:11:11.274  2264-2476  ReactNativeJS           <my-package>                        I  'STATE:', 'playing'
2022-11-22 17:11:11.372  2264-2476  ReactNativeJS           <my-package>                        I  Metadata is undefined
2022-11-22 17:11:12.523  2264-2476  ReactNativeJS           <my-package>                        I  Token is not expired
2022-11-22 17:11:12.651  2264-2478  RNFBCrashlyticsInit     <my-package>                        D  isCrashlyticsCollectionEnabled via RNFBJSON: true
2022-11-22 17:11:12.652  2264-2478  RNFBCrashlyticsInit     <my-package>                        D  isCrashlyticsCollectionEnabled after checking crashlytics_debug_enabled: false
2022-11-22 17:11:12.652  2264-2478  RNFBCrashlyticsInit     <my-package>                        D  isCrashlyticsCollectionEnabled final value: false
2022-11-22 17:11:14.406  2264-2977  CCodecBufferChannel     <my-package>                        D  [c2.android.aac.decoder#96] DEBUG: elapsed: n=6 [in=0 pipeline=0 out=2]
2022-11-22 17:11:16.930  2264-2264  ViewRootIm...nActivity] <my-package>                        I  ViewPostIme pointer 0
2022-11-22 17:11:17.069  2264-2264  ViewRootIm...nActivity] <my-package>                        I  ViewPostIme pointer 1
2022-11-22 17:11:17.082  2264-2264  PlaybackService         <my-package>                        D  Stopping stream.
2022-11-22 17:11:17.086  2264-9864  CCodec                  <my-package>                        I  state->set(FLUSHING)
2022-11-22 17:11:17.086  2264-9864  CCodec                  <my-package>                        I  state->set(FLUSHED)
2022-11-22 17:11:17.087  2264-2521  MediaCodec              <my-package>                        D  keep callback message for reclaim
2022-11-22 17:11:17.087  2264-9864  CCodec                  <my-package>                        I  state->set(RESUMING)
2022-11-22 17:11:17.087  2264-9864  CCodecConfig            <my-package>                        I  query failed after returning 19 values (BAD_INDEX)
2022-11-22 17:11:17.088  2264-9864  CCodec                  <my-package>                        I  state->set(RUNNING)
2022-11-22 17:11:17.089  2264-9864  CCodec                  <my-package>                        I  state->set(RELEASING)
2022-11-22 17:11:17.090  2264-9929  CCodec                  <my-package>                        I  state->set(RELEASED)
2022-11-22 17:11:17.090  2264-9864  hw-BpHwBinder           <my-package>                        I  onLastStrongRef automatically unlinking death recipients
2022-11-22 17:11:17.090  2264-9864  MediaCodec              <my-package>                        I  Codec shutdown complete
2022-11-22 17:11:17.095  2264-2264  PlaybackService         <my-package>                        D  PlayerState: Idle
2022-11-22 17:11:17.097  2264-2476  ReactNativeJS           <my-package>                        I  'STATE:', 'idle'
2022-11-22 17:11:17.097  2264-2264  PlaybackService         <my-package>                        D  PlayerState: Paused (Stopping and clearing)
2022-11-22 17:11:17.201  2264-2476  ReactNativeJS           <my-package>                        I  'STATE:', 'paused'
2022-11-22 17:11:22.137  2264-2979  BufferPoolAccessor2.0   <my-package>                        D  bufferpool2 0xb40000701c6606a8 : 0(0 size) total buffers - 0(0 size) used buffers - 311/316 (recycle/alloc) - 5/311 (fetch/transfer)
2022-11-22 17:11:22.137  2264-2979  BufferPoolAccessor2.0   <my-package>                        D  evictor expired: 1, evicted: 1

Pastebin of media session dump while streaming: https://pastebin.com/5is1h2Mk

If there are any other files that you think may be helpful in finding an answer, I will try to provide though I have to sanitize some things.


Solution

  • I was finally able to solve the issue. I had been following the Google-published UAMP project as an example for how to implement the MediaBrowserServiceCompat class. When they create their MediaSessionCompat object in the onCreate function, they pass this for the context parameter. I found another guide from Google where they instead passed in baseContext.

    So the fix in the code above was to go from

    mediaSession = MediaSessionCompat(this, TAG).apply {
        setSessionActivity(sessionActivityPendingIntent)
        isActive = true
    }
    

    to

    mediaSession = MediaSessionCompat(baseContext, TAG).apply { // Param changed to baseContext
        setSessionActivity(sessionActivityPendingIntent)
        isActive = true
    }