11package com.codeboy.mediafacerkotlin.musicSession
22
3+ import android.app.Notification
34import android.app.PendingIntent
45import android.content.*
6+ import android.content.pm.ServiceInfo
7+ import android.graphics.Bitmap
8+ import android.graphics.BitmapFactory
59import android.media.AudioFocusRequest
610import android.media.AudioManager
711import android.media.AudioManager.OnAudioFocusChangeListener
12+ import android.net.Uri
813import android.os.Build
914import android.os.Bundle
1015import android.os.SystemClock
@@ -20,10 +25,16 @@ import androidx.media3.common.MediaItem
2025import androidx.media3.common.Player
2126import androidx.media3.exoplayer.ExoPlayer
2227import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
28+ import androidx.media3.ui.PlayerNotificationManager
29+ import androidx.media3.ui.PlayerNotificationManager.MediaDescriptionAdapter
30+ import androidx.media3.ui.PlayerNotificationManager.NotificationListener
2331import com.codeboy.mediafacer.models.AudioContent
32+ import com.codeboy.mediafacerkotlin.MainActivity
2433import com.codeboy.mediafacerkotlin.R
2534import com.codeboy.mediafacerkotlin.utils.MusicDataUtil
2635import com.codeboy.mediafacerkotlin.viewModels.AudioViewModel
36+ import java.io.FileNotFoundException
37+ import java.io.InputStream
2738
2839class MusicService : MediaBrowserServiceCompat (), OnAudioFocusChangeListener, Player.Listener{
2940
@@ -33,6 +44,9 @@ class MusicService : MediaBrowserServiceCompat(), OnAudioFocusChangeListener, Pl
3344 private lateinit var player: ExoPlayer
3445 private var musicList: ArrayList <AudioContent > = ArrayList ()
3546 private var musicMetaDataList: ArrayList <MediaMetadataCompat > = ArrayList ()
47+ private lateinit var currentTrack: MediaMetadataCompat
48+ private var trackPosition = 0
49+
3650
3751 private val model = AudioViewModel ()
3852 private val observer = Observer <ArrayList <AudioContent >> { it ->
@@ -41,12 +55,12 @@ class MusicService : MediaBrowserServiceCompat(), OnAudioFocusChangeListener, Pl
4155 PlaybackProtocol .setCurrentMusic(musicList[0 ])
4256 setupUpMusicList(musicList)
4357 }
58+ private lateinit var playerNotification: PlayerNotificationManager
4459
4560 private val mNoisyReceiver: BroadcastReceiver = object : BroadcastReceiver () {
4661 override fun onReceive (context : Context , intent : Intent ) {
4762 // handles playback when earphones are plugged and unplugged
4863 // pause exo player when unplugged and play when plugged
49-
5064 }
5165 }
5266
@@ -81,7 +95,7 @@ class MusicService : MediaBrowserServiceCompat(), OnAudioFocusChangeListener, Pl
8195 model.audios.observeForever(observer)
8296 model.loadNewItems(this ,0 ,150 ,false )
8397 }else {
84- PlaybackProtocol .setCurrentMusic(musicList[0 ])
98+ PlaybackProtocol .setCurrentMusic(musicList[trackPosition ])
8599 setupUpMusicList(musicList)
86100 }
87101
@@ -92,13 +106,10 @@ class MusicService : MediaBrowserServiceCompat(), OnAudioFocusChangeListener, Pl
92106
93107 override fun onStartCommand (intent : Intent ? , flags : Int , startId : Int ): Int {
94108 MediaButtonReceiver .handleIntent(mMediaSessionCompat, intent)
95- return super .onStartCommand(intent, flags, startId)
109+ // return super.onStartCommand(intent, flags, startId)
110+ return START_STICKY
96111 }
97112
98- /* override fun onBind(intent: Intent): IBinder {
99- TODO("Return the communication channel to the service.")
100- }*/
101-
102113 override fun onGetRoot (clientPackageName : String , clientUid : Int , rootHints : Bundle ? ): BrowserRoot ? {
103114 return if (TextUtils .equals(clientPackageName, packageName)) {
104115 BrowserRoot (getString(R .string.app_name), null )
@@ -109,7 +120,9 @@ class MusicService : MediaBrowserServiceCompat(), OnAudioFocusChangeListener, Pl
109120
110121 }
111122
112- override fun onDestroy () {
123+ @androidx.annotation.OptIn (androidx.media3.common.util.UnstableApi ::class )
124+ override fun onDestroy () {
125+ playerNotification.setPlayer(null )
113126 // tell activity components that service is stopping
114127 PlaybackProtocol .isMusicServiceRunning = false
115128 // save the last playlist so next time it reads from preferences
@@ -118,20 +131,22 @@ class MusicService : MediaBrowserServiceCompat(), OnAudioFocusChangeListener, Pl
118131 if (model.audios.hasActiveObservers()){
119132 model.audios.removeObserver(observer)
120133 }
134+ player.removeListener(this )
135+ player.release()
121136 super .onDestroy()
122137 }
123138
124139 // service setup methods______________________________________________________________________________________________________________________
125140
126141 // build media items for media playback
127142 private fun setupUpMusicList (musicList : ArrayList <AudioContent >){
128-
129143 /* val imageUri = Uri.parse("https://example.com/image.jpg")
130144 val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, imageUri)*/
131145
132146 for (musicItem in musicList){
133147 musicMetaDataList.add(musicItem.getMediaMetaDataCompat())
134148 }
149+ currentTrack = musicMetaDataList[trackPosition]
135150 }
136151
137152 @androidx.annotation.OptIn (androidx.media3.common.util.UnstableApi ::class )
@@ -142,7 +157,7 @@ class MusicService : MediaBrowserServiceCompat(), OnAudioFocusChangeListener, Pl
142157 mediaItems.add(MediaItem .fromUri(item.musicUri))
143158 }
144159 val trackSelector = DefaultTrackSelector (this ).apply {
145- setParameters(buildUponParameters().setMaxVideoSizeSd() )
160+ setParameters(buildUponParameters())
146161 }
147162 val currentItem = 0
148163 val playbackPosition = 0L
@@ -156,16 +171,61 @@ class MusicService : MediaBrowserServiceCompat(), OnAudioFocusChangeListener, Pl
156171 exoPlayer.addListener(this )
157172 exoPlayer.prepare()
158173 }
174+
175+ val playerNotificationBuilder = PlayerNotificationManager .Builder (
176+ this ,1010 ," exoChannel"
177+ ).setMediaDescriptionAdapter(object : MediaDescriptionAdapter {
178+ override fun getCurrentContentTitle (player : Player ): CharSequence {
179+ return musicList[trackPosition].title
180+ }
181+
182+ override fun createCurrentContentIntent (player : Player ): PendingIntent ? {
183+ val playerIntent = Intent (this @MusicService, MainActivity ::class .java)
184+ return PendingIntent .getActivity(this @MusicService, 0 ,playerIntent, PendingIntent .FLAG_UPDATE_CURRENT )
185+ }
186+
187+ override fun getCurrentContentText (player : Player ): CharSequence? {
188+ return mMediaSessionCompat.controller.metadata.description.description
189+ }
190+
191+ override fun getCurrentLargeIcon (player : Player , callback : PlayerNotificationManager .BitmapCallback ): Bitmap ? {
192+ val imageUri: Uri = musicList[trackPosition].artUri
193+ var inputStream: InputStream ? = null
194+ try {
195+ inputStream = contentResolver.openInputStream(imageUri)
196+ } catch (e: FileNotFoundException ) {
197+ e.printStackTrace()
198+ }
199+ return BitmapFactory .decodeStream(inputStream)
200+ }
201+ }).setNotificationListener(object : NotificationListener {
202+ override fun onNotificationCancelled (notificationId : Int , dismissedByUser : Boolean ) {
203+ stopSelf()
204+ super .onNotificationCancelled(notificationId, dismissedByUser)
205+ }
206+
207+ override fun onNotificationPosted (notificationId : Int , notification : Notification , ongoing : Boolean ) {
208+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .Q ) {
209+ startForeground(1010 ,notification, ServiceInfo .FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK )
210+ }else {
211+ startForeground(1010 , notification)
212+ }
213+ super .onNotificationPosted(notificationId, notification, ongoing)
214+ }
215+ })
216+
217+ playerNotification = playerNotificationBuilder.build()
218+ playerNotification.setPlayer(player)
219+
159220 }
160221
161222 private fun setupMediaSession (){
162- val mediaButtonReceiverName = ComponentName (applicationContext, MusicPlaybackButtonReceiver ::class .java)
223+ val mediaButtonReceiverName = ComponentName (applicationContext, MediaButtonReceiver ::class .java)
163224 mMediaSessionCompat = MediaSessionCompat (applicationContext, LOG_TAG , mediaButtonReceiverName, null )
164225 mMediaSessionCompat.setFlags(MediaSessionCompat .FLAG_HANDLES_QUEUE_COMMANDS )
165226
166227 val mediaButtonIntent = Intent (Intent .ACTION_MEDIA_BUTTON )
167- // mediaButtonIntent.setClass(this, MediaButtonReceiver::class.java)
168- mediaButtonIntent.setClass(this , MusicPlaybackButtonReceiver ::class .java)
228+ mediaButtonIntent.setClass(this , MediaButtonReceiver ::class .java)
169229
170230 val pendingIntent = if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
171231 PendingIntent .getBroadcast(this ,0 , mediaButtonIntent, PendingIntent .FLAG_IMMUTABLE .and (PendingIntent .FLAG_UPDATE_CURRENT ))
@@ -210,9 +270,9 @@ class MusicService : MediaBrowserServiceCompat(), OnAudioFocusChangeListener, Pl
210270 }
211271 })
212272
273+ mMediaSessionCompat.setMetadata(currentTrack)
213274 mMediaSessionCompat.isActive = true
214275 sessionToken = mMediaSessionCompat.sessionToken
215-
216276 }
217277
218278 private fun buildState (state : Long ): PlaybackStateCompat ? {
@@ -275,6 +335,10 @@ class MusicService : MediaBrowserServiceCompat(), OnAudioFocusChangeListener, Pl
275335 }
276336 ExoPlayer .STATE_ENDED -> {
277337 // playWhenReady = false
338+ trackPosition + = 1
339+ currentTrack = musicMetaDataList[trackPosition]
340+ mMediaSessionCompat.setMetadata(currentTrack)
341+ player.seekToNextMediaItem()
278342 }
279343 ExoPlayer .STATE_IDLE -> {
280344 // here you can set items and prepare
0 commit comments