mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
Feature/music skip delay (#115)
* feat: Add configurable delay for music module skip when nothing playing - Add 'skip_when_nothing_playing' and 'skip_delay_seconds' config options - Implement timer-based skip logic that waits 2 seconds before skipping - Reset timer when music resumes or display is deactivated - Improve user experience by preventing jarring immediate skips - Maintains backward compatibility with existing configurations * feat: Add live priority support to music manager - Add 'live_priority' and 'live_game_duration' config options for music - Integrate music manager into live priority rotation system - Add 'music_live' mode that participates in live priority takeover - Music manager joins live priority rotation when actively playing - Supports future plugin ecosystem with consistent live priority interface - Maintains backward compatibility with existing music configurations * feat: Add new music settings to web interface - Add 'Skip When Nothing Playing' checkbox and delay configuration - Add 'Enable Live Priority' checkbox and duration configuration - Update music form JavaScript to save all new settings - Users can now configure music skip behavior and live priority through web UI * fix: Improve music live priority integration - Enhanced music live content detection with detailed logging - Modified live priority logic to properly handle music mode - Added music to live priority sports collection when actively playing - Updated live priority rotation to handle music mode correctly - Improved live priority takeover logic for music manager - Music now properly participates in live priority rotation when playing * perf: Reduce logging frequency for music live priority - Add timestamp-based throttling to music live priority logging - Info messages now only appear every 30 seconds instead of every few milliseconds - Debug messages are also throttled to reduce log spam - Maintains visibility of important events while reducing noise * Fix unreachable code bug in music live priority handling - Moved music live priority logic outside the sports iteration loop where it was unreachable - The 'if sport == music' block was never executed since 'music' wasn't in the sports list - Consolidated music handling in its own separate section for cleaner code structure - Added music configuration options to config template - Maintained exact same functionality while eliminating dead code * Fix music_live_game_duration setting not being used - Modified get_current_duration() method to check for music live priority mode - When music is in live priority mode, use configured live_game_duration setting - Added proper config reading for music_live_game_duration during initialization - Ensures music displays for the configured duration when in live priority mode * Fix stale timestamp bug in live priority debug logging - Fixed issue where debug log at line 1240 was using a stale current_time variable - The current_time variable was conditionally reassigned in music live priority check - This caused inconsistent log throttling behavior - Now uses fresh current_time_for_debug variable for accurate throttling
This commit is contained in:
@@ -577,7 +577,11 @@
|
|||||||
"enabled": false,
|
"enabled": false,
|
||||||
"preferred_source": "ytm",
|
"preferred_source": "ytm",
|
||||||
"YTM_COMPANION_URL": "http://192.168.86.12:9863",
|
"YTM_COMPANION_URL": "http://192.168.86.12:9863",
|
||||||
"POLLING_INTERVAL_SECONDS": 1
|
"POLLING_INTERVAL_SECONDS": 1,
|
||||||
|
"skip_when_nothing_playing": true,
|
||||||
|
"skip_delay_seconds": 2,
|
||||||
|
"live_priority": true,
|
||||||
|
"live_game_duration": 30
|
||||||
},
|
},
|
||||||
"of_the_day": {
|
"of_the_day": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ from src.youtube_display import YouTubeDisplay
|
|||||||
from src.calendar_manager import CalendarManager
|
from src.calendar_manager import CalendarManager
|
||||||
from src.text_display import TextDisplay
|
from src.text_display import TextDisplay
|
||||||
from src.static_image_manager import StaticImageManager
|
from src.static_image_manager import StaticImageManager
|
||||||
from src.music_manager import MusicManager
|
from src.music_manager import MusicManager, SkipModuleException
|
||||||
from src.of_the_day_manager import OfTheDayManager
|
from src.of_the_day_manager import OfTheDayManager
|
||||||
from src.news_manager import NewsManager
|
from src.news_manager import NewsManager
|
||||||
|
|
||||||
@@ -103,8 +103,11 @@ class DisplayController:
|
|||||||
self.music_manager = None
|
self.music_manager = None
|
||||||
else:
|
else:
|
||||||
logger.info("Music module is disabled in main configuration (config.json).")
|
logger.info("Music module is disabled in main configuration (config.json).")
|
||||||
|
# Read music live game duration setting regardless of whether music is enabled
|
||||||
|
self.music_live_game_duration = music_config_main.get('live_game_duration', 30)
|
||||||
else:
|
else:
|
||||||
logger.error("Config not loaded before MusicManager initialization attempt.")
|
logger.error("Config not loaded before MusicManager initialization attempt.")
|
||||||
|
self.music_live_game_duration = 30 # Default fallback
|
||||||
logger.info("MusicManager initialized in %.3f seconds", time.time() - music_init_time)
|
logger.info("MusicManager initialized in %.3f seconds", time.time() - music_init_time)
|
||||||
|
|
||||||
# Initialize NHL managers if enabled
|
# Initialize NHL managers if enabled
|
||||||
@@ -324,6 +327,12 @@ class DisplayController:
|
|||||||
self.ncaaw_basketball_live_priority = self.config.get('ncaaw_basketball_scoreboard', {}).get('live_priority', True)
|
self.ncaaw_basketball_live_priority = self.config.get('ncaaw_basketball_scoreboard', {}).get('live_priority', True)
|
||||||
self.ncaam_hockey_live_priority = self.config.get('ncaam_hockey_scoreboard', {}).get('live_priority', True)
|
self.ncaam_hockey_live_priority = self.config.get('ncaam_hockey_scoreboard', {}).get('live_priority', True)
|
||||||
self.ncaaw_hockey_live_priority = self.config.get('ncaaw_hockey_scoreboard', {}).get('live_priority', True)
|
self.ncaaw_hockey_live_priority = self.config.get('ncaaw_hockey_scoreboard', {}).get('live_priority', True)
|
||||||
|
self.music_live_priority = self.config.get('music', {}).get('live_priority', True)
|
||||||
|
|
||||||
|
# Live priority logging throttling
|
||||||
|
self._last_music_live_priority_log = 0
|
||||||
|
self._last_music_rotation_log = 0
|
||||||
|
self._music_live_priority_log_interval = 30.0 # Log every 30 seconds
|
||||||
|
|
||||||
# List of available display modes (adjust order as desired)
|
# List of available display modes (adjust order as desired)
|
||||||
self.available_modes = []
|
self.available_modes = []
|
||||||
@@ -493,6 +502,7 @@ class DisplayController:
|
|||||||
'ncaa_fb_recent': 15,
|
'ncaa_fb_recent': 15,
|
||||||
'ncaa_fb_upcoming': 15,
|
'ncaa_fb_upcoming': 15,
|
||||||
'music': 20, # Default duration for music, will be overridden by config if present
|
'music': 20, # Default duration for music, will be overridden by config if present
|
||||||
|
'music_live': 30, # Default duration for music live mode
|
||||||
'ncaa_baseball_live': 30, # Added NCAA Baseball durations
|
'ncaa_baseball_live': 30, # Added NCAA Baseball durations
|
||||||
'ncaa_baseball_recent': 15,
|
'ncaa_baseball_recent': 15,
|
||||||
'ncaa_baseball_upcoming': 15,
|
'ncaa_baseball_upcoming': 15,
|
||||||
@@ -644,6 +654,16 @@ class DisplayController:
|
|||||||
# Fall back to configured duration
|
# Fall back to configured duration
|
||||||
return self.display_durations.get(mode_key, 600)
|
return self.display_durations.get(mode_key, 600)
|
||||||
|
|
||||||
|
# Handle music live priority mode
|
||||||
|
elif mode_key == 'music' and self.music_live_priority and self.music_manager and self._music_has_live_content():
|
||||||
|
# Use the configured live game duration for music when in live priority mode
|
||||||
|
music_live_duration = getattr(self, 'music_live_game_duration', 30)
|
||||||
|
# Only log if duration has changed or we haven't logged this duration yet
|
||||||
|
if not hasattr(self, '_last_logged_music_live_duration') or self._last_logged_music_live_duration != music_live_duration:
|
||||||
|
logger.info(f"Using music live priority duration: {music_live_duration} seconds")
|
||||||
|
self._last_logged_music_live_duration = music_live_duration
|
||||||
|
return music_live_duration
|
||||||
|
|
||||||
# Simplify weather key handling
|
# Simplify weather key handling
|
||||||
elif mode_key.startswith('weather_'):
|
elif mode_key.startswith('weather_'):
|
||||||
return self.display_durations.get(mode_key, 15)
|
return self.display_durations.get(mode_key, 15)
|
||||||
@@ -653,7 +673,7 @@ class DisplayController:
|
|||||||
# elif duration_key == 'daily': duration_key = 'weather_daily'
|
# elif duration_key == 'daily': duration_key = 'weather_daily'
|
||||||
# else: duration_key = 'weather_current' # Default to current
|
# else: duration_key = 'weather_current' # Default to current
|
||||||
# return self.display_durations.get(duration_key, 15)
|
# return self.display_durations.get(duration_key, 15)
|
||||||
|
|
||||||
return self.display_durations.get(mode_key, 15)
|
return self.display_durations.get(mode_key, 15)
|
||||||
|
|
||||||
def _update_modules(self):
|
def _update_modules(self):
|
||||||
@@ -1032,14 +1052,36 @@ class DisplayController:
|
|||||||
if mode_name in self.available_modes:
|
if mode_name in self.available_modes:
|
||||||
self.available_modes.remove(mode_name)
|
self.available_modes.remove(mode_name)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not live_priority:
|
if not live_priority:
|
||||||
# Only add to rotation if manager exists and has live games
|
# Only add to rotation if manager exists and has live games/content
|
||||||
if manager and getattr(manager, 'live_games', None):
|
if manager:
|
||||||
live_games = getattr(manager, 'live_games', None)
|
# Special handling for music manager - check if music is actively playing
|
||||||
if mode_name not in self.available_modes:
|
if mode_name == 'music_live':
|
||||||
self.available_modes.append(mode_name)
|
has_live_content = self._music_has_live_content()
|
||||||
logger.debug(f"Added {mode_name} to rotation (found {len(live_games)} live games)")
|
logger.debug(f"Music live mode update: mode_name={mode_name}, has_live_content={has_live_content}, in_available_modes={mode_name in self.available_modes}")
|
||||||
|
if has_live_content and mode_name not in self.available_modes:
|
||||||
|
self.available_modes.append(mode_name)
|
||||||
|
# Throttle logging - only log every 30 seconds
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - self._last_music_rotation_log >= self._music_live_priority_log_interval:
|
||||||
|
logger.info(f"Added {mode_name} to rotation (music is playing)")
|
||||||
|
self._last_music_rotation_log = current_time
|
||||||
|
elif not has_live_content and mode_name in self.available_modes:
|
||||||
|
self.available_modes.remove(mode_name)
|
||||||
|
# Throttle logging - only log every 30 seconds
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - self._last_music_rotation_log >= self._music_live_priority_log_interval:
|
||||||
|
logger.info(f"Removed {mode_name} from rotation (music stopped)")
|
||||||
|
self._last_music_rotation_log = current_time
|
||||||
|
elif getattr(manager, 'live_games', None):
|
||||||
|
live_games = getattr(manager, 'live_games', None)
|
||||||
|
if mode_name not in self.available_modes:
|
||||||
|
self.available_modes.append(mode_name)
|
||||||
|
logger.debug(f"Added {mode_name} to rotation (found {len(live_games)} live games)")
|
||||||
|
else:
|
||||||
|
if mode_name in self.available_modes:
|
||||||
|
self.available_modes.remove(mode_name)
|
||||||
else:
|
else:
|
||||||
if mode_name in self.available_modes:
|
if mode_name in self.available_modes:
|
||||||
self.available_modes.remove(mode_name)
|
self.available_modes.remove(mode_name)
|
||||||
@@ -1077,6 +1119,32 @@ class DisplayController:
|
|||||||
update_mode('ncaaw_basketball_live', getattr(self, 'ncaaw_basketball_live', None), self.ncaaw_basketball_live_priority, ncaaw_basketball_enabled)
|
update_mode('ncaaw_basketball_live', getattr(self, 'ncaaw_basketball_live', None), self.ncaaw_basketball_live_priority, ncaaw_basketball_enabled)
|
||||||
update_mode('ncaam_hockey_live', getattr(self, 'ncaam_hockey_live', None), self.ncaam_hockey_live_priority, ncaam_hockey_enabled)
|
update_mode('ncaam_hockey_live', getattr(self, 'ncaam_hockey_live', None), self.ncaam_hockey_live_priority, ncaam_hockey_enabled)
|
||||||
update_mode('ncaaw_hockey_live', getattr(self, 'ncaaw_hockey_live', None), self.ncaaw_hockey_live_priority, ncaaw_hockey_enabled)
|
update_mode('ncaaw_hockey_live', getattr(self, 'ncaaw_hockey_live', None), self.ncaaw_hockey_live_priority, ncaaw_hockey_enabled)
|
||||||
|
# Add music to live priority rotation if enabled and music is playing
|
||||||
|
music_enabled = self.config.get('music', {}).get('enabled', False)
|
||||||
|
# Throttle debug logging - only log every 30 seconds
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - self._last_music_rotation_log >= self._music_live_priority_log_interval:
|
||||||
|
logger.debug(f"Music enabled: {music_enabled}, live_priority: {self.music_live_priority}, manager: {self.music_manager is not None}")
|
||||||
|
update_mode('music_live', self.music_manager, self.music_live_priority, music_enabled)
|
||||||
|
|
||||||
|
def _music_has_live_content(self) -> bool:
|
||||||
|
"""Check if music manager has live content (actively playing music)."""
|
||||||
|
if not self.music_manager:
|
||||||
|
logger.debug("Music manager not initialized")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get current track info from music manager
|
||||||
|
current_track_info = self.music_manager.get_current_display_info()
|
||||||
|
if not current_track_info:
|
||||||
|
logger.debug("No current track info from music manager")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if music is currently playing
|
||||||
|
is_playing = current_track_info.get('is_playing', False)
|
||||||
|
title = current_track_info.get('title', '')
|
||||||
|
has_live_content = is_playing and title != 'Nothing Playing'
|
||||||
|
|
||||||
|
return has_live_content
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Run the display controller, switching between displays."""
|
"""Run the display controller, switching between displays."""
|
||||||
@@ -1113,6 +1181,15 @@ class DisplayController:
|
|||||||
# Check for live games and live_priority
|
# Check for live games and live_priority
|
||||||
has_live_games, live_sport_type = self._check_live_games()
|
has_live_games, live_sport_type = self._check_live_games()
|
||||||
is_currently_live = self.current_display_mode.endswith('_live')
|
is_currently_live = self.current_display_mode.endswith('_live')
|
||||||
|
|
||||||
|
# Special case: if we're in music mode and music is actively playing, treat it as live
|
||||||
|
if self.current_display_mode == 'music' and self._music_has_live_content():
|
||||||
|
is_currently_live = True
|
||||||
|
# Throttle logging - only log every 30 seconds
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - self._last_music_rotation_log >= self._music_live_priority_log_interval:
|
||||||
|
logger.debug("Music mode with active content treated as live priority")
|
||||||
|
self._last_music_rotation_log = current_time
|
||||||
|
|
||||||
# Collect all sports with live_priority=True that have live games
|
# Collect all sports with live_priority=True that have live games
|
||||||
live_priority_sports = []
|
live_priority_sports = []
|
||||||
@@ -1132,39 +1209,56 @@ class DisplayController:
|
|||||||
('ncaaw_hockey', 'ncaaw_hockey_live', self.ncaaw_hockey_live_priority)
|
('ncaaw_hockey', 'ncaaw_hockey_live', self.ncaaw_hockey_live_priority)
|
||||||
]:
|
]:
|
||||||
manager = getattr(self, attr, None)
|
manager = getattr(self, attr, None)
|
||||||
# Only consider sports that are enabled (manager is not None) and have actual live games
|
|
||||||
|
# Standard sports logic
|
||||||
live_games = getattr(manager, 'live_games', None) if manager is not None else None
|
live_games = getattr(manager, 'live_games', None) if manager is not None else None
|
||||||
# Check that manager exists, has live_priority enabled, has live_games attribute, and has at least one live game
|
# Check that manager exists, has live_priority enabled, has live_games attribute, and has at least one live game
|
||||||
if (manager is not None and
|
if (manager is not None and
|
||||||
priority and
|
priority and
|
||||||
live_games is not None and
|
live_games is not None and
|
||||||
len(live_games) > 0):
|
len(live_games) > 0):
|
||||||
live_priority_sports.append(sport)
|
live_priority_sports.append(sport)
|
||||||
logger.debug(f"Live priority sport found: {sport} with {len(live_games)} live games")
|
logger.debug(f"Live priority sport found: {sport} with {len(live_games)} live games")
|
||||||
elif manager is not None and priority and live_games is not None:
|
elif manager is not None and priority and live_games is not None:
|
||||||
logger.debug(f"{sport} has live_priority=True but {len(live_games)} live games (not taking over)")
|
logger.debug(f"{sport} has live_priority=True but {len(live_games)} live games (not taking over)")
|
||||||
|
|
||||||
|
# Special handling for music - check if music is actively playing
|
||||||
|
if (self.music_manager is not None and
|
||||||
|
self.music_live_priority and
|
||||||
|
self._music_has_live_content()):
|
||||||
|
live_priority_sports.append('music')
|
||||||
|
# Throttle logging - only log every 30 seconds
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - self._last_music_live_priority_log >= self._music_live_priority_log_interval:
|
||||||
|
logger.info("Live priority music found: music is playing - added to live priority rotation")
|
||||||
|
self._last_music_live_priority_log = current_time
|
||||||
|
|
||||||
# Determine if we have any live priority sports
|
# Determine if we have any live priority sports
|
||||||
live_priority_takeover = len(live_priority_sports) > 0
|
live_priority_takeover = len(live_priority_sports) > 0
|
||||||
|
# Throttle debug logging - only log every 30 seconds
|
||||||
|
current_time_for_debug = time.time()
|
||||||
|
if current_time_for_debug - self._last_music_rotation_log >= self._music_live_priority_log_interval:
|
||||||
|
logger.debug(f"Live priority sports: {live_priority_sports}, takeover: {live_priority_takeover}")
|
||||||
|
|
||||||
manager_to_display = None
|
manager_to_display = None
|
||||||
# --- State Machine for Display Logic ---
|
# --- State Machine for Display Logic ---
|
||||||
if is_currently_live:
|
if is_currently_live:
|
||||||
if live_priority_takeover:
|
if live_priority_takeover:
|
||||||
# Check if we need to rotate to the next live priority sport
|
# Check if we need to rotate to the next live priority sport
|
||||||
current_sport_type = self.current_display_mode.replace('_live', '')
|
current_sport_type = self.current_display_mode.replace('_live', '') if self.current_display_mode.endswith('_live') else self.current_display_mode
|
||||||
|
|
||||||
# If current sport is not in live priority sports, switch to first one
|
# If current sport is not in live priority sports, switch to first one
|
||||||
if current_sport_type not in live_priority_sports:
|
if current_sport_type not in live_priority_sports:
|
||||||
next_sport = live_priority_sports[0]
|
next_sport = live_priority_sports[0]
|
||||||
new_mode = f"{next_sport}_live"
|
# Special case for music - use "music" mode instead of "music_live"
|
||||||
|
new_mode = "music" if next_sport == "music" else f"{next_sport}_live"
|
||||||
logger.info(f"Current live sport {current_sport_type} no longer has priority, switching to {new_mode}")
|
logger.info(f"Current live sport {current_sport_type} no longer has priority, switching to {new_mode}")
|
||||||
self.current_display_mode = new_mode
|
self.current_display_mode = new_mode
|
||||||
if hasattr(self, '_last_logged_duration'):
|
if hasattr(self, '_last_logged_duration'):
|
||||||
delattr(self, '_last_logged_duration')
|
delattr(self, '_last_logged_duration')
|
||||||
self.force_clear = True
|
self.force_clear = True
|
||||||
self.last_switch = current_time
|
self.last_switch = current_time
|
||||||
manager_to_display = getattr(self, f"{next_sport}_live", None)
|
manager_to_display = self.music_manager if next_sport == "music" else getattr(self, f"{next_sport}_live", None)
|
||||||
else:
|
else:
|
||||||
# Check if duration has elapsed for current sport
|
# Check if duration has elapsed for current sport
|
||||||
current_duration = self.get_current_duration()
|
current_duration = self.get_current_duration()
|
||||||
@@ -1173,18 +1267,20 @@ class DisplayController:
|
|||||||
current_index = live_priority_sports.index(current_sport_type)
|
current_index = live_priority_sports.index(current_sport_type)
|
||||||
next_index = (current_index + 1) % len(live_priority_sports)
|
next_index = (current_index + 1) % len(live_priority_sports)
|
||||||
next_sport = live_priority_sports[next_index]
|
next_sport = live_priority_sports[next_index]
|
||||||
new_mode = f"{next_sport}_live"
|
# Special case for music - use "music" mode instead of "music_live"
|
||||||
|
new_mode = "music" if next_sport == "music" else f"{next_sport}_live"
|
||||||
|
|
||||||
logger.info(f"Rotating live priority sports: {current_sport_type} -> {next_sport} (duration: {current_duration}s)")
|
logger.info(f"Rotating live priority sports: {current_sport_type} -> {next_sport} (duration: {current_duration}s)")
|
||||||
self.current_display_mode = new_mode
|
self.current_display_mode = new_mode
|
||||||
if hasattr(self, '_last_logged_duration'):
|
if hasattr(self, '_last_logged_duration'):
|
||||||
delattr(self, '_last_logged_duration')
|
delattr(self, '_last_logged_duration')
|
||||||
self.force_clear = True
|
self.force_clear = True
|
||||||
self.last_switch = current_time
|
self.last_switch = current_time
|
||||||
manager_to_display = getattr(self, f"{next_sport}_live", None)
|
manager_to_display = self.music_manager if next_sport == "music" else getattr(self, f"{next_sport}_live", None)
|
||||||
else:
|
else:
|
||||||
self.force_clear = False
|
self.force_clear = False
|
||||||
manager_to_display = getattr(self, f"{current_sport_type}_live", None)
|
# Special case for music - use "music" mode instead of "music_live"
|
||||||
|
manager_to_display = self.music_manager if current_sport_type == "music" else getattr(self, f"{current_sport_type}_live", None)
|
||||||
else:
|
else:
|
||||||
# If no sport has live_priority takeover, treat as regular rotation
|
# If no sport has live_priority takeover, treat as regular rotation
|
||||||
is_currently_live = False
|
is_currently_live = False
|
||||||
@@ -1193,14 +1289,19 @@ class DisplayController:
|
|||||||
if live_priority_takeover:
|
if live_priority_takeover:
|
||||||
# Switch to first live priority sport
|
# Switch to first live priority sport
|
||||||
next_sport = live_priority_sports[0]
|
next_sport = live_priority_sports[0]
|
||||||
new_mode = f"{next_sport}_live"
|
# Special case for music - use "music" mode instead of "music_live"
|
||||||
|
new_mode = "music" if next_sport == "music" else f"{next_sport}_live"
|
||||||
# Double-check that the manager actually has live games before switching
|
|
||||||
target_manager = getattr(self, f"{next_sport}_live", None)
|
# Double-check that the manager actually has live content before switching
|
||||||
if target_manager and hasattr(target_manager, 'live_games') and len(target_manager.live_games) > 0:
|
if next_sport == "music":
|
||||||
|
target_manager = self.music_manager if self._music_has_live_content() else None
|
||||||
|
else:
|
||||||
|
target_manager = getattr(self, f"{next_sport}_live", None)
|
||||||
|
|
||||||
|
if target_manager and ((next_sport == "music") or (hasattr(target_manager, 'live_games') and len(target_manager.live_games) > 0)):
|
||||||
logger.info(f"Live priority takeover: Switching to {new_mode} from {self.current_display_mode}")
|
logger.info(f"Live priority takeover: Switching to {new_mode} from {self.current_display_mode}")
|
||||||
logger.debug(f"[DisplayController] Live priority takeover details: sport={next_sport}, manager={target_manager}, live_games={target_manager.live_games}")
|
logger.debug(f"[DisplayController] Live priority takeover details: sport={next_sport}, manager={target_manager}")
|
||||||
if previous_mode_before_switch == 'music' and self.music_manager:
|
if previous_mode_before_switch == 'music' and self.music_manager and next_sport != "music":
|
||||||
self.music_manager.deactivate_music_display()
|
self.music_manager.deactivate_music_display()
|
||||||
self.current_display_mode = new_mode
|
self.current_display_mode = new_mode
|
||||||
# Reset logged duration when mode changes
|
# Reset logged duration when mode changes
|
||||||
@@ -1210,7 +1311,7 @@ class DisplayController:
|
|||||||
self.last_switch = current_time
|
self.last_switch = current_time
|
||||||
manager_to_display = target_manager
|
manager_to_display = target_manager
|
||||||
else:
|
else:
|
||||||
logger.warning(f"[DisplayController] Live priority takeover attempted for {new_mode} but manager has no live games, skipping takeover")
|
logger.warning(f"[DisplayController] Live priority takeover attempted for {new_mode} but manager has no live content, skipping takeover")
|
||||||
live_priority_takeover = False
|
live_priority_takeover = False
|
||||||
else:
|
else:
|
||||||
# No live_priority takeover, regular rotation
|
# No live_priority takeover, regular rotation
|
||||||
@@ -1329,6 +1430,8 @@ class DisplayController:
|
|||||||
manager_to_display = self.soccer_upcoming
|
manager_to_display = self.soccer_upcoming
|
||||||
elif self.current_display_mode == 'music' and self.music_manager:
|
elif self.current_display_mode == 'music' and self.music_manager:
|
||||||
manager_to_display = self.music_manager
|
manager_to_display = self.music_manager
|
||||||
|
elif self.current_display_mode == 'music_live' and self.music_manager:
|
||||||
|
manager_to_display = self.music_manager
|
||||||
elif self.current_display_mode == 'nhl_live' and self.nhl_live:
|
elif self.current_display_mode == 'nhl_live' and self.nhl_live:
|
||||||
manager_to_display = self.nhl_live
|
manager_to_display = self.nhl_live
|
||||||
elif self.current_display_mode == 'nba_live' and self.nba_live:
|
elif self.current_display_mode == 'nba_live' and self.nba_live:
|
||||||
@@ -1377,12 +1480,22 @@ class DisplayController:
|
|||||||
logger.info(f"manager_to_display is {current_manager_type}")
|
logger.info(f"manager_to_display is {current_manager_type}")
|
||||||
self._last_logged_manager_type = current_manager_type
|
self._last_logged_manager_type = current_manager_type
|
||||||
|
|
||||||
if self.current_display_mode == 'music' and self.music_manager:
|
if self.current_display_mode in ['music', 'music_live'] and self.music_manager:
|
||||||
# Call MusicManager's display method
|
# Call MusicManager's display method
|
||||||
self.music_manager.display(force_clear=self.force_clear)
|
try:
|
||||||
# Reset force_clear if it was true for this mode
|
self.music_manager.display(force_clear=self.force_clear)
|
||||||
if self.force_clear:
|
# Reset force_clear if it was true for this mode
|
||||||
self.force_clear = False
|
if self.force_clear:
|
||||||
|
self.force_clear = False
|
||||||
|
except SkipModuleException as e:
|
||||||
|
# Music module requested skip (nothing playing and configured to skip)
|
||||||
|
logger.info(f"Music module requested skip: {e}")
|
||||||
|
# Force change to next module
|
||||||
|
self.force_change = True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error in music display: {e}", exc_info=True)
|
||||||
|
# Reset force_clear on error
|
||||||
|
self.force_clear = True
|
||||||
elif manager_to_display:
|
elif manager_to_display:
|
||||||
logger.debug(f"Attempting to display mode: {self.current_display_mode} using manager {type(manager_to_display).__name__} with force_clear={self.force_clear}")
|
logger.debug(f"Attempting to display mode: {self.current_display_mode} using manager {type(manager_to_display).__name__} with force_clear={self.force_clear}")
|
||||||
# Call the appropriate display method based on mode/manager type
|
# Call the appropriate display method based on mode/manager type
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ class MusicSource(Enum):
|
|||||||
SPOTIFY = auto()
|
SPOTIFY = auto()
|
||||||
YTM = auto()
|
YTM = auto()
|
||||||
|
|
||||||
|
class SkipModuleException(StopIteration):
|
||||||
|
"""Exception raised when a module should be skipped to the next one."""
|
||||||
|
pass
|
||||||
|
|
||||||
class MusicManager:
|
class MusicManager:
|
||||||
def __init__(self, display_manager, config, update_callback=None):
|
def __init__(self, display_manager, config, update_callback=None):
|
||||||
self.display_manager = display_manager
|
self.display_manager = display_manager
|
||||||
@@ -65,6 +69,7 @@ class MusicManager:
|
|||||||
self.album_scroll_tick = 0
|
self.album_scroll_tick = 0
|
||||||
self.is_music_display_active = False # New state variable
|
self.is_music_display_active = False # New state variable
|
||||||
self.is_currently_showing_nothing_playing = False # To prevent flashing
|
self.is_currently_showing_nothing_playing = False # To prevent flashing
|
||||||
|
self.nothing_playing_start_time = None # Timestamp when "Nothing Playing" started being displayed
|
||||||
self._needs_immediate_full_refresh = False # Flag for forcing refresh from YTM updates
|
self._needs_immediate_full_refresh = False # Flag for forcing refresh from YTM updates
|
||||||
self.ytm_event_data_queue = queue.Queue(maxsize=1) # Queue for event data
|
self.ytm_event_data_queue = queue.Queue(maxsize=1) # Queue for event data
|
||||||
|
|
||||||
@@ -306,6 +311,10 @@ class MusicManager:
|
|||||||
def deactivate_music_display(self):
|
def deactivate_music_display(self):
|
||||||
logger.info("Music display deactivated.")
|
logger.info("Music display deactivated.")
|
||||||
self.is_music_display_active = False
|
self.is_music_display_active = False
|
||||||
|
# Reset the nothing playing timer when display is deactivated
|
||||||
|
if self.nothing_playing_start_time is not None:
|
||||||
|
logger.debug("MusicManager: Display deactivated, resetting nothing playing timer")
|
||||||
|
self.nothing_playing_start_time = None
|
||||||
if self.ytm and self.ytm.is_connected:
|
if self.ytm and self.ytm.is_connected:
|
||||||
logger.info("Disconnecting YTM client due to music display deactivation.")
|
logger.info("Disconnecting YTM client due to music display deactivation.")
|
||||||
self.ytm.disconnect_client()
|
self.ytm.disconnect_client()
|
||||||
@@ -692,6 +701,23 @@ class MusicManager:
|
|||||||
if perform_full_refresh_this_cycle:
|
if perform_full_refresh_this_cycle:
|
||||||
logger.debug(f"MusicManager.display (Full Refresh Render): Using snapshot - Title: '{snapshot_title_for_log}'")
|
logger.debug(f"MusicManager.display (Full Refresh Render): Using snapshot - Title: '{snapshot_title_for_log}'")
|
||||||
|
|
||||||
|
# --- Check if we should skip when nothing is playing ---
|
||||||
|
music_config = self.config.get('music', {})
|
||||||
|
skip_when_nothing_playing = music_config.get('skip_when_nothing_playing', True)
|
||||||
|
skip_delay_seconds = music_config.get('skip_delay_seconds', 2)
|
||||||
|
|
||||||
|
if (not current_track_info_snapshot or current_track_info_snapshot.get('title') == 'Nothing Playing') and skip_when_nothing_playing:
|
||||||
|
# Check if we've been showing "Nothing Playing" for the configured delay
|
||||||
|
current_time = time.time()
|
||||||
|
if self.nothing_playing_start_time is None:
|
||||||
|
# We just started showing "Nothing Playing", set the start time
|
||||||
|
self.nothing_playing_start_time = current_time
|
||||||
|
logger.debug(f"MusicManager: Started showing 'Nothing Playing', timer set (will skip after {skip_delay_seconds}s)")
|
||||||
|
|
||||||
|
elif current_time - self.nothing_playing_start_time >= skip_delay_seconds:
|
||||||
|
logger.info(f"MusicManager: Nothing playing for {skip_delay_seconds}+ seconds, raising SkipModuleException to advance to next module.")
|
||||||
|
raise SkipModuleException(f"Music module: Nothing playing for {skip_delay_seconds}+ seconds and skip_when_nothing_playing is enabled")
|
||||||
|
|
||||||
# --- Original Nothing Playing Logic ---
|
# --- Original Nothing Playing Logic ---
|
||||||
if not current_track_info_snapshot or current_track_info_snapshot.get('title') == 'Nothing Playing':
|
if not current_track_info_snapshot or current_track_info_snapshot.get('title') == 'Nothing Playing':
|
||||||
if not hasattr(self, '_last_nothing_playing_log_time') or time.time() - getattr(self, '_last_nothing_playing_log_time', 0) > 30:
|
if not hasattr(self, '_last_nothing_playing_log_time') or time.time() - getattr(self, '_last_nothing_playing_log_time', 0) > 30:
|
||||||
@@ -701,7 +727,7 @@ class MusicManager:
|
|||||||
if not self.is_currently_showing_nothing_playing or perform_full_refresh_this_cycle:
|
if not self.is_currently_showing_nothing_playing or perform_full_refresh_this_cycle:
|
||||||
if perform_full_refresh_this_cycle or not self.is_currently_showing_nothing_playing:
|
if perform_full_refresh_this_cycle or not self.is_currently_showing_nothing_playing:
|
||||||
self.display_manager.clear()
|
self.display_manager.clear()
|
||||||
|
|
||||||
text_width = self.display_manager.get_text_width("Nothing Playing", self.display_manager.regular_font)
|
text_width = self.display_manager.get_text_width("Nothing Playing", self.display_manager.regular_font)
|
||||||
x_pos = (self.display_manager.matrix.width - text_width) // 2
|
x_pos = (self.display_manager.matrix.width - text_width) // 2
|
||||||
y_pos = (self.display_manager.matrix.height // 2) - 4
|
y_pos = (self.display_manager.matrix.height // 2) - 4
|
||||||
@@ -709,7 +735,7 @@ class MusicManager:
|
|||||||
self.display_manager.update_display()
|
self.display_manager.update_display()
|
||||||
self.is_currently_showing_nothing_playing = True
|
self.is_currently_showing_nothing_playing = True
|
||||||
|
|
||||||
with self.track_info_lock:
|
with self.track_info_lock:
|
||||||
self.scroll_position_title = 0
|
self.scroll_position_title = 0
|
||||||
self.scroll_position_artist = 0
|
self.scroll_position_artist = 0
|
||||||
self.scroll_position_album = 0
|
self.scroll_position_album = 0
|
||||||
@@ -722,7 +748,12 @@ class MusicManager:
|
|||||||
self.last_album_art_url = None
|
self.last_album_art_url = None
|
||||||
return
|
return
|
||||||
|
|
||||||
self.is_currently_showing_nothing_playing = False
|
# Reset the nothing playing timer when music starts playing
|
||||||
|
if self.nothing_playing_start_time is not None:
|
||||||
|
logger.debug("MusicManager: Music started playing, resetting nothing playing timer")
|
||||||
|
self.nothing_playing_start_time = None
|
||||||
|
|
||||||
|
self.is_currently_showing_nothing_playing = False
|
||||||
|
|
||||||
if perform_full_refresh_this_cycle:
|
if perform_full_refresh_this_cycle:
|
||||||
title_being_displayed = current_track_info_snapshot.get('title','N/A') if current_track_info_snapshot else "N/A"
|
title_being_displayed = current_track_info_snapshot.get('title','N/A') if current_track_info_snapshot else "N/A"
|
||||||
|
|||||||
@@ -1790,6 +1790,37 @@
|
|||||||
<input type="number" class="form-control" id="music_polling_interval" name="music_polling_interval" value="{{ safe_config_get(main_config, 'music', 'POLLING_INTERVAL_SECONDS', default=1) }}" min="1" max="60">
|
<input type="number" class="form-control" id="music_polling_interval" name="music_polling_interval" value="{{ safe_config_get(main_config, 'music', 'POLLING_INTERVAL_SECONDS', default=1) }}" min="1" max="60">
|
||||||
<div class="description">How often to check for music updates</div>
|
<div class="description">How often to check for music updates</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="music_skip_when_nothing_playing" name="music_skip_when_nothing_playing" {% if safe_config_get(main_config, 'music', 'skip_when_nothing_playing', default=True) %}checked{% endif %}>
|
||||||
|
Skip When Nothing Playing
|
||||||
|
</label>
|
||||||
|
<div class="description">Skip to next module when no music is playing</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="music_skip_delay_seconds">Skip Delay (seconds):</label>
|
||||||
|
<input type="number" class="form-control" id="music_skip_delay_seconds" name="music_skip_delay_seconds" value="{{ safe_config_get(main_config, 'music', 'skip_delay_seconds', default=2) }}" min="0" max="60">
|
||||||
|
<div class="description">Wait time before skipping when nothing playing</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="music_live_priority" name="music_live_priority" {% if safe_config_get(main_config, 'music', 'live_priority', default=True) %}checked{% endif %}>
|
||||||
|
Enable Live Priority
|
||||||
|
</label>
|
||||||
|
<div class="description">Include music in live priority rotation when playing</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="music_live_game_duration">Live Duration (seconds):</label>
|
||||||
|
<input type="number" class="form-control" id="music_live_game_duration" name="music_live_game_duration" value="{{ safe_config_get(main_config, 'music', 'live_game_duration', default=30) }}" min="10" max="300">
|
||||||
|
<div class="description">How long music stays in live priority rotation</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-success">Save Music Settings</button>
|
<button type="submit" class="btn btn-success">Save Music Settings</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -3222,7 +3253,11 @@
|
|||||||
enabled: document.getElementById('music_enabled').checked,
|
enabled: document.getElementById('music_enabled').checked,
|
||||||
preferred_source: document.getElementById('music_preferred_source').value,
|
preferred_source: document.getElementById('music_preferred_source').value,
|
||||||
YTM_COMPANION_URL: document.getElementById('ytm_companion_url').value,
|
YTM_COMPANION_URL: document.getElementById('ytm_companion_url').value,
|
||||||
POLLING_INTERVAL_SECONDS: parseInt(document.getElementById('music_polling_interval').value)
|
POLLING_INTERVAL_SECONDS: parseInt(document.getElementById('music_polling_interval').value),
|
||||||
|
skip_when_nothing_playing: document.getElementById('music_skip_when_nothing_playing').checked,
|
||||||
|
skip_delay_seconds: parseInt(document.getElementById('music_skip_delay_seconds').value),
|
||||||
|
live_priority: document.getElementById('music_live_priority').checked,
|
||||||
|
live_game_duration: parseInt(document.getElementById('music_live_game_duration').value)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
await saveConfigJson(payload);
|
await saveConfigJson(payload);
|
||||||
|
|||||||
Reference in New Issue
Block a user