@xiboplayer/renderer 0.5.17 → 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.
- package/docs/RENDERER_COMPARISON.md +2 -1
- package/package.json +4 -3
- package/src/index.d.ts +74 -0
- package/src/layout.js +1 -1
- package/src/renderer-lite.js +73 -17
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Renderer Comparison: XLR vs Arexibo vs RendererLite
|
|
2
2
|
|
|
3
|
-
**Date**: 2026-02-
|
|
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,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiboplayer/renderer",
|
|
3
|
-
"version": "0.5.
|
|
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",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
7
8
|
"exports": {
|
|
8
9
|
".": "./src/index.js",
|
|
9
10
|
"./renderer-lite": "./src/renderer-lite.js",
|
|
@@ -12,8 +13,8 @@
|
|
|
12
13
|
"dependencies": {
|
|
13
14
|
"nanoevents": "^9.1.0",
|
|
14
15
|
"pdfjs-dist": "^4.10.38",
|
|
15
|
-
"@xiboplayer/
|
|
16
|
-
"@xiboplayer/
|
|
16
|
+
"@xiboplayer/cache": "0.5.19",
|
|
17
|
+
"@xiboplayer/utils": "0.5.19"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
20
|
"vitest": "^2.0.0",
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export const VERSION: string;
|
|
2
|
+
|
|
3
|
+
export interface RendererConfig {
|
|
4
|
+
cmsUrl: string;
|
|
5
|
+
hardwareKey: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface RendererOptions {
|
|
9
|
+
getMediaUrl?: (fileId: number) => Promise<string>;
|
|
10
|
+
getWidgetHtml?: (widget: any) => Promise<string | { url: string; fallback?: string }>;
|
|
11
|
+
logLevel?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class LayoutPool {
|
|
15
|
+
layouts: Map<number, any>;
|
|
16
|
+
maxSize: number;
|
|
17
|
+
hotLayoutId: number | null;
|
|
18
|
+
has(layoutId: number): boolean;
|
|
19
|
+
get(layoutId: number): any | undefined;
|
|
20
|
+
add(layoutId: number, entry: any): void;
|
|
21
|
+
clearWarmNotIn(keepIds: Set<number>): number;
|
|
22
|
+
makeHot(layoutId: number): void;
|
|
23
|
+
remove(layoutId: number): void;
|
|
24
|
+
clear(): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class RendererLite {
|
|
28
|
+
constructor(config: RendererConfig, container: HTMLElement, options?: RendererOptions);
|
|
29
|
+
|
|
30
|
+
config: RendererConfig;
|
|
31
|
+
container: HTMLElement;
|
|
32
|
+
options: RendererOptions;
|
|
33
|
+
currentLayout: any;
|
|
34
|
+
currentLayoutId: number | null;
|
|
35
|
+
regions: Map<string, any>;
|
|
36
|
+
layoutPool: LayoutPool;
|
|
37
|
+
activeOverlays: Map<number, any>;
|
|
38
|
+
scaleFactor: number;
|
|
39
|
+
offsetX: number;
|
|
40
|
+
offsetY: number;
|
|
41
|
+
|
|
42
|
+
on(event: string, callback: (...args: any[]) => void): () => void;
|
|
43
|
+
emit(event: string, ...args: any[]): void;
|
|
44
|
+
|
|
45
|
+
renderLayout(xlfXml: string, layoutId: number): Promise<void>;
|
|
46
|
+
stopCurrentLayout(): void;
|
|
47
|
+
preloadLayout(xlfXml: string, layoutId: number): Promise<boolean>;
|
|
48
|
+
hasPreloadedLayout(layoutId: number): boolean;
|
|
49
|
+
|
|
50
|
+
renderOverlay(xlfXml: string, layoutId: number, priority?: number): Promise<void>;
|
|
51
|
+
stopOverlay(layoutId: number): void;
|
|
52
|
+
stopAllOverlays(): void;
|
|
53
|
+
getActiveOverlays(): number[];
|
|
54
|
+
|
|
55
|
+
navigateToWidget(targetWidgetId: string): void;
|
|
56
|
+
nextWidget(regionId?: string): void;
|
|
57
|
+
previousWidget(regionId?: string): void;
|
|
58
|
+
|
|
59
|
+
pause(): void;
|
|
60
|
+
resume(): void;
|
|
61
|
+
isPaused(): boolean;
|
|
62
|
+
resumeRegionMedia?(regionId: string): void;
|
|
63
|
+
|
|
64
|
+
parseXlf(xlfXml: string): any;
|
|
65
|
+
parseWidget(mediaEl: Element): any;
|
|
66
|
+
|
|
67
|
+
calculateScale(layout: any): void;
|
|
68
|
+
rescaleRegions(): void;
|
|
69
|
+
|
|
70
|
+
updateLayoutDuration(): void;
|
|
71
|
+
checkLayoutComplete(): void;
|
|
72
|
+
|
|
73
|
+
cleanup(): void;
|
|
74
|
+
}
|
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');
|
package/src/renderer-lite.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
2265
|
-
const
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
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
|
|
2272
|
-
|
|
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
|
-
|
|
2278
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|