fix(cast): keep cast running after app close

This commit is contained in:
2026-08-14 23:06:48 +02:00
parent da73a72217
commit 0545e06a04
3 changed files with 36 additions and 12 deletions
@@ -4,6 +4,8 @@ import android.content.Context
import com.google.android.gms.cast.CastMediaControlIntent
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.media.CastMediaOptions
import com.google.android.gms.cast.framework.media.MediaIntentReceiver
import com.google.android.gms.cast.framework.media.NotificationOptions
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.SessionProvider
@@ -12,9 +14,27 @@ class OmniCastOptionsProvider : OptionsProvider {
val appId = OmniPlayer.receiverApplicationId
?: CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID
val notificationOptions = NotificationOptions.Builder()
.setActions(
listOf(
MediaIntentReceiver.ACTION_SKIP_PREV,
MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK,
MediaIntentReceiver.ACTION_SKIP_NEXT,
),
intArrayOf(0, 1, 2),
)
.setSmallIconDrawableResId(
androidx.media3.session.R.drawable.media3_notification_small_icon
)
.apply {
context.packageManager.getLaunchIntentForPackage(context.packageName)
?.component?.className
?.let { setTargetActivityClassName(it) }
}
.build()
val mediaOptions = CastMediaOptions.Builder()
.setMediaSessionEnabled(false)
.setNotificationOptions(null)
.setNotificationOptions(notificationOptions)
.build()
return CastOptions.Builder()
@@ -42,7 +42,6 @@ import androidx.media3.common.MediaItem.SubtitleConfiguration
import androidx.media3.common.TrackSelectionOverride
import androidx.media3.cast.CastPlayer
import androidx.media3.cast.RemoteCastPlayer
import androidx.media3.session.DefaultMediaNotificationProvider
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService
import androidx.mediarouter.app.MediaRouteChooserDialog
@@ -105,6 +104,7 @@ class OmniPlayer(
private val castStateListener = CastStateListener {
eventMap.emitCastStatus(computeCastStatus())
listenToReceiver()
syncNotificationService()
}
// receivers have no queue to skip in (a queue would make them play things on
@@ -166,7 +166,8 @@ class OmniPlayer(
private var serviceRunning = false
private fun syncNotificationService() {
val shouldShow = showNotification == true && source != null
val shouldShow = showNotification == true && source != null &&
!runOnMainThreadSync { isCasting }
when {
shouldShow && !serviceRunning -> {
val otherIsPlaying = notificationPlayer?.let { other ->
@@ -232,7 +233,7 @@ class OmniPlayer(
runOnMainThread {
eventMap.dispose()
castContext?.removeCastStateListener(castStateListener)
player.release()
if (!isCasting) player.release()
}
}
@@ -681,10 +682,6 @@ class OmniPlayerService : MediaSessionService() {
}
.build()
setMediaNotificationProvider(DefaultMediaNotificationProvider.Builder(this).build().apply {
setSmallIcon(applicationInfo.icon.takeIf { it != 0 }
?: android.R.drawable.ic_media_play)
})
addSession(mediaSession)
setShowNotificationForIdlePlayer(SHOW_NOTIFICATION_FOR_IDLE_PLAYER_ALWAYS)
@@ -725,8 +722,7 @@ class OmniPlayerService : MediaSessionService() {
)
return NotificationCompat.Builder(this, "omni_playback")
.setSmallIcon(applicationInfo.icon.takeIf { it != 0 }
?: android.R.drawable.ic_media_play)
.setSmallIcon(androidx.media3.session.R.drawable.media3_notification_small_icon)
.setContentTitle("Omni Player")
.setContentText("Preparing playback...")
.setContentIntent(pendingIntent)
@@ -737,7 +733,14 @@ class OmniPlayerService : MediaSessionService() {
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession
override fun onTaskRemoved(rootIntent: Intent?) {
pauseAllPlayersAndStopSelf()
val casting = try {
CastContext.getSharedInstance(this)
.sessionManager.currentCastSession?.isConnected == true
} catch (_: Throwable) {
false
}
// keep service open if casting
if (!casting) pauseAllPlayersAndStopSelf()
}
override fun onDestroy() {
+1
View File
@@ -66,6 +66,7 @@ module.exports = {
port: 3000,
hot: true,
historyApiFallback: true,
allowedHosts: "all",
// jassub's wasm is multithreaded (pthreads), which needs SharedArrayBuffer
// and therefore a cross-origin-isolated page. `credentialless` keeps
// cross-origin video/subtitle/font requests working without CORP headers.