diff --git a/android/app/src/main/java/com/audiobookshelf/app/managers/SleepTimerManager.kt b/android/app/src/main/java/com/audiobookshelf/app/managers/SleepTimerManager.kt index eb2e51ac..ca359edc 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/managers/SleepTimerManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/managers/SleepTimerManager.kt @@ -527,4 +527,18 @@ constructor(private val playerNotificationService: PlayerNotificationService) { checkAutoSleepTimer() } + + /** + * Sends the current sleep timer state to the client. + * Called when app resumes from background to sync UI state. + */ + fun sendCurrentSleepTimerState() { + if (sleepTimerRunning) { + val timeRemaining = getSleepTimerTimeRemainingSeconds(getPlaybackSpeed()) + playerNotificationService.clientEventEmitter?.onSleepTimerSet(timeRemaining, isAutoSleepTimer) + } else { + // No timer running - send 0 to clear any stale UI state + playerNotificationService.clientEventEmitter?.onSleepTimerSet(0, false) + } + } } diff --git a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt index 79a460d1..41d7cbf7 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt @@ -33,6 +33,9 @@ class AbsAudioPlayer : Plugin() { private var isCastAvailable:Boolean = false + // Track foreground state to avoid flooding WebView with events while backgrounded + private var isInForeground: Boolean = true + override fun load() { mainActivity = (activity as MainActivity) apiHandler = ApiHandler(mainActivity) @@ -60,6 +63,8 @@ class AbsAudioPlayer : Plugin() { } override fun onMetadata(metadata: PlaybackMetadata) { + // Skip frequent metadata updates when app is backgrounded to prevent event queue buildup + if (!isInForeground) return notifyListeners("onMetadata", JSObject(jacksonMapper.writeValueAsString(metadata))) } @@ -68,6 +73,8 @@ class AbsAudioPlayer : Plugin() { } override fun onSleepTimerSet(sleepTimeRemaining: Int, isAutoSleepTimer:Boolean) { + // Skip sleep timer updates when app is backgrounded to prevent event queue buildup + if (!isInForeground) return val ret = JSObject() ret.put("value", sleepTimeRemaining) ret.put("isAuto", isAutoSleepTimer) @@ -75,6 +82,8 @@ class AbsAudioPlayer : Plugin() { } override fun onLocalMediaProgressUpdate(localMediaProgress: LocalMediaProgress) { + // Skip progress updates when app is backgrounded to prevent event queue buildup + if (!isInForeground) return notifyListeners("onLocalMediaProgressUpdate", JSObject(jacksonMapper.writeValueAsString(localMediaProgress))) } @@ -118,6 +127,27 @@ class AbsAudioPlayer : Plugin() { notifyListeners(evtName, ret) } + override fun handleOnPause() { + super.handleOnPause() + isInForeground = false + } + + override fun handleOnResume() { + super.handleOnResume() + isInForeground = true + + // Send current state to UI after resume to sync up (with small delay to let WebView fully resume) + if (::playerNotificationService.isInitialized && playerNotificationService.currentPlaybackSession != null) { + Handler(Looper.getMainLooper()).postDelayed({ + playerNotificationService.sendClientMetadata(PlayerState.READY) + playerNotificationService.sleepTimerManager.sendCurrentSleepTimerState() + playerNotificationService.mediaProgressSyncer.currentLocalMediaProgress?.let { + playerNotificationService.clientEventEmitter?.onLocalMediaProgressUpdate(it) + } + }, 100) + } + } + private fun initCastManager() { val googleApi = GoogleApiAvailability.getInstance() val statusCode = googleApi.isGooglePlayServicesAvailable(mainActivity)