Add multi-player and remote bridge support
This commit is contained in:
@@ -6,6 +6,10 @@ function normalizeClientName(value) {
|
||||
return String(value || '').trim();
|
||||
}
|
||||
|
||||
function normalizePlayerBaseUrl(value) {
|
||||
return String(value || '').trim().replace(/\/$/, '');
|
||||
}
|
||||
|
||||
function enrichScreensWithConnections(screens, connectionsBySlug, onboardingNameBySlug, playerUrlsBySlug) {
|
||||
return (screens || []).map(function (screen) {
|
||||
const connectionState = connectionsBySlug[screen.slug] || { count: 0, connections: [] };
|
||||
@@ -18,11 +22,12 @@ function enrichScreensWithConnections(screens, connectionsBySlug, onboardingName
|
||||
});
|
||||
}
|
||||
|
||||
function buildClientRows(screens, connectionsBySlug, onboardingNameBySlug, onboardingNameByDeviceId, formatDashboardDate) {
|
||||
function buildClientRows(screens, connectionsBySlug, onboardingNameBySlug, onboardingNameByDeviceId, playerIdentifierByBaseUrl, formatDashboardDate) {
|
||||
return (screens || []).flatMap(function (screen) {
|
||||
const connectionState = connectionsBySlug[screen.slug] || { count: 0, connections: [] };
|
||||
return (connectionState.connections || []).map(function (connection) {
|
||||
const deviceId = String(connection.deviceId || '').trim();
|
||||
const playerBaseUrl = normalizePlayerBaseUrl(connection.playerPublicBaseUrl);
|
||||
return Object.assign({}, connection, {
|
||||
screen_slug: screen.slug,
|
||||
screen_name: screen.name,
|
||||
@@ -30,12 +35,55 @@ function buildClientRows(screens, connectionsBySlug, onboardingNameBySlug, onboa
|
||||
playlist_name: screen.playlist_name || null,
|
||||
connectedAtLabel: formatDashboardDate(connection.connectedAt),
|
||||
lastSeenAtLabel: formatDashboardDate(connection.lastSeenAt),
|
||||
player_url: screen.player_url || String(connection.page || '').trim() || null
|
||||
player_identifier: playerIdentifierByBaseUrl && playerBaseUrl ? (playerIdentifierByBaseUrl[playerBaseUrl] || null) : null,
|
||||
player_url: playerBaseUrl || null
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isRecentPlayerRegistration(player, staleSeconds) {
|
||||
const lastSeenAt = player && player.last_seen_at;
|
||||
const lastSeenAtValue = lastSeenAt instanceof Date ? lastSeenAt.getTime() : new Date(lastSeenAt).getTime();
|
||||
const cutoffTime = Date.now() - Math.max(30, Number(staleSeconds || 60)) * 1000;
|
||||
|
||||
return Number.isFinite(lastSeenAtValue) && lastSeenAtValue >= cutoffTime;
|
||||
}
|
||||
|
||||
function buildKioskLauncherPlayers(playerRegistrations, staleSeconds) {
|
||||
return (Array.isArray(playerRegistrations) ? playerRegistrations : [])
|
||||
.filter(function (player) {
|
||||
return Boolean(player && String(player.identifier || '').trim() && String(player.public_base_url || '').trim() && isRecentPlayerRegistration(player, staleSeconds));
|
||||
})
|
||||
.map(function (player) {
|
||||
return {
|
||||
player_identifier: String(player.identifier || '').trim(),
|
||||
player_url: String(player.public_base_url || '').trim().replace(/\/$/, '')
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchConnectedPlayerCount(pool, connectedPlayerCountStaleSeconds) {
|
||||
const staleSeconds = Math.max(30, Number(connectedPlayerCountStaleSeconds || 60));
|
||||
if (!pool || typeof pool.query !== 'function' || !Number.isFinite(staleSeconds)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT COUNT(*) AS connected_count
|
||||
FROM d_players
|
||||
WHERE last_seen_at IS NOT NULL
|
||||
AND last_seen_at >= DATE_SUB(NOW(), INTERVAL ${staleSeconds} SECOND)`
|
||||
);
|
||||
|
||||
const count = Number(rows && rows[0] && rows[0].connected_count);
|
||||
return Number.isFinite(count) && count >= 0 ? count : null;
|
||||
} catch (_error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function compareScreenNames(left, right) {
|
||||
const leftName = String(left && left.name || '').trim();
|
||||
const rightName = String(right && right.name || '').trim();
|
||||
@@ -51,6 +99,7 @@ function compareScreenNames(left, right) {
|
||||
function createDashboardStateService(options) {
|
||||
const pool = options && options.pool;
|
||||
const common = options && options.common;
|
||||
const connectedPlayerCountStaleSeconds = options && options.connectedPlayerCountStaleSeconds;
|
||||
const playerSnapshotCache = options && options.playerSnapshotCache;
|
||||
const playerSnapshotSockets = options && options.playerSnapshotSockets;
|
||||
const ensurePlayerSnapshotSubscription = options && options.ensurePlayerSnapshotSubscription;
|
||||
@@ -63,9 +112,13 @@ function createDashboardStateService(options) {
|
||||
async function buildDashboardState() {
|
||||
const data = await common.fetchAdminData(pool);
|
||||
const screensData = data.screens || [];
|
||||
const connectedPlayersCount = await fetchConnectedPlayerCount(pool, connectedPlayerCountStaleSeconds);
|
||||
const playerUrlsBySlug = typeof common.fetchScreenPlayerUrls === 'function'
|
||||
? await common.fetchScreenPlayerUrls(pool)
|
||||
: {};
|
||||
const playerRegistrations = typeof common.fetchPlayerRegistrations === 'function'
|
||||
? await common.fetchPlayerRegistrations(pool)
|
||||
: [];
|
||||
screensData.forEach(function (screen) {
|
||||
ensurePlayerSnapshotSubscription(screen.slug);
|
||||
});
|
||||
@@ -91,6 +144,15 @@ function createDashboardStateService(options) {
|
||||
}
|
||||
});
|
||||
|
||||
const playerIdentifierByBaseUrl = {};
|
||||
(Array.isArray(playerRegistrations) ? playerRegistrations : []).forEach(function (player) {
|
||||
const baseUrl = normalizePlayerBaseUrl(player && player.public_base_url);
|
||||
const identifier = normalizeClientName(player && player.identifier);
|
||||
if (baseUrl && identifier) {
|
||||
playerIdentifierByBaseUrl[baseUrl] = identifier;
|
||||
}
|
||||
});
|
||||
|
||||
const connectionsBySlug = {};
|
||||
screensData.forEach(function (screen) {
|
||||
const cached = playerSnapshotCache.get(String(screen.slug || '').trim());
|
||||
@@ -106,7 +168,8 @@ function createDashboardStateService(options) {
|
||||
});
|
||||
})
|
||||
.sort(compareScreenNames);
|
||||
const clients = buildClientRows(screens, connectionsBySlug, onboardingNameBySlug, onboardingNameByDeviceId, formatDashboardDate);
|
||||
const clients = buildClientRows(screens, connectionsBySlug, onboardingNameBySlug, onboardingNameByDeviceId, playerIdentifierByBaseUrl, formatDashboardDate);
|
||||
const kioskPlayers = buildKioskLauncherPlayers(playerRegistrations, connectedPlayerCountStaleSeconds);
|
||||
const playerServiceConnected = Array.from(playerSnapshotSockets.values()).some(function (socket) {
|
||||
return socket && socket.readyState === WebSocket.OPEN;
|
||||
});
|
||||
@@ -115,8 +178,10 @@ function createDashboardStateService(options) {
|
||||
playlists: data.playlists || [],
|
||||
screens: screens,
|
||||
clients: clients,
|
||||
kioskPlayers: kioskPlayers,
|
||||
slides: data.slides || [],
|
||||
playerServiceConnected: playerServiceConnected,
|
||||
connectedPlayersCount: connectedPlayersCount !== null ? connectedPlayersCount : 0,
|
||||
connectedClientsCount: screens.reduce(function (total, screen) {
|
||||
return total + Number(screen.player_connection_count || 0);
|
||||
}, 0)
|
||||
|
||||
Reference in New Issue
Block a user