Fix foreground service crash during Ogg duration extraction

Call startForeground() immediately in onStartCommand() with a placeholder
notification before returning. This satisfies Android's foreground timeout
requirement while the blocking Ogg duration extraction completes. Media3's
MediaNotificationManager replaces the placeholder with the real notification
once the player starts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Davíð Steinn Geirsson 2026-01-14 18:42:35 +00:00
parent 6808012a53
commit 50a8c69ec6

View file

@ -1,12 +1,17 @@
package `is`.dsg.placeboplayer.playback.service
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.DefaultMediaNotificationProvider
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService
import `is`.dsg.placeboplayer.MainActivity
import `is`.dsg.placeboplayer.R
import `is`.dsg.placeboplayer.data.repository.PlaybackRepositoryImpl
import kotlinx.coroutines.runBlocking
import org.koin.android.ext.android.inject
@ -62,6 +67,36 @@ class PlaybackMediaSessionService : MediaSessionService() {
mediaSession?.let { addSession(it) }
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Immediately start foreground to satisfy Android's timeout requirement.
// Media3's MediaNotificationManager will replace this with the real notification
// once the player is prepared and playing.
ensureNotificationChannel()
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(androidx.media3.session.R.drawable.media3_notification_small_icon)
.setContentTitle(getString(R.string.app_name))
.setSilent(true)
.build()
startForeground(NOTIFICATION_ID, notification)
return super.onStartCommand(intent, flags, startId)
}
private fun ensureNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(NotificationManager::class.java)
if (manager.getNotificationChannel(CHANNEL_ID) == null) {
val channel = NotificationChannel(
CHANNEL_ID,
"Playback",
NotificationManager.IMPORTANCE_LOW
)
channel.setShowBadge(false)
manager.createNotificationChannel(channel)
}
}
}
/**
* Return the MediaSession for this service.
* Called by the system when a media controller connects.
@ -116,5 +151,7 @@ class PlaybackMediaSessionService : MediaSessionService() {
companion object {
private const val TAG = "MediaSessionService"
const val ACTION_SHOW_NOW_PLAYING = "is.dsg.placeboplayer.SHOW_NOW_PLAYING"
private const val CHANNEL_ID = "default_channel_id"
private const val NOTIFICATION_ID = 1001
}
}