@xiboplayer/renderer 0.6.8 → 0.6.10

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiboplayer/renderer",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
4
4
  "description": "RendererLite - Fast, efficient XLF layout rendering engine",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -13,9 +13,9 @@
13
13
  "dependencies": {
14
14
  "nanoevents": "^9.1.0",
15
15
  "pdfjs-dist": "^4.10.38",
16
- "@xiboplayer/cache": "0.6.8",
17
- "@xiboplayer/utils": "0.6.8",
18
- "@xiboplayer/schedule": "0.6.8"
16
+ "@xiboplayer/cache": "0.6.10",
17
+ "@xiboplayer/schedule": "0.6.10",
18
+ "@xiboplayer/utils": "0.6.10"
19
19
  },
20
20
  "devDependencies": {
21
21
  "vitest": "^2.0.0",
@@ -193,6 +193,11 @@ export class LayoutPool {
193
193
  a.load();
194
194
  });
195
195
 
196
+ // Destroy PDF documents and release GPU canvas backing stores
197
+ container.querySelectorAll('.pdf-widget').forEach(el => {
198
+ if (el._pdfDestroy) el._pdfDestroy();
199
+ });
200
+
196
201
  if (videoCount > 0) {
197
202
  log.info(`Released ${videoCount} video(s)${hlsCount ? ` (${hlsCount} HLS)` : ''}`);
198
203
  }
@@ -1291,6 +1291,14 @@ export class RendererLite {
1291
1291
  // Emit layout start event
1292
1292
  this.emit('layoutStart', layoutId, layout);
1293
1293
 
1294
+ // Report calculated duration so the schedule queue/timeline uses it
1295
+ // instead of the 60s default. For layouts with unprobed videos, this
1296
+ // is an estimate that will be corrected by updateLayoutDuration().
1297
+ if (layout.duration > 0) {
1298
+ const final_ = !this._hasUnprobedVideos();
1299
+ this.emit('layoutDurationUpdated', layoutId, layout.duration, final_);
1300
+ }
1301
+
1294
1302
  // Start all regions (except drawers — they're action-triggered)
1295
1303
  for (const [regionId, region] of this.regions) {
1296
1304
  if (region.isDrawer) continue;
@@ -1681,6 +1689,11 @@ export class RendererLite {
1681
1689
  element.style.opacity = '1';
1682
1690
  }
1683
1691
 
1692
+ // Resume PDF page cycling if this widget was previously paused
1693
+ if (element._pdfResume) {
1694
+ element._pdfResume();
1695
+ }
1696
+
1684
1697
  // Start audio overlays attached to this widget
1685
1698
  this._startAudioOverlays(widget);
1686
1699
 
@@ -2087,7 +2100,9 @@ export class RendererLite {
2087
2100
  if (!region) return;
2088
2101
 
2089
2102
  const { widget, animPromise } = this._hideWidget(region, widgetIndex);
2090
- if (animPromise) await animPromise;
2103
+ // Emit widgetEnd immediately — don't wait for exit animation.
2104
+ // If we await animPromise first, a pool eviction can remove the DOM element,
2105
+ // causing the animation's onfinish to never fire and widgetEnd to be lost.
2091
2106
  if (widget) {
2092
2107
  this.emit('widgetEnd', {
2093
2108
  widgetId: widget.id, regionId, layoutId: this.currentLayoutId,
@@ -2096,6 +2111,7 @@ export class RendererLite {
2096
2111
  enableStat: widget.enableStat
2097
2112
  });
2098
2113
  }
2114
+ if (animPromise) await animPromise;
2099
2115
  }
2100
2116
 
2101
2117
  /**
@@ -2605,16 +2621,36 @@ export class RendererLite {
2605
2621
 
2606
2622
  await cyclePage();
2607
2623
 
2608
- // Cleanup: cancel active render, clear timer, release PDF document
2624
+ // Pause: stop page cycling (called by _hideWidget during region cycling / replay)
2625
+ // Returns a promise that resolves when the active render is fully cancelled.
2626
+ let cancelPromise = null;
2609
2627
  container._pdfCleanup = () => {
2610
2628
  stopped = true;
2611
2629
  if (cycleTimer) clearTimeout(cycleTimer);
2612
2630
  cycleTimer = null;
2613
2631
  if (activeRenderTask) {
2614
- activeRenderTask.cancel();
2632
+ const task = activeRenderTask;
2615
2633
  activeRenderTask = null;
2634
+ task.cancel();
2635
+ cancelPromise = task.promise.catch(() => {}); // wait for cancellation to propagate
2616
2636
  }
2617
- // Zero canvas dimensions to release GPU backing store
2637
+ };
2638
+
2639
+ // Resume: restart page cycling from page 1 (called by _showWidget on reuse)
2640
+ // Always cleanup first — the PDF may still be rendering from preload
2641
+ // (pre-create starts cyclePage immediately, but the widget isn't "shown"
2642
+ // until the layout swap, so _pdfCleanup was never called).
2643
+ container._pdfResume = async () => {
2644
+ container._pdfCleanup(); // stop any in-flight render
2645
+ if (cancelPromise) { await cancelPromise; cancelPromise = null; }
2646
+ stopped = false;
2647
+ currentPage = 1;
2648
+ cyclePage();
2649
+ };
2650
+
2651
+ // Destroy: release GPU + PDF resources (called on element removal / eviction)
2652
+ container._pdfDestroy = () => {
2653
+ container._pdfCleanup();
2618
2654
  canvas.width = 0;
2619
2655
  canvas.height = 0;
2620
2656
  pdf.destroy();
@@ -2964,10 +3000,13 @@ export class RendererLite {
2964
3000
  const alreadyEmittedEnd = this.layoutEndEmitted;
2965
3001
 
2966
3002
  this.layoutEndEmitted = false;
2967
- this.currentLayout = null;
2968
- this.currentLayoutId = null;
3003
+ // Keep currentLayout/currentLayoutId until widgets are stopped,
3004
+ // so widgetEnd events carry the correct layoutId (not null).
2969
3005
 
2970
3006
  if (oldLayoutId && this.layoutPool.has(oldLayoutId)) {
3007
+ // Stop all widgets before evicting (symmetric widgetEnd events)
3008
+ this._clearRegionTimers(this.regions);
3009
+ this._stopAllRegionWidgets(this.regions, (rid, idx) => this.stopWidget(rid, idx));
2971
3010
  // Old layout was preloaded — evict from pool (safe: removes its wrapper div)
2972
3011
  this.layoutPool.evict(oldLayoutId);
2973
3012
  } else {
@@ -3001,6 +3040,9 @@ export class RendererLite {
3001
3040
  }
3002
3041
  }
3003
3042
 
3043
+ // Now safe to clear old layout state — widgets have been stopped with correct layoutId
3044
+ this.currentLayout = null;
3045
+ this.currentLayoutId = null;
3004
3046
  this.regions.clear();
3005
3047
 
3006
3048
  // ── Activate preloaded layout ──
@@ -3356,12 +3398,13 @@ export class RendererLite {
3356
3398
  if (!region) return;
3357
3399
 
3358
3400
  const { widget, animPromise } = this._hideWidget(region, widgetIndex);
3359
- if (animPromise) await animPromise;
3401
+ // Emit immediately — don't wait for exit animation (same fix as stopWidget)
3360
3402
  if (widget) {
3361
3403
  this.emit('overlayWidgetEnd', {
3362
3404
  overlayId, widgetId: widget.id, regionId, type: widget.type
3363
3405
  });
3364
3406
  }
3407
+ if (animPromise) await animPromise;
3365
3408
  }
3366
3409
 
3367
3410
  /**