@xiboplayer/renderer 0.7.0 → 0.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiboplayer/renderer",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "RendererLite - Fast, efficient XLF layout rendering engine",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -11,11 +11,10 @@
11
11
  "./layout": "./src/layout.js"
12
12
  },
13
13
  "dependencies": {
14
- "nanoevents": "^9.1.0",
15
14
  "pdfjs-dist": "^4.10.38",
16
- "@xiboplayer/cache": "0.7.0",
17
- "@xiboplayer/utils": "0.7.0",
18
- "@xiboplayer/schedule": "0.7.0"
15
+ "@xiboplayer/cache": "0.7.1",
16
+ "@xiboplayer/schedule": "0.7.1",
17
+ "@xiboplayer/utils": "0.7.1"
19
18
  },
20
19
  "devDependencies": {
21
20
  "vitest": "^2.0.0",
package/src/index.d.ts CHANGED
@@ -64,6 +64,7 @@ export class RendererLite {
64
64
  isPaused(): boolean;
65
65
  resumeRegionMedia?(regionId: string): void;
66
66
  showLayout(layoutId?: number): void;
67
+ getCurrentLayoutId(): number | null;
67
68
 
68
69
  parseXlf(xlfXml: string): any;
69
70
  parseWidget(mediaEl: Element): any;
@@ -41,7 +41,7 @@
41
41
  * ```
42
42
  */
43
43
 
44
- import { createNanoEvents } from 'nanoevents';
44
+ import { EventEmitter } from '@xiboplayer/utils';
45
45
  import { createLogger, isDebug, PLAYER_API } from '@xiboplayer/utils';
46
46
  import { parseLayoutDuration } from '@xiboplayer/schedule';
47
47
  import { LayoutPool } from './layout-pool.js';
@@ -205,7 +205,7 @@ export class RendererLite {
205
205
  this.log = createLogger('RendererLite', options.logLevel);
206
206
 
207
207
  // Event emitter for lifecycle hooks
208
- this.emitter = createNanoEvents();
208
+ this.emitter = new EventEmitter();
209
209
 
210
210
  // State
211
211
  this.currentLayout = null;
@@ -223,6 +223,10 @@ export class RendererLite {
223
223
  this.layoutBlobUrls = new Map(); // layoutId => Set<blobUrl> (for lifecycle tracking)
224
224
  this.audioOverlays = new Map(); // widgetId => [HTMLAudioElement] (audio overlays for widgets)
225
225
 
226
+ // Bound methods (avoid lambda allocation per call in startRegion/_advanceRegion)
227
+ this._stopWidgetBound = (rid, idx) => this.stopWidget(rid, idx);
228
+ this._renderWidgetBound = (rid, idx) => this.renderWidget(rid, idx);
229
+
226
230
  // Scale state (for fitting layout to screen)
227
231
  this.scaleFactor = 1;
228
232
  this.offsetX = 0;
@@ -356,7 +360,7 @@ export class RendererLite {
356
360
  * Event emitter interface (like XMR wrapper)
357
361
  */
358
362
  on(event, callback) {
359
- return this.emitter.on(event, callback);
363
+ this.emitter.on(event, callback);
360
364
  }
361
365
 
362
366
  emit(event, ...args) {
@@ -953,8 +957,8 @@ export class RendererLite {
953
957
  const isMain = regionMap === this.regions;
954
958
  this._startRegionCycle(
955
959
  region, regionId,
956
- isMain ? (rid, idx) => this.renderWidget(rid, idx) : (rid, idx) => this.renderWidget(rid, idx),
957
- isMain ? (rid, idx) => this.stopWidget(rid, idx) : (rid, idx) => this.stopWidget(rid, idx),
960
+ isMain ? this._renderWidgetBound : this._renderWidgetBound,
961
+ isMain ? this._stopWidgetBound : this._stopWidgetBound,
958
962
  isMain ? () => this.checkLayoutComplete() : undefined
959
963
  );
960
964
  }
@@ -1200,7 +1204,7 @@ export class RendererLite {
1200
1204
 
1201
1205
  // Stop all region timers and widgets, then reset to first widget
1202
1206
  this._clearRegionTimers(this.regions);
1203
- this._stopAllRegionWidgets(this.regions, (rid, idx) => this.stopWidget(rid, idx));
1207
+ this._stopAllRegionWidgets(this.regions, this._stopWidgetBound);
1204
1208
  for (const [, region] of this.regions) {
1205
1209
  region.currentIndex = 0;
1206
1210
  region.complete = false;
@@ -1389,8 +1393,8 @@ export class RendererLite {
1389
1393
  const region = this.regions.get(regionId);
1390
1394
  this._startRegionCycle(
1391
1395
  region, regionId,
1392
- (rid, idx) => this.renderWidget(rid, idx),
1393
- (rid, idx) => this.stopWidget(rid, idx),
1396
+ this._renderWidgetBound,
1397
+ this._stopWidgetBound,
1394
1398
  () => {
1395
1399
  this.log.info(`Region ${regionId} completed one full cycle`);
1396
1400
  this.checkLayoutComplete();
@@ -3011,7 +3015,7 @@ export class RendererLite {
3011
3015
  if (oldLayoutId && this.layoutPool.has(oldLayoutId)) {
3012
3016
  // Stop all widgets before evicting (symmetric widgetEnd events)
3013
3017
  this._clearRegionTimers(this.regions);
3014
- this._stopAllRegionWidgets(this.regions, (rid, idx) => this.stopWidget(rid, idx));
3018
+ this._stopAllRegionWidgets(this.regions, this._stopWidgetBound);
3015
3019
  // Old layout was preloaded — evict from pool (safe: removes its wrapper div)
3016
3020
  this.layoutPool.evict(oldLayoutId);
3017
3021
  } else {
@@ -3019,7 +3023,7 @@ export class RendererLite {
3019
3023
  // Region elements live directly in this.container (not a wrapper),
3020
3024
  // so we must remove them individually.
3021
3025
  this._clearRegionTimers(this.regions);
3022
- this._stopAllRegionWidgets(this.regions, (rid, idx) => this.stopWidget(rid, idx));
3026
+ this._stopAllRegionWidgets(this.regions, this._stopWidgetBound);
3023
3027
  for (const [, region] of this.regions) {
3024
3028
  // Release video/audio resources before removing from DOM
3025
3029
  LayoutPool.releaseMediaElements(region.element);
@@ -3112,6 +3116,14 @@ export class RendererLite {
3112
3116
  this.log.info(`Swapped to preloaded layout ${layoutId} (instant transition)`);
3113
3117
  }
3114
3118
 
3119
+ /**
3120
+ * Get the currently showing layout ID.
3121
+ * @returns {number|null}
3122
+ */
3123
+ getCurrentLayoutId() {
3124
+ return this.currentLayoutId;
3125
+ }
3126
+
3115
3127
  /**
3116
3128
  * Show a preloaded layout (swap from pool to visible).
3117
3129
  * If no layoutId, shows the most recently preloaded layout.
@@ -3212,7 +3224,7 @@ export class RendererLite {
3212
3224
 
3213
3225
  // Stop all regions — use helper to stop ALL started widgets (canvas fix)
3214
3226
  this._clearRegionTimers(this.regions);
3215
- this._stopAllRegionWidgets(this.regions, (rid, idx) => this.stopWidget(rid, idx));
3227
+ this._stopAllRegionWidgets(this.regions, this._stopWidgetBound);
3216
3228
  for (const [, region] of this.regions) {
3217
3229
  // Release video/audio resources before removing from DOM
3218
3230
  LayoutPool.releaseMediaElements(region.element);
@@ -9,15 +9,24 @@
9
9
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
10
10
  import { RendererLite } from './renderer-lite.js';
11
11
 
12
- // Mock logger
13
- vi.mock('@xiboplayer/utils', () => ({
14
- createLogger: () => ({
15
- info: vi.fn(),
16
- warn: vi.fn(),
17
- error: vi.fn(),
18
- debug: vi.fn()
19
- })
20
- }));
12
+ // Mock logger (keep real EventEmitter for RendererLite construction)
13
+ vi.mock('@xiboplayer/utils', () => {
14
+ // Inline minimal EventEmitter — avoids async importActual which breaks vitest mock hoisting
15
+ class EventEmitter {
16
+ constructor() { this._listeners = new Map(); }
17
+ on(event, cb) { if (!this._listeners.has(event)) this._listeners.set(event, []); this._listeners.get(event).push(cb); }
18
+ emit(event, ...args) { for (const cb of this._listeners.get(event) || []) cb(...args); }
19
+ }
20
+ return {
21
+ EventEmitter,
22
+ createLogger: () => ({
23
+ info: vi.fn(),
24
+ warn: vi.fn(),
25
+ error: vi.fn(),
26
+ debug: vi.fn()
27
+ })
28
+ };
29
+ });
21
30
 
22
31
  describe('RendererLite - Overlay Rendering', () => {
23
32
  let renderer;