@xiboplayer/renderer 0.3.5 → 0.3.7
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 +3 -2
- package/src/layout.js +11 -8
- package/src/renderer-lite.js +6 -2
- package/src/renderer-lite.test.js +80 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiboplayer/renderer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "RendererLite - Fast, efficient XLF layout rendering engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"nanoevents": "^9.1.0",
|
|
14
14
|
"pdfjs-dist": "^4.10.38",
|
|
15
|
-
"@xiboplayer/
|
|
15
|
+
"@xiboplayer/cache": "0.3.7",
|
|
16
|
+
"@xiboplayer/utils": "0.3.7"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
18
19
|
"vitest": "^2.0.0",
|
package/src/layout.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Based on arexibo layout.rs
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { cacheWidgetHtml } from '@xiboplayer/cache';
|
|
7
|
+
|
|
6
8
|
export class LayoutTranslator {
|
|
7
9
|
constructor(xmds) {
|
|
8
10
|
this.xmds = xmds;
|
|
@@ -11,7 +13,7 @@ export class LayoutTranslator {
|
|
|
11
13
|
/**
|
|
12
14
|
* Translate XLF XML to playable HTML
|
|
13
15
|
*/
|
|
14
|
-
async translateXLF(layoutId, xlfXml
|
|
16
|
+
async translateXLF(layoutId, xlfXml) {
|
|
15
17
|
const parser = new DOMParser();
|
|
16
18
|
const doc = parser.parseFromString(xlfXml, 'text/xml');
|
|
17
19
|
|
|
@@ -26,7 +28,7 @@ export class LayoutTranslator {
|
|
|
26
28
|
|
|
27
29
|
const regions = [];
|
|
28
30
|
for (const regionEl of doc.querySelectorAll('region')) {
|
|
29
|
-
regions.push(await this.translateRegion(layoutId, regionEl
|
|
31
|
+
regions.push(await this.translateRegion(layoutId, regionEl));
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
return this.generateHTML(width, height, bgcolor, regions);
|
|
@@ -35,7 +37,7 @@ export class LayoutTranslator {
|
|
|
35
37
|
/**
|
|
36
38
|
* Translate a single region
|
|
37
39
|
*/
|
|
38
|
-
async translateRegion(layoutId, regionEl
|
|
40
|
+
async translateRegion(layoutId, regionEl) {
|
|
39
41
|
const id = regionEl.getAttribute('id');
|
|
40
42
|
const width = parseInt(regionEl.getAttribute('width'));
|
|
41
43
|
const height = parseInt(regionEl.getAttribute('height'));
|
|
@@ -45,7 +47,7 @@ export class LayoutTranslator {
|
|
|
45
47
|
|
|
46
48
|
const media = [];
|
|
47
49
|
for (const mediaEl of regionEl.querySelectorAll('media')) {
|
|
48
|
-
media.push(await this.translateMedia(layoutId, id, mediaEl
|
|
50
|
+
media.push(await this.translateMedia(layoutId, id, mediaEl));
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
return {
|
|
@@ -62,7 +64,7 @@ export class LayoutTranslator {
|
|
|
62
64
|
/**
|
|
63
65
|
* Translate a single media item
|
|
64
66
|
*/
|
|
65
|
-
async translateMedia(layoutId, regionId, mediaEl
|
|
67
|
+
async translateMedia(layoutId, regionId, mediaEl) {
|
|
66
68
|
const type = mediaEl.getAttribute('type');
|
|
67
69
|
const duration = parseInt(mediaEl.getAttribute('duration') || '10');
|
|
68
70
|
const id = mediaEl.getAttribute('id');
|
|
@@ -127,7 +129,7 @@ export class LayoutTranslator {
|
|
|
127
129
|
console.log(`[Layout] Got resource HTML (${raw.length} chars)`);
|
|
128
130
|
|
|
129
131
|
// Store widget HTML in cache and save cache key for iframe src generation
|
|
130
|
-
const widgetCacheKey = await
|
|
132
|
+
const widgetCacheKey = await cacheWidgetHtml(layoutId, regionId, id, raw);
|
|
131
133
|
options.widgetCacheKey = widgetCacheKey;
|
|
132
134
|
|
|
133
135
|
// Success - break retry loop
|
|
@@ -150,10 +152,11 @@ export class LayoutTranslator {
|
|
|
150
152
|
if (!raw && lastError) {
|
|
151
153
|
console.warn(`[Layout] All retries failed, checking for cached widget HTML...`);
|
|
152
154
|
|
|
153
|
-
// Try to get cached widget HTML
|
|
155
|
+
// Try to get cached widget HTML directly from Cache API
|
|
154
156
|
try {
|
|
155
157
|
const cachedKey = `/cache/widget/${layoutId}/${regionId}/${id}.html`;
|
|
156
|
-
const
|
|
158
|
+
const cache = await caches.open('xibo-media-v1');
|
|
159
|
+
const cached = await cache.match(new Request(window.location.origin + '/player' + cachedKey));
|
|
157
160
|
|
|
158
161
|
if (cached) {
|
|
159
162
|
raw = await cached.text();
|
package/src/renderer-lite.js
CHANGED
|
@@ -378,6 +378,7 @@ export class RendererLite {
|
|
|
378
378
|
duration: layoutDurationAttr ? parseInt(layoutDurationAttr) : 0, // 0 = calculate from widgets
|
|
379
379
|
bgcolor: layoutEl.getAttribute('bgcolor') || '#000000',
|
|
380
380
|
background: layoutEl.getAttribute('background') || null, // Background image fileId
|
|
381
|
+
enableStat: layoutEl.getAttribute('enableStat') !== '0', // absent or "1" = enabled
|
|
381
382
|
regions: []
|
|
382
383
|
};
|
|
383
384
|
|
|
@@ -495,6 +496,7 @@ export class RendererLite {
|
|
|
495
496
|
useDuration, // Whether to use specified duration (1) or media length (0)
|
|
496
497
|
id,
|
|
497
498
|
fileId, // Media library file ID for cache lookup
|
|
499
|
+
enableStat: mediaEl.getAttribute('enableStat') !== '0', // absent or "1" = enabled
|
|
498
500
|
options,
|
|
499
501
|
raw,
|
|
500
502
|
transitions,
|
|
@@ -1318,7 +1320,8 @@ export class RendererLite {
|
|
|
1318
1320
|
this.emit('widgetStart', {
|
|
1319
1321
|
widgetId: widget.id, regionId, layoutId: this.currentLayoutId,
|
|
1320
1322
|
mediaId: parseInt(widget.fileId || widget.id) || null,
|
|
1321
|
-
type: widget.type, duration: widget.duration
|
|
1323
|
+
type: widget.type, duration: widget.duration,
|
|
1324
|
+
enableStat: widget.enableStat
|
|
1322
1325
|
});
|
|
1323
1326
|
}
|
|
1324
1327
|
} catch (error) {
|
|
@@ -1342,7 +1345,8 @@ export class RendererLite {
|
|
|
1342
1345
|
this.emit('widgetEnd', {
|
|
1343
1346
|
widgetId: widget.id, regionId, layoutId: this.currentLayoutId,
|
|
1344
1347
|
mediaId: parseInt(widget.fileId || widget.id) || null,
|
|
1345
|
-
type: widget.type
|
|
1348
|
+
type: widget.type,
|
|
1349
|
+
enableStat: widget.enableStat
|
|
1346
1350
|
});
|
|
1347
1351
|
}
|
|
1348
1352
|
}
|
|
@@ -157,6 +157,86 @@ describe('RendererLite', () => {
|
|
|
157
157
|
});
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
+
describe('enableStat parsing', () => {
|
|
161
|
+
it('should parse enableStat="1" as true on layout', () => {
|
|
162
|
+
const xlf = `
|
|
163
|
+
<layout enableStat="1">
|
|
164
|
+
<region id="r1">
|
|
165
|
+
<media id="m1" type="image" duration="10"></media>
|
|
166
|
+
</region>
|
|
167
|
+
</layout>
|
|
168
|
+
`;
|
|
169
|
+
const layout = renderer.parseXlf(xlf);
|
|
170
|
+
expect(layout.enableStat).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should parse enableStat="0" as false on layout', () => {
|
|
174
|
+
const xlf = `
|
|
175
|
+
<layout enableStat="0">
|
|
176
|
+
<region id="r1">
|
|
177
|
+
<media id="m1" type="image" duration="10"></media>
|
|
178
|
+
</region>
|
|
179
|
+
</layout>
|
|
180
|
+
`;
|
|
181
|
+
const layout = renderer.parseXlf(xlf);
|
|
182
|
+
expect(layout.enableStat).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should default enableStat to true when absent on layout', () => {
|
|
186
|
+
const xlf = `
|
|
187
|
+
<layout>
|
|
188
|
+
<region id="r1">
|
|
189
|
+
<media id="m1" type="image" duration="10"></media>
|
|
190
|
+
</region>
|
|
191
|
+
</layout>
|
|
192
|
+
`;
|
|
193
|
+
const layout = renderer.parseXlf(xlf);
|
|
194
|
+
expect(layout.enableStat).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should parse enableStat="0" as false on widget', () => {
|
|
198
|
+
const xlf = `
|
|
199
|
+
<layout>
|
|
200
|
+
<region id="r1">
|
|
201
|
+
<media id="m1" type="image" duration="10" enableStat="0">
|
|
202
|
+
<options><uri>test.png</uri></options>
|
|
203
|
+
</media>
|
|
204
|
+
</region>
|
|
205
|
+
</layout>
|
|
206
|
+
`;
|
|
207
|
+
const layout = renderer.parseXlf(xlf);
|
|
208
|
+
expect(layout.regions[0].widgets[0].enableStat).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should parse enableStat="1" as true on widget', () => {
|
|
212
|
+
const xlf = `
|
|
213
|
+
<layout>
|
|
214
|
+
<region id="r1">
|
|
215
|
+
<media id="m1" type="image" duration="10" enableStat="1">
|
|
216
|
+
<options><uri>test.png</uri></options>
|
|
217
|
+
</media>
|
|
218
|
+
</region>
|
|
219
|
+
</layout>
|
|
220
|
+
`;
|
|
221
|
+
const layout = renderer.parseXlf(xlf);
|
|
222
|
+
expect(layout.regions[0].widgets[0].enableStat).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should default enableStat to true when absent on widget', () => {
|
|
226
|
+
const xlf = `
|
|
227
|
+
<layout>
|
|
228
|
+
<region id="r1">
|
|
229
|
+
<media id="m1" type="image" duration="10">
|
|
230
|
+
<options><uri>test.png</uri></options>
|
|
231
|
+
</media>
|
|
232
|
+
</region>
|
|
233
|
+
</layout>
|
|
234
|
+
`;
|
|
235
|
+
const layout = renderer.parseXlf(xlf);
|
|
236
|
+
expect(layout.regions[0].widgets[0].enableStat).toBe(true);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
160
240
|
describe('Region Creation', () => {
|
|
161
241
|
it('should create region element with correct positioning', async () => {
|
|
162
242
|
const regionConfig = {
|