@xiboplayer/renderer 0.7.7 → 0.7.8
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 +4 -4
- package/src/index.d.ts +1 -0
- package/src/layout-pool.js +41 -2
- package/src/renderer-lite.js +24 -0
- package/src/renderer-lite.test.js +3 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiboplayer/renderer",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
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/
|
|
16
|
-
"@xiboplayer/
|
|
17
|
-
"@xiboplayer/
|
|
15
|
+
"@xiboplayer/cache": "0.7.8",
|
|
16
|
+
"@xiboplayer/schedule": "0.7.8",
|
|
17
|
+
"@xiboplayer/utils": "0.7.8"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"jsdom": "^25.0.1",
|
package/src/index.d.ts
CHANGED
package/src/layout-pool.js
CHANGED
|
@@ -167,6 +167,15 @@ export class LayoutPool {
|
|
|
167
167
|
* @param {HTMLElement} container
|
|
168
168
|
*/
|
|
169
169
|
static releaseMediaElements(container) {
|
|
170
|
+
// Defer the actual release by one animation frame to give the GPU compositor
|
|
171
|
+
// time to stop referencing textures from the old layout. Without this delay,
|
|
172
|
+
// the compositor may still hold stale mailbox references when we destroy the
|
|
173
|
+
// video backing, causing SharedImageManager::ProduceSkia "non-existent mailbox"
|
|
174
|
+
// errors (Chrome bug: race in shared_image_manager.cc acknowledged in a TODO).
|
|
175
|
+
requestAnimationFrame(() => LayoutPool._releaseMediaElementsSync(container));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
static _releaseMediaElementsSync(container) {
|
|
170
179
|
let videoCount = 0;
|
|
171
180
|
let hlsCount = 0;
|
|
172
181
|
|
|
@@ -195,13 +204,43 @@ export class LayoutPool {
|
|
|
195
204
|
a.load();
|
|
196
205
|
});
|
|
197
206
|
|
|
207
|
+
// Release media inside iframes (embedded widgets with HLS streams, webcams, etc.)
|
|
208
|
+
// We can't querySelectorAll('video') across iframe boundaries, but we can:
|
|
209
|
+
// 1. Try to access same-origin iframe contentDocument
|
|
210
|
+
// 2. Force-remove the iframe src to stop all network activity
|
|
211
|
+
let iframeCount = 0;
|
|
212
|
+
container.querySelectorAll('iframe').forEach(iframe => {
|
|
213
|
+
try {
|
|
214
|
+
// Same-origin iframes: reach inside and release videos
|
|
215
|
+
const doc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
216
|
+
if (doc) {
|
|
217
|
+
doc.querySelectorAll('video').forEach(v => {
|
|
218
|
+
v.pause();
|
|
219
|
+
v.removeAttribute('src');
|
|
220
|
+
v.load();
|
|
221
|
+
videoCount++;
|
|
222
|
+
});
|
|
223
|
+
doc.querySelectorAll('audio').forEach(a => {
|
|
224
|
+
a.pause();
|
|
225
|
+
a.removeAttribute('src');
|
|
226
|
+
a.load();
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
} catch (_) {
|
|
230
|
+
// Cross-origin: can't access contentDocument
|
|
231
|
+
}
|
|
232
|
+
// Force stop all iframe network activity (HLS segments, SSE, WebSocket, etc.)
|
|
233
|
+
iframe.src = 'about:blank';
|
|
234
|
+
iframeCount++;
|
|
235
|
+
});
|
|
236
|
+
|
|
198
237
|
// Destroy PDF documents and release GPU canvas backing stores
|
|
199
238
|
container.querySelectorAll('.pdf-widget').forEach(el => {
|
|
200
239
|
if (el._pdfDestroy) el._pdfDestroy();
|
|
201
240
|
});
|
|
202
241
|
|
|
203
|
-
if (videoCount > 0) {
|
|
204
|
-
log.info(`Released ${videoCount} video(s)${hlsCount ? ` (${hlsCount} HLS)` : ''}`);
|
|
242
|
+
if (videoCount > 0 || iframeCount > 0) {
|
|
243
|
+
log.info(`Released ${videoCount} video(s)${hlsCount ? ` (${hlsCount} HLS)` : ''}${iframeCount ? `, ${iframeCount} iframe(s)` : ''}`);
|
|
205
244
|
}
|
|
206
245
|
}
|
|
207
246
|
|
package/src/renderer-lite.js
CHANGED
|
@@ -1911,6 +1911,21 @@ export class RendererLite {
|
|
|
1911
1911
|
widgetElement._pdfCleanup();
|
|
1912
1912
|
}
|
|
1913
1913
|
|
|
1914
|
+
// Stop embedded widget iframes (HLS live streams, webcams, etc.)
|
|
1915
|
+
// Setting src=about:blank kills all network activity (HLS segment fetches,
|
|
1916
|
+
// WebSocket connections, SSE streams) and releases video decode buffers.
|
|
1917
|
+
const iframes = widgetElement.querySelectorAll('iframe');
|
|
1918
|
+
for (const iframe of iframes) {
|
|
1919
|
+
try {
|
|
1920
|
+
const doc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
1921
|
+
if (doc) {
|
|
1922
|
+
doc.querySelectorAll('video').forEach(v => { v.pause(); v.removeAttribute('src'); v.load(); });
|
|
1923
|
+
doc.querySelectorAll('audio').forEach(a => { a.pause(); a.removeAttribute('src'); a.load(); });
|
|
1924
|
+
}
|
|
1925
|
+
} catch (_) {}
|
|
1926
|
+
iframe.src = 'about:blank';
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1914
1929
|
return { widget, animPromise };
|
|
1915
1930
|
}
|
|
1916
1931
|
|
|
@@ -3192,6 +3207,15 @@ export class RendererLite {
|
|
|
3192
3207
|
this._swapToPreloadedLayout(layoutId);
|
|
3193
3208
|
}
|
|
3194
3209
|
|
|
3210
|
+
/**
|
|
3211
|
+
* Check if the layout timer is active (running or deferred waiting for metadata).
|
|
3212
|
+
* Used to detect stalled layouts that need timer restart.
|
|
3213
|
+
* @returns {boolean}
|
|
3214
|
+
*/
|
|
3215
|
+
hasActiveLayoutTimer() {
|
|
3216
|
+
return this.layoutTimer !== null || this._deferredTimerLayoutId !== null;
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3195
3219
|
/**
|
|
3196
3220
|
* Check if all regions have completed one full cycle
|
|
3197
3221
|
* This is informational only - layout timer is authoritative
|
|
@@ -817,12 +817,9 @@ describe('RendererLite', () => {
|
|
|
817
817
|
const region = { id: 'r1', widgets: [widget1, widget2], isDrawer: false };
|
|
818
818
|
renderer.regions = new Map([['r1', region]]);
|
|
819
819
|
|
|
820
|
-
//
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
// Probe the second
|
|
824
|
-
widget2.duration = 30;
|
|
825
|
-
widget2._probed = true;
|
|
820
|
+
// Once any video is probed, updateLayoutDuration has already run with
|
|
821
|
+
// real metadata, so _hasUnprobedVideos() returns false — no further
|
|
822
|
+
// deferral needed.
|
|
826
823
|
expect(renderer._hasUnprobedVideos()).toBe(false);
|
|
827
824
|
});
|
|
828
825
|
});
|