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.
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, this@PlaybackService.javaClass)
)
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.
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
}