@xiboplayer/renderer 0.7.2 → 0.7.3
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 +6 -6
- package/src/index.d.ts +3 -3
- package/src/renderer-lite.js +97 -145
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiboplayer/renderer",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "RendererLite - Fast, efficient XLF layout rendering engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"pdfjs-dist": "^4.10.38",
|
|
15
|
-
"@xiboplayer/
|
|
16
|
-
"@xiboplayer/
|
|
17
|
-
"@xiboplayer/
|
|
15
|
+
"@xiboplayer/schedule": "0.7.3",
|
|
16
|
+
"@xiboplayer/utils": "0.7.3",
|
|
17
|
+
"@xiboplayer/cache": "0.7.3"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"
|
|
21
|
-
"
|
|
20
|
+
"jsdom": "^25.0.1",
|
|
21
|
+
"vitest": "^2.1.9"
|
|
22
22
|
},
|
|
23
23
|
"keywords": [
|
|
24
24
|
"xibo",
|
package/src/index.d.ts
CHANGED
|
@@ -21,8 +21,8 @@ export class LayoutPool {
|
|
|
21
21
|
get(layoutId: number): any | undefined;
|
|
22
22
|
add(layoutId: number, entry: any): void;
|
|
23
23
|
clearWarmNotIn(keepIds: Set<number>): number;
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
setHot(layoutId: number): void;
|
|
25
|
+
evict(layoutId: number): void;
|
|
26
26
|
clear(): void;
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -59,10 +59,10 @@ export class RendererLite {
|
|
|
59
59
|
nextWidget(regionId?: string): void;
|
|
60
60
|
previousWidget(regionId?: string): void;
|
|
61
61
|
|
|
62
|
+
resumeRegionMedia(regionId: string): void;
|
|
62
63
|
pause(): void;
|
|
63
64
|
resume(): void;
|
|
64
65
|
isPaused(): boolean;
|
|
65
|
-
resumeRegionMedia?(regionId: string): void;
|
|
66
66
|
showLayout(layoutId?: number): void;
|
|
67
67
|
getCurrentLayoutId(): number | null;
|
|
68
68
|
|
package/src/renderer-lite.js
CHANGED
|
@@ -219,7 +219,6 @@ export class RendererLite {
|
|
|
219
219
|
this._paused = false;
|
|
220
220
|
this._layoutTimerStartedAt = null; // Date.now() when layout timer started
|
|
221
221
|
this._layoutTimerDurationMs = null; // Total layout duration in ms
|
|
222
|
-
this.widgetTimers = new Map(); // widgetId => timer
|
|
223
222
|
this.layoutBlobUrls = new Map(); // layoutId => Set<blobUrl> (for lifecycle tracking)
|
|
224
223
|
this.audioOverlays = new Map(); // widgetId => [HTMLAudioElement] (audio overlays for widgets)
|
|
225
224
|
|
|
@@ -1339,26 +1338,64 @@ export class RendererLite {
|
|
|
1339
1338
|
}
|
|
1340
1339
|
|
|
1341
1340
|
/**
|
|
1342
|
-
*
|
|
1343
|
-
*
|
|
1341
|
+
* Build a region DOM element and state entry.
|
|
1342
|
+
* Shared by createRegion, preloadLayout, and renderOverlay.
|
|
1343
|
+
*
|
|
1344
|
+
* @param {Object} regionConfig - Region configuration from parsed XLF
|
|
1345
|
+
* @param {string} elementId - DOM element ID for the region div
|
|
1346
|
+
* @param {HTMLElement} parentEl - Parent element to append the region to
|
|
1347
|
+
* @param {Object} [extraState] - Additional properties merged into region state
|
|
1348
|
+
* @returns {Object} Region state object { element, config, widgets, ... }
|
|
1344
1349
|
*/
|
|
1345
|
-
|
|
1350
|
+
_createRegionEntry(regionConfig, elementId, parentEl, extraState = {}) {
|
|
1351
|
+
const { className = 'renderer-lite-region', ...stateProps } = extraState;
|
|
1352
|
+
|
|
1346
1353
|
const regionEl = document.createElement('div');
|
|
1347
|
-
regionEl.id =
|
|
1348
|
-
regionEl.className =
|
|
1354
|
+
regionEl.id = elementId;
|
|
1355
|
+
regionEl.className = className;
|
|
1349
1356
|
regionEl.style.position = 'absolute';
|
|
1350
|
-
regionEl.style.zIndex = regionConfig.zindex;
|
|
1357
|
+
regionEl.style.zIndex = String(regionConfig.zindex);
|
|
1351
1358
|
regionEl.style.overflow = 'hidden';
|
|
1352
1359
|
|
|
1353
|
-
// Drawer regions start fully hidden — shown only by navWidget actions
|
|
1354
|
-
if (regionConfig.isDrawer) {
|
|
1355
|
-
regionEl.style.display = 'none';
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
1360
|
// Apply scaled positioning
|
|
1359
1361
|
this.applyRegionScale(regionEl, regionConfig);
|
|
1360
1362
|
|
|
1361
|
-
|
|
1363
|
+
parentEl.appendChild(regionEl);
|
|
1364
|
+
|
|
1365
|
+
const sf = this.scaleFactor;
|
|
1366
|
+
return {
|
|
1367
|
+
element: regionEl,
|
|
1368
|
+
config: regionConfig,
|
|
1369
|
+
widgets: regionConfig.widgets,
|
|
1370
|
+
currentIndex: 0,
|
|
1371
|
+
timer: null,
|
|
1372
|
+
width: regionConfig.width * sf,
|
|
1373
|
+
height: regionConfig.height * sf,
|
|
1374
|
+
complete: false,
|
|
1375
|
+
widgetElements: new Map(),
|
|
1376
|
+
...stateProps,
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
/**
|
|
1381
|
+
* Create a region element
|
|
1382
|
+
* @param {Object} regionConfig - Region configuration
|
|
1383
|
+
*/
|
|
1384
|
+
async createRegion(regionConfig) {
|
|
1385
|
+
const region = this._createRegionEntry(
|
|
1386
|
+
regionConfig,
|
|
1387
|
+
`region_${regionConfig.id}`,
|
|
1388
|
+
this.container,
|
|
1389
|
+
{
|
|
1390
|
+
isDrawer: regionConfig.isDrawer || false,
|
|
1391
|
+
isCanvas: regionConfig.isCanvas || false,
|
|
1392
|
+
}
|
|
1393
|
+
);
|
|
1394
|
+
|
|
1395
|
+
// Drawer regions start fully hidden — shown only by navWidget actions
|
|
1396
|
+
if (regionConfig.isDrawer) {
|
|
1397
|
+
region.element.style.display = 'none';
|
|
1398
|
+
}
|
|
1362
1399
|
|
|
1363
1400
|
// Filter expired widgets (fromDt/toDt time-gating within XLF)
|
|
1364
1401
|
let widgets = regionConfig.widgets.filter(w => this._isWidgetActive(w));
|
|
@@ -1367,22 +1404,9 @@ export class RendererLite {
|
|
|
1367
1404
|
if (widgets.some(w => w.cyclePlayback)) {
|
|
1368
1405
|
widgets = this._applyCyclePlayback(widgets);
|
|
1369
1406
|
}
|
|
1407
|
+
region.widgets = widgets;
|
|
1370
1408
|
|
|
1371
|
-
|
|
1372
|
-
const sf = this.scaleFactor;
|
|
1373
|
-
this.regions.set(regionConfig.id, {
|
|
1374
|
-
element: regionEl,
|
|
1375
|
-
config: regionConfig,
|
|
1376
|
-
widgets,
|
|
1377
|
-
currentIndex: 0,
|
|
1378
|
-
timer: null,
|
|
1379
|
-
width: regionConfig.width * sf,
|
|
1380
|
-
height: regionConfig.height * sf,
|
|
1381
|
-
complete: false, // Track if region has played all widgets once
|
|
1382
|
-
isDrawer: regionConfig.isDrawer || false,
|
|
1383
|
-
isCanvas: regionConfig.isCanvas || false, // Canvas regions render all widgets simultaneously
|
|
1384
|
-
widgetElements: new Map() // widgetId -> DOM element (for element reuse)
|
|
1385
|
-
});
|
|
1409
|
+
this.regions.set(regionConfig.id, region);
|
|
1386
1410
|
}
|
|
1387
1411
|
|
|
1388
1412
|
/**
|
|
@@ -2502,45 +2526,7 @@ export class RendererLite {
|
|
|
2502
2526
|
* Render text/ticker widget
|
|
2503
2527
|
*/
|
|
2504
2528
|
async renderTextWidget(widget, region) {
|
|
2505
|
-
|
|
2506
|
-
iframe.className = 'renderer-lite-widget';
|
|
2507
|
-
iframe.style.width = '100%';
|
|
2508
|
-
iframe.style.height = '100%';
|
|
2509
|
-
iframe.style.border = 'none';
|
|
2510
|
-
iframe.style.opacity = '0';
|
|
2511
|
-
|
|
2512
|
-
// Get widget HTML (may return { url } for cache-path loading or string for blob)
|
|
2513
|
-
let html = widget.raw;
|
|
2514
|
-
if (this.options.getWidgetHtml) {
|
|
2515
|
-
const result = await this.options.getWidgetHtml(widget);
|
|
2516
|
-
if (result && typeof result === 'object' && result.url) {
|
|
2517
|
-
// Use cache URL — SW serves HTML and intercepts sub-resources
|
|
2518
|
-
iframe.src = result.url;
|
|
2519
|
-
|
|
2520
|
-
// Parse NUMITEMS/DURATION from fallback HTML (cache path)
|
|
2521
|
-
if (result.fallback) {
|
|
2522
|
-
this._parseDurationComments(result.fallback, widget);
|
|
2523
|
-
}
|
|
2524
|
-
|
|
2525
|
-
return iframe;
|
|
2526
|
-
}
|
|
2527
|
-
html = result;
|
|
2528
|
-
}
|
|
2529
|
-
|
|
2530
|
-
if (html) {
|
|
2531
|
-
// Parse NUMITEMS/DURATION HTML comments for dynamic widget duration
|
|
2532
|
-
this._parseDurationComments(html, widget);
|
|
2533
|
-
}
|
|
2534
|
-
|
|
2535
|
-
// Fallback: Create blob URL for iframe
|
|
2536
|
-
const blob = new Blob([html], { type: 'text/html' });
|
|
2537
|
-
const blobUrl = URL.createObjectURL(blob);
|
|
2538
|
-
iframe.src = blobUrl;
|
|
2539
|
-
|
|
2540
|
-
// Track blob URL for lifecycle management
|
|
2541
|
-
this.trackBlobUrl(blobUrl);
|
|
2542
|
-
|
|
2543
|
-
return iframe;
|
|
2529
|
+
return await this._renderIframeWidget(widget, region);
|
|
2544
2530
|
}
|
|
2545
2531
|
|
|
2546
2532
|
/**
|
|
@@ -2723,6 +2709,15 @@ export class RendererLite {
|
|
|
2723
2709
|
* Render generic widget (clock, calendar, weather, etc.)
|
|
2724
2710
|
*/
|
|
2725
2711
|
async renderGenericWidget(widget, region) {
|
|
2712
|
+
return await this._renderIframeWidget(widget, region);
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
/**
|
|
2716
|
+
* Shared iframe rendering for text/ticker and generic widgets.
|
|
2717
|
+
* Creates an iframe, resolves widget HTML via getWidgetHtml (cache URL or blob),
|
|
2718
|
+
* and parses NUMITEMS/DURATION comments for dynamic widget duration.
|
|
2719
|
+
*/
|
|
2720
|
+
async _renderIframeWidget(widget, region) {
|
|
2726
2721
|
const iframe = document.createElement('iframe');
|
|
2727
2722
|
iframe.className = 'renderer-lite-widget';
|
|
2728
2723
|
iframe.style.width = '100%';
|
|
@@ -2886,33 +2881,12 @@ export class RendererLite {
|
|
|
2886
2881
|
|
|
2887
2882
|
// Create regions in the hidden wrapper
|
|
2888
2883
|
const preloadRegions = new Map();
|
|
2889
|
-
const sf = this.scaleFactor;
|
|
2890
|
-
|
|
2891
2884
|
for (const regionConfig of layout.regions) {
|
|
2892
|
-
const
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
regionEl.style.overflow = 'hidden';
|
|
2898
|
-
|
|
2899
|
-
// Apply scaled positioning
|
|
2900
|
-
this.applyRegionScale(regionEl, regionConfig);
|
|
2901
|
-
|
|
2902
|
-
wrapper.appendChild(regionEl);
|
|
2903
|
-
|
|
2904
|
-
const region = {
|
|
2905
|
-
element: regionEl,
|
|
2906
|
-
config: regionConfig,
|
|
2907
|
-
widgets: regionConfig.widgets,
|
|
2908
|
-
currentIndex: 0,
|
|
2909
|
-
timer: null,
|
|
2910
|
-
width: regionConfig.width * sf,
|
|
2911
|
-
height: regionConfig.height * sf,
|
|
2912
|
-
complete: false,
|
|
2913
|
-
widgetElements: new Map()
|
|
2914
|
-
};
|
|
2915
|
-
|
|
2885
|
+
const region = this._createRegionEntry(
|
|
2886
|
+
regionConfig,
|
|
2887
|
+
`preload_region_${layoutId}_${regionConfig.id}`,
|
|
2888
|
+
wrapper
|
|
2889
|
+
);
|
|
2916
2890
|
preloadRegions.set(regionConfig.id, region);
|
|
2917
2891
|
}
|
|
2918
2892
|
|
|
@@ -2993,20 +2967,7 @@ export class RendererLite {
|
|
|
2993
2967
|
|
|
2994
2968
|
// ── Tear down old layout ──
|
|
2995
2969
|
this.removeActionListeners();
|
|
2996
|
-
|
|
2997
|
-
if (this.layoutTimer) {
|
|
2998
|
-
clearTimeout(this.layoutTimer);
|
|
2999
|
-
this.layoutTimer = null;
|
|
3000
|
-
}
|
|
3001
|
-
|
|
3002
|
-
if (this.preloadTimer) {
|
|
3003
|
-
clearTimeout(this.preloadTimer);
|
|
3004
|
-
this.preloadTimer = null;
|
|
3005
|
-
}
|
|
3006
|
-
if (this._preloadRetryTimer) {
|
|
3007
|
-
clearTimeout(this._preloadRetryTimer);
|
|
3008
|
-
this._preloadRetryTimer = null;
|
|
3009
|
-
}
|
|
2970
|
+
this._clearLayoutTimers();
|
|
3010
2971
|
|
|
3011
2972
|
const oldLayoutId = this.currentLayoutId;
|
|
3012
2973
|
const alreadyEmittedEnd = this.layoutEndEmitted;
|
|
@@ -3176,6 +3137,24 @@ export class RendererLite {
|
|
|
3176
3137
|
}
|
|
3177
3138
|
}
|
|
3178
3139
|
|
|
3140
|
+
/**
|
|
3141
|
+
* Clear all layout-level timers (layout duration, preload, preload retry).
|
|
3142
|
+
*/
|
|
3143
|
+
_clearLayoutTimers() {
|
|
3144
|
+
if (this.layoutTimer) {
|
|
3145
|
+
clearTimeout(this.layoutTimer);
|
|
3146
|
+
this.layoutTimer = null;
|
|
3147
|
+
}
|
|
3148
|
+
if (this.preloadTimer) {
|
|
3149
|
+
clearTimeout(this.preloadTimer);
|
|
3150
|
+
this.preloadTimer = null;
|
|
3151
|
+
}
|
|
3152
|
+
if (this._preloadRetryTimer) {
|
|
3153
|
+
clearTimeout(this._preloadRetryTimer);
|
|
3154
|
+
this._preloadRetryTimer = null;
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3179
3158
|
/**
|
|
3180
3159
|
* Stop current layout
|
|
3181
3160
|
*/
|
|
@@ -3197,18 +3176,7 @@ export class RendererLite {
|
|
|
3197
3176
|
this.currentLayoutId = null;
|
|
3198
3177
|
|
|
3199
3178
|
// Clear timers
|
|
3200
|
-
|
|
3201
|
-
clearTimeout(this.layoutTimer);
|
|
3202
|
-
this.layoutTimer = null;
|
|
3203
|
-
}
|
|
3204
|
-
if (this.preloadTimer) {
|
|
3205
|
-
clearTimeout(this.preloadTimer);
|
|
3206
|
-
this.preloadTimer = null;
|
|
3207
|
-
}
|
|
3208
|
-
if (this._preloadRetryTimer) {
|
|
3209
|
-
clearTimeout(this._preloadRetryTimer);
|
|
3210
|
-
this._preloadRetryTimer = null;
|
|
3211
|
-
}
|
|
3179
|
+
this._clearLayoutTimers();
|
|
3212
3180
|
|
|
3213
3181
|
// Remove interactive action listeners before teardown
|
|
3214
3182
|
this.removeActionListeners();
|
|
@@ -3298,33 +3266,17 @@ export class RendererLite {
|
|
|
3298
3266
|
|
|
3299
3267
|
// Create regions for overlay
|
|
3300
3268
|
const overlayRegions = new Map();
|
|
3301
|
-
const sf = this.scaleFactor;
|
|
3302
3269
|
for (const regionConfig of layout.regions) {
|
|
3303
|
-
const
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
overlayDiv.appendChild(regionEl);
|
|
3314
|
-
|
|
3315
|
-
// Store region state (dimensions use scaled values)
|
|
3316
|
-
overlayRegions.set(regionConfig.id, {
|
|
3317
|
-
element: regionEl,
|
|
3318
|
-
config: regionConfig,
|
|
3319
|
-
widgets: regionConfig.widgets,
|
|
3320
|
-
currentIndex: 0,
|
|
3321
|
-
timer: null,
|
|
3322
|
-
width: regionConfig.width * sf,
|
|
3323
|
-
height: regionConfig.height * sf,
|
|
3324
|
-
complete: false,
|
|
3325
|
-
isCanvas: regionConfig.isCanvas || false,
|
|
3326
|
-
widgetElements: new Map()
|
|
3327
|
-
});
|
|
3270
|
+
const region = this._createRegionEntry(
|
|
3271
|
+
regionConfig,
|
|
3272
|
+
`overlay_${layoutId}_region_${regionConfig.id}`,
|
|
3273
|
+
overlayDiv,
|
|
3274
|
+
{
|
|
3275
|
+
className: 'renderer-lite-region overlay-region',
|
|
3276
|
+
isCanvas: regionConfig.isCanvas || false,
|
|
3277
|
+
}
|
|
3278
|
+
);
|
|
3279
|
+
overlayRegions.set(regionConfig.id, region);
|
|
3328
3280
|
}
|
|
3329
3281
|
|
|
3330
3282
|
// Pre-create widget elements for overlay
|