I have implemented music service in my spotifyclone android app but when I run project I am getting following exception
java.lang.RuntimeException: Unable to create service com.example.spotifyclone.exoplayer.MusicService: java.lang.IllegalArgumentException
at android.app.ActivityThread.handleCreateService(ActivityThread.java:3610)
at android.app.ActivityThread.access$1500(ActivityThread.java:206)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1716)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6820)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:922)
Caused by: java.lang.IllegalArgumentException
at com.google.android.exoplayer2.util.Assertions.checkArgument(Assertions.java:39)
at com.google.android.exoplayer2.ui.PlayerNotificationManager$Builder.<init>(PlayerNotificationManager.java:353)
at com.example.spotifyclone.exoplayer.MusicNotificationManager.<init>(MusicNotificationManager.kt:33)
at com.example.spotifyclone.exoplayer.MusicService.onCreate(MusicService.kt:71)
at android.app.ActivityThread.handleCreateService(ActivityThread.java:3598)
below my MusicService.kt
import android.app.PendingIntent
import android.content.Intent
import android.os.Bundle
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 androidx.media.MediaBrowserServiceCompat
import com.example.spotifyclone.exoplayer.callbacks.MusicPlaybackPreparer
import com.example.spotifyclone.exoplayer.callbacks.MusicPlayerEventListener
import com.example.spotifyclone.exoplayer.callbacks.MusicPlayerNotificationListener
import com.example.spotifyclone.other.Constants.MEDIA_ROOT_ID
import com.example.spotifyclone.other.Constants.NETWORK_ERROR
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.*
import javax.inject.Inject
private const val SERVICE_TAG = "MusicService"
@AndroidEntryPoint
class MusicService : MediaBrowserServiceCompat() {
@Inject
lateinit var dataSourceFactory: DefaultDataSourceFactory
@Inject
lateinit var exoPlayer: ExoPlayer
@Inject
lateinit var firebaseMusicSource: FirebaseMusicSource
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 curPlayingSong: MediaMetadataCompat? = null
private lateinit var musicNotificationManager: MusicNotificationManager
private var isPlayerInitialized = false
private lateinit var musicPlayerEventListener: MusicPlayerEventListener
companion object {
var curSongDuration = 0L
private set
}
override fun onCreate() {
super.onCreate()
serviceScope.launch {
firebaseMusicSource.fetchMediaData()
}
val activityIntent = packageManager?.getLaunchIntentForPackage(packageName)?.let {
PendingIntent.getActivity(this, 0, 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(firebaseMusicSource) {
curPlayingSong = it
preparePlayer(
firebaseMusicSource.songs,
it,
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 firebaseMusicSource.songs[windowIndex].description
}
}
private fun preparePlayer(
songs: List<MediaMetadataCompat>,
itemToPlay: MediaMetadataCompat?,
playNow: Boolean
) {
val curSongIndex = if (curPlayingSong == null) 0 else songs.indexOf(itemToPlay)
exoPlayer.setMediaSource(firebaseMusicSource.asMediaSource(dataSourceFactory))
exoPlayer.prepare()
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 onl oadChildren(
parentId: String,
result: Result<MutableList<MediaBrowserCompat.MediaItem>>
) {
when (parentId) {
MEDIA_ROOT_ID -> {
val resultsSent = firebaseMusicSource.whenReady { isInitialized ->
if (isPlayerInitialized) {
result.sendResult(firebaseMusicSource.asMediaItems())
if (!isInitialized && firebaseMusicSource.songs.isNotEmpty()) {
preparePlayer(
firebaseMusicSource.songs,
firebaseMusicSource.songs[0],
false
)
isPlayerInitialized = true
}
} else {
mediaSession.sendSessionEvent(NETWORK_ERROR, null)
result.sendResult(null)
}
}
if (!resultsSent) {
result.detach()
}
}
}
}
}
below my AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.spotifyclone">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name=".SpotifyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SpotifyClone"
tools:targetApi="31">
<service android:name="com.example.spotifyclone.exoplayer.MusicService"
android:exported="false">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<activity
android:name=".ui.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
below MusicNotificationManager.kt
@SuppressLint("Range")
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.Builder(
context,
NOTIFICATION_ID, NOTIFICATION_CHANNEL_ID
)
.setChannelNameResourceId(R.string.notification_channel_name)
.setChannelDescriptionResourceId(R.string.notification_channel_description)
.setMediaDescriptionAdapter(DescriptionAdapter(mediaController))
.setNotificationListener(notificationListener)
.setSmallIconResourceId(R.drawable.ic_music)
.build()
}
fun showNotification(player: Player) {
notificationManager.setPlayer(player)
}
private inner class DescriptionAdapter(
private val mediaController: MediaControllerCompat
) : PlayerNotificationManager.MediaDescriptionAdapter {
override fun getCurrentContentTitle(player: Player): CharSequence {
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 onl oadCleared(placeholder: Drawable?) = Unit
})
return null
}
}
}
what I have done rebuild invalidate cache and restart and followed allstackoverflow answers I want to know where exactly I am making mistake what I have to do avoid crash in my app
CodePudding user response:
The stacktrace mentions the line 353 in the PlayerNotificationManager
source code:
at com.google.android.exoplayer2.ui.PlayerNotificationManager$Builder.<init>(PlayerNotificationManager.java:353)
Here is exactly that line (You can find the source code on github):
checkArgument(notificationId > 0);
As You can see, the library checks if the NOTIFICATION_ID
that You're passing is greater than 0. The solution is to change the definition of NOTIFICATION_ID
to an integer greater than 0. Good luck with the projet :)