fix(widget): resume progress updates during playback

The widget was only updating on play/pause state changes, so the timer
did not advance while playback continued. Added a Handler loop in
MediaService that updates the widget every second while playback is
running and clears it when playback stops, ensuring the progress bar
refreshes regularly.

Co-Authored-By: Firehawk <firehawk@opayq.net>
This commit is contained in:
mucahit-kaya 2025-09-18 12:26:43 +02:00
parent e81e1a5356
commit b79cfa4af0
3 changed files with 111 additions and 0 deletions

View file

@ -8,6 +8,8 @@ import android.content.Intent
import android.os.Binder
import android.os.Bundle
import android.os.IBinder
import android.os.Handler
import android.os.Looper
import androidx.media3.common.*
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.DefaultLoadControl
@ -40,6 +42,18 @@ class MediaService : MediaLibraryService() {
lateinit var equalizerManager: EqualizerManager
private var customLayout = ImmutableList.of<CommandButton>()
private val widgetUpdateHandler = Handler(Looper.getMainLooper())
private var widgetUpdateScheduled = false
private val widgetUpdateRunnable = object : Runnable {
override fun run() {
if (!player.isPlaying) {
widgetUpdateScheduled = false
return
}
updateWidget()
widgetUpdateHandler.postDelayed(this, WIDGET_UPDATE_INTERVAL_MS)
}
}
inner class LocalBinder : Binder() {
fun getEqualizerManager(): EqualizerManager {
@ -81,6 +95,7 @@ class MediaService : MediaLibraryService() {
override fun onDestroy() {
equalizerManager.release()
stopWidgetUpdates()
releasePlayer()
super.onDestroy()
}
@ -281,6 +296,11 @@ class MediaService : MediaLibraryService() {
} else {
MediaManager.scrobble(player.currentMediaItem, false)
}
if (isPlaying) {
scheduleWidgetUpdates()
} else {
stopWidgetUpdates()
}
updateWidget()
}
@ -327,6 +347,9 @@ class MediaService : MediaLibraryService() {
mediaLibrarySession.setCustomLayout(customLayout)
}
})
if (player.isPlaying) {
scheduleWidgetUpdates()
}
}
private fun setPlayer(player: Player) {
@ -407,9 +430,23 @@ class MediaService : MediaLibraryService() {
)
}
private fun scheduleWidgetUpdates() {
if (widgetUpdateScheduled) return
widgetUpdateHandler.postDelayed(widgetUpdateRunnable, WIDGET_UPDATE_INTERVAL_MS)
widgetUpdateScheduled = true
}
private fun stopWidgetUpdates() {
if (!widgetUpdateScheduled) return
widgetUpdateHandler.removeCallbacks(widgetUpdateRunnable)
widgetUpdateScheduled = false
}
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
private fun getMediaSourceFactory() =
DefaultMediaSourceFactory(this).setDataSourceFactory(DownloadUtil.getDataSourceFactory(this))
}
private const val WIDGET_UPDATE_INTERVAL_MS = 1000L

View file

@ -6,6 +6,8 @@ import android.app.TaskStackBuilder
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.os.Handler
import android.os.Looper
import androidx.media3.cast.CastPlayer
import androidx.media3.cast.SessionAvailabilityListener
import androidx.media3.common.AudioAttributes
@ -50,6 +52,18 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
companion object {
const val ACTION_BIND_EQUALIZER = "com.cappielloantonio.tempo.service.BIND_EQUALIZER"
}
private val widgetUpdateHandler = Handler(Looper.getMainLooper())
private var widgetUpdateScheduled = false
private val widgetUpdateRunnable = object : Runnable {
override fun run() {
if (!player.isPlaying) {
widgetUpdateScheduled = false
return
}
updateWidget()
widgetUpdateHandler.postDelayed(this, WIDGET_UPDATE_INTERVAL_MS)
}
}
override fun onCreate() {
super.onCreate()
@ -81,6 +95,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
override fun onDestroy() {
equalizerManager.release()
stopWidgetUpdates()
releasePlayer()
super.onDestroy()
}
@ -182,6 +197,11 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
} else {
MediaManager.scrobble(player.currentMediaItem, false)
}
if (isPlaying) {
scheduleWidgetUpdates()
} else {
stopWidgetUpdates()
}
updateWidget()
}
@ -231,6 +251,9 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
)
}
})
if (player.isPlaying) {
scheduleWidgetUpdates()
}
}
private fun updateWidget() {
@ -254,6 +277,18 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
)
}
private fun scheduleWidgetUpdates() {
if (widgetUpdateScheduled) return
widgetUpdateHandler.postDelayed(widgetUpdateRunnable, WIDGET_UPDATE_INTERVAL_MS)
widgetUpdateScheduled = true
}
private fun stopWidgetUpdates() {
if (!widgetUpdateScheduled) return
widgetUpdateHandler.removeCallbacks(widgetUpdateRunnable)
widgetUpdateScheduled = false
}
private fun initializeLoadControl(): DefaultLoadControl {
return DefaultLoadControl.Builder()
.setBufferDurationsMs(
@ -316,3 +351,5 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
player.prepare()
}
}
private const val WIDGET_UPDATE_INTERVAL_MS = 1000L

View file

@ -6,6 +6,8 @@ import android.app.TaskStackBuilder
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.os.Handler
import android.os.Looper
import androidx.media3.cast.CastPlayer
import androidx.media3.cast.SessionAvailabilityListener
import androidx.media3.common.AudioAttributes
@ -50,6 +52,18 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
companion object {
const val ACTION_BIND_EQUALIZER = "com.cappielloantonio.tempo.service.BIND_EQUALIZER"
}
private val widgetUpdateHandler = Handler(Looper.getMainLooper())
private var widgetUpdateScheduled = false
private val widgetUpdateRunnable = object : Runnable {
override fun run() {
if (!player.isPlaying) {
widgetUpdateScheduled = false
return
}
updateWidget()
widgetUpdateHandler.postDelayed(this, WIDGET_UPDATE_INTERVAL_MS)
}
}
override fun onCreate() {
super.onCreate()
@ -81,6 +95,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
override fun onDestroy() {
equalizerManager.release()
stopWidgetUpdates()
releasePlayer()
super.onDestroy()
}
@ -182,6 +197,11 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
} else {
MediaManager.scrobble(player.currentMediaItem, false)
}
if (isPlaying) {
scheduleWidgetUpdates()
} else {
stopWidgetUpdates()
}
updateWidget()
}
@ -231,6 +251,9 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
)
}
})
if (player.isPlaying) {
scheduleWidgetUpdates()
}
}
private fun updateWidget() {
@ -253,6 +276,18 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
)
}
private fun scheduleWidgetUpdates() {
if (widgetUpdateScheduled) return
widgetUpdateHandler.postDelayed(widgetUpdateRunnable, WIDGET_UPDATE_INTERVAL_MS)
widgetUpdateScheduled = true
}
private fun stopWidgetUpdates() {
if (!widgetUpdateScheduled) return
widgetUpdateHandler.removeCallbacks(widgetUpdateRunnable)
widgetUpdateScheduled = false
}
private fun initializeLoadControl(): DefaultLoadControl {
return DefaultLoadControl.Builder()
.setBufferDurationsMs(
@ -314,3 +349,5 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
player.prepare()
}
}
private const val WIDGET_UPDATE_INTERVAL_MS = 1000L