@xiboplayer/schedule
Complete scheduling solution: campaigns, dayparting, interrupts, overlays, and timeline prediction.
Overview
Manages all aspects of digital signage scheduling to determine which layouts play when:
- Campaign scheduling -- groups of layouts with time windows and priorities
- Dayparting -- weekly time slots (Mon-Fri 09:00-17:00, evenings, weekends) with midnight-crossing support
- Priority fallback -- higher-priority layouts hide lower-priority ones; rate limiting triggers automatic fallback
- Rate limiting --
maxPlaysPerHour with even distribution (prevents bursts, ensures spacing)
- Interrupts (Share of Voice) -- layouts that must play X% of each hour, interleaved with normal content
- Overlays -- layouts that appear on top of main layouts without interrupting playback
- Criteria evaluation -- conditional display based on time, weather, custom display properties
- Geo-fencing -- location-based filtering (point + radius, Haversine distance)
- Timeline prediction -- deterministic simulation of future playback for UI overlays
- Default layout -- fallback when no campaigns are active
Architecture
CMS Schedule XML
|
v
+-------------------------------------+
| Schedule Parser |
| +- campaigns[] |
| +- layouts[] |
| +- overlays[] |
| +- default layout |
+-------------------------------------+
|
v
+-------------------------------------+
| Evaluation Engine |
| +- Recurrence (Week/Day/Month) |
| +- Time windows (dayparting) |
| +- Criteria (weather, properties) |
| +- Geo-fencing |
| +- Priority + rate-limit filtering |
+-------------------------------------+
|
v
+-------------------------------------+
| Schedule Queue Builder (LCM-based) |
| Deterministic round-robin with: |
| +- Rate-limited slots (even spaced)|
| +- Priority fallback |
| +- Default fills gaps |
+-------------------------------------+
|
+-> getCurrentLayouts() -> Renderer
+-> getLayoutsInTimeRange() -> Timeline Overlay
+-> Track play history -> Rate limiting
Installation
npm install @xiboplayer/schedule
Usage
Basic scheduling
import { ScheduleManager } from '@xiboplayer/schedule';
const schedule = new ScheduleManager();
schedule.setSchedule({
campaigns: [
{
id: 1,
priority: 100,
fromdt: '2025-01-01 09:00',
todt: '2025-12-31 17:00',
recurrenceType: 'Week',
recurrenceRepeatsOn: '1,2,3,4,5',
layouts: [
{ id: 10, file: '10.xlf', duration: 30 },
{ id: 11, file: '11.xlf', duration: 30 },
],
},
],
default: '99.xlf',
});
const layoutsToPlay = schedule.getCurrentLayouts();
Dayparting with midnight crossing
schedule.setSchedule({
layouts: [
{
id: 1,
file: '1.xlf',
recurrenceType: 'Week',
recurrenceRepeatsOn: '1,2,3,4,5,6,7',
fromdt: '1970-01-01 22:00:00',
todt: '1970-01-01 02:00:00',
},
],
});
Rate limiting with even distribution
schedule.setSchedule({
layouts: [
{
id: 1,
file: '1.xlf',
maxPlaysPerHour: 3,
},
],
});
schedule.recordPlay('1');
Interrupts (Share of Voice)
schedule.setSchedule({
layouts: [
{ id: 1, file: '1.xlf', duration: 30 },
{ id: 2, file: '2.xlf', duration: 30, shareOfVoice: 20 },
],
});
const layouts = schedule.getCurrentLayouts();
Criteria-based display
schedule.setSchedule({
layouts: [
{
id: 1,
file: '1.xlf',
criteria: [
{ metric: 'weatherTemp', condition: 'greaterThan', value: '25', type: 'number' },
],
},
],
});
schedule.setWeatherData({ temperature: 28, humidity: 65 });
Geo-fencing
schedule.setLocation(37.7749, -122.4194);
schedule.setSchedule({
layouts: [
{
id: 1,
file: '1.xlf',
isGeoAware: true,
geoLocation: '37.7749,-122.4194,500',
},
],
});
Timeline prediction
const timeline = calculateTimeline(queue, queuePosition, {
from: new Date(),
hours: 2,
defaultLayout: schedule.schedule.default,
durations: durations,
});
Campaign Evaluation Algorithm
When getCurrentLayouts() is called:
- Filter time-active items -- campaigns and standalone layouts within their date/time window and recurrence rules
- Apply criteria -- filter by weather, display properties, geo-fencing
- Apply rate limiting -- exclude layouts that exceeded
maxPlaysPerHour
- Find max priority -- only max priority items win
- Extract layouts -- campaigns return all their layouts; standalone layouts contribute themselves
- Process interrupts -- separate interrupt layouts, calculate share-of-voice, interleave
- Return layout files -- ready for the renderer
Key Concepts
Schedule Queue (LCM-based)
The queue is a pre-computed, deterministic round-robin cycle:
- LCM period -- Least Common Multiple of all
maxPlaysPerHour intervals (capped at 2 hours)
- Simulation -- walks the period applying priority and rate-limit rules at each step
- Caching -- reused until the active layout set changes
- Predictable -- answers "what's playing in 30 minutes?" offline
Dayparting
| Week | Specific days + time-of-day | Mon-Fri 09:00-17:00 |
| Day | Daily with optional interval | Every 2 days |
| Month | Specific days of month | 1st, 15th (monthly) |
Midnight crossing: 22:00 - 02:00 works across day boundaries.
Criteria Evaluation
Built-in metrics: dayOfWeek, dayOfMonth, month, hour, isoDay
Weather metrics: weatherTemp, weatherHumidity, weatherWindSpeed, weatherCondition, weatherCloudCover
Operators: equals, notEquals, greaterThan, greaterThanOrEquals, lessThan, lessThanOrEquals, contains, startsWith, endsWith, in
Geo-fencing
- Format:
"lat,lng,radius" (e.g., "37.7749,-122.4194,500")
- Default radius: 500 meters
- Calculation: Haversine formula (great-circle distance)
- Permissive: if no location available, layout displays (fail-open for offline)
API Reference
Constructor
new ScheduleManager(options?)
interruptScheduler | InterruptScheduler? | Optional interrupt handler |
displayProperties | Object? | Custom display fields from CMS |
Methods
setSchedule(schedule) | void | Load schedule from XMDS response |
getCurrentLayouts() | string[] | Layouts active now |
getLayoutsAtTime(date) | string[] | Layouts at specific time |
getAllLayoutsAtTime(date) | Array | All time-active layouts with metadata |
getScheduleQueue(durations) | {queue, periodSeconds} | Pre-computed round-robin queue |
popNextFromQueue(durations) | {layoutId, duration} | Pop next entry, advance position |
peekNextInQueue(durations) | {layoutId, duration} | Peek without advancing |
recordPlay(layoutId) | void | Track a play for rate limiting |
canPlayLayout(layoutId, max) | boolean | Check if layout can play now |
setWeatherData(data) | void | Update weather for criteria |
setLocation(lat, lng) | void | Set GPS location for geo-fencing |
setDisplayProperties(props) | void | Set custom display fields |
detectConflicts(options) | Array | Find priority-shadowing conflicts |
Overlay Methods
setOverlays(overlays) | void | Update overlay list |
getCurrentOverlays() | Array | Active overlays (sorted by priority) |
shouldCheckOverlays(lastCheck) | boolean | Check interval (every 60s) |
Timeline Functions
import { calculateTimeline, parseLayoutDuration, buildScheduleQueue } from '@xiboplayer/schedule';
const { duration } = parseLayoutDuration(xlfXml, videoDurations?);
const { queue, periodSeconds } = buildScheduleQueue(allLayouts, durations);
const timeline = calculateTimeline(queue, position, { from, hours, defaultLayout, durations });
Dependencies
No external dependencies -- fully self-contained scheduling engine.
@xiboplayer/utils -- logging only
xiboplayer.org · Part of the XiboPlayer SDK