
Security News
Agent Baseline Publishes Security Reference Architecture for Software Factories
Agent Baseline’s new reference architecture shows how to secure software factories with four boundaries enforced outside the AI agent.
@xiboplayer/sync
Advanced tools
Multi-display synchronization for Xibo video walls — same-machine and cross-device. New in v0.7.0, zero-config mDNS discovery in v0.7.1.
Coordinates layout transitions and video playback across multiple displays with <8ms precision:
Both modes share the same sync protocol — only the transport layer differs.
syncGroupIdSame-machine (BroadcastChannel): Cross-device (WebSocket relay):
Tab 1 (Lead) Tab 2 (Follower) PC 1 (Lead) PC 2 (Follower)
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│SyncMgr │ │SyncMgr │ │SyncMgr │ │SyncMgr │
│ └─BC │◄──►│ └─BC │ │ └─WS │ │ └─WS │
└──────────┘ └──────────┘ └────┬─────┘ └────┬─────┘
BroadcastChannel │ │
▼ │
┌────────────┐ │
│Proxy :8765 │◄────────────┘
│ └─SyncRelay│ (LAN WebSocket)
└────────────┘
The relay is a dumb pipe — it broadcasts each message to all other connected clients. The sync protocol (heartbeats, ready-waits, layout changes) runs entirely in SyncManager.
npm install @xiboplayer/sync
No extra configuration needed. When multiple tabs/windows run on the same origin, BroadcastChannel handles message passing automatically.
import { SyncManager } from '@xiboplayer/sync';
// Lead display
const lead = new SyncManager({
displayId: 'screen-1',
syncConfig: { isLead: true, syncGroup: 'lead', syncSwitchDelay: 750 },
onLayoutShow: (layoutId) => renderer.show(layoutId),
});
lead.start();
// Request synchronized layout change (waits for followers)
await lead.requestLayoutChange('42');
// Follower display (different tab)
const follower = new SyncManager({
displayId: 'screen-2',
syncConfig: { isLead: false, syncGroup: '192.168.1.100', syncSwitchDelay: 750 },
onLayoutChange: async (layoutId) => {
await renderer.prepareLayout(layoutId);
follower.reportReady(layoutId);
},
onLayoutShow: (layoutId) => renderer.show(layoutId),
});
follower.start();
When syncGroup is an IP address (not "lead") and syncPublisherPort is set, the PWA automatically builds a WebSocket relay URL. The lead connects to its own proxy at ws://localhost:<port>/sync; followers connect to ws://<lead-ip>:<port>/sync.
Lead config.json (e.g. ~/.config/xiboplayer/electron/config.json):
{
"cmsUrl": "https://cms.example.com",
"cmsKey": "yourKey",
"displayName": "videowall-lead",
"listenAddress": "0.0.0.0"
}
The listenAddress: "0.0.0.0" makes the proxy reachable from the LAN. The CMS sync settings (syncGroup, syncPublisherPort) are sent via the RegisterDisplay response.
In v0.7.1+, the listenAddress: "0.0.0.0" is no longer needed — the lead automatically binds to 0.0.0.0 when sync.isLead is true.
More importantly, followers no longer need the lead's IP address from the CMS. The lead advertises its relay via mDNS (_xibo-sync._tcp), and followers discover it automatically by matching syncGroupId. This means:
The mDNS discovery is transparent — it happens automatically in the PWA's sync-config handler.
CMS Display Settings:
| Setting | Lead | Follower |
|---|---|---|
| Sync Group | lead | (auto-discovered via mDNS in v0.7.1+) |
| Sync Publisher Port | 8765 | 8765 |
The SyncManager detects this configuration and selects the WebSocket transport:
// This happens automatically in packages/pwa/src/main.ts:
if (syncConfig.syncPublisherPort && syncConfig.syncGroup !== 'lead') {
const host = syncConfig.isLead ? 'localhost' : syncConfig.syncGroup;
syncConfig.relayUrl = `ws://${host}:${syncConfig.syncPublisherPort}/sync`;
}
For testing or custom setups, you can inject any object that implements the transport interface:
const transport = {
send(msg) { /* ... */ },
onMessage(callback) { /* ... */ },
close() { /* ... */ },
get connected() { return true; },
};
const sync = new SyncManager({
displayId: 'test-1',
syncConfig: { isLead: true },
transport,
});
sync.start();
Both BroadcastChannelTransport and WebSocketTransport implement:
interface SyncTransport {
send(msg: any): void; // Send message to peers
onMessage(cb: (msg) => void); // Register message handler
close(): void; // Clean up resources
readonly connected: boolean; // Connection status
}
Lead Follower(s)
──── ──────────
heartbeat (every 5s) → discovers peers
layout-change(layoutId, showAt) → loads layout, prepares DOM
← layout-ready(layoutId, displayId)
(waits for all or timeout 10s)
layout-show(layoutId) → shows layout simultaneously
video-start(layoutId, regionId) → unpauses video
stats-report / logs-report ← delegates stats to lead
stats-ack / logs-ack → confirms submission
┌─────────────┬─────────────┐
│ Screen 1 │ Screen 2 │
│ (LEAD) │ (follower) │
│ 192.168.1.10│ 192.168.1.11│
├─────────────┼─────────────┤
│ Screen 3 │ Screen 4 │
│ (follower) │ (follower) │
│ 192.168.1.12│ 192.168.1.13│
└─────────────┴─────────────┘
CMS setup: Create 4 displays. Set Screen 1's sync group to lead. Set Screens 2-4's sync group to 192.168.1.10. Set sync publisher port to 8765 on all four.
Screen 1 config.json: Add "listenAddress": "0.0.0.0" so the proxy listens on all interfaces.
All four screens run the same Electron/Chromium player. The lead drives layout transitions; followers load content in parallel and show simultaneously when all are ready.
new SyncManager(options)| Option | Type | Description |
|---|---|---|
displayId | string | This display's unique hardware key |
syncConfig | SyncConfig | Sync configuration from CMS RegisterDisplay |
transport | SyncTransport? | Optional pre-built transport (for testing) |
onLayoutChange | Function? | Called when lead requests layout change |
onLayoutShow | Function? | Called when lead gives show signal |
onVideoStart | Function? | Called when lead gives video start signal |
onStatsReport | Function? | (Lead) Called when follower sends stats |
onLogsReport | Function? | (Lead) Called when follower sends logs |
onStatsAck | Function? | (Follower) Called when lead confirms stats |
onLogsAck | Function? | (Follower) Called when lead confirms logs |
| Method | Role | Description |
|---|---|---|
start() | Both | Opens transport, begins heartbeats |
stop() | Both | Closes transport, clears timers |
requestLayoutChange(layoutId) | Lead | Sends layout-change, waits for ready, sends show |
requestVideoStart(layoutId, regionId) | Lead | Signals synchronized video start |
reportReady(layoutId) | Follower | Reports layout is loaded and ready |
reportStats(statsXml) | Follower | Delegates stats submission to lead |
reportLogs(logsXml) | Follower | Delegates logs submission to lead |
getStatus() | Both | Returns sync status including follower details |
xiboplayer.org · Part of the XiboPlayer SDK
FAQs
Multi-display synchronization for xiboplayer
The npm package @xiboplayer/sync receives a total of 5 weekly downloads. As such, @xiboplayer/sync popularity was classified as not popular.
We found that @xiboplayer/sync 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.

Security News
Agent Baseline’s new reference architecture shows how to secure software factories with four boundaries enforced outside the AI agent.

Security News
arXiv now limits authors to two submissions a month as AI slop overwhelms moderators, delays good papers, and sparks debate over applying the limit to everyone.

Research
/Security News
A new GhostAction wave hits hundreds of GitHub repos, expanding CI/CD secret theft to cloud and AI credentials in source code and git history.