fb4b14b282
Aggiunge il flag `timeout` nello stato e l'action `toggleTimeout`: dal controller il pulsante Time Out commuta la modalità e il display copre il tabellone con i video .mp4 della cartella `spot/` (non versionata). - un solo video → loop; più video → sequenza alfabetica con ritorno al primo; cartella vuota/assente → overlay "TIME OUT". - `src/spot-utils.js` (listSpotVideos) elenca i .mp4; endpoint `/spot/list` + file statici `/spot/<file>.mp4` serviti sia in prod (server.js) sia in dev (vite-plugin-websocket.js). - il display avvia i video con audio e ripiega su muto se il browser blocca l'autoplay con suono. - il controller resta operativo ma evidenzia la modalità con banner e pulsante arancione pulsante, in entrambi i layout (mobile ed esteso).
70 lines
2.5 KiB
JavaScript
70 lines
2.5 KiB
JavaScript
import { createServer } from 'http'
|
|
import express from 'express'
|
|
import { WebSocketServer } from 'ws'
|
|
import { fileURLToPath, pathToFileURL } from 'url'
|
|
import { dirname, join } from 'path'
|
|
import { setupWebSocketHandler } from './src/websocket-handler.js'
|
|
import { printServerInfo } from './src/server-utils.js'
|
|
import { loadState, saveState } from './src/persist.js'
|
|
import { listSpotVideos } from './src/spot-utils.js'
|
|
|
|
const __filename = fileURLToPath(import.meta.url)
|
|
const __dirname = dirname(__filename)
|
|
|
|
const DIST_DIR = join(__dirname, 'dist')
|
|
const SPOT_DIR = join(__dirname, 'spot')
|
|
|
|
// Crea l'app Express (asset statici + route display/controller) senza avviare il
|
|
// listen né il WebSocket: così il routing è testabile in isolamento.
|
|
export function createApp(distDir = DIST_DIR, spotDir = SPOT_DIR) {
|
|
const app = express()
|
|
|
|
app.use(express.static(distDir, { index: false }))
|
|
|
|
// Video spot del time out: elenco JSON + file statici (range requests gestiti
|
|
// da express.static, così il seeking dei video funziona). La route /spot/list
|
|
// va prima dello static per non collidere con un eventuale file "list".
|
|
app.get('/spot/list', (_req, res) => res.json(listSpotVideos(spotDir)))
|
|
app.use('/spot', express.static(spotDir))
|
|
|
|
app.get(['/', '/display', '/display/*splat'], (_req, res) => {
|
|
res.sendFile(join(distDir, 'index.html'))
|
|
})
|
|
|
|
app.get(['/controller', '/controller/*splat'], (_req, res) => {
|
|
res.sendFile(join(distDir, 'controller.html'))
|
|
})
|
|
|
|
return app
|
|
}
|
|
|
|
// Avvia HTTP + WebSocket. Lo stato viene caricato da disco e ripersistito ad ogni azione.
|
|
export function startServer(port = process.env.PORT || 3000) {
|
|
const app = createApp()
|
|
const server = createServer(app)
|
|
const wss = new WebSocketServer({ noServer: true })
|
|
setupWebSocketHandler(wss, { initialState: loadState(), onStateChange: saveState })
|
|
|
|
server.on('upgrade', (request, socket, head) => {
|
|
const pathname = new URL(request.url, `http://${request.headers.host}`).pathname
|
|
if (pathname === '/ws') {
|
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
wss.emit('connection', ws, request)
|
|
})
|
|
} else {
|
|
socket.destroy()
|
|
}
|
|
})
|
|
|
|
server.listen(port, '0.0.0.0', () => {
|
|
printServerInfo(port)
|
|
})
|
|
|
|
return server
|
|
}
|
|
|
|
// Avvia solo se eseguito direttamente (`node server.js`), non quando importato nei test.
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
startServer()
|
|
}
|