@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 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 objectFit contain, blob URL from cache |
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**: ⚠️ **Good with Minor Gap**
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. **Blob URL lifecycle tracking**
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. **Blob URL Lifecycle**: Needs layout-scoped tracking
421
- 2. **Widget Actions**: Event propagation from iframes
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**: ~95% (missing only blob URL tracking and widget actions)
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.7",
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.3.7",
16
- "@xiboplayer/utils": "0.3.7"
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
- console.log(`[Layout] Fetching resource for ${type} widget (layout=${layoutId}, region=${regionId}, media=${id}) - attempt ${attempt}/${retries}`);
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
- console.log(`[Layout] Got resource HTML (${raw.length} chars)`);
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
- console.warn(`[Layout] Failed to get resource (attempt ${attempt}/${retries}):`, error.message);
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
- console.log(`[Layout] Retrying in ${delay}ms...`);
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
- console.warn(`[Layout] All retries failed, checking for cached widget HTML...`);
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
- console.log(`[Layout] Using cached widget HTML (${raw.length} chars) - CMS update pending`);
167
+ log.info(`Using cached widget HTML (${raw.length} chars) - CMS update pending`);
165
168
  } else {
166
- console.error(`[Layout] No cached version available for widget ${id}`);
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
- console.error(`[Layout] Cache fallback failed:`, cacheError);
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
- console.warn(`[Layout] Unsupported media type: ${media.type}`);
900
+ log.warn(`Unsupported media type: ${media.type}`);
898
901
  startFn = `() => console.log('Unsupported media type: ${media.type}')`;
899
902
  }
900
903
  }