npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xiboplayer/schedule

v0.7.16

Published

Complete scheduling solution: campaigns, dayparting, interrupts, and overlays

Readme

@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', // 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)

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', // 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)

Rate limiting with even distribution

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)

Interrupts (Share of Voice)

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, ...

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 });
// Layout 1 displays only when temperature > 25

Geo-fencing

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

Timeline prediction

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 },
//   ...
// ]

Campaign Evaluation Algorithm

When getCurrentLayouts() is called:

  1. Filter time-active items -- campaigns and standalone layouts within their date/time window and recurrence rules
  2. Apply criteria -- filter by weather, display properties, geo-fencing
  3. Apply rate limiting -- exclude layouts that exceeded maxPlaysPerHour
  4. Find max priority -- only max priority items win
  5. Extract layouts -- campaigns return all their layouts; standalone layouts contribute themselves
  6. Process interrupts -- separate interrupt layouts, calculate share-of-voice, interleave
  7. 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

| 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.

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?)

| Option | Type | Description | |--------|------|-------------| | interruptScheduler | InterruptScheduler? | Optional interrupt handler | | displayProperties | Object? | Custom display fields from CMS |

Methods

| 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 |

Overlay Methods

| Method | Returns | Description | |--------|---------|-------------| | 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