
Research
/Security News
PolinRider Spreads Through Compromised GitHub Accounts and Packagist
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.
@xiboplayer/schedule
Advanced tools
Complete scheduling solution: campaigns, dayparting, interrupts, and overlays
Complete scheduling solution: campaigns, dayparting, interrupts, overlays, and timeline prediction.
Manages all aspects of digital signage scheduling to determine which layouts play when:
maxPlaysPerHour with even distribution (prevents bursts, ensures spacing)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
npm install @xiboplayer/schedule
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', // Mon-Fri
layouts: [
{ id: 10, file: '10.xlf', duration: 30 },
{ id: 11, file: '11.xlf', duration: 30 },
],
},
],
default: '99.xlf',
});
const layoutsToPlay = schedule.getCurrentLayouts();
// Business hours: ['10.xlf', '11.xlf']
// After hours: ['99.xlf'] (default)
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', // 10 PM
todt: '1970-01-01 02:00:00', // 2 AM (next day)
},
],
});
// Friday 23:00: returns ['1.xlf']
// Saturday 01:00: returns ['1.xlf'] (midnight crossing works)
schedule.setSchedule({
layouts: [
{
id: 1,
file: '1.xlf',
maxPlaysPerHour: 3, // 3 times per hour, evenly spaced
},
],
});
schedule.recordPlay('1'); // Play at 09:00
// Can't play again until 09:20 (60 / 3 = 20 min minimum gap)
schedule.setSchedule({
layouts: [
{ id: 1, file: '1.xlf', duration: 30 },
{ id: 2, file: '2.xlf', duration: 30, shareOfVoice: 20 }, // 20% of each hour
],
});
const layouts = schedule.getCurrentLayouts();
// Interleaved: normal, normal, interrupt, normal, normal, interrupt, ...
schedule.setSchedule({
layouts: [
{
id: 1,
file: '1.xlf',
criteria: [
{ metric: 'weatherTemp', condition: 'greaterThan', value: '25', type: 'number' },
],
},
],
});
schedule.setWeatherData({ temperature: 28, humidity: 65 });
// Layout 1 displays only when temperature > 25
schedule.setLocation(37.7749, -122.4194);
schedule.setSchedule({
layouts: [
{
id: 1,
file: '1.xlf',
isGeoAware: true,
geoLocation: '37.7749,-122.4194,500', // lat,lng,radius_meters
},
],
});
// Returns ['1.xlf'] only if player is within 500m
const timeline = calculateTimeline(queue, queuePosition, {
from: new Date(),
hours: 2,
defaultLayout: schedule.schedule.default,
durations: durations,
});
// Returns:
// [
// { layoutFile: '10.xlf', startTime, endTime, duration: 30, isDefault: false },
// { layoutFile: '11.xlf', startTime, endTime, duration: 30, isDefault: false },
// ...
// ]
When getCurrentLayouts() is called:
maxPlaysPerHourThe queue is a pre-computed, deterministic round-robin cycle:
maxPlaysPerHour intervals (capped at 2 hours)| Type | Pattern | Example |
|---|---|---|
| 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.
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
"lat,lng,radius" (e.g., "37.7749,-122.4194,500")new ScheduleManager(options?)
| Option | Type | Description |
|---|---|---|
interruptScheduler | InterruptScheduler? | Optional interrupt handler |
displayProperties | Object? | Custom display fields from CMS |
| Method | Returns | Description |
|---|---|---|
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 |
| Method | Returns | Description |
|---|---|---|
setOverlays(overlays) | void | Update overlay list |
getCurrentOverlays() | Array | Active overlays (sorted by priority) |
shouldCheckOverlays(lastCheck) | boolean | Check interval (every 60s) |
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 });
No external dependencies -- fully self-contained scheduling engine.
@xiboplayer/utils -- logging onlyxiboplayer.org · Part of the XiboPlayer SDK
FAQs
Complete scheduling solution: campaigns, dayparting, interrupts, and overlays
We found that @xiboplayer/schedule demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.

Company News
Allow myself to introduce... myself.