mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 22:03:13 -06:00
fix(android): stop zero-duration widget self-loop pegging the main thread (#198)
A solo fullscreen widget (or image) with duration_sec=0 hit an unclamped scheduleAdvance(item.durationSec * 1000L) in PlaylistController.playCurrentItem, scheduling a 0ms auto-advance. For a single-item playlist next() re-selects the same item, so it re-played every looper tick (~20x/sec) — black-screening the TV and locking the UI (couldn't even reach home). Triggered when a schedule collapses the playlist to a single always-on duration-0 widget. Use slotMs() (the max(1, duration||10) contract shared with the web/Tizen players) so a zero/negative duration floors to 10s. Also floor scheduleAdvance() itself to MIN_ADVANCE_MS (500ms) as a backstop so no future path can busy-loop the main thread. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
059ee1744e
commit
af286826dc
|
|
@ -36,7 +36,13 @@ class PlaylistController(
|
|||
// nothing has ever played (fresh device). Never used while content is on screen.
|
||||
private val onWaitingForContent: (() -> Unit)? = null
|
||||
) {
|
||||
private companion object { const val CONTENT_RECHECK_MS = 3000L }
|
||||
private companion object {
|
||||
const val CONTENT_RECHECK_MS = 3000L
|
||||
// Backstop against a busy-loop: an auto-advance is never scheduled faster than
|
||||
// this, so a bad/zero duration on any path can't peg the main thread (see #widget
|
||||
// zero-duration self-loop — a solo fullscreen widget with duration_sec=0).
|
||||
const val MIN_ADVANCE_MS = 500L
|
||||
}
|
||||
|
||||
private val items = mutableListOf<PlaylistItem>()
|
||||
private var currentIndex = -1
|
||||
|
|
@ -325,14 +331,19 @@ class PlaylistController(
|
|||
// for the completion callback. Wall followers never auto-advance — the
|
||||
// leader's wall:sync index drives every switch.
|
||||
if (!wallFollower && (item.mimeType.startsWith("image/") || item.isWidget)) {
|
||||
scheduleAdvance(item.durationSec * 1000L)
|
||||
// slotMs() floors a zero/negative duration to 10s (the max(1, duration||10)
|
||||
// contract shared with the web/Tizen players). A raw durationSec*1000 here let a
|
||||
// solo fullscreen widget with duration_sec=0 schedule a 0ms advance -> self-loop.
|
||||
scheduleAdvance(slotMs(item))
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleAdvance(delayMs: Long) {
|
||||
cancelAdvance()
|
||||
// Backstop: never busy-loop, even if a future caller passes a tiny/zero delay.
|
||||
val safeDelayMs = maxOf(delayMs, MIN_ADVANCE_MS)
|
||||
advanceRunnable = Runnable { next() }
|
||||
handler.postDelayed(advanceRunnable!!, delayMs)
|
||||
handler.postDelayed(advanceRunnable!!, safeDelayMs)
|
||||
}
|
||||
|
||||
private fun cancelAdvance() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue