From 046f5e5efd6d0dfe910b02f6afcccd047a4ec8e9 Mon Sep 17 00:00:00 2001 From: scheianu Date: Wed, 5 Aug 2026 11:43:02 +0300 Subject: [PATCH] Add audio parameter to player playlist and auto-mark deployment status - api.py: include 'audio' (on/off) field in playlist JSON served to players, derived from the muted toggle in the manage playlist page - players.py: include 'audio' field in legacy player playlist builder - api.py: auto-flip deployment_status pending/deploying -> deployed when a player sends feedback (player is confirmed running) - players_list.html: poll deployment status for pending/failed badges so the players list live-updates once a player starts sending feedback - .dockerignore: exclude runtime data/ volume dir from build context --- .dockerignore | 3 +++ app/blueprints/api.py | 19 +++++++++++++++++- app/blueprints/players.py | 1 + app/templates/players/players_list.html | 26 +++++++++++++++++-------- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/.dockerignore b/.dockerignore index 412633d..261955e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -44,6 +44,9 @@ flask_session/ .DS_Store Thumbs.db +# Runtime data volumes (mounted at runtime, NOT part of the image) +data/ + # Documentation BLUEPRINT_GUIDE.md ICON_INTEGRATION.md diff --git a/app/blueprints/api.py b/app/blueprints/api.py index a60a10e..1152740 100644 --- a/app/blueprints/api.py +++ b/app/blueprints/api.py @@ -413,7 +413,12 @@ def get_cached_playlist(player_id: int) -> List[Dict]: 'position': content._playlist_position or idx, 'url': item_url, # Web page URL for weblinks, file download URL otherwise 'description': content.description, - 'edit_on_player': getattr(content, '_playlist_edit_on_player_enabled', False) + 'edit_on_player': getattr(content, '_playlist_edit_on_player_enabled', False), + 'muted': getattr(content, '_playlist_muted', True), + # Player expects 'audio' = "on"/"off" to set playback sound. + # The playlist_content.muted column stores True when audio is muted, + # so audio is "on" exactly when muted is False. + 'audio': 'off' if getattr(content, '_playlist_muted', True) else 'on' }) return playlist_data @@ -502,6 +507,18 @@ def receive_player_feedback(): player.last_seen = datetime.utcnow() player.status = status + # A player that is sending feedback is running, so its deployment + # finished successfully. Auto-mark it as deployed so the players list + # doesn't stay stuck on "pending"/"deploying" after deployment is done. + if player.deployment_status in ('pending', 'deploying'): + player.deployment_status = 'deployed' + player.last_deployment_status = 'success' + player.last_deployment_message = ( + 'Player is running and sending feedback' + ) + if not player.last_deployment_at: + player.last_deployment_at = datetime.utcnow() + db.session.commit() log_action('info', f'Feedback received from {player.name} ({player.hostname}): {status} - {message}') diff --git a/app/blueprints/players.py b/app/blueprints/players.py index 6500cb0..fce3d54 100644 --- a/app/blueprints/players.py +++ b/app/blueprints/players.py @@ -578,6 +578,7 @@ def get_player_playlist(player_id: int) -> List[dict]: 'duration': getattr(content, '_playlist_duration', content.duration or 10), 'position': getattr(content, '_playlist_position', 0), 'muted': getattr(content, '_playlist_muted', True), + 'audio': 'off' if getattr(content, '_playlist_muted', True) else 'on', 'filename': content.filename }) diff --git a/app/templates/players/players_list.html b/app/templates/players/players_list.html index 25bb897..adab8a5 100644 --- a/app/templates/players/players_list.html +++ b/app/templates/players/players_list.html @@ -279,9 +279,13 @@ var POLL_INTERVAL = 5000; // 5 seconds var polling = false; - // Initial check: are there any "Deploying..." badges? - var deployingBadges = document.querySelectorAll('.deploy-badge.deploying'); - if (deployingBadges.length > 0) { + // Initial check: any badge that isn't yet "Deployed" yet (deploying, + // pending, or failed). We keep polling so that players which send + // feedback auto-transition from "pending" to "deployed". + var activeBadges = document.querySelectorAll( + '.deploy-badge.deploying, .deploy-badge.pending, .deploy-badge.failed' + ); + if (activeBadges.length > 0) { polling = true; schedulePoll(); } @@ -296,7 +300,7 @@ fetch('{{ url_for("players.deployment_status") }}') .then(function(r) { return r.json(); }) .then(function(data) { - var anyDeploying = false; + var anyActive = false; for (var playerId in data) { if (!data.hasOwnProperty(playerId)) continue; @@ -314,19 +318,25 @@ if (ds === 'deployed') { cell.innerHTML = '\u2705 Deployed' + ts + ''; } else if (ds === 'failed') { + // Terminal state - no need to keep polling for this one cell.innerHTML = '\u274c Failed' + ts + ''; } else if (ds === 'deploying') { - anyDeploying = true; + anyActive = true; if (!cell.querySelector('.deploying')) { cell.innerHTML = 'Deploying...'; } } else { - // No deployment or not started (pending, null, etc.) - // Don't set anyDeploying = false here — we only care about active deployments + // pending / null - deployment not confirmed yet. + // Keep polling: the player may start sending feedback, + // which flips the status to "deployed" on the server. + anyActive = true; + if (!cell.querySelector('.pending')) { + cell.innerHTML = '\u23f3 Pending'; + } } } - if (anyDeploying) { + if (anyActive) { schedulePoll(); } else { polling = false;