Multi-Display Sync
XiboPlayer is the only Xibo player with cross-device synchronization. Multiple screens — same machine or across a LAN — stay in perfect sync: coordinated layout transitions, synchronized video start, and automatic follower discovery.
Two Sync Modes
Mirror Mode (default)
All displays show the same layout at the same time. The lead decides which layout to show and all followers load the same content.
Use case: multiple screens in a lobby, retail store, or airport — all showing identical content with perfectly synchronized transitions.
Lead shows layout 42 ──→ Follower 1 shows layout 42
──→ Follower 2 shows layout 42
──→ Follower 3 shows layout 42
Wall Mode (with layoutMap)
Each display shows a different layout per position, but transitions are coordinated by the lead. This is how you build a video wall where each screen shows a portion of a larger composition.
You design individual layouts for each screen position in the CMS, then map them in the follower's config:
Lead shows layout 42 ──→ Follower 1 maps 42 → 101 (top-right)
──→ Follower 2 maps 42 → 102 (bottom-left)
──→ Follower 3 maps 42 → 103 (bottom-right)
Follower config.json (sync.layoutMap):
{
"cmsUrl": "https://cms.example.com",
"cmsKey": "yourKey",
"displayName": "lobby-top-right",
"sync": {
"layoutMap": {
"42": 101,
"43": 105,
"44": 109
}
}
}
Each key is the lead's layout ID, each value is this display's corresponding layout ID. If a layout isn't in the map, the display falls back to mirror mode for that layout.
How to set up a video wall:
- In the CMS, design N layouts (one per screen position) — e.g., layout 42 (lead/top-left), 101 (top-right), 102 (bottom-left), 103 (bottom-right)
- Schedule layout 42 on the lead — it drives the timing
- Schedule layouts 101, 102, 103 on followers so they're downloaded (or rely on the lead's schedule to push them via
RequiredFiles) - Add
sync.layoutMapto each follower'sconfig.json - All screens transition at the same moment, each showing its position-specific content
How It Works
Lead Player (Screen 1)
┌────────────────────────┐
│ SyncManager (lead) │
│ ┌──────────────────┐ │
│ │ Proxy Server │ │
│ │ ┌────────────┐ │ │
│ │ │ Sync Relay │ │ │
│ │ │ /sync WS │ │ │
│ │ └─────┬──────┘ │ │
│ └────────┼──────────┘ │
└───────────┼──────────────┘
│
┌─────────────────┼─────────────────┐
│ WebSocket │ WebSocket │ WebSocket
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Follower 1 │ │ Follower 2 │ │ Follower 3 │
│ (Screen 2) │ │ (Screen 3) │ │ (Screen 4) │
│ SyncManager │ │ SyncManager │ │ SyncManager │
└─────────────────┘ └─────────────────┘ └─────────────────┘
The lead player orchestrates layout transitions. Its proxy server includes a built-in WebSocket relay — followers connect to it over the LAN, no additional infrastructure needed.
Two Transport Layers
| Transport | Scope | Use Case | How |
|---|---|---|---|
| BroadcastChannel | Same machine | Multiple tabs or windows on one device | Browser-native API, zero config |
| WebSocket relay | Cross-device LAN | Separate devices on the same network | Connects to lead's proxy at /sync |
The transport is auto-selected based on configuration. If relayUrl is set, WebSocket is used; otherwise BroadcastChannel.
Group Isolation
When multiple display groups share the same relay server, group isolation ensures messages stay within their group. Each display declares its sync group on connect, and the relay only broadcasts to peers in the same group.
Relay Server (port 9590)
├── Group "lobby"
│ ├── lobby-lead (Screen 1)
│ ├── lobby-left (Screen 2)
│ └── lobby-right (Screen 3)
│
└── Group "cafeteria"
├── cafe-lead (Screen 4)
└── cafe-right (Screen 5)
A layout change on lobby-lead reaches lobby-left and lobby-right but never cafe-lead or cafe-right.
Backward compatible: Displays that don't declare a group fall back to broadcast-to-all behavior.
Sync Protocol
Lead Follower(s)
──── ──────────
layout-change(layoutId, showAt) → receives, loads layout (or mapped layout)
← layout-ready(layoutId, displayId)
(waits for all followers ready)
layout-show(layoutId) → shows layout simultaneously
video-start(layoutId, regionId) → unpauses video simultaneously
- Heartbeat: every 5s, each node broadcasts its presence. The lead tracks active followers.
- Stale detection: if a follower goes silent for 15s, it's excluded from the ready-wait.
- Ready timeout: the lead waits up to 10s for followers, then proceeds without unresponsive ones.
- Stats delegation: followers send their stats/logs to the lead, which submits them to the CMS on their behalf.
Configuration Examples
Same-machine sync (two tabs)
No special config needed. Open the player in two browser tabs — BroadcastChannel handles sync automatically. Both tabs show the same content (mirror mode).
Cross-device mirrored displays (3 screens on LAN)
Lead player (config.json on 192.168.1.100):
{
"cmsUrl": "https://cms.example.com",
"cmsKey": "yourKey",
"displayName": "lobby-lead",
"serverPort": 8765
}
The lead's sync group is set by the CMS via RegisterDisplay. The proxy server at :8765/sync acts as the relay.
Follower 1 (config.json on 192.168.1.101):
{
"cmsUrl": "https://cms.example.com",
"cmsKey": "yourKey",
"displayName": "lobby-left"
}
All three screens show the same content with synchronized transitions.
Video wall (2x2 grid, position-specific content)
Lead (top-left) — normal config, runs the schedule:
{
"displayName": "wall-top-left",
"serverPort": 8765
}
Follower (top-right) — maps lead layouts to its position:
{
"displayName": "wall-top-right",
"sync": {
"layoutMap": {
"42": 101,
"43": 105
}
}
}
Follower (bottom-left):
{
"displayName": "wall-bottom-left",
"sync": {
"layoutMap": {
"42": 102,
"43": 106
}
}
}
Follower (bottom-right):
{
"displayName": "wall-bottom-right",
"sync": {
"layoutMap": {
"42": 103,
"43": 107
}
}
}
When the lead transitions from layout 42 to layout 43, all four screens transition simultaneously — each to its own position-specific layout.
Standalone relay server
For large deployments, run a dedicated relay instead of using the lead's proxy:
# Install
npm install @xiboplayer/proxy
# Run standalone relay
npx xiboplayer-relay --port=9590
Or with the RPM package:
xiboplayer-relay --port=9590
The relay is a lightweight WebSocket hub (~100 lines) with no CMS dependency. It handles group isolation, heartbeat monitoring, and stale client cleanup.
Multiple display groups, one relay
Configure each group's displays with the same syncGroup in the CMS. The relay isolates messages by group automatically:
Group "lobby" → syncGroup: "lobby" → relay at relay.lan:9590
Group "cafeteria"→ syncGroup: "cafeteria"→ relay at relay.lan:9590
Features
- Mirror mode — all screens show the same layout (default)
- Wall mode — each screen shows a position-specific layout via
layoutMap - Coordinated layout transitions — all screens change layout simultaneously
- <8ms sync precision — measured across 4 Chromium displays on LAN
- 12 choreography effects — dramatic cascading transitions across the wall (v0.7.0)
- Token-authenticated relay — shared CMS key secures WebSocket connections (v0.7.0)
- Synchronized video start — videos unpause at the same moment across all screens
- Automatic follower discovery — heartbeat-based, no manual registration
- Stats delegation — followers route stats/logs through the lead to the CMS
- Group isolation — multiple display groups share one relay via
syncGroupId(v0.7.0) - Offline LAN sync — persisted config enables sync without CMS connectivity (v0.7.0)
- Auto-reconnect — exponential backoff (1s to 30s) with automatic group re-join
- Graceful degradation — lead proceeds after timeout if a follower is unresponsive
- Transport-agnostic — same SyncManager API for BroadcastChannel and WebSocket
- Works on Electron and Chromium — both kiosk platforms fully tested (v0.7.0)
Choreography Effects (v0.7.0)
When displays transition between layouts, choreography controls the cascade pattern. Each display shows the new layout at a staggered time based on its grid position.
| Effect | Description |
|---|---|
simultaneous | All displays switch at once (no stagger) |
wave-right | Sweep left → right |
wave-left | Sweep right → left |
wave-down | Sweep top → bottom |
wave-up | Sweep bottom → top |
diagonal-tl | Cascade from top-left corner |
diagonal-tr | Cascade from top-right corner |
diagonal-bl | Cascade from bottom-left corner |
diagonal-br | Cascade from bottom-right corner |
center-out | Center displays first, edges last |
outside-in | Edges first, center last |
random | Random delay per display |
Example: 2×2 grid with diagonal-tl at 200ms stagger:
┌──────┬──────┐
│ 0ms │200ms │
├──────┼──────┤
│200ms │400ms │
└──────┴──────┘
Configure per display in config.json:
{
"sync": {
"topology": { "x": 0, "y": 0, "orientation": 0 },
"choreography": "diagonal-tl",
"staggerMs": 200,
"gridCols": 2,
"gridRows": 2
}
}
Comparison with Other Players
| Feature | Xibo Windows v4 | Xibo XLR | Arexibo | XiboPlayer |
|---|---|---|---|---|
| Same-machine sync | No | BroadcastChannel | No | BroadcastChannel |
| Cross-device sync | ZeroMQ | No | No | WebSocket relay |
| Sync precision | ~50-100ms | N/A | N/A | <8ms |
| Choreography effects | None | None | None | 12 effects |
| Mirror mode | Yes | Yes (same-tab) | No | Yes (LAN + same-machine) |
| Wall mode | Yes | No | No | Position-specific layouts |
| Group isolation | Yes | N/A | N/A | syncGroupId routing |
| Token auth | No | N/A | N/A | CMS key auth |
| Offline LAN sync | No | No | No | Persisted config |
| Standalone relay | No | N/A | N/A | xiboplayer-relay CLI |
| Stats delegation | No | Yes (BC only) | No | Yes (BC + WebSocket) |
| Auto-reconnect | No | N/A | N/A | Exponential backoff |
| Video start sync | Yes | No | No | Coordinated unpause |
| Platform | Windows only | Browser (Chrome) | Android | Any (Electron, Chromium, Android, webOS) |
| Firewall friendly | ZeroMQ ports | N/A | N/A | Uses existing HTTP port |
XiboPlayer is the most capable Xibo player for multi-display deployments. It's the only player with choreography effects, sub-10ms sync precision, token auth, and cross-platform support.
Architecture
The sync system is split across two SDK packages:
- @xiboplayer/sync —
SyncManager(protocol logic),BroadcastChannelTransport,WebSocketTransport - @xiboplayer/proxy —
attachSyncRelay()(server-side relay),xiboplayer-relayCLI
The transport interface is pluggable:
interface SyncTransport {
send(msg: object): void;
onMessage(callback: (msg: object) => void): void;
close(): void;
readonly connected: boolean;
}
Custom transports (e.g., WebRTC for ultra-low-latency) can be injected via the transport constructor option.
