问题描述
我想:
即使关闭应用程序也能继续播放歌曲。
我还想在通知托盘中添加一个关闭 (X) 图标(如果可能,用户可以从那里关闭媒体会话)
现在,当我关闭应用程序时。歌曲停止播放。
音乐服务
@AndroidEntryPoint
class MusicService : MediabrowserServiceCompat() {
@Inject
lateinit var dataSourceFactory: DefaultDataSourceFactory
@Inject
lateinit var exoPlayer: SimpleExoPlayer
@Inject
lateinit var musicSource: MusicSource
private lateinit var musicnotificationmanager: Musicnotificationmanager
private val serviceJob = Job()
private val serviceScope = Coroutinescope(dispatchers.Main + serviceJob)
private lateinit var mediaSession: MediaSessionCompat
private lateinit var mediaSessionConnector: MediaSessionConnector
var isForegroundService = false
private var currPlayingSong: MediaMetadataCompat? = null
private var isPlayerInitialized = false
private lateinit var musicPlayerEventListener: MusicPlayerEventListener
companion object {
var curSongDuration = 0L
private set
}
override fun onCreate() {
super.onCreate()
serviceScope.launch {
musicSource.fetchMediaData()
}
val activityIntent = packageManager?.getLaunchIntentForPackage(packageName)?.let {
PendingIntent.getActivity(this,it,0)
}
mediaSession = MediaSessionCompat(this,SERVICE_TAG).apply {
setSessionActivity(activityIntent)
isActive = true
}
sessionToken = mediaSession.sessionToken
musicnotificationmanager = Musicnotificationmanager(
this,mediaSession.sessionToken,MusicPlayerNotificationListener(this)
) {
curSongDuration = exoPlayer.duration
}
val musicPlayBackPreparer = MusicPlaybackPreparer(musicSource) {
currPlayingSong = it
preparePlayer(
musicSource.songs,true
)
}
mediaSessionConnector = MediaSessionConnector(mediaSession)
mediaSessionConnector.setPlaybackPreparer(musicPlayBackPreparer)
mediaSessionConnector.setQueueNavigator(MusicQueueNavigator())
mediaSessionConnector.setPlayer(exoPlayer)
musicPlayerEventListener = MusicPlayerEventListener(this)
exoPlayer.addListener(musicPlayerEventListener)
musicnotificationmanager.showNotification(exoPlayer)
}
private inner class MusicQueueNavigator : TimelineQueueNavigator(mediaSession) {
override fun getMediaDescription(player: Player,windowIndex: Int): MediaDescriptionCompat {
return musicSource.songs[windowIndex].description
}
}
private fun preparePlayer(
songs: List<MediaMetadataCompat>,itemToPlay: MediaMetadataCompat?,playNow: Boolean
) {
val curSongIndex = if (currPlayingSong == null) 0 else songs.indexOf(itemToPlay)
exoPlayer.prepare(musicSource.asMediaSource(dataSourceFactory))
exoPlayer.seekTo(curSongIndex,0L)
exoPlayer.playWhenReady = playNow
}
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
exoPlayer.stop()
}
override fun onDestroy() {
super.onDestroy()
serviceScope.cancel()
exoPlayer.removeListener(musicPlayerEventListener)
exoPlayer.release()
}
override fun onGetRoot(
clientPackageName: String,clientUid: Int,rootHints: Bundle?
): browserRoot {
return browserRoot(MEDIA_ROOT_ID,null)
}
override fun onLoadChildren(
parentId: String,result: Result<MutableList<MediabrowserCompat.MediaItem>>
) {
when (parentId) {
MEDIA_ROOT_ID -> {
val resultsSent = musicSource.whenReady { isInitialized ->
if (isInitialized) {
result.sendResult(musicSource.asMediaItem())
if (!isInitialized && musicSource.songs.isNotEmpty()) {
preparePlayer(musicSource.songs,musicSource.songs[0],false)
isPlayerInitialized = true
}
} else {
mediaSession.sendSessionEvent(NETWORK_ERROR,null)
result.sendResult(null)
}
}
if (!resultsSent) {
result.detach()
}
}
}
}
}
音乐服务连接
class MusicServiceConnection(context: Context) {
private val _isConnected = mutablelivedata<Event<Resource<Boolean>>>()
val isConnected: LiveData<Event<Resource<Boolean>>> = _isConnected
private val _networkError = mutablelivedata<Event<Resource<Boolean>>>()
val networkError: LiveData<Event<Resource<Boolean>>> = _networkError
private val _playbackState = mutablelivedata<PlaybackStateCompat?>()
val playbackState: LiveData<PlaybackStateCompat?> = _playbackState
private val _currentPlayingSong = mutablelivedata<MediaMetadataCompat?>()
val currentPlayingSong: LiveData<MediaMetadataCompat?> = _currentPlayingSong
lateinit var mediaControllerCompat: MediaControllerCompat
private val mediabrowserControllerCallback = MediabrowserConnectionCallback(context)
private val mediabrowser = MediabrowserCompat(
context,ComponentName(
context,MusicService::class.java
),mediabrowserControllerCallback,null
).apply { connect() }
val transportControls : MediaControllerCompat.TransportControls
get() = mediaControllerCompat.transportControls
fun subscribe(parentId: String,callback: MediabrowserCompat.SubscriptionCallback){
mediabrowser.subscribe(parentId,callback)
}
fun unSubscribe(parentId: String,callback: MediabrowserCompat.SubscriptionCallback){
mediabrowser.unsubscribe(parentId,callback)
}
private inner class MediabrowserConnectionCallback(
private val context: Context
): MediabrowserCompat.ConnectionCallback(){
override fun onConnected() {
mediaControllerCompat = MediaControllerCompat(context,mediabrowser.sessionToken).apply {
registerCallback(MediaControllerCallback())
}
_isConnected.postValue(Event(Resource.success(true)))
}
override fun onConnectionSuspended() {
_isConnected.postValue(Event(Resource.error("Data was suspended",false)))
}
override fun onConnectionFailed() {
_isConnected.postValue(Event(Resource.error("Couldn't connect",false)))
}
}
private inner class MediaControllerCallback: MediaControllerCompat.Callback(){
override fun onPlaybackStateChanged(state: PlaybackStateCompat?) {
_playbackState.postValue(state)
}
override fun onMetadataChanged(Metadata: MediaMetadataCompat?) {
_currentPlayingSong.postValue(Metadata)
}
override fun onSessionEvent(event: String?,extras: Bundle?) {
super.onSessionEvent(event,extras)
when(event){
NETWORK_ERROR -> _networkError.postValue(
Event(
Resource.error(
"Something went wrong",null
)
)
)
}
}
override fun onSessionDestroyed() {
mediabrowserControllerCallback.onConnectionSuspended()
}
}
}
Musicnotificationmanager
class Musicnotificationmanager(
private val context: Context,sessionToken: MediaSessionCompat.Token,notificationListener: Playernotificationmanager.NotificationListener,private val newSongCallback: () -> Unit
) {
private val notificationmanager: Playernotificationmanager
init {
val mediaController = MediaControllerCompat(context,sessionToken)
notificationmanager = Playernotificationmanager.createWithNotificationChannel(
context,NOTIFICATION_CHANNEL_ID,R.string.notification_channel_name,R.string.notification_channel_description,NOTIFICATION_ID,DescriptionAdapter(mediaController),notificationListener
).apply {
setSmallIcon(R.drawable.ic_logo_svg)
setMediaSessionToken(sessionToken)
setRewindIncrementMs(0)
setUseNavigationActionsInCompactView(true)
setFastForwardIncrementMs(0)
setPriority(NotificationCompat.PRIORITY_HIGH)
}
}
fun showNotification(player: Player) {
notificationmanager.setPlayer(player)
}
private inner class DescriptionAdapter(
private val mediaController: MediaControllerCompat
) : Playernotificationmanager.MediaDescriptionAdapter {
override fun getCurrentContentTitle(player: Player): CharSequence {
newSongCallback()
return mediaController.Metadata.description.title.toString()
}
override fun createCurrentContentIntent(player: Player): PendingIntent? {
return mediaController.sessionActivity
}
override fun getCurrentContentText(player: Player): CharSequence {
return mediaController.Metadata.description.subtitle.toString()
}
override fun getCurrentLargeIcon(
player: Player,callback: Playernotificationmanager.BitmapCallback
): Bitmap? {
Glide.with(context).asBitmap()
.load(mediaController.Metadata.description.iconUri)
.into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(
resource: Bitmap,transition: Transition<in Bitmap>?
) {
callback.onBitmap(resource)
}
override fun onLoadCleared(placeholder: Drawable?) = Unit
})
return null
}
}
}
MusicPlayerNotificationListener
class MusicPlayerNotificationListener(
private val musicService: MusicService
) : Playernotificationmanager.NotificationListener {
override fun onNotificationCancelled(notificationId: Int,dismissedByUser: Boolean) {
super.onNotificationCancelled(notificationId,dismissedByUser)
musicService.apply {
stopForeground(true)
isForegroundService = false
stopSelf()
}
}
override fun onNotificationPosted(
notificationId: Int,notification: Notification,ongoing: Boolean
) {
super.onNotificationPosted(notificationId,notification,ongoing)
musicService.apply {
if(ongoing && !isForegroundService){
ContextCompat.startForegroundService(this,Intent(applicationContext,this::class.java)
)
startForeground(NOTIFICATION_ID,notification)
isForegroundService = true
}
}
}
}
MusicPlayerEventListener
class MusicPlayerEventListener(
private val musicService: MusicService): Player.EventListener {
override fun onPlayerStateChanged(playWhenReady: Boolean,playbackState: Int) {
super.onPlayerStateChanged(playWhenReady,playbackState)
if (playbackState == Player.STATE_READY && !playWhenReady){
musicService.stopForeground(false)
}
}
override fun onPlayerError(error: ExoPlaybackException) {
super.onPlayerError(error)
Toast.makeText(musicService,"An unkNown error occurred",Toast.LENGTH_LONG).show()
}
}
MusicPlayBackPreparer
class MusicPlaybackPreparer(
private val musicSource: MusicSource,private val playerPreparer: (MediaMetadataCompat?) -> Unit) :
MediaSessionConnector.PlaybackPreparer {
override fun onCommand(
player: Player,controldispatcher: Controldispatcher,command: String,extras: Bundle?,cb: ResultReceiver?
) = false
override fun getSupportedPrepareActions(): Long {
return PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID or
PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
}
override fun onPrepare(playWhenReady: Boolean) = Unit
override fun onPrepareFromMediaId(mediaId: String,playWhenReady: Boolean,extras: Bundle?) {
musicSource.whenReady {
val itemToPlay = musicSource.songs.find {
mediaId == it.description.mediaId
}
playerPreparer(itemToPlay)
}
}
override fun onPrepareFromSearch(query: String,extras: Bundle?) = Unit
override fun onPrepareFromUri(uri: Uri,extras: Bundle?) = Unit
}
我可以做什么:
解决方法
删除 exoPlayer.stop()
类中的 fun onTaskRemoved(rootIntent: Intent?)
表单 MusicService
。这将使您的播放器在您关闭应用程序后继续播放。