
Security News
arXiv Is Rate Limiting Authors Following a Flood of AI Slop Submissions
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.
@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 16 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
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.

Research
/Security News
Tensorlake npm SDK version 0.5.144 was compromised in a ChainDrop / Shai-Hulud attack, delivering credential-stealing malware.