@xiboplayer/renderer 0.5.18 → 0.5.19

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.
@@ -1,6 +1,6 @@
1
1
  # Renderer Comparison: XLR vs Arexibo vs RendererLite
2
2
 
3
- **Date**: 2026-02-06
3
+ **Date**: 2026-02-28
4
4
  **Purpose**: Comprehensive feature comparison to identify gaps and validate implementation
5
5
 
6
6
  ---
@@ -35,6 +35,7 @@
35
35
  | Text/HTML | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Complete |
36
36
  | Ticker | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Complete |
37
37
  | PDF | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Complete |
38
+ | PDF multi-page cycling | ❌ No | ❌ No | ✅ Yes | ✅ Timed transitions |
38
39
  | Webpage (iframe) | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Complete |
39
40
  | Clock | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Complete |
40
41
  | Weather | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Complete |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiboplayer/renderer",
3
- "version": "0.5.18",
3
+ "version": "0.5.19",
4
4
  "description": "RendererLite - Fast, efficient XLF layout rendering engine",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -13,8 +13,8 @@
13
13
  "dependencies": {
14
14
  "nanoevents": "^9.1.0",
15
15
  "pdfjs-dist": "^4.10.38",
16
- "@xiboplayer/utils": "0.5.18",
17
- "@xiboplayer/cache": "0.5.18"
16
+ "@xiboplayer/cache": "0.5.19",
17
+ "@xiboplayer/utils": "0.5.19"
18
18
  },
19
19
  "devDependencies": {
20
20
  "vitest": "^2.0.0",
package/src/layout.js CHANGED
@@ -871,7 +871,7 @@ ${mediaJS}
871
871
  break;
872
872
 
873
873
  case 'webpage':
874
- const url = media.options.uri;
874
+ const url = decodeURIComponent(media.options.uri || '');
875
875
  startFn = `() => {
876
876
  const region = document.getElementById('region_${regionId}');
877
877
  const iframe = document.createElement('iframe');
@@ -1598,6 +1598,11 @@ export class RendererLite {
1598
1598
  // Stop audio overlays attached to this widget
1599
1599
  this._stopAudioOverlays(widget.id);
1600
1600
 
1601
+ // Stop PDF page cycling timers
1602
+ if (widgetElement._pdfCleanup) {
1603
+ widgetElement._pdfCleanup();
1604
+ }
1605
+
1601
1606
  return { widget, animPromise };
1602
1607
  }
1603
1608
 
@@ -2255,29 +2260,78 @@ export class RendererLite {
2255
2260
  pdfUrl = `${window.location.origin}/player/cache/media/${widget.options.uri}`;
2256
2261
  }
2257
2262
 
2258
- // Render PDF
2263
+ // Render PDF with multi-page cycling
2259
2264
  try {
2260
2265
  const loadingTask = window.pdfjsLib.getDocument(pdfUrl);
2261
2266
  const pdf = await loadingTask.promise;
2262
- const page = await pdf.getPage(1); // Render first page
2267
+ const totalPages = pdf.numPages;
2268
+ const duration = widget.duration || 60;
2269
+ const timePerPage = (duration * 1000) / totalPages;
2270
+ this.log.info(`[pdf] PDF loaded: ${totalPages} pages, ${duration}s duration, ${(timePerPage / 1000).toFixed(1)}s/page`);
2271
+
2272
+ // Render a single page to a canvas, scaled to fit the region
2273
+ const renderPage = async (pageNum) => {
2274
+ const page = await pdf.getPage(pageNum);
2275
+ const viewport = page.getViewport({ scale: 1 });
2276
+ const scale = Math.min(region.width / viewport.width, region.height / viewport.height);
2277
+ const scaledViewport = page.getViewport({ scale });
2278
+
2279
+ const canvas = document.createElement('canvas');
2280
+ canvas.className = 'pdf-page';
2281
+ canvas.width = scaledViewport.width;
2282
+ canvas.height = scaledViewport.height;
2283
+ canvas.style.cssText = 'display:block;margin:auto;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);';
2284
+
2285
+ const context = canvas.getContext('2d');
2286
+ await page.render({ canvasContext: context, viewport: scaledViewport }).promise;
2287
+ return canvas;
2288
+ };
2263
2289
 
2264
- const viewport = page.getViewport({ scale: 1 });
2265
- const scale = Math.min(
2266
- region.width / viewport.width,
2267
- region.height / viewport.height
2268
- );
2269
- const scaledViewport = page.getViewport({ scale });
2290
+ // Page indicator (bottom-right)
2291
+ const indicator = document.createElement('div');
2292
+ indicator.style.cssText = 'position:absolute;bottom:8px;right:12px;color:rgba(255,255,255,0.6);font:12px system-ui;z-index:1;';
2293
+ container.appendChild(indicator);
2294
+
2295
+ let currentPage = 1;
2296
+ const pageTimers = [];
2270
2297
 
2271
- const canvas = document.createElement('canvas');
2272
- canvas.width = scaledViewport.width;
2273
- canvas.height = scaledViewport.height;
2274
- canvas.style.display = 'block';
2275
- canvas.style.margin = 'auto';
2298
+ const cyclePage = async () => {
2299
+ indicator.textContent = `${currentPage} / ${totalPages}`;
2276
2300
 
2277
- const context = canvas.getContext('2d');
2278
- await page.render({ canvasContext: context, viewport: scaledViewport }).promise;
2301
+ // Fade out old canvas
2302
+ const oldCanvas = container.querySelector('.pdf-page');
2303
+ if (oldCanvas) {
2304
+ oldCanvas.style.transition = 'opacity 0.3s';
2305
+ oldCanvas.style.opacity = '0';
2306
+ setTimeout(() => oldCanvas.remove(), 300);
2307
+ }
2279
2308
 
2280
- container.appendChild(canvas);
2309
+ // Render and show new page
2310
+ const canvas = await renderPage(currentPage);
2311
+ canvas.style.opacity = '0';
2312
+ container.appendChild(canvas);
2313
+ // Trigger reflow then fade in
2314
+ canvas.offsetHeight; // eslint-disable-line no-unused-expressions
2315
+ canvas.style.transition = 'opacity 0.3s';
2316
+ canvas.style.opacity = '1';
2317
+
2318
+ // Schedule next page
2319
+ if (totalPages > 1) {
2320
+ const timer = setTimeout(() => {
2321
+ currentPage = currentPage >= totalPages ? 1 : currentPage + 1;
2322
+ cyclePage();
2323
+ }, timePerPage);
2324
+ pageTimers.push(timer);
2325
+ }
2326
+ };
2327
+
2328
+ await cyclePage();
2329
+
2330
+ // Store cleanup function on container for when widget is removed
2331
+ container._pdfCleanup = () => {
2332
+ pageTimers.forEach(t => clearTimeout(t));
2333
+ pageTimers.length = 0;
2334
+ };
2281
2335
 
2282
2336
  } catch (error) {
2283
2337
  this.log.error('PDF render failed:', error);
@@ -2305,7 +2359,9 @@ export class RendererLite {
2305
2359
  iframe.style.height = '100%';
2306
2360
  iframe.style.border = 'none';
2307
2361
  iframe.style.opacity = '0';
2308
- iframe.src = widget.options.uri;
2362
+ // CMS may percent-encode the URI in XLF (e.g. https%3A%2F%2F → https://)
2363
+ const uri = decodeURIComponent(widget.options.uri || '');
2364
+ iframe.src = uri;
2309
2365
 
2310
2366
  return iframe;
2311
2367
  }