Skip to content

feat: Android Auto Support #80

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

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions kotlin-audio-sample/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.fragment.app.Fragment
import com.doublesymmetry.kotlin_audio_sample.databinding.FragmentFirstBinding
import com.doublesymmetry.kotlinaudio.models.*
import com.doublesymmetry.kotlinaudio.players.QueuedAudioPlayer
import com.doublesymmetry.kotlinaudio.players.AAMediaSessionCallBack
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -36,6 +37,15 @@ class FirstFragment : Fragment() {
return binding.root
}

private val myMediaSessionCallBack = object: AAMediaSessionCallBack {
override fun handlePlayFromMediaId(mediaId: String?, extra: Bundle?) {
Timber.tag("Test").d("playing from mediaID: %s", mediaId)
}

override fun handlePlayFromSearch(query: String?, extras: Bundle?) {
Timber.tag("Test").d("playing from query: %s", query)
}
}
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Expand All @@ -45,7 +55,7 @@ class FirstFragment : Fragment() {
interceptPlayerActionsTriggeredExternally = true,
handleAudioBecomingNoisy = true,
handleAudioFocus = true
)
), mediaSessionCallback = myMediaSessionCallBack
)
player.add(firstItem)
player.add(secondItem)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.support.v4.media.MediaDescriptionCompat
Expand Down Expand Up @@ -351,6 +352,8 @@ class NotificationManager internal constructor(
putString(MediaMetadataCompat.METADATA_KEY_ARTIST, it)
}
})
setIconUri(mediaItem.mediaMetadata.artworkUri ?: Uri.parse(audioItemHolder.audioItem.artwork))
setIconBitmap(audioItemHolder.artworkBitmap)
}.build()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ import android.content.Context
import com.doublesymmetry.kotlinaudio.models.BufferConfig
import com.doublesymmetry.kotlinaudio.models.CacheConfig
import com.doublesymmetry.kotlinaudio.models.PlayerConfig
import com.doublesymmetry.kotlinaudio.players.AAMediaSessionCallBack

class AudioPlayer(context: Context, playerConfig: PlayerConfig = PlayerConfig(), bufferConfig: BufferConfig? = null, cacheConfig: CacheConfig? = null): BaseAudioPlayer(context, playerConfig, bufferConfig, cacheConfig)
class AudioPlayer(context: Context, playerConfig: PlayerConfig = PlayerConfig(), bufferConfig: BufferConfig? = null, cacheConfig: CacheConfig? = null, mediaSessionCallback: AAMediaSessionCallBack): BaseAudioPlayer(context, playerConfig, bufferConfig, cacheConfig, mediaSessionCallback)
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,25 @@ import timber.log.Timber
import java.util.Locale
import java.util.concurrent.TimeUnit

interface AAMediaSessionCallBack {
fun handlePlayFromMediaId(mediaId: String?, extras: Bundle?)
fun handlePlayFromSearch(query: String?, extras: Bundle?)
fun handleSkipToQueueItem(id: Long)
}

abstract class BaseAudioPlayer internal constructor(
internal val context: Context,
playerConfig: PlayerConfig,
private val bufferConfig: BufferConfig?,
private val cacheConfig: CacheConfig?
private val cacheConfig: CacheConfig?,
mediaSessionCallback: AAMediaSessionCallBack
) : AudioManager.OnAudioFocusChangeListener {
protected val exoPlayer: ExoPlayer

private var cache: SimpleCache? = null
private val scope = MainScope()
private var playerConfig: PlayerConfig = playerConfig
var mediaSessionCallBack: AAMediaSessionCallBack = mediaSessionCallback

val notificationManager: NotificationManager

Expand Down Expand Up @@ -229,6 +237,75 @@ abstract class BaseAudioPlayer internal constructor(
val playerToUse =
if (playerConfig.interceptPlayerActionsTriggeredExternally) createForwardingPlayer() else exoPlayer

mediaSession.setCallback(object: MediaSessionCompat.Callback() {
override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) {
Timber.tag("GVATest").d("playing from mediaID: %s", mediaId)
mediaSessionCallback.handlePlayFromMediaId(mediaId, extras)
}

override fun onPlayFromSearch(query: String?, extras: Bundle?) {
super.onPlayFromSearch(query, extras)
Timber.tag("GVATest").d("playing from query: %s", query)
mediaSessionCallback.handlePlayFromSearch(query, extras)
}
// https://stackoverflow.com/questions/53837783/selecting-media-item-in-android-auto-queue-does-nothing
override fun onSkipToQueueItem(id: Long) {
mediaSessionCallback.handleSkipToQueueItem(id)
}
// TODO: what's missing?
override fun onPlay() {
playerToUse.play()
}

override fun onPause() {
playerToUse.pause()
}

override fun onSkipToNext() {
playerToUse.seekToNext()
}

override fun onSkipToPrevious() {
playerToUse.seekToPrevious()
}

override fun onFastForward() {
playerToUse.seekForward()
}

override fun onRewind() {
playerToUse.seekBack()
}

override fun onStop() {
playerToUse.stop()
}

override fun onSeekTo(pos: Long) {
playerToUse.seekTo(pos)
}

override fun onSetRating(rating: RatingCompat?) {
if (rating == null) return
playerEventHolder.updateOnPlayerActionTriggeredExternally(
MediaSessionCallback.RATING(
rating, null
)
)
}

override fun onSetRating(rating: RatingCompat?, extras: Bundle?) {
if (rating == null) return
playerEventHolder.updateOnPlayerActionTriggeredExternally(
MediaSessionCallback.RATING(
rating,
extras
)
)
}
})


notificationManager = NotificationManager(
context,
playerToUse,
Expand Down Expand Up @@ -264,6 +341,10 @@ abstract class BaseAudioPlayer internal constructor(

playerEventHolder.updateAudioPlayerState(AudioPlayerState.IDLE)
}

public fun getMediaSessionToken(): MediaSessionCompat.Token {
return mediaSession.sessionToken
}

private fun createForwardingPlayer(): ForwardingPlayer {
return object : ForwardingPlayer(exoPlayer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ class QueuedAudioPlayer(
context: Context,
playerConfig: PlayerConfig = PlayerConfig(),
bufferConfig: BufferConfig? = null,
cacheConfig: CacheConfig? = null
) : BaseAudioPlayer(context, playerConfig, bufferConfig, cacheConfig) {
cacheConfig: CacheConfig? = null,
mediaSessionCallback: AAMediaSessionCallBack
) : BaseAudioPlayer(context, playerConfig, bufferConfig, cacheConfig, mediaSessionCallback) {
private val queue = LinkedList<MediaSource>()
override val playerOptions = DefaultQueuedPlayerOptions(exoPlayer)

Expand Down