@xiboplayer/renderer 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 +6 -6
- package/src/index.d.ts +3 -3
- package/src/renderer-lite.js +105 -155
- package/src/renderer-lite.test.js +51 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiboplayer/renderer",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
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/cache": "0.7.
|
|
16
|
-
"@xiboplayer/schedule": "0.7.
|
|
17
|
-
"@xiboplayer/utils": "0.7.
|
|
15
|
+
"@xiboplayer/cache": "0.7.4",
|
|
16
|
+
"@xiboplayer/schedule": "0.7.4",
|
|
17
|
+
"@xiboplayer/utils": "0.7.4"
|
|
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
|
/**
|
|
@@ -1493,11 +1517,10 @@ export class RendererLite {
|
|
|
1493
1517
|
el.play().catch(() => {});
|
|
1494
1518
|
};
|
|
1495
1519
|
el.addEventListener('seeked', playAfterSeek);
|
|
1496
|
-
//
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
}
|
|
1520
|
+
// Always call play() — for preloaded-then-paused videos, seeked may not
|
|
1521
|
+
// fire (currentTime already 0) and readyState may be < 2 (not buffered yet).
|
|
1522
|
+
// play() handles both cases: if not ready, it queues; if ready, it plays.
|
|
1523
|
+
el.play().catch(() => {});
|
|
1501
1524
|
}
|
|
1502
1525
|
|
|
1503
1526
|
/**
|
|
@@ -2276,10 +2299,9 @@ export class RendererLite {
|
|
|
2276
2299
|
}
|
|
2277
2300
|
|
|
2278
2301
|
// Detect video duration for dynamic layout timing (when useDuration=0)
|
|
2279
|
-
// Capture the layout ID at creation time —
|
|
2280
|
-
//
|
|
2281
|
-
|
|
2282
|
-
const createdForLayoutId = this.currentLayoutId;
|
|
2302
|
+
// Capture the layout ID at creation time — during preload, _preloadingLayoutId
|
|
2303
|
+
// is the target layout (currentLayoutId is still the playing layout).
|
|
2304
|
+
const createdForLayoutId = this._preloadingLayoutId || this.currentLayoutId;
|
|
2283
2305
|
const onLoadedMetadata = () => {
|
|
2284
2306
|
const videoDuration = video.duration;
|
|
2285
2307
|
this.log.info(`Video ${storedAs} duration detected: ${videoDuration}s`);
|
|
@@ -2440,7 +2462,7 @@ export class RendererLite {
|
|
|
2440
2462
|
audio.addEventListener('ended', onAudioEnded);
|
|
2441
2463
|
|
|
2442
2464
|
// Detect audio duration for dynamic layout timing (when useDuration=0)
|
|
2443
|
-
const audioCreatedForLayoutId = this.currentLayoutId;
|
|
2465
|
+
const audioCreatedForLayoutId = this._preloadingLayoutId || this.currentLayoutId;
|
|
2444
2466
|
const onAudioLoadedMetadata = () => {
|
|
2445
2467
|
const audioDuration = Math.floor(audio.duration);
|
|
2446
2468
|
this.log.info(`Audio ${storedAs} duration detected: ${audioDuration}s`);
|
|
@@ -2502,45 +2524,7 @@ export class RendererLite {
|
|
|
2502
2524
|
* Render text/ticker widget
|
|
2503
2525
|
*/
|
|
2504
2526
|
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;
|
|
2527
|
+
return await this._renderIframeWidget(widget, region);
|
|
2544
2528
|
}
|
|
2545
2529
|
|
|
2546
2530
|
/**
|
|
@@ -2723,6 +2707,15 @@ export class RendererLite {
|
|
|
2723
2707
|
* Render generic widget (clock, calendar, weather, etc.)
|
|
2724
2708
|
*/
|
|
2725
2709
|
async renderGenericWidget(widget, region) {
|
|
2710
|
+
return await this._renderIframeWidget(widget, region);
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
/**
|
|
2714
|
+
* Shared iframe rendering for text/ticker and generic widgets.
|
|
2715
|
+
* Creates an iframe, resolves widget HTML via getWidgetHtml (cache URL or blob),
|
|
2716
|
+
* and parses NUMITEMS/DURATION comments for dynamic widget duration.
|
|
2717
|
+
*/
|
|
2718
|
+
async _renderIframeWidget(widget, region) {
|
|
2726
2719
|
const iframe = document.createElement('iframe');
|
|
2727
2720
|
iframe.className = 'renderer-lite-widget';
|
|
2728
2721
|
iframe.style.width = '100%';
|
|
@@ -2886,33 +2879,12 @@ export class RendererLite {
|
|
|
2886
2879
|
|
|
2887
2880
|
// Create regions in the hidden wrapper
|
|
2888
2881
|
const preloadRegions = new Map();
|
|
2889
|
-
const sf = this.scaleFactor;
|
|
2890
|
-
|
|
2891
2882
|
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
|
-
|
|
2883
|
+
const region = this._createRegionEntry(
|
|
2884
|
+
regionConfig,
|
|
2885
|
+
`preload_region_${layoutId}_${regionConfig.id}`,
|
|
2886
|
+
wrapper
|
|
2887
|
+
);
|
|
2916
2888
|
preloadRegions.set(regionConfig.id, region);
|
|
2917
2889
|
}
|
|
2918
2890
|
|
|
@@ -2993,20 +2965,7 @@ export class RendererLite {
|
|
|
2993
2965
|
|
|
2994
2966
|
// ── Tear down old layout ──
|
|
2995
2967
|
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
|
-
}
|
|
2968
|
+
this._clearLayoutTimers();
|
|
3010
2969
|
|
|
3011
2970
|
const oldLayoutId = this.currentLayoutId;
|
|
3012
2971
|
const alreadyEmittedEnd = this.layoutEndEmitted;
|
|
@@ -3176,6 +3135,24 @@ export class RendererLite {
|
|
|
3176
3135
|
}
|
|
3177
3136
|
}
|
|
3178
3137
|
|
|
3138
|
+
/**
|
|
3139
|
+
* Clear all layout-level timers (layout duration, preload, preload retry).
|
|
3140
|
+
*/
|
|
3141
|
+
_clearLayoutTimers() {
|
|
3142
|
+
if (this.layoutTimer) {
|
|
3143
|
+
clearTimeout(this.layoutTimer);
|
|
3144
|
+
this.layoutTimer = null;
|
|
3145
|
+
}
|
|
3146
|
+
if (this.preloadTimer) {
|
|
3147
|
+
clearTimeout(this.preloadTimer);
|
|
3148
|
+
this.preloadTimer = null;
|
|
3149
|
+
}
|
|
3150
|
+
if (this._preloadRetryTimer) {
|
|
3151
|
+
clearTimeout(this._preloadRetryTimer);
|
|
3152
|
+
this._preloadRetryTimer = null;
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3179
3156
|
/**
|
|
3180
3157
|
* Stop current layout
|
|
3181
3158
|
*/
|
|
@@ -3197,18 +3174,7 @@ export class RendererLite {
|
|
|
3197
3174
|
this.currentLayoutId = null;
|
|
3198
3175
|
|
|
3199
3176
|
// 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
|
-
}
|
|
3177
|
+
this._clearLayoutTimers();
|
|
3212
3178
|
|
|
3213
3179
|
// Remove interactive action listeners before teardown
|
|
3214
3180
|
this.removeActionListeners();
|
|
@@ -3298,33 +3264,17 @@ export class RendererLite {
|
|
|
3298
3264
|
|
|
3299
3265
|
// Create regions for overlay
|
|
3300
3266
|
const overlayRegions = new Map();
|
|
3301
|
-
const sf = this.scaleFactor;
|
|
3302
3267
|
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
|
-
});
|
|
3268
|
+
const region = this._createRegionEntry(
|
|
3269
|
+
regionConfig,
|
|
3270
|
+
`overlay_${layoutId}_region_${regionConfig.id}`,
|
|
3271
|
+
overlayDiv,
|
|
3272
|
+
{
|
|
3273
|
+
className: 'renderer-lite-region overlay-region',
|
|
3274
|
+
isCanvas: regionConfig.isCanvas || false,
|
|
3275
|
+
}
|
|
3276
|
+
);
|
|
3277
|
+
overlayRegions.set(regionConfig.id, region);
|
|
3328
3278
|
}
|
|
3329
3279
|
|
|
3330
3280
|
// Pre-create widget elements for overlay
|
|
@@ -2917,4 +2917,55 @@ describe('RendererLite', () => {
|
|
|
2917
2917
|
vi.useRealTimers();
|
|
2918
2918
|
});
|
|
2919
2919
|
});
|
|
2920
|
+
|
|
2921
|
+
// ── Video layout ID tracking during preload ──────────────────────
|
|
2922
|
+
// Regression test: createdForLayoutId must use _preloadingLayoutId
|
|
2923
|
+
// during preload, not currentLayoutId (which is the *playing* layout).
|
|
2924
|
+
// Without this, video duration updates for preloaded layouts are
|
|
2925
|
+
// rejected, causing layouts to play with wrong (10s) duration.
|
|
2926
|
+
|
|
2927
|
+
describe('Video createdForLayoutId during preload', () => {
|
|
2928
|
+
it('should capture _preloadingLayoutId for video elements created during preload', () => {
|
|
2929
|
+
renderer.currentLayoutId = 100; // currently playing
|
|
2930
|
+
renderer._preloadingLayoutId = 200; // preloading next
|
|
2931
|
+
|
|
2932
|
+
// The fix: _preloadingLayoutId || currentLayoutId should give 200
|
|
2933
|
+
const capturedId = renderer._preloadingLayoutId || renderer.currentLayoutId;
|
|
2934
|
+
expect(capturedId).toBe(200);
|
|
2935
|
+
});
|
|
2936
|
+
|
|
2937
|
+
it('should fall back to currentLayoutId when not preloading', () => {
|
|
2938
|
+
renderer.currentLayoutId = 100;
|
|
2939
|
+
renderer._preloadingLayoutId = null;
|
|
2940
|
+
|
|
2941
|
+
const capturedId = renderer._preloadingLayoutId || renderer.currentLayoutId;
|
|
2942
|
+
expect(capturedId).toBe(100);
|
|
2943
|
+
});
|
|
2944
|
+
|
|
2945
|
+
it('should allow duration update when preloaded layout becomes current', () => {
|
|
2946
|
+
// Simulate: video created during preload of layout 200
|
|
2947
|
+
renderer._preloadingLayoutId = 200;
|
|
2948
|
+
const createdForLayoutId = renderer._preloadingLayoutId || renderer.currentLayoutId;
|
|
2949
|
+
|
|
2950
|
+
// Now layout 200 swaps in
|
|
2951
|
+
renderer.currentLayoutId = 200;
|
|
2952
|
+
renderer._preloadingLayoutId = null;
|
|
2953
|
+
|
|
2954
|
+
// Duration update check should pass
|
|
2955
|
+
expect(renderer.currentLayoutId === createdForLayoutId).toBe(true);
|
|
2956
|
+
});
|
|
2957
|
+
|
|
2958
|
+
it('should reject duration update when a different layout is current', () => {
|
|
2959
|
+
// Video created during preload of layout 200
|
|
2960
|
+
renderer._preloadingLayoutId = 200;
|
|
2961
|
+
const createdForLayoutId = renderer._preloadingLayoutId || renderer.currentLayoutId;
|
|
2962
|
+
|
|
2963
|
+
// But layout 300 swaps in instead (e.g., schedule changed)
|
|
2964
|
+
renderer.currentLayoutId = 300;
|
|
2965
|
+
renderer._preloadingLayoutId = null;
|
|
2966
|
+
|
|
2967
|
+
// Duration update should be rejected — wrong layout
|
|
2968
|
+
expect(renderer.currentLayoutId === createdForLayoutId).toBe(false);
|
|
2969
|
+
});
|
|
2970
|
+
});
|
|
2920
2971
|
});
|