@xiboplayer/renderer 0.3.7 → 0.4.1
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/README.md +3 -1
- package/docs/RENDERER_COMPARISON.md +9 -18
- package/package.json +3 -3
- package/src/layout.js +12 -9
- package/src/renderer-lite.js +700 -35
- package/src/renderer-lite.test.js +1269 -0
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ RendererLite parses Xibo Layout Format (XLF) files and builds a live DOM with:
|
|
|
12
12
|
- **Layout preloading** — 2-layout pool pre-builds upcoming layouts at 75% of current duration for zero-gap transitions
|
|
13
13
|
- **Proportional scaling** — ResizeObserver-based scaling to fit any screen resolution
|
|
14
14
|
- **Overlay support** — multiple simultaneous overlay layouts with independent z-index (1000+)
|
|
15
|
+
- **Absolute widget positioning** — widget elements use `position: absolute` within regions to layer correctly in multi-widget regions
|
|
16
|
+
- **Animation cleanup** — `fill: forwards` animations cancelled between widgets to prevent stale visual state (e.g. video hidden after PDF)
|
|
15
17
|
|
|
16
18
|
## Installation
|
|
17
19
|
|
|
@@ -37,7 +39,7 @@ await renderer.renderLayout(xlf, { mediaBaseUrl: '/cache/' });
|
|
|
37
39
|
| Widget | Implementation |
|
|
38
40
|
|--------|---------------|
|
|
39
41
|
| Video | `<video>` with native HLS (Safari) + hls.js fallback, pause-on-last-frame |
|
|
40
|
-
| Image | `<img>` with
|
|
42
|
+
| Image | `<img>` with CMS scaleType mapping (center->contain, stretch->fill, fit->cover), blob URL from cache |
|
|
41
43
|
| PDF | PDF.js canvas rendering (dynamically imported) |
|
|
42
44
|
| Text / Ticker | iframe with CMS-rendered HTML via GetResource |
|
|
43
45
|
| Web page | bare `<iframe src="...">` |
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
| Visibility toggle | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Complete |
|
|
27
27
|
| Avoid DOM recreation | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Complete |
|
|
28
28
|
| Layout reuse detection | ⚠️ Partial | ✅ Yes | ✅ Yes | ✅ Better than XLR! |
|
|
29
|
+
| Widget absolute positioning | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Complete |
|
|
30
|
+
| Image scaleType mapping | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Complete (center->contain, stretch->fill, fit->cover) |
|
|
29
31
|
| **Widget Types** | | | | |
|
|
30
32
|
| Image | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Complete |
|
|
31
33
|
| Video | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Complete |
|
|
@@ -206,20 +208,15 @@ RendererLite events match XLR/Arexibo with additions:
|
|
|
206
208
|
|
|
207
209
|
### 7. Memory Management
|
|
208
210
|
|
|
209
|
-
**Status**:
|
|
211
|
+
**Status**: ✅ **Complete**
|
|
210
212
|
|
|
211
213
|
**What's correct**:
|
|
212
214
|
- ✅ Elements reused (not recreated)
|
|
213
|
-
- ✅ Blob URLs revoked on layout change
|
|
215
|
+
- ✅ Blob URLs revoked on layout change (layout-scoped tracking)
|
|
214
216
|
- ✅ Cache cleared appropriately
|
|
215
217
|
- ✅ Timers cleared before new layout
|
|
216
218
|
- ✅ Event listeners managed properly
|
|
217
|
-
|
|
218
|
-
**Gap identified**:
|
|
219
|
-
- ⚠️ Layout-scoped blob URL tracking missing
|
|
220
|
-
- ⚠️ Could accumulate blob URLs across many layout cycles
|
|
221
|
-
|
|
222
|
-
**Impact**: Low (only affects 24/7 deployments with frequent layout changes)
|
|
219
|
+
- ✅ `fill: forwards` animations cancelled between widgets to prevent stale visual state
|
|
223
220
|
|
|
224
221
|
---
|
|
225
222
|
|
|
@@ -231,12 +228,7 @@ RendererLite events match XLR/Arexibo with additions:
|
|
|
231
228
|
|
|
232
229
|
### Important Features (Should Have)
|
|
233
230
|
|
|
234
|
-
1. **
|
|
235
|
-
- **Priority**: Medium
|
|
236
|
-
- **Impact**: Memory leak in long-running deployments
|
|
237
|
-
- **Effort**: Low (add Map tracking)
|
|
238
|
-
|
|
239
|
-
2. **Widget action events**
|
|
231
|
+
1. **Widget action events**
|
|
240
232
|
- **Priority**: Low
|
|
241
233
|
- **Impact**: Interactive widgets might need action callbacks
|
|
242
234
|
- **Effort**: Medium (event propagation from widget iframes)
|
|
@@ -417,9 +409,8 @@ XLF → Parse → Pre-create Elements → Toggle Visibility → Transitions
|
|
|
417
409
|
|
|
418
410
|
### ⚠️ Features Needing Work
|
|
419
411
|
|
|
420
|
-
1. **
|
|
421
|
-
2. **
|
|
422
|
-
3. **Service Worker**: Currently disabled (HTTP 202 issues)
|
|
412
|
+
1. **Widget Actions**: Event propagation from iframes
|
|
413
|
+
2. **Service Worker**: Currently disabled (HTTP 202 issues)
|
|
423
414
|
|
|
424
415
|
### ❌ Features Not Applicable
|
|
425
416
|
|
|
@@ -472,7 +463,7 @@ XLF → Parse → Pre-create Elements → Toggle Visibility → Transitions
|
|
|
472
463
|
|
|
473
464
|
**RendererLite successfully implements the Arexibo pattern** and adds significant performance improvements through parallelization. The implementation is production-ready with minor improvements needed for blob URL lifecycle management.
|
|
474
465
|
|
|
475
|
-
**Feature Parity**: ~
|
|
466
|
+
**Feature Parity**: ~98% (missing only widget action event propagation)
|
|
476
467
|
**Performance**: Exceeds XLR and Arexibo benchmarks
|
|
477
468
|
**Memory**: Stable with Arexibo pattern correctly implemented
|
|
478
469
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiboplayer/renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "RendererLite - Fast, efficient XLF layout rendering engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"nanoevents": "^9.1.0",
|
|
14
14
|
"pdfjs-dist": "^4.10.38",
|
|
15
|
-
"@xiboplayer/cache": "0.
|
|
16
|
-
"@xiboplayer/utils": "0.
|
|
15
|
+
"@xiboplayer/cache": "0.4.1",
|
|
16
|
+
"@xiboplayer/utils": "0.4.1"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"vitest": "^2.0.0",
|
package/src/layout.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { cacheWidgetHtml } from '@xiboplayer/cache';
|
|
7
|
+
import { createLogger } from '@xiboplayer/utils';
|
|
8
|
+
|
|
9
|
+
const log = createLogger('Layout');
|
|
7
10
|
|
|
8
11
|
export class LayoutTranslator {
|
|
9
12
|
constructor(xmds) {
|
|
@@ -124,9 +127,9 @@ export class LayoutTranslator {
|
|
|
124
127
|
|
|
125
128
|
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
126
129
|
try {
|
|
127
|
-
|
|
130
|
+
log.info(`Fetching resource for ${type} widget (layout=${layoutId}, region=${regionId}, media=${id}) - attempt ${attempt}/${retries}`);
|
|
128
131
|
raw = await this.xmds.getResource(layoutId, regionId, id);
|
|
129
|
-
|
|
132
|
+
log.info(`Got resource HTML (${raw.length} chars)`);
|
|
130
133
|
|
|
131
134
|
// Store widget HTML in cache and save cache key for iframe src generation
|
|
132
135
|
const widgetCacheKey = await cacheWidgetHtml(layoutId, regionId, id, raw);
|
|
@@ -137,12 +140,12 @@ export class LayoutTranslator {
|
|
|
137
140
|
|
|
138
141
|
} catch (error) {
|
|
139
142
|
lastError = error;
|
|
140
|
-
|
|
143
|
+
log.warn(`Failed to get resource (attempt ${attempt}/${retries}):`, error.message);
|
|
141
144
|
|
|
142
145
|
// If not last attempt, wait before retry
|
|
143
146
|
if (attempt < retries) {
|
|
144
147
|
const delay = attempt * 2000; // 2s, 4s backoff
|
|
145
|
-
|
|
148
|
+
log.info(`Retrying in ${delay}ms...`);
|
|
146
149
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
147
150
|
}
|
|
148
151
|
}
|
|
@@ -150,7 +153,7 @@ export class LayoutTranslator {
|
|
|
150
153
|
|
|
151
154
|
// If all retries failed, try to use cached version as fallback
|
|
152
155
|
if (!raw && lastError) {
|
|
153
|
-
|
|
156
|
+
log.warn('All retries failed, checking for cached widget HTML...');
|
|
154
157
|
|
|
155
158
|
// Try to get cached widget HTML directly from Cache API
|
|
156
159
|
try {
|
|
@@ -161,14 +164,14 @@ export class LayoutTranslator {
|
|
|
161
164
|
if (cached) {
|
|
162
165
|
raw = await cached.text();
|
|
163
166
|
options.widgetCacheKey = cachedKey;
|
|
164
|
-
|
|
167
|
+
log.info(`Using cached widget HTML (${raw.length} chars) - CMS update pending`);
|
|
165
168
|
} else {
|
|
166
|
-
|
|
169
|
+
log.error(`No cached version available for widget ${id}`);
|
|
167
170
|
// Show minimal placeholder that doesn't look like an error
|
|
168
171
|
raw = `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#999;font-size:18px;">Content updating...</div>`;
|
|
169
172
|
}
|
|
170
173
|
} catch (cacheError) {
|
|
171
|
-
|
|
174
|
+
log.error('Cache fallback failed:', cacheError);
|
|
172
175
|
raw = `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#999;font-size:18px;">Content updating...</div>`;
|
|
173
176
|
}
|
|
174
177
|
}
|
|
@@ -894,7 +897,7 @@ ${mediaJS}
|
|
|
894
897
|
startFn = iframe.startFn;
|
|
895
898
|
stopFn = iframe.stopFn;
|
|
896
899
|
} else {
|
|
897
|
-
|
|
900
|
+
log.warn(`Unsupported media type: ${media.type}`);
|
|
898
901
|
startFn = `() => console.log('Unsupported media type: ${media.type}')`;
|
|
899
902
|
}
|
|
900
903
|
}
|