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;