@xiboplayer/sync 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -2
- package/src/sync-manager.js +21 -18
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiboplayer/sync",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Multi-display synchronization for Xibo Player",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/sync-manager.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/sync-manager.js"
|
|
9
9
|
},
|
|
10
|
-
"dependencies": {
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@xiboplayer/utils": "0.4.0"
|
|
12
|
+
},
|
|
11
13
|
"devDependencies": {
|
|
12
14
|
"vitest": "^2.0.0"
|
|
13
15
|
},
|
package/src/sync-manager.js
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
* @property {boolean} isLead - Whether this display is the leader
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
+
import { createLogger } from '@xiboplayer/utils';
|
|
33
|
+
|
|
32
34
|
const CHANNEL_NAME = 'xibo-sync';
|
|
33
35
|
const HEARTBEAT_INTERVAL = 5000; // Send heartbeat every 5s
|
|
34
36
|
const FOLLOWER_TIMEOUT = 15000; // Consider follower offline after 15s silence
|
|
@@ -64,8 +66,9 @@ export class SyncManager {
|
|
|
64
66
|
this._pendingLayoutId = null; // Layout we're waiting for readiness on
|
|
65
67
|
this._started = false;
|
|
66
68
|
|
|
67
|
-
//
|
|
69
|
+
// Logger with role prefix for clarity in multi-tab console
|
|
68
70
|
this._tag = this.isLead ? '[Sync:LEAD]' : '[Sync:FOLLOW]';
|
|
71
|
+
this._log = createLogger(this.isLead ? 'Sync:LEAD' : 'Sync:FOLLOW');
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
/**
|
|
@@ -76,7 +79,7 @@ export class SyncManager {
|
|
|
76
79
|
this._started = true;
|
|
77
80
|
|
|
78
81
|
if (typeof BroadcastChannel === 'undefined') {
|
|
79
|
-
|
|
82
|
+
this._log.warn( 'BroadcastChannel not available — sync disabled');
|
|
80
83
|
return;
|
|
81
84
|
}
|
|
82
85
|
|
|
@@ -92,7 +95,7 @@ export class SyncManager {
|
|
|
92
95
|
this._cleanupTimer = setInterval(() => this._cleanupStaleFollowers(), HEARTBEAT_INTERVAL);
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
|
|
98
|
+
this._log.info( 'Started. DisplayId:', this.displayId);
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
/**
|
|
@@ -116,7 +119,7 @@ export class SyncManager {
|
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
this.followers.clear();
|
|
119
|
-
|
|
122
|
+
this._log.info( 'Stopped');
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
// ── Lead API ──────────────────────────────────────────────────────
|
|
@@ -130,7 +133,7 @@ export class SyncManager {
|
|
|
130
133
|
*/
|
|
131
134
|
async requestLayoutChange(layoutId) {
|
|
132
135
|
if (!this.isLead) {
|
|
133
|
-
|
|
136
|
+
this._log.warn( 'requestLayoutChange called on follower — ignoring');
|
|
134
137
|
return;
|
|
135
138
|
}
|
|
136
139
|
|
|
@@ -145,7 +148,7 @@ export class SyncManager {
|
|
|
145
148
|
|
|
146
149
|
const showAt = Date.now() + this.switchDelay;
|
|
147
150
|
|
|
148
|
-
|
|
151
|
+
this._log.info( `Requesting layout change: ${layoutId} (show at ${new Date(showAt).toISOString()}, ${this.followers.size} followers)`);
|
|
149
152
|
|
|
150
153
|
// Broadcast layout-change to all followers
|
|
151
154
|
this._send({
|
|
@@ -167,7 +170,7 @@ export class SyncManager {
|
|
|
167
170
|
}
|
|
168
171
|
|
|
169
172
|
// Send show signal
|
|
170
|
-
|
|
173
|
+
this._log.info( `Sending layout-show: ${layoutId}`);
|
|
171
174
|
this._send({
|
|
172
175
|
type: 'layout-show',
|
|
173
176
|
layoutId,
|
|
@@ -214,7 +217,7 @@ export class SyncManager {
|
|
|
214
217
|
reportReady(layoutId) {
|
|
215
218
|
layoutId = String(layoutId);
|
|
216
219
|
|
|
217
|
-
|
|
220
|
+
this._log.info( `Reporting ready for layout ${layoutId}`);
|
|
218
221
|
|
|
219
222
|
this._send({
|
|
220
223
|
type: 'layout-ready',
|
|
@@ -238,7 +241,7 @@ export class SyncManager {
|
|
|
238
241
|
case 'layout-change':
|
|
239
242
|
// Follower: lead is requesting a layout change
|
|
240
243
|
if (!this.isLead) {
|
|
241
|
-
|
|
244
|
+
this._log.info( `Layout change requested: ${msg.layoutId}`);
|
|
242
245
|
this.onLayoutChange(msg.layoutId, msg.showAt);
|
|
243
246
|
}
|
|
244
247
|
break;
|
|
@@ -253,7 +256,7 @@ export class SyncManager {
|
|
|
253
256
|
case 'layout-show':
|
|
254
257
|
// Follower: lead says show now
|
|
255
258
|
if (!this.isLead) {
|
|
256
|
-
|
|
259
|
+
this._log.info( `Layout show signal: ${msg.layoutId}`);
|
|
257
260
|
this.onLayoutShow(msg.layoutId);
|
|
258
261
|
}
|
|
259
262
|
break;
|
|
@@ -261,13 +264,13 @@ export class SyncManager {
|
|
|
261
264
|
case 'video-start':
|
|
262
265
|
// Follower: lead says start video
|
|
263
266
|
if (!this.isLead) {
|
|
264
|
-
|
|
267
|
+
this._log.info( `Video start signal: ${msg.layoutId} region ${msg.regionId}`);
|
|
265
268
|
this.onVideoStart(msg.layoutId, msg.regionId);
|
|
266
269
|
}
|
|
267
270
|
break;
|
|
268
271
|
|
|
269
272
|
default:
|
|
270
|
-
|
|
273
|
+
this._log.warn( 'Unknown message type:', msg.type);
|
|
271
274
|
}
|
|
272
275
|
}
|
|
273
276
|
|
|
@@ -284,7 +287,7 @@ export class SyncManager {
|
|
|
284
287
|
readyLayoutId: null,
|
|
285
288
|
role: msg.role || 'unknown',
|
|
286
289
|
});
|
|
287
|
-
|
|
290
|
+
this._log.info( `Follower joined: ${msg.displayId} (${this.followers.size} total)`);
|
|
288
291
|
}
|
|
289
292
|
}
|
|
290
293
|
|
|
@@ -304,12 +307,12 @@ export class SyncManager {
|
|
|
304
307
|
follower.lastSeen = Date.now();
|
|
305
308
|
}
|
|
306
309
|
|
|
307
|
-
|
|
310
|
+
this._log.info( `Follower ${msg.displayId} ready for layout ${msg.layoutId}`);
|
|
308
311
|
|
|
309
312
|
// Check if all followers are now ready
|
|
310
313
|
if (this._pendingLayoutId === msg.layoutId && this._readyResolve) {
|
|
311
314
|
if (this._allFollowersReady(msg.layoutId)) {
|
|
312
|
-
|
|
315
|
+
this._log.info( 'All followers ready');
|
|
313
316
|
this._readyResolve();
|
|
314
317
|
this._readyResolve = null;
|
|
315
318
|
}
|
|
@@ -348,7 +351,7 @@ export class SyncManager {
|
|
|
348
351
|
notReady.push(id);
|
|
349
352
|
}
|
|
350
353
|
}
|
|
351
|
-
|
|
354
|
+
this._log.warn( `Ready timeout — proceeding without: ${notReady.join(', ')}`);
|
|
352
355
|
this._readyResolve = null;
|
|
353
356
|
resolve();
|
|
354
357
|
}
|
|
@@ -373,7 +376,7 @@ export class SyncManager {
|
|
|
373
376
|
const now = Date.now();
|
|
374
377
|
for (const [id, follower] of this.followers) {
|
|
375
378
|
if (now - follower.lastSeen > FOLLOWER_TIMEOUT) {
|
|
376
|
-
|
|
379
|
+
this._log.info( `Removing stale follower: ${id} (last seen ${Math.round((now - follower.lastSeen) / 1000)}s ago)`);
|
|
377
380
|
this.followers.delete(id);
|
|
378
381
|
}
|
|
379
382
|
}
|
|
@@ -385,7 +388,7 @@ export class SyncManager {
|
|
|
385
388
|
try {
|
|
386
389
|
this.channel.postMessage(msg);
|
|
387
390
|
} catch (e) {
|
|
388
|
-
|
|
391
|
+
this._log.error( 'Failed to send:', e);
|
|
389
392
|
}
|
|
390
393
|
}
|
|
391
394
|
|