Add player control-plane and dashboard updates
This commit is contained in:
@@ -40,6 +40,14 @@ function normalizePlayerRowBaseUrl(player) {
|
||||
return normalizeBaseUrl(player && player.internal_base_url);
|
||||
}
|
||||
|
||||
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 createUploadSyncService(options) {
|
||||
const pool = options && options.pool;
|
||||
const common = options && options.common;
|
||||
@@ -68,7 +76,11 @@ function createUploadSyncService(options) {
|
||||
|
||||
async function getPlayerInternalBaseUrl() {
|
||||
const metadata = await getPlayerTaskMetadata();
|
||||
return metadata && metadata.playerInternalBaseUrl ? metadata.playerInternalBaseUrl : null;
|
||||
if (!metadata || metadata.playerActive === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return metadata.playerInternalBaseUrl ? metadata.playerInternalBaseUrl : null;
|
||||
}
|
||||
|
||||
async function getPlayerTaskMetadata() {
|
||||
@@ -85,26 +97,45 @@ function createUploadSyncService(options) {
|
||||
if (pool && typeof fetchPlayerRegistrations === 'function') {
|
||||
const configuredPlayerIdentifier = getConfiguredPlayerIdentifier();
|
||||
const players = await fetchPlayerRegistrations(pool);
|
||||
const exactPlayer = Array.isArray(players)
|
||||
? players.find(function (player) {
|
||||
return String(player && player.identifier || '').trim() === configuredPlayerIdentifier;
|
||||
})
|
||||
: null;
|
||||
const registeredPlayers = Array.isArray(players) ? players : [];
|
||||
const preferredPlayer = registeredPlayers.find(function (player) {
|
||||
const recentPlayers = registeredPlayers.filter(function (player) {
|
||||
return isRecentPlayerRegistration(player, 60);
|
||||
});
|
||||
const preferredPlayer = recentPlayers.find(function (player) {
|
||||
const internalBaseUrl = normalizePlayerRowBaseUrl(player);
|
||||
return internalBaseUrl && !isLocalLikeBaseUrl(internalBaseUrl);
|
||||
}) || exactPlayer || registeredPlayers[0] || null;
|
||||
const resolvedInternalBaseUrl = normalizePlayerRowBaseUrl(preferredPlayer);
|
||||
const resolvedPublicBaseUrl = normalizeBaseUrl(preferredPlayer && preferredPlayer.public_base_url);
|
||||
const resolvedIdentifier = String(preferredPlayer && preferredPlayer.identifier || '').trim();
|
||||
if (resolvedInternalBaseUrl || resolvedPublicBaseUrl || resolvedIdentifier) {
|
||||
}) || recentPlayers.find(function (player) {
|
||||
return String(player && player.identifier || '').trim() === configuredPlayerIdentifier;
|
||||
}) || recentPlayers[0] || null;
|
||||
if (preferredPlayer) {
|
||||
const resolvedInternalBaseUrl = normalizePlayerRowBaseUrl(preferredPlayer);
|
||||
const resolvedPublicBaseUrl = normalizeBaseUrl(preferredPlayer && preferredPlayer.public_base_url);
|
||||
const resolvedIdentifier = String(preferredPlayer && preferredPlayer.identifier || '').trim();
|
||||
playerInternalBaseUrl = resolvedInternalBaseUrl || null;
|
||||
playerTaskMetadata = {
|
||||
playerIdentifier: resolvedIdentifier || null,
|
||||
playerPublicBaseUrl: resolvedPublicBaseUrl || null,
|
||||
playerInternalBaseUrl: resolvedInternalBaseUrl || null,
|
||||
playerLabel: resolvedIdentifier || resolvedPublicBaseUrl || resolvedInternalBaseUrl || null
|
||||
playerLabel: resolvedIdentifier || resolvedPublicBaseUrl || resolvedInternalBaseUrl || null,
|
||||
playerActive: true
|
||||
};
|
||||
return playerTaskMetadata;
|
||||
}
|
||||
|
||||
if (registeredPlayers.length) {
|
||||
const stalePlayer = registeredPlayers.find(function (player) {
|
||||
return String(player && player.identifier || '').trim() === configuredPlayerIdentifier;
|
||||
}) || registeredPlayers[0] || null;
|
||||
const staleInternalBaseUrl = normalizePlayerRowBaseUrl(stalePlayer);
|
||||
const stalePublicBaseUrl = normalizeBaseUrl(stalePlayer && stalePlayer.public_base_url);
|
||||
const staleIdentifier = String(stalePlayer && stalePlayer.identifier || '').trim();
|
||||
playerInternalBaseUrl = staleInternalBaseUrl || null;
|
||||
playerTaskMetadata = {
|
||||
playerIdentifier: staleIdentifier || null,
|
||||
playerPublicBaseUrl: stalePublicBaseUrl || null,
|
||||
playerInternalBaseUrl: staleInternalBaseUrl || null,
|
||||
playerLabel: staleIdentifier || stalePublicBaseUrl || staleInternalBaseUrl || null,
|
||||
playerActive: false
|
||||
};
|
||||
return playerTaskMetadata;
|
||||
}
|
||||
@@ -117,7 +148,8 @@ function createUploadSyncService(options) {
|
||||
playerIdentifier: getConfiguredPlayerIdentifier() || null,
|
||||
playerPublicBaseUrl: null,
|
||||
playerInternalBaseUrl: playerInternalBaseUrl,
|
||||
playerLabel: getConfiguredPlayerIdentifier() || playerInternalBaseUrl || null
|
||||
playerLabel: getConfiguredPlayerIdentifier() || playerInternalBaseUrl || null,
|
||||
playerActive: true
|
||||
};
|
||||
return playerTaskMetadata;
|
||||
})().then(function (metadata) {
|
||||
@@ -377,6 +409,10 @@ function createUploadSyncService(options) {
|
||||
return code === 'ENOTFOUND' || code === 'ECONNREFUSED' || code === 'EAI_AGAIN' || code === 'ETIMEDOUT';
|
||||
}
|
||||
|
||||
function isPlayerUnavailableResponse(response) {
|
||||
return Boolean(response) && Number(response.status) === 503;
|
||||
}
|
||||
|
||||
function queuePlayerUploadSync(operation) {
|
||||
if (!operation || !operation.uploadPath) {
|
||||
return;
|
||||
@@ -404,13 +440,13 @@ function createUploadSyncService(options) {
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
async function pushUploadFileToPlayer(uploadPath, localUploadDir) {
|
||||
async function pushUploadFileToPlayer(uploadPath, localUploadDir, resolvedPlayerInternalBaseUrl) {
|
||||
if (!uploadPath || !shouldMirrorUploads(localUploadDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const resolvedPlayerInternalBaseUrl = await getPlayerInternalBaseUrl();
|
||||
if (!resolvedPlayerInternalBaseUrl) {
|
||||
const targetBaseUrl = String(resolvedPlayerInternalBaseUrl || '').trim() || await getPlayerInternalBaseUrl();
|
||||
if (!targetBaseUrl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -435,7 +471,7 @@ function createUploadSyncService(options) {
|
||||
pathname: `/api/media/${encodeURIComponent(relativePath)}`,
|
||||
body: fileBuffer
|
||||
});
|
||||
const response = await fetch(`${resolvedPlayerInternalBaseUrl}/api/media/${encodeURIComponent(relativePath)}`, {
|
||||
const response = await fetch(`${targetBaseUrl}/api/media/${encodeURIComponent(relativePath)}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
@@ -444,7 +480,9 @@ function createUploadSyncService(options) {
|
||||
body: fileBuffer
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.warn('Unable to sync upload to player:', relativePath, response.status, response.statusText);
|
||||
if (!isPlayerUnavailableResponse(response)) {
|
||||
console.warn('Unable to sync upload to player:', relativePath, response.status, response.statusText);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -456,13 +494,13 @@ function createUploadSyncService(options) {
|
||||
}
|
||||
}
|
||||
|
||||
async function removeUploadFileFromPlayer(uploadPath, localUploadDir) {
|
||||
async function removeUploadFileFromPlayer(uploadPath, localUploadDir, resolvedPlayerInternalBaseUrl) {
|
||||
if (!uploadPath || !shouldMirrorUploads(localUploadDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const resolvedPlayerInternalBaseUrl = await getPlayerInternalBaseUrl();
|
||||
if (!resolvedPlayerInternalBaseUrl) {
|
||||
const targetBaseUrl = String(resolvedPlayerInternalBaseUrl || '').trim() || await getPlayerInternalBaseUrl();
|
||||
if (!targetBaseUrl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -475,7 +513,7 @@ function createUploadSyncService(options) {
|
||||
method: 'DELETE',
|
||||
pathname: `/api/media/${encodeURIComponent(relativePath)}`
|
||||
});
|
||||
const response = await fetch(`${resolvedPlayerInternalBaseUrl}/api/media/${encodeURIComponent(relativePath)}`, {
|
||||
const response = await fetch(`${targetBaseUrl}/api/media/${encodeURIComponent(relativePath)}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
@@ -483,7 +521,9 @@ function createUploadSyncService(options) {
|
||||
}
|
||||
});
|
||||
if (!response.ok && response.status !== 404) {
|
||||
console.warn('Unable to remove upload from player:', relativePath, response.status, response.statusText);
|
||||
if (!isPlayerUnavailableResponse(response)) {
|
||||
console.warn('Unable to remove upload from player:', relativePath, response.status, response.statusText);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -500,9 +540,10 @@ function createUploadSyncService(options) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resolvedPlayerInternalBaseUrl = await getPlayerInternalBaseUrl();
|
||||
const uniqueRefs = Array.from(new Set((uploadRefs || []).map(normalizeUploadReference).filter(Boolean)));
|
||||
for (let i = 0; i < uniqueRefs.length; i += 1) {
|
||||
const success = await pushUploadFileToPlayer(uniqueRefs[i], localUploadDir);
|
||||
const success = await pushUploadFileToPlayer(uniqueRefs[i], localUploadDir, resolvedPlayerInternalBaseUrl);
|
||||
if (!success) {
|
||||
queuePlayerUploadSync({
|
||||
type: 'put',
|
||||
@@ -683,15 +724,23 @@ function createUploadSyncService(options) {
|
||||
const playerMetadata = pendingEntries.length && pendingEntries[0] && pendingEntries[0].metadata
|
||||
? pendingEntries[0].metadata
|
||||
: await getPlayerTaskMetadata();
|
||||
if (playerMetadata && playerMetadata.playerActive === false) {
|
||||
pendingPlayerUploadSyncs.clear();
|
||||
pendingPlayerUploadSyncRetryLogAt = 0;
|
||||
return;
|
||||
}
|
||||
const resolvedPlayerInternalBaseUrl = playerMetadata && playerMetadata.playerInternalBaseUrl
|
||||
? playerMetadata.playerInternalBaseUrl
|
||||
: await getPlayerInternalBaseUrl();
|
||||
let successCount = 0;
|
||||
let failureCount = 0;
|
||||
for (let i = 0; i < pendingEntries.length; i += 1) {
|
||||
const operation = pendingEntries[i];
|
||||
let success = false;
|
||||
if (operation.type === 'delete') {
|
||||
success = await removeUploadFileFromPlayer(operation.uploadPath, operation.uploadDir);
|
||||
success = await removeUploadFileFromPlayer(operation.uploadPath, operation.uploadDir, resolvedPlayerInternalBaseUrl);
|
||||
} else {
|
||||
success = await pushUploadFileToPlayer(operation.uploadPath, operation.uploadDir);
|
||||
success = await pushUploadFileToPlayer(operation.uploadPath, operation.uploadDir, resolvedPlayerInternalBaseUrl);
|
||||
}
|
||||
if (success) {
|
||||
successCount += 1;
|
||||
|
||||
Reference in New Issue
Block a user