@xiboplayer/renderer 0.7.6 → 0.7.7

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.7.6",
3
+ "version": "0.7.7",
4
4
  "description": "RendererLite - Fast, efficient XLF layout rendering engine",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -12,9 +12,9 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "pdfjs-dist": "^4.10.38",
15
- "@xiboplayer/cache": "0.7.6",
16
- "@xiboplayer/utils": "0.7.6",
17
- "@xiboplayer/schedule": "0.7.6"
15
+ "@xiboplayer/schedule": "0.7.7",
16
+ "@xiboplayer/utils": "0.7.7",
17
+ "@xiboplayer/cache": "0.7.7"
18
18
  },
19
19
  "devDependencies": {
20
20
  "jsdom": "^25.0.1",
@@ -1860,27 +1860,36 @@ export class RendererLite {
1860
1860
  }
1861
1861
 
1862
1862
  const videoEl = widgetElement.querySelector('video');
1863
- if (videoEl && widget.options.loop !== '1') videoEl.pause();
1863
+ if (videoEl) {
1864
+ videoEl.pause();
1864
1865
 
1865
- // Stop MediaStream tracks (webcam/mic) to release the device
1866
- if (videoEl?._mediaStream) {
1867
- videoEl._mediaStream.getTracks().forEach(t => t.stop());
1868
- videoEl._mediaStream = null;
1869
- videoEl.srcObject = null;
1870
- }
1866
+ // Stop MediaStream tracks (webcam/mic) to release the device
1867
+ if (videoEl._mediaStream) {
1868
+ videoEl._mediaStream.getTracks().forEach(t => t.stop());
1869
+ videoEl._mediaStream = null;
1870
+ videoEl.srcObject = null;
1871
+ }
1871
1872
 
1872
- // Destroy HLS.js instance to free worker + buffers
1873
- if (videoEl?._hlsInstance) {
1874
- videoEl._hlsInstance.destroy();
1875
- videoEl._hlsInstance = null;
1876
- }
1873
+ // Destroy HLS.js instance to free worker + buffers
1874
+ if (videoEl._hlsInstance) {
1875
+ videoEl._hlsInstance.destroy();
1876
+ videoEl._hlsInstance = null;
1877
+ }
1878
+
1879
+ // Release decoded video buffers (GPU dmabufs) — without this, paused
1880
+ // videos hold texture memory until the layout is evicted from the pool.
1881
+ // removeAttribute('src') + load() forces the browser to drop the decoded
1882
+ // frame, releasing GPU dmabufs immediately instead of at pool eviction.
1883
+ videoEl.removeAttribute('src');
1884
+ videoEl.load();
1877
1885
 
1878
- // Remove event listeners to prevent accumulation across widget cycles
1879
- if (videoEl?._eventCleanup) {
1880
- for (const [event, handler] of videoEl._eventCleanup) {
1881
- videoEl.removeEventListener(event, handler);
1886
+ // Remove event listeners to prevent accumulation across widget cycles
1887
+ if (videoEl._eventCleanup) {
1888
+ for (const [event, handler] of videoEl._eventCleanup) {
1889
+ videoEl.removeEventListener(event, handler);
1890
+ }
1891
+ videoEl._eventCleanup = null;
1882
1892
  }
1883
- videoEl._eventCleanup = null;
1884
1893
  }
1885
1894
 
1886
1895
  const audioEl = widgetElement.querySelector('audio');
@@ -2863,6 +2872,13 @@ export class RendererLite {
2863
2872
  return true;
2864
2873
  }
2865
2874
 
2875
+ // Don't preload if already in-flight (prevents triple preload when
2876
+ // 75% and 90% timers both fire before the async preload completes)
2877
+ if (this._preloadingLayoutId === layoutId) {
2878
+ this.log.info(`Layout ${layoutId} preload already in-flight, skipping`);
2879
+ return true;
2880
+ }
2881
+
2866
2882
  try {
2867
2883
  this.log.info(`Preloading layout ${layoutId} into pool...`);
2868
2884
 
@@ -3095,6 +3111,50 @@ export class RendererLite {
3095
3111
  }
3096
3112
 
3097
3113
  this.log.info(`Swapped to preloaded layout ${layoutId} (instant transition)`);
3114
+ this._logResourceStats(layoutId);
3115
+ }
3116
+
3117
+ /**
3118
+ * Log resource allocation stats for debugging memory/GPU leaks.
3119
+ * Called after every layout swap to track DOM node accumulation,
3120
+ * video element lifecycle, and pool state.
3121
+ */
3122
+ _logResourceStats(layoutId) {
3123
+ const domNodes = document.querySelectorAll('*').length;
3124
+ const videos = document.querySelectorAll('video').length;
3125
+ const videosSrc = document.querySelectorAll('video[src]').length;
3126
+ const canvases = document.querySelectorAll('canvas').length;
3127
+ const iframes = document.querySelectorAll('iframe').length;
3128
+ const images = document.querySelectorAll('img').length;
3129
+ const poolSize = this.layoutPool ? this.layoutPool.size : 0;
3130
+ const regionCount = this.regions ? this.regions.size : 0;
3131
+ const widgetElements = [...(this.regions?.values() || [])].reduce(
3132
+ (sum, r) => sum + (r.widgetElements?.size || 0), 0
3133
+ );
3134
+ const jsHeap = performance?.memory ? {
3135
+ used: Math.round(performance.memory.usedJSHeapSize / 1048576),
3136
+ total: Math.round(performance.memory.totalJSHeapSize / 1048576),
3137
+ limit: Math.round(performance.memory.jsHeapSizeLimit / 1048576),
3138
+ } : null;
3139
+
3140
+ // Count blob URLs still tracked (potential leak indicator)
3141
+ const blobUrls = this._blobUrls ? [...this._blobUrls.values()].reduce((s, set) => s + set.size, 0) : 0;
3142
+ const blobLayouts = this._blobUrls ? this._blobUrls.size : 0;
3143
+
3144
+ // Preload wrapper divs in DOM (should be 0-1 in normal operation)
3145
+ const preloadWrappers = document.querySelectorAll('.renderer-lite-preload-wrapper').length;
3146
+
3147
+ // Audio overlay elements
3148
+ const audioEls = document.querySelectorAll('audio').length;
3149
+
3150
+ const heapStr = jsHeap ? `heap=${jsHeap.used}/${jsHeap.total}MB (limit ${jsHeap.limit}MB)` : 'heap=N/A';
3151
+ this.log.info(
3152
+ `[Resources] layout=${layoutId} dom=${domNodes} videos=${videos}(src=${videosSrc}) ` +
3153
+ `canvas=${canvases} iframe=${iframes} img=${images} audio=${audioEls} ` +
3154
+ `pool=${poolSize} preloadWrappers=${preloadWrappers} ` +
3155
+ `regions=${regionCount} widgets=${widgetElements} ` +
3156
+ `blobs=${blobUrls}(${blobLayouts} layouts) ${heapStr}`
3157
+ );
3098
3158
  }
3099
3159
 
3100
3160
  /**