Add multi-player and remote bridge support

This commit is contained in:
2026-08-07 02:58:18 +01:00
parent a4f8a807ff
commit 74318eb34e
58 changed files with 3785 additions and 432 deletions
+72 -25
View File
@@ -267,24 +267,20 @@
function renderScreenTile(screen) {
var clientCount = Number(screen.player_connection_count || 0);
var playerUrl = String(screen.player_url || '').trim();
var connectionLabel = clientCount ? clientCount + ' live' : 'No clients';
var connectionStateClass = clientCount ? 'is-live' : 'is-idle';
var playlistLabel = screen.playlist_name ? escapeHtml(screen.playlist_name) : 'Unassigned';
var connectionsLabel = clientCount ? clientCount + ' connected' : 'No clients connected';
return [
'<article class="dashboard-screen-tile" data-screen-key="' + escapeHtml(screen.id || '') + '">',
'<div class="dashboard-screen-tile-top">',
'<div class="dashboard-screen-tile-text">',
'<h4 class="dashboard-screen-name">' + escapeHtml(screen.name || '') + '</h4>',
'<a class="dashboard-screen-link" href="' + escapeHtml(playerUrl) + '" target="_blank">' + escapeHtml(playerUrl) + '</a>',
'</div>',
'<span class="dashboard-screen-pill ' + connectionStateClass + '">' + escapeHtml(connectionLabel) + '</span>',
'</div>',
'<dl class="dashboard-screen-meta">',
'<div><dt>Playlist</dt><dd>' + playlistLabel + '</dd></div>',
'<div><dt>Connections</dt><dd>' + escapeHtml(connectionsLabel) + '</dd></div>',
'</dl>',
'</article>'
].join('');
@@ -310,7 +306,7 @@
}
function updateStats(state) {
var clientCount = document.getElementById('dashboard-client-count');
var playerCount = document.getElementById('dashboard-player-count');
var screenCount = document.getElementById('dashboard-screen-count');
var slideCount = document.getElementById('dashboard-slide-count');
var playlistCount = document.getElementById('dashboard-playlist-count');
@@ -324,8 +320,8 @@
if (screenCount && Array.isArray(state.screens)) {
screenCount.textContent = String(state.screens.length);
}
if (clientCount) {
clientCount.textContent = String(Number(state.connectedClientsCount || 0));
if (playerCount) {
playerCount.textContent = String(Number(state.connectedPlayersCount || 0));
}
}
@@ -399,6 +395,62 @@
}
function updateKioskLauncherModal(state) {
var modal = document.getElementById('dashboard-kiosk-launcher-modal');
if (!modal) {
return;
}
var checkbox = modal.querySelector('[data-kiosk-launcher-confirm]');
var select = modal.querySelector('[data-kiosk-launcher-player-select]');
var downloadLinks = Array.prototype.slice.call(modal.querySelectorAll('[data-kiosk-launcher-download]'));
var playersState = state && Array.isArray(state.kioskPlayers)
? state.kioskPlayers
: (state && Array.isArray(state.clients) ? state.clients : []);
var players = playersState.filter(function (client) {
return Boolean(client && String(client.player_url || '').trim());
});
if (select && playersState.length) {
var currentValue = String(select.value || '').trim();
var options = ['<option value="">Select a player</option>'];
players.forEach(function (player) {
var playerUrl = String(player.player_url || '').trim();
var playerIdentifier = String(player.player_identifier || 'Connected player').trim();
if (!playerUrl) {
return;
}
options.push('<option value="' + escapeHtml(playerUrl) + '" data-player-identifier="' + escapeHtml(playerIdentifier) + '">' + escapeHtml(playerIdentifier + ' - ' + playerUrl) + '</option>');
});
select.innerHTML = options.join('');
if (currentValue) {
select.value = currentValue;
}
}
var selectedUrl = select ? String(select.value || '').trim() : '';
var canEnableDownloads = Boolean(checkbox && checkbox.checked && selectedUrl);
downloadLinks.forEach(function (link) {
if (!link) {
return;
}
var baseHref = String(link.getAttribute('data-kiosk-launcher-download-base') || link.getAttribute('href') || '').trim();
if (canEnableDownloads) {
link.setAttribute('href', baseHref + '?playerUrl=' + encodeURIComponent(selectedUrl));
link.classList.remove('disabled');
link.setAttribute('aria-disabled', 'false');
link.removeAttribute('tabindex');
} else {
link.removeAttribute('href');
link.classList.add('disabled');
link.setAttribute('aria-disabled', 'true');
link.setAttribute('tabindex', '-1');
}
});
}
function updateScreenGrid(state) {
var grid = document.getElementById('dashboard-screens-grid');
if (!grid || !Array.isArray(state.screens)) {
@@ -603,6 +655,7 @@
updateScreenGrid(state);
updateScreenCommandControls(state);
updateClientTable(state);
updateKioskLauncherModal(state);
updateDashboardQuickActions(state);
}
@@ -780,34 +833,28 @@
}
var checkbox = modal.querySelector('[data-kiosk-launcher-confirm]');
var select = modal.querySelector('[data-kiosk-launcher-player-select]');
var downloadLinks = Array.prototype.slice.call(modal.querySelectorAll('[data-kiosk-launcher-download]'));
function setDownloadsEnabled(enabled) {
downloadLinks.forEach(function (link) {
if (!link) {
return;
}
link.classList.toggle('disabled', !enabled);
link.setAttribute('aria-disabled', enabled ? 'false' : 'true');
if (enabled) {
link.removeAttribute('tabindex');
} else {
link.setAttribute('tabindex', '-1');
}
});
}
function resetModalState() {
if (checkbox) {
checkbox.checked = false;
}
setDownloadsEnabled(false);
if (select) {
select.value = '';
}
updateKioskLauncherModal(latestDashboardState);
}
if (checkbox) {
checkbox.addEventListener('change', function () {
setDownloadsEnabled(Boolean(checkbox.checked));
updateKioskLauncherModal(latestDashboardState || readScreenCommandStateFromDom());
});
}
if (select) {
select.addEventListener('change', function () {
updateKioskLauncherModal(latestDashboardState || readScreenCommandStateFromDom());
});
}
+10 -1
View File
@@ -1,4 +1,13 @@
(function () {
function buildLivePlayerFeedLabel(connectedPlayersCount) {
var count = Number(connectedPlayersCount || 0);
if (count <= 0) {
return 'Player feed online - no players connected';
}
return 'Live player feed - ' + count + ' player' + (count === 1 ? '' : 's') + ' connected';
}
function updateSidebarStatus(status, label) {
var dot = document.getElementById('sidebar-status-dot');
var text = document.getElementById('sidebar-status-text');
@@ -72,7 +81,7 @@
if (!screenCount) {
updateSidebarStatus('unknown', 'No screens configured');
} else {
updateSidebarStatus(Boolean(payload.state && payload.state.playerServiceConnected), 'Live player feed');
updateSidebarStatus(Boolean(payload.state && payload.state.playerServiceConnected), buildLivePlayerFeedLabel(payload.state && payload.state.connectedPlayersCount));
}
}
} catch (_error) {