fix: live priority now interrupts long display durations (#196) (#298)

* fix: check live priority during display loops to interrupt long durations (#196)

_check_live_priority() was only called once per main loop iteration,
before entering the display duration loop. With dynamic duration enabled,
the loop could run for 60-120+ seconds without ever checking if a
favorite team's live game started — so the display stayed on leaderboard,
weather, etc. while the live game played.

Now both the high-FPS and normal FPS display loops check for live
priority every ~30 seconds (throttled to avoid overhead). When live
content is detected, the loop breaks immediately and switches to the
live game mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update rotation index when live priority interrupts display loop

The live priority break set current_display_mode but not
current_mode_index, so the post-loop rotation logic (which checks the
old active_mode) would overwrite the live mode on the next advance.

Now both loops also set current_mode_index to match the live mode,
mirroring the existing pattern at the top of the main loop (line 1385).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use timestamp throttle for live priority and skip post-loop rotation

Two issues fixed:

1. The modulo-based throttle (elapsed % 30.0 < display_interval) could
   miss the narrow 8ms window due to timing jitter. Replaced with an
   explicit timestamp check (_next_live_priority_check) that fires
   reliably every 30 seconds.

2. After breaking out of the display loop for live priority, the
   post-loop code (remaining-duration sleep and rotation advancement)
   would still run and overwrite the live mode. Now a continue skips
   directly to the next main loop iteration when current_display_mode
   was changed during the loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-03-27 20:40:06 -04:00
committed by GitHub
parent 35df06b8e1
commit 5ddf8b1aea

View File

@@ -79,7 +79,8 @@ class DisplayController:
logger.info("Display modes initialized in %.3f seconds", time.time() - init_time) logger.info("Display modes initialized in %.3f seconds", time.time() - init_time)
self.force_change = False self.force_change = False
self._next_live_priority_check = 0.0 # timestamp for throttled live priority checks
# All sports and content managers now handled via plugins # All sports and content managers now handled via plugins
logger.info("All sports and content managers now handled via plugin system") logger.info("All sports and content managers now handled via plugin system")
@@ -1790,11 +1791,29 @@ class DisplayController:
self._poll_on_demand_requests() self._poll_on_demand_requests()
self._check_on_demand_expiration() self._check_on_demand_expiration()
# Check for live priority every ~30s so live
# games can interrupt long display durations
elapsed = time.time() - start_time
now = time.time()
if not self.on_demand_active and now >= self._next_live_priority_check:
self._next_live_priority_check = now + 30.0
live_mode = self._check_live_priority()
if live_mode and live_mode != active_mode:
logger.info("Live priority detected during high-FPS loop: %s", live_mode)
self.current_display_mode = live_mode
self.force_change = True
try:
self.current_mode_index = self.available_modes.index(live_mode)
except ValueError:
pass
# continue the main while loop to skip
# post-loop rotation/sleep logic
break
if self.current_display_mode != active_mode: if self.current_display_mode != active_mode:
logger.debug("Mode changed during high-FPS loop, breaking early") logger.debug("Mode changed during high-FPS loop, breaking early")
break break
elapsed = time.time() - start_time
if elapsed >= target_duration: if elapsed >= target_duration:
logger.debug( logger.debug(
"Reached high-FPS target duration %.2fs for mode %s", "Reached high-FPS target duration %.2fs for mode %s",
@@ -1853,6 +1872,23 @@ class DisplayController:
self._poll_on_demand_requests() self._poll_on_demand_requests()
self._check_on_demand_expiration() self._check_on_demand_expiration()
# Check for live priority every ~30s so live
# games can interrupt long display durations
now = time.time()
if not self.on_demand_active and now >= self._next_live_priority_check:
self._next_live_priority_check = now + 30.0
live_mode = self._check_live_priority()
if live_mode and live_mode != active_mode:
logger.info("Live priority detected during display loop: %s", live_mode)
self.current_display_mode = live_mode
self.force_change = True
try:
self.current_mode_index = self.available_modes.index(live_mode)
except ValueError:
pass
break
if self.current_display_mode != active_mode: if self.current_display_mode != active_mode:
logger.info("Mode changed during display loop from %s to %s, breaking early", active_mode, self.current_display_mode) logger.info("Mode changed during display loop from %s to %s, breaking early", active_mode, self.current_display_mode)
break break
@@ -1866,6 +1902,13 @@ class DisplayController:
loop_completed = True loop_completed = True
break break
# If live priority preempted the display loop, skip
# all post-loop logic (remaining sleep, rotation) and
# restart the main loop so the live mode displays
# immediately.
if self.current_display_mode != active_mode:
continue
# Ensure we honour minimum duration when not dynamic and loop ended early # Ensure we honour minimum duration when not dynamic and loop ended early
if ( if (
not dynamic_enabled not dynamic_enabled