@xiboplayer/schedule 0.7.2 → 0.7.4
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 +3 -3
- package/src/overlays.js +16 -22
- package/src/overlays.test.js +8 -11
- package/src/schedule.js +10 -44
- package/src/timeline.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiboplayer/schedule",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "Complete scheduling solution: campaigns, dayparting, interrupts, and overlays",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
"./overlays": "./src/overlays.js"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@xiboplayer/utils": "0.7.
|
|
15
|
+
"@xiboplayer/utils": "0.7.4"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"vitest": "^2.
|
|
18
|
+
"vitest": "^2.1.9"
|
|
19
19
|
},
|
|
20
20
|
"keywords": [
|
|
21
21
|
"xibo",
|
package/src/overlays.js
CHANGED
|
@@ -111,23 +111,29 @@ export class OverlayScheduler {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
|
-
* Check if overlay is within its time window
|
|
114
|
+
* Check if overlay is within its time window.
|
|
115
|
+
* Delegates to ScheduleManager.isTimeActive() which handles both
|
|
116
|
+
* simple date ranges and recurring schedule dayparting.
|
|
117
|
+
* Falls back to basic date-range check if no scheduleManager is set.
|
|
118
|
+
*
|
|
115
119
|
* @param {Object} overlay - Overlay object
|
|
116
120
|
* @param {Date} now - Current time
|
|
117
121
|
* @returns {boolean}
|
|
118
122
|
*/
|
|
119
123
|
isTimeActive(overlay, now) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
if (to && now > to) {
|
|
128
|
-
return false;
|
|
124
|
+
if (this.scheduleManager) {
|
|
125
|
+
// Normalize fromDt → fromdt for ScheduleManager compatibility
|
|
126
|
+
const normalized = { ...overlay };
|
|
127
|
+
if (!normalized.fromdt && normalized.fromDt) normalized.fromdt = normalized.fromDt;
|
|
128
|
+
if (!normalized.todt && normalized.toDt) normalized.todt = normalized.toDt;
|
|
129
|
+
return this.scheduleManager.isTimeActive(normalized, now);
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
// Fallback: basic date-range check (no scheduleManager available)
|
|
133
|
+
const from = (overlay.fromdt || overlay.fromDt) ? new Date(overlay.fromdt || overlay.fromDt) : null;
|
|
134
|
+
const to = (overlay.todt || overlay.toDt) ? new Date(overlay.todt || overlay.toDt) : null;
|
|
135
|
+
if (from && now < from) return false;
|
|
136
|
+
if (to && now > to) return false;
|
|
131
137
|
return true;
|
|
132
138
|
}
|
|
133
139
|
|
|
@@ -159,18 +165,6 @@ export class OverlayScheduler {
|
|
|
159
165
|
logger.debug('Cleared all overlays');
|
|
160
166
|
}
|
|
161
167
|
|
|
162
|
-
/**
|
|
163
|
-
* Process overlay layouts (compatibility method for interrupt scheduler pattern)
|
|
164
|
-
* @param {Array} layouts - Base layouts
|
|
165
|
-
* @param {Array} overlays - Overlay layouts
|
|
166
|
-
* @returns {Array} Layouts (unchanged, overlays are separate)
|
|
167
|
-
*/
|
|
168
|
-
processOverlays(layouts, overlays) {
|
|
169
|
-
// Overlays don't modify the main layout loop
|
|
170
|
-
// They are rendered separately on top
|
|
171
|
-
this.setOverlays(overlays);
|
|
172
|
-
return layouts;
|
|
173
|
-
}
|
|
174
168
|
}
|
|
175
169
|
|
|
176
170
|
export const overlayScheduler = new OverlayScheduler();
|
package/src/overlays.test.js
CHANGED
|
@@ -162,6 +162,13 @@ describe('OverlayScheduler', () => {
|
|
|
162
162
|
it('filters by geo-fence when isGeoAware', () => {
|
|
163
163
|
const mockScheduleManager = {
|
|
164
164
|
isWithinGeoFence: () => false,
|
|
165
|
+
isTimeActive: (item, now) => {
|
|
166
|
+
const from = item.fromdt ? new Date(item.fromdt) : null;
|
|
167
|
+
const to = item.todt ? new Date(item.todt) : null;
|
|
168
|
+
if (from && now < from) return false;
|
|
169
|
+
if (to && now > to) return false;
|
|
170
|
+
return true;
|
|
171
|
+
},
|
|
165
172
|
};
|
|
166
173
|
scheduler.setScheduleManager(mockScheduleManager);
|
|
167
174
|
|
|
@@ -219,7 +226,7 @@ describe('OverlayScheduler', () => {
|
|
|
219
226
|
});
|
|
220
227
|
});
|
|
221
228
|
|
|
222
|
-
// ── clear
|
|
229
|
+
// ── clear ───────────────────────────────────
|
|
223
230
|
|
|
224
231
|
describe('clear', () => {
|
|
225
232
|
it('removes all overlays', () => {
|
|
@@ -228,14 +235,4 @@ describe('OverlayScheduler', () => {
|
|
|
228
235
|
expect(scheduler.overlays).toHaveLength(0);
|
|
229
236
|
});
|
|
230
237
|
});
|
|
231
|
-
|
|
232
|
-
describe('processOverlays', () => {
|
|
233
|
-
it('sets overlays and returns layouts unchanged', () => {
|
|
234
|
-
const layouts = [{ id: 1 }, { id: 2 }];
|
|
235
|
-
const overlays = [makeOverlay({ file: 10 })];
|
|
236
|
-
const result = scheduler.processOverlays(layouts, overlays);
|
|
237
|
-
expect(result).toBe(layouts);
|
|
238
|
-
expect(scheduler.overlays).toHaveLength(1);
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
238
|
});
|
package/src/schedule.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { createLogger } from '@xiboplayer/utils';
|
|
8
8
|
import { evaluateCriteria } from './criteria.js';
|
|
9
|
-
import { buildScheduleQueue, parseLayoutFile } from './timeline.js';
|
|
9
|
+
import { buildScheduleQueue, canSimulatedPlay, parseLayoutFile } from './timeline.js';
|
|
10
10
|
|
|
11
11
|
const log = createLogger('Schedule');
|
|
12
12
|
|
|
@@ -531,41 +531,8 @@ export class ScheduleManager {
|
|
|
531
531
|
* @returns {boolean} True if layout can play, false if exceeded limit
|
|
532
532
|
*/
|
|
533
533
|
canPlayLayout(layoutId, maxPlaysPerHour) {
|
|
534
|
-
// If maxPlaysPerHour is 0 or undefined, unlimited plays
|
|
535
|
-
if (!maxPlaysPerHour || maxPlaysPerHour === 0) {
|
|
536
|
-
return true;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
const now = Date.now();
|
|
540
|
-
const oneHourAgo = now - (60 * 60 * 1000);
|
|
541
|
-
|
|
542
|
-
// Get play history for this layout
|
|
543
534
|
const history = this.playHistory.get(layoutId) || [];
|
|
544
|
-
|
|
545
|
-
// Filter to plays within the last hour
|
|
546
|
-
const playsInLastHour = history.filter(timestamp => timestamp > oneHourAgo);
|
|
547
|
-
|
|
548
|
-
// Check 1: Total plays in last hour must be under limit
|
|
549
|
-
if (playsInLastHour.length >= maxPlaysPerHour) {
|
|
550
|
-
log.info(`Layout ${layoutId} has reached max plays per hour (${playsInLastHour.length}/${maxPlaysPerHour})`);
|
|
551
|
-
return false;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// Check 2: Minimum gap between plays for even distribution
|
|
555
|
-
// e.g., 3/hour → 1 every 20 min, 6/hour → 1 every 10 min
|
|
556
|
-
if (playsInLastHour.length > 0) {
|
|
557
|
-
const minGapMs = (60 * 60 * 1000) / maxPlaysPerHour;
|
|
558
|
-
const lastPlayTime = Math.max(...playsInLastHour);
|
|
559
|
-
const elapsed = now - lastPlayTime;
|
|
560
|
-
|
|
561
|
-
if (elapsed < minGapMs) {
|
|
562
|
-
const remainingMin = ((minGapMs - elapsed) / 60000).toFixed(1);
|
|
563
|
-
log.info(`Layout ${layoutId} spacing: next play in ${remainingMin} min (${playsInLastHour.length}/${maxPlaysPerHour} plays, ${Math.round(minGapMs/60000)} min gap)`);
|
|
564
|
-
return false;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
return true;
|
|
535
|
+
return canSimulatedPlay(history, maxPlaysPerHour, Date.now());
|
|
569
536
|
}
|
|
570
537
|
|
|
571
538
|
/**
|
|
@@ -588,15 +555,6 @@ export class ScheduleManager {
|
|
|
588
555
|
log.info(`Recorded play for layout ${layoutId} (${cleaned.length} plays in last hour)`);
|
|
589
556
|
}
|
|
590
557
|
|
|
591
|
-
/**
|
|
592
|
-
* Get the max priority of any time-active layout (ignoring rate-limit filtering).
|
|
593
|
-
* Returns 0 if no layouts are active or if getCurrentLayouts() hasn't been called.
|
|
594
|
-
* @returns {number}
|
|
595
|
-
*/
|
|
596
|
-
getMaxActivePriority() {
|
|
597
|
-
return this._maxActivePriority || 0;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
558
|
/**
|
|
601
559
|
* Check if a layout file is a sync event (part of multi-display sync group)
|
|
602
560
|
* @param {string} layoutFile - Layout file identifier (e.g., '123')
|
|
@@ -736,6 +694,14 @@ export class ScheduleManager {
|
|
|
736
694
|
return queue[(this._queuePosition + 1) % queue.length];
|
|
737
695
|
}
|
|
738
696
|
|
|
697
|
+
/**
|
|
698
|
+
* Public API to invalidate the cached schedule queue.
|
|
699
|
+
* Called by PlayerCore when layout durations are corrected at runtime.
|
|
700
|
+
*/
|
|
701
|
+
invalidateQueue() {
|
|
702
|
+
this._invalidateQueue();
|
|
703
|
+
}
|
|
704
|
+
|
|
739
705
|
/**
|
|
740
706
|
* Invalidate the cached queue (called on schedule change, time boundaries, etc.)
|
|
741
707
|
*/
|
package/src/timeline.js
CHANGED
|
@@ -112,7 +112,7 @@ function arraysEqual(a, b) {
|
|
|
112
112
|
* @param {number} timeMs - Current simulated time in ms
|
|
113
113
|
* @returns {boolean}
|
|
114
114
|
*/
|
|
115
|
-
function canSimulatedPlay(history, maxPlaysPerHour, timeMs) {
|
|
115
|
+
export function canSimulatedPlay(history, maxPlaysPerHour, timeMs) {
|
|
116
116
|
if (!maxPlaysPerHour || maxPlaysPerHour === 0) return true;
|
|
117
117
|
|
|
118
118
|
const oneHourAgo = timeMs - 3600000;
|