Release v2.2.0

This commit is contained in:
2026-08-01 21:41:44 +01:00
parent c643d2fb07
commit d6417b667c
673 changed files with 146752 additions and 7389 deletions
+52
View File
@@ -1,3 +1,5 @@
// Player route registration and filesystem-backed media helpers.
const fs = require('fs');
const express = require('express');
const path = require('path');
@@ -18,6 +20,9 @@ function registerPlayerRoutes(app, options) {
const playerRuntime = options && options.playerRuntime ? options.playerRuntime : null;
const playerPlaylistService = options && options.playerPlaylistService ? options.playerPlaylistService : null;
const rtmpStreamService = options && options.rtmpStreamService ? options.rtmpStreamService : null;
const playerPublicBaseUrl = String(options && options.playerPublicBaseUrl || process.env.PLAYER_PUBLIC_BASE_URL || process.env.PLAYER_BASE_URL || '').trim().replace(/\/$/, '');
const playerInternalBaseUrl = String(options && options.playerInternalBaseUrl || process.env.PLAYER_INTERNAL_BASE_URL || process.env.PLAYER_BASE_URL || '').trim().replace(/\/$/, '');
const playerIdentifier = String(options && options.playerIdentifier || '1').trim() || '1';
if (!app || !pool || !common || !mediaDir || !assetDir || !playerRuntime || !playerPlaylistService || !rtmpStreamService) {
throw new Error('registerPlayerRoutes requires app, pool, common, mediaDir, assetDir, playerRuntime, playerPlaylistService, and rtmpStreamService.');
@@ -75,6 +80,7 @@ function registerPlayerRoutes(app, options) {
}
app.use('/assets', express.static(assetDir));
app.use('/assets/adminlte/bootstrap-icons', express.static(path.join(__dirname, '..', 'web', 'public', 'adminlte', 'bootstrap-icons')));
app.use('/media', express.static(mediaDir));
app.use('/assets/vendor', express.static(path.join(__dirname, '..', '..', 'node_modules', 'hls.js', 'dist')));
@@ -207,6 +213,13 @@ function registerPlayerRoutes(app, options) {
app.get('/screen/:slug', function (req, res) {
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
res.set('Pragma', 'no-cache');
const { bindPlayerToScreen } = require('./onboarding');
if (playerIdentifier) {
void bindPlayerToScreen(pool, playerIdentifier, req.params.slug)
.catch(function (error) {
console.error(error);
});
}
playerPlaylistService.buildScreenPlaylist(req.params.slug).then(function (data) {
res.send(common.renderPlayerPage(req.params.slug, data));
}).catch(function (error) {
@@ -273,6 +286,45 @@ function registerPlayerRoutes(app, options) {
}
});
app.get('/api/screens/:slug/announcement', requirePageAuth(['player']), async function (req, res, next) {
try {
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
const announcement = typeof common.fetchActiveAnnouncement === 'function'
? await common.fetchActiveAnnouncement(pool, req.params.slug)
: null;
const revision = announcement
? [announcement.id, announcement.modified_at || '', announcement.expires_at || '', announcement.enabled ? '1' : '0'].join(':')
: 'none';
const etag = '"' + String(revision || 'none') + '"';
res.set('ETag', etag);
if (String(req.headers['if-none-match'] || '').split(',').map(function (value) {
return String(value || '').trim();
}).includes(etag)) {
return res.status(304).end();
}
res.json({
announcement: announcement,
revision: revision
});
} catch (error) {
if (isTransientDbError(error)) {
return res.status(503).json({ error: 'Announcement state unavailable.' });
}
next(error);
}
});
app.post('/api/screens/:slug/announcements/refresh', requireRequestAuth, async function (req, res, next) {
try {
const sent = typeof playerRuntime.broadcastAnnouncementRefresh === 'function'
? playerRuntime.broadcastAnnouncementRefresh(req.params.slug)
: 0;
res.json({ ok: true, screenSlug: req.params.slug, sent: sent });
} catch (error) {
next(error);
}
});
app.get(['/api/screens/:slug/connections', '/api/screens/:slug/clients'], requireRequestAuth, async function (req, res, next) {
try {
const connections = playerRuntime.snapshotConnections(req.params.slug);