screentinker/server/services/scheduler.js
ScreenTinker 6d4d39c2d8 Phase 2: buildPlaylistPayload reads from playlist_items via device.playlist_id
Replaces the assignments-table query with a playlist_items query keyed on
device.playlist_id. Also eliminates the duplicate payload builder in
scheduler.js — it now calls the shared buildPlaylistPayload.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 22:04:26 -05:00

91 lines
3 KiB
JavaScript

const { db } = require('../db/database');
let io = null;
function startScheduler(socketIo) {
io = socketIo;
// Check schedules every 60 seconds
setInterval(evaluateSchedules, 60000);
console.log('Scheduler service started');
}
function evaluateSchedules() {
const deviceNs = io?.of('/device');
if (!deviceNs) return;
const now = new Date();
const onlineDevices = db.prepare("SELECT * FROM devices WHERE status = 'online'").all();
for (const device of onlineDevices) {
const schedules = db.prepare(`
SELECT s.*, c.filename, c.mime_type, c.filepath, c.file_size, c.remote_url,
c.duration_sec as content_duration
FROM schedules s
LEFT JOIN content c ON s.content_id = c.id
WHERE s.device_id = ? AND s.enabled = 1
ORDER BY s.priority DESC
`).all(device.id);
// Find currently active schedule
const active = schedules.find(s => isScheduleActiveNow(s, now));
if (active && active.content_id) {
// Check if this is different from current playback
const currentLayout = device.layout_id;
if (active.layout_id && active.layout_id !== currentLayout) {
// Switch layout
db.prepare("UPDATE devices SET layout_id = ? WHERE id = ?").run(active.layout_id, device.id);
// Push updated playlist
pushPlaylistToDevice(device.id, deviceNs);
}
}
}
}
function isScheduleActiveNow(schedule, now) {
const start = new Date(schedule.start_time);
const end = new Date(schedule.end_time);
if (!schedule.recurrence) {
return now >= start && now <= end;
}
// For recurring schedules, check if current time-of-day falls within range
// and current day matches recurrence pattern
const rule = parseSimpleRRule(schedule.recurrence);
if (!rule) return now >= start && now <= end;
// Check day of week
if (rule.byDay && !rule.byDay.includes(now.getDay())) return false;
// Check time of day
const nowMinutes = now.getHours() * 60 + now.getMinutes();
const startMinutes = start.getHours() * 60 + start.getMinutes();
const endMinutes = end.getHours() * 60 + end.getMinutes();
return nowMinutes >= startMinutes && nowMinutes <= endMinutes;
}
function parseSimpleRRule(rrule) {
if (!rrule) return null;
const parts = rrule.split(';');
const rule = {};
const dayMap = { SU: 0, MO: 1, TU: 2, WE: 3, TH: 4, FR: 5, SA: 6 };
for (const part of parts) {
const [key, val] = part.split('=');
if (key === 'FREQ') rule.freq = val;
if (key === 'BYDAY') rule.byDay = val.split(',').map(d => dayMap[d]).filter(d => d !== undefined);
if (key === 'INTERVAL') rule.interval = parseInt(val);
}
return rule;
}
function pushPlaylistToDevice(deviceId, deviceNs) {
// Use the single-source buildPlaylistPayload from deviceSocket
const { buildPlaylistPayload } = require('../ws/deviceSocket');
const payload = buildPlaylistPayload(deviceId);
deviceNs.to(deviceId).emit('device:playlist-update', payload);
}
module.exports = { startScheduler, pushPlaylistToDevice };