@spatialwalk/avatarkit-rtc 1.0.0-beta.7 → 1.0.0-beta.9

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.
Files changed (41) hide show
  1. package/README.md +2 -571
  2. package/dist/assets/animation-worker-DOGeTjF0.js.map +1 -0
  3. package/dist/core/AvatarPlayer.d.ts +8 -3
  4. package/dist/core/AvatarPlayer.d.ts.map +1 -1
  5. package/dist/core/RTCProvider.d.ts +12 -5
  6. package/dist/core/RTCProvider.d.ts.map +1 -1
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +3 -2
  10. package/dist/index10.js +8 -2
  11. package/dist/index10.js.map +1 -1
  12. package/dist/index11.js.map +1 -1
  13. package/dist/index12.js +9 -5
  14. package/dist/index12.js.map +1 -1
  15. package/dist/index14.js.map +1 -1
  16. package/dist/index15.js +1 -1
  17. package/dist/index2.js +40 -17
  18. package/dist/index2.js.map +1 -1
  19. package/dist/index3.js +138 -39
  20. package/dist/index3.js.map +1 -1
  21. package/dist/index4.js +99 -68
  22. package/dist/index4.js.map +1 -1
  23. package/dist/index5.js +6 -2
  24. package/dist/index5.js.map +1 -1
  25. package/dist/index6.js +78 -18
  26. package/dist/index6.js.map +1 -1
  27. package/dist/index8.js +4 -1
  28. package/dist/index8.js.map +1 -1
  29. package/dist/index9.js +5 -1
  30. package/dist/index9.js.map +1 -1
  31. package/dist/providers/agora/AgoraProvider.d.ts.map +1 -1
  32. package/dist/providers/agora/types.d.ts.map +1 -1
  33. package/dist/providers/base/BaseProvider.d.ts +9 -13
  34. package/dist/providers/base/BaseProvider.d.ts.map +1 -1
  35. package/dist/providers/livekit/LiveKitProvider.d.ts +4 -2
  36. package/dist/providers/livekit/LiveKitProvider.d.ts.map +1 -1
  37. package/dist/providers/livekit/animation-worker.d.ts.map +1 -1
  38. package/dist/types/index.d.ts +21 -0
  39. package/dist/types/index.d.ts.map +1 -1
  40. package/package.json +14 -5
  41. package/dist/assets/animation-worker-CUXZycUw.js.map +0 -1
package/dist/index6.js CHANGED
@@ -103,11 +103,17 @@ const _AnimationHandler = class _AnimationHandler {
103
103
  const now = Date.now();
104
104
  if (this.hasReportedStall) {
105
105
  const stallDuration = now - this.lastFrameReceivedTime;
106
- logger.info("AnimationHandler", `Data stream resumed after ${stallDuration}ms stall`);
106
+ logger.info(
107
+ "AnimationHandler",
108
+ `Data stream resumed after ${stallDuration}ms stall`
109
+ );
107
110
  this.hasReportedStall = false;
108
111
  }
109
112
  if (this.isStalledFallback) {
110
- logger.info("AnimationHandler", "Resuming from stall fallback, rendering directly without transition");
113
+ logger.info(
114
+ "AnimationHandler",
115
+ "Resuming from stall fallback, rendering directly without transition"
116
+ );
111
117
  this.isStalledFallback = false;
112
118
  }
113
119
  this.lastFrameReceivedTime = now;
@@ -196,14 +202,20 @@ const _AnimationHandler = class _AnimationHandler {
196
202
  this.ensureSessionActive();
197
203
  const targetFrame = keyframes[0];
198
204
  const frames = frameCount ?? this.config.transitionStartFrameCount;
199
- logger.info("AnimationHandler", `Generating ${frames} transition frames to target`);
205
+ logger.info(
206
+ "AnimationHandler",
207
+ `Generating ${frames} transition frames to target`
208
+ );
200
209
  this.isGeneratingStartTransition = true;
201
210
  try {
202
211
  const transitionFrames = await this.renderer.generateTransitionFromIdle(
203
212
  targetFrame,
204
213
  frames
205
214
  );
206
- logger.info("AnimationHandler", `Generated ${transitionFrames.length} transition frames`);
215
+ logger.info(
216
+ "AnimationHandler",
217
+ `Generated ${transitionFrames.length} transition frames`
218
+ );
207
219
  this.isPlayingTransition = true;
208
220
  this.isTransitioningToIdle = false;
209
221
  this.transitionFrames = transitionFrames;
@@ -225,6 +237,13 @@ const _AnimationHandler = class _AnimationHandler {
225
237
  * @internal
226
238
  */
227
239
  async handleTransitionToIdle(protobufData, frameCount) {
240
+ if (!this.isInSession) {
241
+ logger.info(
242
+ "AnimationHandler",
243
+ "Ignoring transition end packet with no active session"
244
+ );
245
+ return;
246
+ }
228
247
  if (this.hasHandledTransitionEnd) {
229
248
  return;
230
249
  }
@@ -235,29 +254,42 @@ const _AnimationHandler = class _AnimationHandler {
235
254
  return;
236
255
  }
237
256
  if (!this.renderer.isReady()) {
238
- logger.warn("AnimationHandler", "Renderer not ready for transition to idle");
257
+ logger.warn(
258
+ "AnimationHandler",
259
+ "Renderer not ready for transition to idle"
260
+ );
239
261
  this.renderer.renderFrame(void 0, true);
240
262
  this.logRenderedFrame("idle");
241
263
  return;
242
264
  }
243
265
  const keyframes = this.decoder(protobufData);
244
266
  if (!keyframes || keyframes.length === 0) {
245
- logger.warn("AnimationHandler", "No last keyframe in transition end data, starting idle directly");
267
+ logger.warn(
268
+ "AnimationHandler",
269
+ "No last keyframe in transition end data, starting idle directly"
270
+ );
246
271
  this.renderer.renderFrame(void 0, true);
247
272
  this.logRenderedFrame("idle");
248
273
  return;
249
274
  }
250
275
  this.hasHandledTransitionEnd = true;
276
+ this.flushBuffer();
251
277
  const lastFrame = keyframes[0];
252
278
  const frames = frameCount ?? this.config.transitionEndFrameCount;
253
- logger.info("AnimationHandler", `Generating ${frames} reverse transition frames to idle`);
279
+ logger.info(
280
+ "AnimationHandler",
281
+ `Generating ${frames} reverse transition frames to idle`
282
+ );
254
283
  this.isGeneratingEndTransition = true;
255
284
  try {
256
285
  const transitionFrames = await this.renderer.generateTransitionFromIdle(
257
286
  lastFrame,
258
287
  frames
259
288
  );
260
- logger.info("AnimationHandler", `Generated ${transitionFrames.length} transition frames, reversing for playback`);
289
+ logger.info(
290
+ "AnimationHandler",
291
+ `Generated ${transitionFrames.length} transition frames, reversing for playback`
292
+ );
261
293
  const reversedFrames = transitionFrames.slice().reverse();
262
294
  this.isPlayingTransition = true;
263
295
  this.isTransitioningToIdle = true;
@@ -265,7 +297,11 @@ const _AnimationHandler = class _AnimationHandler {
265
297
  this.transitionFrameIndex = 0;
266
298
  this.playTransitionFrame();
267
299
  } catch (error) {
268
- logger.error("AnimationHandler", "Failed to generate reverse transition:", error);
300
+ logger.error(
301
+ "AnimationHandler",
302
+ "Failed to generate reverse transition:",
303
+ error
304
+ );
269
305
  this.renderer.renderFrame(void 0, true);
270
306
  this.logRenderedFrame("idle");
271
307
  } finally {
@@ -296,7 +332,10 @@ const _AnimationHandler = class _AnimationHandler {
296
332
  this.startWatchdog();
297
333
  this.startPlaybackStats();
298
334
  if (frameSeq !== void 0) {
299
- logger.info("AnimationHandler", `Session started from animation frame seq=${frameSeq}`);
335
+ logger.info(
336
+ "AnimationHandler",
337
+ `Session started from animation frame seq=${frameSeq}`
338
+ );
300
339
  }
301
340
  }
302
341
  /**
@@ -318,7 +357,7 @@ const _AnimationHandler = class _AnimationHandler {
318
357
  * @internal
319
358
  */
320
359
  isInTransition() {
321
- return this.isPlayingTransition;
360
+ return this.isPlayingTransition || this.isGeneratingStartTransition || this.isGeneratingEndTransition;
322
361
  }
323
362
  /**
324
363
  * Stop transition playback.
@@ -376,7 +415,11 @@ const _AnimationHandler = class _AnimationHandler {
376
415
  try {
377
416
  this.onStreamStalledCallback();
378
417
  } catch (e) {
379
- logger.error("AnimationHandler", "Error in onStreamStalled callback:", e);
418
+ logger.error(
419
+ "AnimationHandler",
420
+ "Error in onStreamStalled callback:",
421
+ e
422
+ );
380
423
  }
381
424
  }
382
425
  }
@@ -449,7 +492,9 @@ const _AnimationHandler = class _AnimationHandler {
449
492
  if (this.playbackFrameTimestamps.length >= 2) {
450
493
  const intervals = [];
451
494
  for (let i = 1; i < this.playbackFrameTimestamps.length; i++) {
452
- intervals.push(this.playbackFrameTimestamps[i] - this.playbackFrameTimestamps[i - 1]);
495
+ intervals.push(
496
+ this.playbackFrameTimestamps[i] - this.playbackFrameTimestamps[i - 1]
497
+ );
453
498
  }
454
499
  const mean = intervals.reduce((a, b) => a + b, 0) / intervals.length;
455
500
  const variance = intervals.reduce((sum, v) => sum + (v - mean) ** 2, 0) / intervals.length;
@@ -505,7 +550,10 @@ const _AnimationHandler = class _AnimationHandler {
505
550
  if (this.bufferNextSeq < 0) {
506
551
  this.bufferNextSeq = seq;
507
552
  }
508
- logger.info("AnimationHandler", `Jitter buffer: filling (first frame seq=${seq})`);
553
+ logger.info(
554
+ "AnimationHandler",
555
+ `Jitter buffer: filling (first frame seq=${seq})`
556
+ );
509
557
  if (this.frameBuffer.size >= _AnimationHandler.BUFFER_INITIAL_FILL) {
510
558
  this.startBufferDrain();
511
559
  }
@@ -528,7 +576,10 @@ const _AnimationHandler = class _AnimationHandler {
528
576
  if (this.frameBuffer.size === 0) {
529
577
  return;
530
578
  }
531
- const minAllowedSeq = Math.max(this.bufferNextSeq, this.lastRenderedFrameSeq + 1);
579
+ const minAllowedSeq = Math.max(
580
+ this.bufferNextSeq,
581
+ this.lastRenderedFrameSeq + 1
582
+ );
532
583
  if (minAllowedSeq < 0) {
533
584
  return;
534
585
  }
@@ -572,7 +623,10 @@ const _AnimationHandler = class _AnimationHandler {
572
623
  }
573
624
  this.bufferNextSeq = minSeq;
574
625
  }
575
- logger.info("AnimationHandler", `Jitter buffer: draining (${this.frameBuffer.size} frames buffered)`);
626
+ logger.info(
627
+ "AnimationHandler",
628
+ `Jitter buffer: draining (${this.frameBuffer.size} frames buffered)`
629
+ );
576
630
  this.bufferLastDrainTime = performance.now();
577
631
  this.drainBufferFrame();
578
632
  }
@@ -596,7 +650,10 @@ const _AnimationHandler = class _AnimationHandler {
596
650
  const nextSeq = this.findLowestBufferedSeqAtOrAfter(this.bufferNextSeq);
597
651
  if (nextSeq === null) {
598
652
  this.bufferState = "starved";
599
- logger.warn("AnimationHandler", "Jitter buffer: no in-order frames available, pausing drain");
653
+ logger.warn(
654
+ "AnimationHandler",
655
+ "Jitter buffer: no in-order frames available, pausing drain"
656
+ );
600
657
  return;
601
658
  }
602
659
  const nextFrame = this.frameBuffer.get(nextSeq);
@@ -673,7 +730,10 @@ const _AnimationHandler = class _AnimationHandler {
673
730
  }
674
731
  logger.info("AnimationHandler", "Transition playback complete");
675
732
  if (wasTransitioningToIdle) {
676
- logger.info("AnimationHandler", "Starting idle animation after transition");
733
+ logger.info(
734
+ "AnimationHandler",
735
+ "Starting idle animation after transition"
736
+ );
677
737
  this.renderer.renderFrame(void 0, true);
678
738
  this.logRenderedFrame("idle");
679
739
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index6.js","sources":["../src/core/AnimationHandler.ts"],"sourcesContent":["/**\n * Animation Handler - Orchestrates animation playback and transitions.\n *\n * This module handles:\n * - Animation frame rendering\n * - Transition playback from idle to animation and back\n * - Frame timing at 25fps\n * - Session state tracking\n *\n * The handler relies on server-sent packet flags (Transition, TransitionEnd, Idle)\n * to determine when to generate transitions, rather than maintaining complex internal state.\n *\n * @internal\n * @packageDocumentation\n */\n\nimport type { Flame } from '../proto/animation';\nimport { decodeAnimationKeyframes } from '../proto/animation';\nimport { logger } from '../utils';\n\n/**\n * Interface for decoding protobuf animation data.\n * Allows custom decoder implementation if needed.\n * @internal\n */\nexport type AnimationDecoder = (protobufData: ArrayBuffer) => Flame[] | null;\n\n/**\n * Interface that applications must implement to render avatar frames.\n * The SDK calls these methods to control the avatar.\n * @internal\n */\nexport interface AvatarRenderer {\n /**\n * Render a single animation frame.\n * @param flame - The frame to render, or undefined to render idle\n * @param startIdle - If true and flame is undefined, start idle animation loop\n */\n renderFrame(flame: Flame | undefined, startIdle?: boolean): void;\n\n /**\n * Generate transition frames from idle position to target frame.\n * @param targetFrame - The target frame to transition to\n * @param frameCount - Number of transition frames to generate\n * @returns Promise resolving to array of transition frames\n */\n generateTransitionFromIdle(\n targetFrame: Flame,\n frameCount: number\n ): Promise<Flame[]>;\n\n /**\n * Check if the renderer is available and ready.\n * @returns true if renderer is ready to accept frames\n */\n isReady(): boolean;\n}\n\n/**\n * Configuration for AnimationHandler.\n * @internal\n */\nexport interface AnimationHandlerConfig {\n /**\n * Number of transition frames when starting animation.\n * Default: 8 (~320ms at 25fps)\n */\n transitionStartFrameCount?: number;\n\n /**\n * Number of transition frames when ending animation.\n * Default: 12 (~480ms at 25fps)\n */\n transitionEndFrameCount?: number;\n\n /**\n * Custom decoder function.\n * Uses built-in protobuf decoder if not provided.\n */\n decoder?: AnimationDecoder;\n\n /**\n * Callback when data stream stalls and fallback to idle is triggered.\n * Called when no animation frames received for 3 seconds during speaking session.\n */\n onStreamStalled?: () => void;\n\n /**\n * Enable jitter buffer for smoother playback.\n * When enabled, frames are buffered and rendered in sequence order at a steady 25fps,\n * absorbing network jitter and out-of-order delivery.\n * Default: true\n */\n enableJitterBuffer?: boolean;\n\n /**\n * Maximum delay (in ms) a frame can sit in the jitter buffer before being rendered.\n * Also controls how long to wait for a missing frame before skipping ahead.\n * Default: 80 (2 frames at 25fps)\n */\n maxBufferDelayMs?: number;\n}\n\n/**\n * Jitter buffer state machine.\n * @internal\n */\ntype BufferState = 'direct' | 'filling' | 'draining' | 'starved';\n\n/**\n * A frame held in the jitter buffer awaiting playback.\n * @internal\n */\ninterface BufferedFrame {\n flame: Flame;\n seq: number;\n receivedAt: number;\n}\n\n/**\n * AnimationHandler manages all animation playback and transition logic.\n *\n * @internal\n * This class is used internally by AvatarPlayer.\n * Applications should not instantiate this class directly.\n */\nexport class AnimationHandler {\n /** @internal */\n private renderer: AvatarRenderer;\n /** @internal */\n private decoder: AnimationDecoder;\n /** @internal */\n private config: Required<Omit<AnimationHandlerConfig, 'decoder' | 'onStreamStalled'>>;\n\n // Frame tracking\n /** @internal */\n private animationFrameCount = 0;\n /** @internal */\n private lastRenderedFrameSeq = -1;\n /** @internal */\n private renderedFrameCount = 0;\n\n // Transition state\n /** @internal */\n private isPlayingTransition = false;\n /** @internal */\n private isTransitioningToIdle = false;\n /** @internal */\n private transitionFrames: Flame[] = [];\n /** @internal */\n private transitionFrameIndex = 0;\n /** @internal */\n private transitionTimeoutId: ReturnType<typeof setTimeout> | null = null;\n\n // Guards against race conditions during async transition generation\n /** @internal */\n private isGeneratingStartTransition = false;\n /** @internal */\n private isGeneratingEndTransition = false;\n\n // Session-level flags to prevent duplicate handling\n // These are reset in resetTracking() for new sessions\n /** @internal */\n private hasHandledTransitionStart = false;\n /** @internal */\n private hasHandledTransitionEnd = false;\n\n // Watchdog for detecting stalled data stream\n /** @internal */\n private lastFrameReceivedTime = 0;\n /** @internal */\n private isInSession = false;\n /** @internal */\n private watchdogTimer: ReturnType<typeof setInterval> | null = null;\n /** @internal */\n private hasReportedStall = false;\n /** @internal */\n private static readonly STALL_TIMEOUT_MS = 3000;\n /** @internal */\n private onStreamStalledCallback: (() => void) | null = null;\n /** @internal */\n private isStalledFallback = false; // True when fallback to idle was triggered due to stall\n\n // Playback stats\n /** @internal */\n private playbackStatsTimer: ReturnType<typeof setInterval> | null = null;\n /** @internal */\n private playbackFrameCount = 0;\n /** @internal */\n private playbackFrameTimestamps: number[] = [];\n /** @internal */\n private playbackGapCount = 0;\n /** @internal */\n private playbackExpectedSeq = -1;\n\n // Jitter buffer\n /** @internal */\n private bufferState: BufferState = 'direct';\n /** @internal */\n private frameBuffer = new Map<number, BufferedFrame>();\n /** @internal */\n private bufferNextSeq = -1;\n /** @internal */\n private bufferDrainTimer: ReturnType<typeof setTimeout> | null = null;\n /** @internal */\n private bufferLastDrainTime = 0;\n /** @internal */\n private static readonly BUFFER_MAX_SIZE = 4;\n /** @internal */\n private static readonly BUFFER_INITIAL_FILL = 2;\n /** @internal */\n private static readonly BUFFER_FRAME_INTERVAL_MS = 40;\n\n /**\n * @internal\n */\n constructor(renderer: AvatarRenderer, config: AnimationHandlerConfig = {}) {\n this.renderer = renderer;\n this.decoder = config.decoder ?? decodeAnimationKeyframes;\n this.config = {\n transitionStartFrameCount: config.transitionStartFrameCount ?? 8,\n transitionEndFrameCount: config.transitionEndFrameCount ?? 12,\n enableJitterBuffer: config.enableJitterBuffer ?? true,\n maxBufferDelayMs: config.maxBufferDelayMs ?? 80,\n };\n this.onStreamStalledCallback = config.onStreamStalled ?? null;\n }\n\n /**\n * Handle animation data received from RTC provider.\n * @param protobufData - Raw protobuf bytes\n * @param frameSeq - Frame sequence number (optional)\n * @param isRecovered - Whether this frame was recovered via ALR\n * @internal\n */\n handleAnimationData(\n protobufData: ArrayBuffer,\n frameSeq?: number,\n isRecovered?: boolean\n ): void {\n // If we were in transition, stop it and play normal animation\n if (this.isPlayingTransition) {\n this.stopTransition();\n }\n\n // Update watchdog state\n const now = Date.now();\n if (this.hasReportedStall) {\n // Recovered from stall\n const stallDuration = now - this.lastFrameReceivedTime;\n logger.info('AnimationHandler', `Data stream resumed after ${stallDuration}ms stall`);\n this.hasReportedStall = false;\n }\n // If we were in stalled fallback, clear the flag (data has resumed, no transition needed)\n if (this.isStalledFallback) {\n logger.info('AnimationHandler', 'Resuming from stall fallback, rendering directly without transition');\n this.isStalledFallback = false;\n }\n this.lastFrameReceivedTime = now;\n\n this.animationFrameCount++;\n\n // Decode\n const keyframes = this.decoder(protobufData);\n if (!keyframes || keyframes.length === 0) {\n return;\n }\n\n // In lossy networks the transition packet may be delayed/lost.\n // Ensure session-level watchdog/stats still start on first valid playback frame.\n this.ensureSessionActive(frameSeq);\n\n // Jitter buffer path: insert into buffer instead of rendering directly\n if (this.config.enableJitterBuffer && frameSeq !== undefined) {\n this.bufferFrame(keyframes[0], frameSeq);\n return;\n }\n\n // Direct path (no buffer)\n // Check for out-of-order delivery\n if (frameSeq !== undefined && this.lastRenderedFrameSeq !== -1) {\n if (frameSeq < this.lastRenderedFrameSeq) {\n // Out-of-order frame - discard (already rendered a later frame)\n logger.warn(\n 'AnimationHandler',\n `OUT-OF-ORDER: seq=${frameSeq}, lastRendered=${this.lastRenderedFrameSeq}${isRecovered ? ' [RECOVERED]' : ''}, discarding`\n );\n return;\n } else if (frameSeq === this.lastRenderedFrameSeq) {\n // Duplicate frame - skip silently (expected when server sends redundant frames)\n return;\n } else if (frameSeq > this.lastRenderedFrameSeq + 1) {\n const gap = frameSeq - this.lastRenderedFrameSeq - 1;\n logger.warn(\n 'AnimationHandler',\n `GAP: ${gap} frame(s) between ${this.lastRenderedFrameSeq} and ${frameSeq}${isRecovered ? ' [RECOVERED]' : ''}`\n );\n }\n }\n\n if (frameSeq !== undefined) {\n this.lastRenderedFrameSeq = frameSeq;\n }\n\n // Render directly\n this.renderedFrameCount++;\n this.renderer.renderFrame(keyframes[0]);\n this.logRenderedFrame('direct', frameSeq, isRecovered);\n\n // Collect playback stats\n this.playbackFrameTimestamps.push(performance.now());\n this.playbackFrameCount++;\n if (frameSeq !== undefined) {\n if (this.playbackExpectedSeq >= 0 && frameSeq > this.playbackExpectedSeq) {\n this.playbackGapCount += frameSeq - this.playbackExpectedSeq;\n }\n this.playbackExpectedSeq = frameSeq + 1;\n }\n }\n\n /**\n * Handle transition packet - generate and play transition from idle to target.\n * Only starts transition on the first packet; subsequent packets are ignored while transitioning.\n * @param protobufData - Protobuf data containing target frame\n * @param frameCount - Number of transition frames (overrides config if provided)\n * @internal\n */\n async handleTransitionData(\n protobufData: ArrayBuffer,\n frameCount?: number\n ): Promise<void> {\n logger.info(\n 'AnimationHandler',\n `Start transition packet received (bytes=${protobufData.byteLength}, requestedFrames=${frameCount ?? this.config.transitionStartFrameCount}, hasHandledStart=${this.hasHandledTransitionStart}, isInSession=${this.isInSession}, isPlayingTransition=${this.isPlayingTransition}, isGeneratingStart=${this.isGeneratingStartTransition}, lastRenderedSeq=${this.lastRenderedFrameSeq}, bufferState=${this.bufferState}, buffered=${this.frameBuffer.size})`\n );\n\n // Ignore if we've already handled a transition start for this session\n if (this.hasHandledTransitionStart) {\n return;\n }\n // Ignore if already playing or generating a start transition\n if (this.isPlayingTransition && !this.isTransitioningToIdle) {\n return;\n }\n if (this.isGeneratingStartTransition) {\n return;\n }\n\n if (!this.renderer.isReady()) {\n logger.warn('AnimationHandler', 'Renderer not ready for transition');\n return;\n }\n\n // Once streaming playback has started, start-transition packets are stale.\n // Ignore them to avoid jumping back into transition and causing visual jitter.\n if (\n this.isInSession && (\n this.lastRenderedFrameSeq >= 0 ||\n this.frameBuffer.size > 0 ||\n this.bufferState !== 'direct'\n )\n ) {\n this.hasHandledTransitionStart = true;\n logger.warn(\n 'AnimationHandler',\n `Ignoring late transition packet after playback start (lastRenderedSeq=${this.lastRenderedFrameSeq}, bufferState=${this.bufferState}, buffered=${this.frameBuffer.size})`\n );\n return;\n }\n\n // Decode target frame\n const keyframes = this.decoder(protobufData);\n if (!keyframes || keyframes.length === 0) {\n logger.warn('AnimationHandler', 'No target keyframe in transition data');\n return;\n }\n\n // Mark that we've handled transition start for this session\n this.hasHandledTransitionStart = true;\n // Reset transition end flag since we're starting a new transition\n this.hasHandledTransitionEnd = false;\n\n // Start session and watchdog\n this.ensureSessionActive();\n\n const targetFrame = keyframes[0];\n const frames = frameCount ?? this.config.transitionStartFrameCount;\n logger.info('AnimationHandler', `Generating ${frames} transition frames to target`);\n\n // Set guard flag before async operation\n this.isGeneratingStartTransition = true;\n\n try {\n // Generate transition frames from idle to target\n const transitionFrames = await this.renderer.generateTransitionFromIdle(\n targetFrame,\n frames\n );\n logger.info('AnimationHandler', `Generated ${transitionFrames.length} transition frames`);\n\n // Start playing transition frames at 25fps\n this.isPlayingTransition = true;\n this.isTransitioningToIdle = false;\n this.transitionFrames = transitionFrames;\n this.transitionFrameIndex = 0;\n\n this.playTransitionFrame();\n } catch (error) {\n logger.error('AnimationHandler', 'Failed to generate transition:', error);\n // Fallback: render target directly\n this.renderer.renderFrame(targetFrame);\n this.logRenderedFrame('transition-fallback');\n } finally {\n this.isGeneratingStartTransition = false;\n }\n }\n\n /**\n * Handle transition end - generate and play reverse transition back to idle.\n * Only starts transition on the first packet; subsequent packets are ignored while transitioning.\n * @param protobufData - Protobuf data containing last animation frame\n * @param frameCount - Number of transition frames (overrides config if provided)\n * @internal\n */\n async handleTransitionToIdle(\n protobufData: ArrayBuffer,\n frameCount?: number\n ): Promise<void> {\n // Ignore if we've already handled a transition end for this session\n if (this.hasHandledTransitionEnd) {\n return;\n }\n // Ignore if already playing or generating an end transition\n if (this.isPlayingTransition && this.isTransitioningToIdle) {\n return;\n }\n if (this.isGeneratingEndTransition) {\n return;\n }\n\n if (!this.renderer.isReady()) {\n logger.warn('AnimationHandler', 'Renderer not ready for transition to idle');\n this.renderer.renderFrame(undefined, true);\n this.logRenderedFrame('idle');\n return;\n }\n\n // Decode last animation frame\n const keyframes = this.decoder(protobufData);\n if (!keyframes || keyframes.length === 0) {\n logger.warn('AnimationHandler', 'No last keyframe in transition end data, starting idle directly');\n this.renderer.renderFrame(undefined, true);\n this.logRenderedFrame('idle');\n return;\n }\n\n // Mark that we've handled transition end for this session\n this.hasHandledTransitionEnd = true;\n\n const lastFrame = keyframes[0];\n const frames = frameCount ?? this.config.transitionEndFrameCount;\n logger.info('AnimationHandler', `Generating ${frames} reverse transition frames to idle`);\n\n // Set guard flag before async operation\n this.isGeneratingEndTransition = true;\n\n try {\n // Generate transition frames from idle to last frame, then reverse\n const transitionFrames = await this.renderer.generateTransitionFromIdle(\n lastFrame,\n frames\n );\n logger.info('AnimationHandler', `Generated ${transitionFrames.length} transition frames, reversing for playback`);\n\n // Reverse frames to play from last animation frame back to idle\n const reversedFrames = transitionFrames.slice().reverse();\n\n // Start playing reversed transition at 25fps\n this.isPlayingTransition = true;\n this.isTransitioningToIdle = true;\n this.transitionFrames = reversedFrames;\n this.transitionFrameIndex = 0;\n\n this.playTransitionFrame();\n } catch (error) {\n logger.error('AnimationHandler', 'Failed to generate reverse transition:', error);\n this.renderer.renderFrame(undefined, true);\n this.logRenderedFrame('idle');\n } finally {\n this.isGeneratingEndTransition = false;\n }\n }\n\n /**\n * Start idle animation.\n * @internal\n */\n startIdle(): void {\n this.isInSession = false;\n this.hasReportedStall = false;\n this.renderer.renderFrame(undefined, true);\n this.logRenderedFrame('idle');\n }\n\n /**\n * Ensure session-level timers/stats are active.\n * @internal\n */\n private ensureSessionActive(frameSeq?: number): void {\n if (this.isInSession) {\n return;\n }\n\n this.isInSession = true;\n this.lastFrameReceivedTime = Date.now();\n this.hasReportedStall = false;\n this.startWatchdog();\n this.startPlaybackStats();\n\n if (frameSeq !== undefined) {\n logger.info('AnimationHandler', `Session started from animation frame seq=${frameSeq}`);\n }\n }\n\n /**\n * Reset animation frame tracking (call on session start).\n * @internal\n */\n resetTracking(): void {\n this.lastRenderedFrameSeq = -1;\n this.renderedFrameCount = 0;\n this.animationFrameCount = 0;\n // Reset session-level transition flags for new session\n this.hasHandledTransitionStart = false;\n this.hasHandledTransitionEnd = false;\n this.resetPlaybackStats();\n this.flushBuffer();\n logger.info('AnimationHandler', 'Frame tracking reset');\n }\n\n /**\n * Check if currently playing transition frames.\n * @internal\n */\n isInTransition(): boolean {\n return this.isPlayingTransition;\n }\n\n /**\n * Stop transition playback.\n * @internal\n */\n stopTransition(): void {\n if (\n this.isPlayingTransition ||\n this.isGeneratingStartTransition ||\n this.isGeneratingEndTransition\n ) {\n logger.info('AnimationHandler', 'Stopping transition playback');\n }\n this.isPlayingTransition = false;\n this.isTransitioningToIdle = false;\n this.isGeneratingStartTransition = false;\n this.isGeneratingEndTransition = false;\n // Note: We intentionally do NOT reset hasHandledTransitionStart/End here\n // Those are session-level flags, only reset in resetTracking()\n this.transitionFrames = [];\n this.transitionFrameIndex = 0;\n if (this.transitionTimeoutId !== null) {\n clearTimeout(this.transitionTimeoutId);\n this.transitionTimeoutId = null;\n }\n this.flushBuffer();\n }\n\n /**\n * Clean up resources.\n * @internal\n */\n dispose(): void {\n this.stopTransition();\n this.stopWatchdog();\n }\n\n /**\n * Start the watchdog timer to detect stalled data streams.\n * @internal\n */\n private startWatchdog(): void {\n if (this.watchdogTimer) {\n return; // Already running\n }\n\n this.watchdogTimer = setInterval(() => {\n if (!this.isInSession) {\n return;\n }\n\n // Don't check during transition playback (we're playing generated frames, not receiving)\n if (this.isPlayingTransition) {\n return;\n }\n\n const elapsed = Date.now() - this.lastFrameReceivedTime;\n if (elapsed > AnimationHandler.STALL_TIMEOUT_MS && !this.hasReportedStall) {\n logger.error(\n 'AnimationHandler',\n `Data stream stalled: no frames received for ${elapsed}ms, falling back to idle`\n );\n this.hasReportedStall = true;\n this.isStalledFallback = true;\n\n // Trigger fallback to idle\n this.startIdle();\n\n // Notify external listener\n if (this.onStreamStalledCallback) {\n try {\n this.onStreamStalledCallback();\n } catch (e) {\n logger.error('AnimationHandler', 'Error in onStreamStalled callback:', e);\n }\n }\n }\n }, 1000); // Check every 1 second\n }\n\n /**\n * Stop the watchdog timer.\n * @internal\n */\n private stopWatchdog(): void {\n if (this.watchdogTimer) {\n clearInterval(this.watchdogTimer);\n this.watchdogTimer = null;\n }\n this.hasReportedStall = false;\n this.isStalledFallback = false;\n this.stopPlaybackStats();\n }\n\n /**\n * Start the playback stats reporting timer.\n * @internal\n */\n private startPlaybackStats(): void {\n if (this.playbackStatsTimer) {\n return; // Already running\n }\n this.resetPlaybackStats();\n this.playbackStatsTimer = setInterval(() => {\n this.reportPlaybackStats();\n }, 1000);\n }\n\n /**\n * Stop the playback stats reporting timer.\n * @internal\n */\n private stopPlaybackStats(): void {\n if (this.playbackStatsTimer) {\n clearInterval(this.playbackStatsTimer);\n this.playbackStatsTimer = null;\n }\n this.resetPlaybackStats();\n }\n\n /**\n * Reset playback stats counters.\n * @internal\n */\n private resetPlaybackStats(): void {\n this.playbackFrameCount = 0;\n this.playbackFrameTimestamps = [];\n this.playbackGapCount = 0;\n this.playbackExpectedSeq = -1;\n }\n\n /**\n * Report playback stats (called every 1s by timer).\n * Logs FPS, frame loss rate, and playback jitter.\n * @internal\n */\n private reportPlaybackStats(): void {\n // Skip reporting during transitions (locally generated frames)\n if (this.isPlayingTransition) {\n this.resetPlaybackStats();\n return;\n }\n\n // Skip if no frames were rendered this interval\n if (this.playbackFrameCount === 0) {\n return;\n }\n\n const fps = this.playbackFrameCount;\n const totalExpected = this.playbackFrameCount + this.playbackGapCount;\n const lossRate = totalExpected > 0 ? (this.playbackGapCount / totalExpected) * 100 : 0;\n\n // Calculate jitter: std deviation of inter-frame intervals\n let jitter = 0;\n if (this.playbackFrameTimestamps.length >= 2) {\n const intervals: number[] = [];\n for (let i = 1; i < this.playbackFrameTimestamps.length; i++) {\n intervals.push(this.playbackFrameTimestamps[i] - this.playbackFrameTimestamps[i - 1]);\n }\n const mean = intervals.reduce((a, b) => a + b, 0) / intervals.length;\n const variance = intervals.reduce((sum, v) => sum + (v - mean) ** 2, 0) / intervals.length;\n jitter = Math.sqrt(variance);\n }\n\n logger.info(\n 'AnimationHandler',\n `Playback stats: fps=${fps}, lossRate=${lossRate.toFixed(1)}%, jitter=${jitter.toFixed(1)}ms`\n );\n\n // Reset counters for next interval\n this.playbackFrameCount = 0;\n this.playbackFrameTimestamps = [];\n this.playbackGapCount = 0;\n }\n\n // ── Jitter Buffer ──\n\n /**\n * Insert a decoded frame into the jitter buffer.\n * Handles dedup, enforces max size, and drives the buffer state machine.\n * @internal\n */\n private bufferFrame(flame: Flame, seq: number): void {\n // Never allow frames older than what we've already rendered.\n if (this.lastRenderedFrameSeq >= 0 && seq <= this.lastRenderedFrameSeq) {\n logger.warn(\n 'AnimationHandler',\n `Jitter buffer: dropping stale frame seq=${seq} (lastRendered=${this.lastRenderedFrameSeq})`\n );\n return;\n }\n\n // If we're already waiting for a newer sequence, this frame arrived too late.\n if (this.bufferNextSeq >= 0 && seq < this.bufferNextSeq) {\n logger.warn(\n 'AnimationHandler',\n `Jitter buffer: dropping late frame seq=${seq} (nextExpected=${this.bufferNextSeq})`\n );\n return;\n }\n\n // Dedup — server sends each frame twice for resilience\n if (this.frameBuffer.has(seq)) {\n return;\n }\n\n this.frameBuffer.set(seq, { flame, seq, receivedAt: performance.now() });\n\n // Enforce max buffer size — drop oldest when full\n if (this.frameBuffer.size > AnimationHandler.BUFFER_MAX_SIZE) {\n let oldestSeq = Infinity;\n for (const k of this.frameBuffer.keys()) {\n if (k < oldestSeq) oldestSeq = k;\n }\n this.frameBuffer.delete(oldestSeq);\n logger.warn(\n 'AnimationHandler',\n `Jitter buffer: overflow, dropping seq=${oldestSeq} (nextExpected=${this.bufferNextSeq})`\n );\n }\n\n // State machine transitions\n switch (this.bufferState) {\n case 'direct':\n this.bufferState = 'filling';\n if (this.bufferNextSeq < 0) {\n this.bufferNextSeq = seq;\n }\n logger.info('AnimationHandler', `Jitter buffer: filling (first frame seq=${seq})`);\n if (this.frameBuffer.size >= AnimationHandler.BUFFER_INITIAL_FILL) {\n this.startBufferDrain();\n }\n break;\n case 'filling':\n if (this.frameBuffer.size >= AnimationHandler.BUFFER_INITIAL_FILL) {\n this.startBufferDrain();\n }\n break;\n case 'starved':\n this.startBufferDrain();\n break;\n case 'draining':\n // Already draining, frame will be picked up by drain loop\n break;\n }\n }\n\n /**\n * Drop buffered frames that are now too old to ever be rendered in-order.\n * @internal\n */\n private dropStaleBufferedFrames(): void {\n if (this.frameBuffer.size === 0) {\n return;\n }\n\n const minAllowedSeq = Math.max(this.bufferNextSeq, this.lastRenderedFrameSeq + 1);\n if (minAllowedSeq < 0) {\n return;\n }\n\n let dropped = 0;\n for (const seq of Array.from(this.frameBuffer.keys())) {\n if (seq < minAllowedSeq) {\n this.frameBuffer.delete(seq);\n dropped++;\n }\n }\n\n if (dropped > 0) {\n logger.warn(\n 'AnimationHandler',\n `Jitter buffer: dropped ${dropped} stale frame(s) older than seq=${minAllowedSeq}`\n );\n }\n }\n\n /**\n * Find the lowest buffered sequence at or after minSeq.\n * @internal\n */\n private findLowestBufferedSeqAtOrAfter(minSeq: number): number | null {\n let candidate = Infinity;\n for (const seq of this.frameBuffer.keys()) {\n if (seq >= minSeq && seq < candidate) {\n candidate = seq;\n }\n }\n return candidate === Infinity ? null : candidate;\n }\n\n /**\n * Begin draining the buffer at 25fps.\n * @internal\n */\n private startBufferDrain(): void {\n this.bufferState = 'draining';\n if (this.bufferNextSeq < 0) {\n let minSeq = Infinity;\n for (const k of this.frameBuffer.keys()) {\n if (k < minSeq) minSeq = k;\n }\n this.bufferNextSeq = minSeq;\n }\n logger.info('AnimationHandler', `Jitter buffer: draining (${this.frameBuffer.size} frames buffered)`);\n this.bufferLastDrainTime = performance.now();\n this.drainBufferFrame();\n }\n\n /**\n * Drain one frame from the buffer and schedule the next drain.\n * Handles missing frames (skip-ahead after maxBufferDelayMs) and starvation.\n * @internal\n */\n private drainBufferFrame(): void {\n this.bufferDrainTimer = null;\n\n if (this.bufferState !== 'draining') {\n return;\n }\n\n this.dropStaleBufferedFrames();\n\n const frame = this.frameBuffer.get(this.bufferNextSeq);\n\n if (frame) {\n // Expected frame found — render it\n this.renderBufferedFrame(frame);\n this.frameBuffer.delete(this.bufferNextSeq);\n this.bufferNextSeq++;\n } else if (this.frameBuffer.size > 0) {\n // Expected frame missing — check if we should skip ahead to the next in-order frame.\n const nextSeq = this.findLowestBufferedSeqAtOrAfter(this.bufferNextSeq);\n\n if (nextSeq === null) {\n this.bufferState = 'starved';\n logger.warn('AnimationHandler', 'Jitter buffer: no in-order frames available, pausing drain');\n return;\n }\n\n const nextFrame = this.frameBuffer.get(nextSeq)!;\n const waitTime = performance.now() - nextFrame.receivedAt;\n\n if (waitTime > this.config.maxBufferDelayMs) {\n // Missing frame didn't arrive in time — skip ahead to the next available sequence.\n const gap = Math.max(0, nextSeq - this.bufferNextSeq);\n this.playbackGapCount += gap;\n logger.warn(\n 'AnimationHandler',\n `Jitter buffer: skipping ${gap} frame(s) from seq ${this.bufferNextSeq} to ${nextSeq} after ${waitTime.toFixed(1)}ms`\n );\n this.renderBufferedFrame(nextFrame);\n this.frameBuffer.delete(nextSeq);\n this.bufferNextSeq = nextSeq + 1;\n }\n // else: wait for the missing frame (render nothing this cycle)\n } else {\n // Buffer empty — starvation\n this.bufferState = 'starved';\n logger.warn('AnimationHandler', 'Jitter buffer: starved, pausing drain');\n return; // Don't schedule next drain\n }\n\n // Schedule next drain with drift correction\n const now = performance.now();\n const nextTarget = this.bufferLastDrainTime + AnimationHandler.BUFFER_FRAME_INTERVAL_MS;\n const delay = Math.max(0, nextTarget - now);\n this.bufferLastDrainTime = nextTarget;\n this.bufferDrainTimer = setTimeout(() => this.drainBufferFrame(), delay);\n }\n\n /**\n * Render a single frame from the buffer and collect playback stats.\n * @internal\n */\n private renderBufferedFrame(frame: BufferedFrame): void {\n if (this.lastRenderedFrameSeq >= 0 && frame.seq <= this.lastRenderedFrameSeq) {\n logger.warn(\n 'AnimationHandler',\n `Jitter buffer: refusing out-of-order render seq=${frame.seq} (lastRendered=${this.lastRenderedFrameSeq})`\n );\n return;\n }\n\n this.renderer.renderFrame(frame.flame);\n this.lastRenderedFrameSeq = frame.seq;\n this.renderedFrameCount++;\n this.logRenderedFrame('buffer', frame.seq);\n\n // Playback stats\n this.playbackFrameTimestamps.push(performance.now());\n this.playbackFrameCount++;\n }\n\n /**\n * Flush the jitter buffer, stop the drain loop, and revert to direct mode.\n * @internal\n */\n private flushBuffer(): void {\n this.frameBuffer.clear();\n this.bufferState = 'direct';\n this.bufferNextSeq = -1;\n this.bufferLastDrainTime = 0;\n if (this.bufferDrainTimer !== null) {\n clearTimeout(this.bufferDrainTimer);\n this.bufferDrainTimer = null;\n }\n }\n\n /**\n * Play a single transition frame and schedule the next one.\n * @internal\n */\n private playTransitionFrame(): void {\n if (\n !this.isPlayingTransition ||\n this.transitionFrameIndex >= this.transitionFrames.length\n ) {\n // Transition complete\n const wasTransitioningToIdle = this.isTransitioningToIdle;\n this.isPlayingTransition = false;\n this.isTransitioningToIdle = false;\n this.transitionFrames = [];\n this.transitionFrameIndex = 0;\n if (this.transitionTimeoutId !== null) {\n clearTimeout(this.transitionTimeoutId);\n this.transitionTimeoutId = null;\n }\n logger.info('AnimationHandler', 'Transition playback complete');\n\n // If transitioning to idle, start idle animation\n if (wasTransitioningToIdle) {\n logger.info('AnimationHandler', 'Starting idle animation after transition');\n this.renderer.renderFrame(undefined, true);\n this.logRenderedFrame('idle');\n }\n return;\n }\n\n if (!this.renderer.isReady()) {\n this.isPlayingTransition = false;\n this.isTransitioningToIdle = false;\n return;\n }\n\n const frame = this.transitionFrames[this.transitionFrameIndex];\n this.renderer.renderFrame(frame);\n this.logRenderedFrame('transition');\n this.transitionFrameIndex++;\n\n // Schedule next frame at 25fps (40ms)\n this.transitionTimeoutId = setTimeout(() => {\n if (this.isPlayingTransition) {\n this.playTransitionFrame();\n }\n }, 40);\n }\n\n /**\n * Emit a per-frame render log for debugging ordering issues.\n * @internal\n */\n private logRenderedFrame(\n source: 'direct' | 'buffer' | 'transition' | 'idle' | 'transition-fallback',\n seq?: number,\n isRecovered?: boolean\n ): void {\n logger.info(\n 'AnimationHandler',\n `Rendered frame: source=${source}, seq=${seq ?? 'n/a'}${isRecovered ? ' [RECOVERED]' : ''}`\n );\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index6.js","sources":["../src/core/AnimationHandler.ts"],"sourcesContent":["/**\n * Animation Handler - Orchestrates animation playback and transitions.\n *\n * This module handles:\n * - Animation frame rendering\n * - Transition playback from idle to animation and back\n * - Frame timing at 25fps\n * - Session state tracking\n *\n * The handler relies on server-sent packet flags (Transition, TransitionEnd, Idle)\n * to determine when to generate transitions, rather than maintaining complex internal state.\n *\n * @internal\n * @packageDocumentation\n */\n\nimport type { Flame } from '../proto/animation';\nimport { decodeAnimationKeyframes } from '../proto/animation';\nimport { logger } from '../utils';\n\n/**\n * Interface for decoding protobuf animation data.\n * Allows custom decoder implementation if needed.\n * @internal\n */\nexport type AnimationDecoder = (protobufData: ArrayBuffer) => Flame[] | null;\n\n/**\n * Interface that applications must implement to render avatar frames.\n * The SDK calls these methods to control the avatar.\n * @internal\n */\nexport interface AvatarRenderer {\n /**\n * Render a single animation frame.\n * @param flame - The frame to render, or undefined to render idle\n * @param startIdle - If true and flame is undefined, start idle animation loop\n */\n renderFrame(flame: Flame | undefined, startIdle?: boolean): void;\n\n /**\n * Generate transition frames from idle position to target frame.\n * @param targetFrame - The target frame to transition to\n * @param frameCount - Number of transition frames to generate\n * @returns Promise resolving to array of transition frames\n */\n generateTransitionFromIdle(\n targetFrame: Flame,\n frameCount: number,\n ): Promise<Flame[]>;\n\n /**\n * Check if the renderer is available and ready.\n * @returns true if renderer is ready to accept frames\n */\n isReady(): boolean;\n}\n\n/**\n * Configuration for AnimationHandler.\n * @internal\n */\nexport interface AnimationHandlerConfig {\n /**\n * Number of transition frames when starting animation.\n * Default: 8 (~320ms at 25fps)\n */\n transitionStartFrameCount?: number;\n\n /**\n * Number of transition frames when ending animation.\n * Default: 12 (~480ms at 25fps)\n */\n transitionEndFrameCount?: number;\n\n /**\n * Custom decoder function.\n * Uses built-in protobuf decoder if not provided.\n */\n decoder?: AnimationDecoder;\n\n /**\n * Callback when data stream stalls and fallback to idle is triggered.\n * Called when no animation frames received for 3 seconds during speaking session.\n */\n onStreamStalled?: () => void;\n\n /**\n * Enable jitter buffer for smoother playback.\n * When enabled, frames are buffered and rendered in sequence order at a steady 25fps,\n * absorbing network jitter and out-of-order delivery.\n * Default: true\n */\n enableJitterBuffer?: boolean;\n\n /**\n * Maximum delay (in ms) a frame can sit in the jitter buffer before being rendered.\n * Also controls how long to wait for a missing frame before skipping ahead.\n * Default: 80 (2 frames at 25fps)\n */\n maxBufferDelayMs?: number;\n}\n\n/**\n * Jitter buffer state machine.\n * @internal\n */\ntype BufferState = 'direct' | 'filling' | 'draining' | 'starved';\n\n/**\n * A frame held in the jitter buffer awaiting playback.\n * @internal\n */\ninterface BufferedFrame {\n flame: Flame;\n seq: number;\n receivedAt: number;\n}\n\n/**\n * AnimationHandler manages all animation playback and transition logic.\n *\n * @internal\n * This class is used internally by AvatarPlayer.\n * Applications should not instantiate this class directly.\n */\nexport class AnimationHandler {\n /** @internal */\n private renderer: AvatarRenderer;\n /** @internal */\n private decoder: AnimationDecoder;\n /** @internal */\n private config: Required<\n Omit<AnimationHandlerConfig, 'decoder' | 'onStreamStalled'>\n >;\n\n // Frame tracking\n /** @internal */\n private animationFrameCount = 0;\n /** @internal */\n private lastRenderedFrameSeq = -1;\n /** @internal */\n private renderedFrameCount = 0;\n\n // Transition state\n /** @internal */\n private isPlayingTransition = false;\n /** @internal */\n private isTransitioningToIdle = false;\n /** @internal */\n private transitionFrames: Flame[] = [];\n /** @internal */\n private transitionFrameIndex = 0;\n /** @internal */\n private transitionTimeoutId: ReturnType<typeof setTimeout> | null = null;\n\n // Guards against race conditions during async transition generation\n /** @internal */\n private isGeneratingStartTransition = false;\n /** @internal */\n private isGeneratingEndTransition = false;\n\n // Session-level flags to prevent duplicate handling\n // These are reset in resetTracking() for new sessions\n /** @internal */\n private hasHandledTransitionStart = false;\n /** @internal */\n private hasHandledTransitionEnd = false;\n\n // Watchdog for detecting stalled data stream\n /** @internal */\n private lastFrameReceivedTime = 0;\n /** @internal */\n private isInSession = false;\n /** @internal */\n private watchdogTimer: ReturnType<typeof setInterval> | null = null;\n /** @internal */\n private hasReportedStall = false;\n /** @internal */\n private static readonly STALL_TIMEOUT_MS = 3000;\n /** @internal */\n private onStreamStalledCallback: (() => void) | null = null;\n /** @internal */\n private isStalledFallback = false; // True when fallback to idle was triggered due to stall\n\n // Playback stats\n /** @internal */\n private playbackStatsTimer: ReturnType<typeof setInterval> | null = null;\n /** @internal */\n private playbackFrameCount = 0;\n /** @internal */\n private playbackFrameTimestamps: number[] = [];\n /** @internal */\n private playbackGapCount = 0;\n /** @internal */\n private playbackExpectedSeq = -1;\n\n // Jitter buffer\n /** @internal */\n private bufferState: BufferState = 'direct';\n /** @internal */\n private frameBuffer = new Map<number, BufferedFrame>();\n /** @internal */\n private bufferNextSeq = -1;\n /** @internal */\n private bufferDrainTimer: ReturnType<typeof setTimeout> | null = null;\n /** @internal */\n private bufferLastDrainTime = 0;\n /** @internal */\n private static readonly BUFFER_MAX_SIZE = 4;\n /** @internal */\n private static readonly BUFFER_INITIAL_FILL = 2;\n /** @internal */\n private static readonly BUFFER_FRAME_INTERVAL_MS = 40;\n\n /**\n * @internal\n */\n constructor(renderer: AvatarRenderer, config: AnimationHandlerConfig = {}) {\n this.renderer = renderer;\n this.decoder = config.decoder ?? decodeAnimationKeyframes;\n this.config = {\n transitionStartFrameCount: config.transitionStartFrameCount ?? 8,\n transitionEndFrameCount: config.transitionEndFrameCount ?? 12,\n enableJitterBuffer: config.enableJitterBuffer ?? true,\n maxBufferDelayMs: config.maxBufferDelayMs ?? 80,\n };\n this.onStreamStalledCallback = config.onStreamStalled ?? null;\n }\n\n /**\n * Handle animation data received from RTC provider.\n * @param protobufData - Raw protobuf bytes\n * @param frameSeq - Frame sequence number (optional)\n * @param isRecovered - Whether this frame was recovered via ALR\n * @internal\n */\n handleAnimationData(\n protobufData: ArrayBuffer,\n frameSeq?: number,\n isRecovered?: boolean,\n ): void {\n // If we were in transition, stop it and play normal animation\n if (this.isPlayingTransition) {\n this.stopTransition();\n }\n\n // Update watchdog state\n const now = Date.now();\n if (this.hasReportedStall) {\n // Recovered from stall\n const stallDuration = now - this.lastFrameReceivedTime;\n logger.info(\n 'AnimationHandler',\n `Data stream resumed after ${stallDuration}ms stall`,\n );\n this.hasReportedStall = false;\n }\n // If we were in stalled fallback, clear the flag (data has resumed, no transition needed)\n if (this.isStalledFallback) {\n logger.info(\n 'AnimationHandler',\n 'Resuming from stall fallback, rendering directly without transition',\n );\n this.isStalledFallback = false;\n }\n this.lastFrameReceivedTime = now;\n\n this.animationFrameCount++;\n\n // Decode\n const keyframes = this.decoder(protobufData);\n if (!keyframes || keyframes.length === 0) {\n return;\n }\n\n // In lossy networks the transition packet may be delayed/lost.\n // Ensure session-level watchdog/stats still start on first valid playback frame.\n this.ensureSessionActive(frameSeq);\n\n // Jitter buffer path: insert into buffer instead of rendering directly\n if (this.config.enableJitterBuffer && frameSeq !== undefined) {\n this.bufferFrame(keyframes[0], frameSeq);\n return;\n }\n\n // Direct path (no buffer)\n // Check for out-of-order delivery\n if (frameSeq !== undefined && this.lastRenderedFrameSeq !== -1) {\n if (frameSeq < this.lastRenderedFrameSeq) {\n // Out-of-order frame - discard (already rendered a later frame)\n logger.warn(\n 'AnimationHandler',\n `OUT-OF-ORDER: seq=${frameSeq}, lastRendered=${this.lastRenderedFrameSeq}${isRecovered ? ' [RECOVERED]' : ''}, discarding`,\n );\n return;\n } else if (frameSeq === this.lastRenderedFrameSeq) {\n // Duplicate frame - skip silently (expected when server sends redundant frames)\n return;\n } else if (frameSeq > this.lastRenderedFrameSeq + 1) {\n const gap = frameSeq - this.lastRenderedFrameSeq - 1;\n logger.warn(\n 'AnimationHandler',\n `GAP: ${gap} frame(s) between ${this.lastRenderedFrameSeq} and ${frameSeq}${isRecovered ? ' [RECOVERED]' : ''}`,\n );\n }\n }\n\n if (frameSeq !== undefined) {\n this.lastRenderedFrameSeq = frameSeq;\n }\n\n // Render directly\n this.renderedFrameCount++;\n this.renderer.renderFrame(keyframes[0]);\n this.logRenderedFrame('direct', frameSeq, isRecovered);\n\n // Collect playback stats\n this.playbackFrameTimestamps.push(performance.now());\n this.playbackFrameCount++;\n if (frameSeq !== undefined) {\n if (\n this.playbackExpectedSeq >= 0 &&\n frameSeq > this.playbackExpectedSeq\n ) {\n this.playbackGapCount += frameSeq - this.playbackExpectedSeq;\n }\n this.playbackExpectedSeq = frameSeq + 1;\n }\n }\n\n /**\n * Handle transition packet - generate and play transition from idle to target.\n * Only starts transition on the first packet; subsequent packets are ignored while transitioning.\n * @param protobufData - Protobuf data containing target frame\n * @param frameCount - Number of transition frames (overrides config if provided)\n * @internal\n */\n async handleTransitionData(\n protobufData: ArrayBuffer,\n frameCount?: number,\n ): Promise<void> {\n logger.info(\n 'AnimationHandler',\n `Start transition packet received (bytes=${protobufData.byteLength}, requestedFrames=${frameCount ?? this.config.transitionStartFrameCount}, hasHandledStart=${this.hasHandledTransitionStart}, isInSession=${this.isInSession}, isPlayingTransition=${this.isPlayingTransition}, isGeneratingStart=${this.isGeneratingStartTransition}, lastRenderedSeq=${this.lastRenderedFrameSeq}, bufferState=${this.bufferState}, buffered=${this.frameBuffer.size})`,\n );\n\n // Ignore if we've already handled a transition start for this session\n if (this.hasHandledTransitionStart) {\n return;\n }\n // Ignore if already playing or generating a start transition\n if (this.isPlayingTransition && !this.isTransitioningToIdle) {\n return;\n }\n if (this.isGeneratingStartTransition) {\n return;\n }\n\n if (!this.renderer.isReady()) {\n logger.warn('AnimationHandler', 'Renderer not ready for transition');\n return;\n }\n\n // Once streaming playback has started, start-transition packets are stale.\n // Ignore them to avoid jumping back into transition and causing visual jitter.\n if (\n this.isInSession &&\n (this.lastRenderedFrameSeq >= 0 ||\n this.frameBuffer.size > 0 ||\n this.bufferState !== 'direct')\n ) {\n this.hasHandledTransitionStart = true;\n logger.warn(\n 'AnimationHandler',\n `Ignoring late transition packet after playback start (lastRenderedSeq=${this.lastRenderedFrameSeq}, bufferState=${this.bufferState}, buffered=${this.frameBuffer.size})`,\n );\n return;\n }\n\n // Decode target frame\n const keyframes = this.decoder(protobufData);\n if (!keyframes || keyframes.length === 0) {\n logger.warn('AnimationHandler', 'No target keyframe in transition data');\n return;\n }\n\n // Mark that we've handled transition start for this session\n this.hasHandledTransitionStart = true;\n // Reset transition end flag since we're starting a new transition\n this.hasHandledTransitionEnd = false;\n\n // Start session and watchdog\n this.ensureSessionActive();\n\n const targetFrame = keyframes[0];\n const frames = frameCount ?? this.config.transitionStartFrameCount;\n logger.info(\n 'AnimationHandler',\n `Generating ${frames} transition frames to target`,\n );\n\n // Set guard flag before async operation\n this.isGeneratingStartTransition = true;\n\n try {\n // Generate transition frames from idle to target\n const transitionFrames = await this.renderer.generateTransitionFromIdle(\n targetFrame,\n frames,\n );\n logger.info(\n 'AnimationHandler',\n `Generated ${transitionFrames.length} transition frames`,\n );\n\n // Start playing transition frames at 25fps\n this.isPlayingTransition = true;\n this.isTransitioningToIdle = false;\n this.transitionFrames = transitionFrames;\n this.transitionFrameIndex = 0;\n\n this.playTransitionFrame();\n } catch (error) {\n logger.error('AnimationHandler', 'Failed to generate transition:', error);\n // Fallback: render target directly\n this.renderer.renderFrame(targetFrame);\n this.logRenderedFrame('transition-fallback');\n } finally {\n this.isGeneratingStartTransition = false;\n }\n }\n\n /**\n * Handle transition end - generate and play reverse transition back to idle.\n * Only starts transition on the first packet; subsequent packets are ignored while transitioning.\n * @param protobufData - Protobuf data containing last animation frame\n * @param frameCount - Number of transition frames (overrides config if provided)\n * @internal\n */\n async handleTransitionToIdle(\n protobufData: ArrayBuffer,\n frameCount?: number,\n ): Promise<void> {\n // Ignore stale transition-end packets after session has already returned to idle.\n // This can happen when duplicate packets arrive late over jittery networks.\n if (!this.isInSession) {\n logger.info(\n 'AnimationHandler',\n 'Ignoring transition end packet with no active session',\n );\n return;\n }\n\n // Ignore if we've already handled a transition end for this session\n if (this.hasHandledTransitionEnd) {\n return;\n }\n // Ignore if already playing or generating an end transition\n if (this.isPlayingTransition && this.isTransitioningToIdle) {\n return;\n }\n if (this.isGeneratingEndTransition) {\n return;\n }\n\n if (!this.renderer.isReady()) {\n logger.warn(\n 'AnimationHandler',\n 'Renderer not ready for transition to idle',\n );\n this.renderer.renderFrame(undefined, true);\n this.logRenderedFrame('idle');\n return;\n }\n\n // Decode last animation frame\n const keyframes = this.decoder(protobufData);\n if (!keyframes || keyframes.length === 0) {\n logger.warn(\n 'AnimationHandler',\n 'No last keyframe in transition end data, starting idle directly',\n );\n this.renderer.renderFrame(undefined, true);\n this.logRenderedFrame('idle');\n return;\n }\n\n // Mark that we've handled transition end for this session\n this.hasHandledTransitionEnd = true;\n\n // Drop any queued speaking frames so they cannot interleave with end transition playback.\n this.flushBuffer();\n\n const lastFrame = keyframes[0];\n const frames = frameCount ?? this.config.transitionEndFrameCount;\n logger.info(\n 'AnimationHandler',\n `Generating ${frames} reverse transition frames to idle`,\n );\n\n // Set guard flag before async operation\n this.isGeneratingEndTransition = true;\n\n try {\n // Generate transition frames from idle to last frame, then reverse\n const transitionFrames = await this.renderer.generateTransitionFromIdle(\n lastFrame,\n frames,\n );\n logger.info(\n 'AnimationHandler',\n `Generated ${transitionFrames.length} transition frames, reversing for playback`,\n );\n\n // Reverse frames to play from last animation frame back to idle\n const reversedFrames = transitionFrames.slice().reverse();\n\n // Start playing reversed transition at 25fps\n this.isPlayingTransition = true;\n this.isTransitioningToIdle = true;\n this.transitionFrames = reversedFrames;\n this.transitionFrameIndex = 0;\n\n this.playTransitionFrame();\n } catch (error) {\n logger.error(\n 'AnimationHandler',\n 'Failed to generate reverse transition:',\n error,\n );\n this.renderer.renderFrame(undefined, true);\n this.logRenderedFrame('idle');\n } finally {\n this.isGeneratingEndTransition = false;\n }\n }\n\n /**\n * Start idle animation.\n * @internal\n */\n startIdle(): void {\n this.isInSession = false;\n this.hasReportedStall = false;\n this.renderer.renderFrame(undefined, true);\n this.logRenderedFrame('idle');\n }\n\n /**\n * Ensure session-level timers/stats are active.\n * @internal\n */\n private ensureSessionActive(frameSeq?: number): void {\n if (this.isInSession) {\n return;\n }\n\n this.isInSession = true;\n this.lastFrameReceivedTime = Date.now();\n this.hasReportedStall = false;\n this.startWatchdog();\n this.startPlaybackStats();\n\n if (frameSeq !== undefined) {\n logger.info(\n 'AnimationHandler',\n `Session started from animation frame seq=${frameSeq}`,\n );\n }\n }\n\n /**\n * Reset animation frame tracking (call on session start).\n * @internal\n */\n resetTracking(): void {\n this.lastRenderedFrameSeq = -1;\n this.renderedFrameCount = 0;\n this.animationFrameCount = 0;\n // Reset session-level transition flags for new session\n this.hasHandledTransitionStart = false;\n this.hasHandledTransitionEnd = false;\n this.resetPlaybackStats();\n this.flushBuffer();\n logger.info('AnimationHandler', 'Frame tracking reset');\n }\n\n /**\n * Check if currently playing transition frames.\n * @internal\n */\n isInTransition(): boolean {\n return (\n this.isPlayingTransition ||\n this.isGeneratingStartTransition ||\n this.isGeneratingEndTransition\n );\n }\n\n /**\n * Stop transition playback.\n * @internal\n */\n stopTransition(): void {\n if (\n this.isPlayingTransition ||\n this.isGeneratingStartTransition ||\n this.isGeneratingEndTransition\n ) {\n logger.info('AnimationHandler', 'Stopping transition playback');\n }\n this.isPlayingTransition = false;\n this.isTransitioningToIdle = false;\n this.isGeneratingStartTransition = false;\n this.isGeneratingEndTransition = false;\n // Note: We intentionally do NOT reset hasHandledTransitionStart/End here\n // Those are session-level flags, only reset in resetTracking()\n this.transitionFrames = [];\n this.transitionFrameIndex = 0;\n if (this.transitionTimeoutId !== null) {\n clearTimeout(this.transitionTimeoutId);\n this.transitionTimeoutId = null;\n }\n this.flushBuffer();\n }\n\n /**\n * Clean up resources.\n * @internal\n */\n dispose(): void {\n this.stopTransition();\n this.stopWatchdog();\n }\n\n /**\n * Start the watchdog timer to detect stalled data streams.\n * @internal\n */\n private startWatchdog(): void {\n if (this.watchdogTimer) {\n return; // Already running\n }\n\n this.watchdogTimer = setInterval(() => {\n if (!this.isInSession) {\n return;\n }\n\n // Don't check during transition playback (we're playing generated frames, not receiving)\n if (this.isPlayingTransition) {\n return;\n }\n\n const elapsed = Date.now() - this.lastFrameReceivedTime;\n if (\n elapsed > AnimationHandler.STALL_TIMEOUT_MS &&\n !this.hasReportedStall\n ) {\n logger.error(\n 'AnimationHandler',\n `Data stream stalled: no frames received for ${elapsed}ms, falling back to idle`,\n );\n this.hasReportedStall = true;\n this.isStalledFallback = true;\n\n // Trigger fallback to idle\n this.startIdle();\n\n // Notify external listener\n if (this.onStreamStalledCallback) {\n try {\n this.onStreamStalledCallback();\n } catch (e) {\n logger.error(\n 'AnimationHandler',\n 'Error in onStreamStalled callback:',\n e,\n );\n }\n }\n }\n }, 1000); // Check every 1 second\n }\n\n /**\n * Stop the watchdog timer.\n * @internal\n */\n private stopWatchdog(): void {\n if (this.watchdogTimer) {\n clearInterval(this.watchdogTimer);\n this.watchdogTimer = null;\n }\n this.hasReportedStall = false;\n this.isStalledFallback = false;\n this.stopPlaybackStats();\n }\n\n /**\n * Start the playback stats reporting timer.\n * @internal\n */\n private startPlaybackStats(): void {\n if (this.playbackStatsTimer) {\n return; // Already running\n }\n this.resetPlaybackStats();\n this.playbackStatsTimer = setInterval(() => {\n this.reportPlaybackStats();\n }, 1000);\n }\n\n /**\n * Stop the playback stats reporting timer.\n * @internal\n */\n private stopPlaybackStats(): void {\n if (this.playbackStatsTimer) {\n clearInterval(this.playbackStatsTimer);\n this.playbackStatsTimer = null;\n }\n this.resetPlaybackStats();\n }\n\n /**\n * Reset playback stats counters.\n * @internal\n */\n private resetPlaybackStats(): void {\n this.playbackFrameCount = 0;\n this.playbackFrameTimestamps = [];\n this.playbackGapCount = 0;\n this.playbackExpectedSeq = -1;\n }\n\n /**\n * Report playback stats (called every 1s by timer).\n * Logs FPS, frame loss rate, and playback jitter.\n * @internal\n */\n private reportPlaybackStats(): void {\n // Skip reporting during transitions (locally generated frames)\n if (this.isPlayingTransition) {\n this.resetPlaybackStats();\n return;\n }\n\n // Skip if no frames were rendered this interval\n if (this.playbackFrameCount === 0) {\n return;\n }\n\n const fps = this.playbackFrameCount;\n const totalExpected = this.playbackFrameCount + this.playbackGapCount;\n const lossRate =\n totalExpected > 0 ? (this.playbackGapCount / totalExpected) * 100 : 0;\n\n // Calculate jitter: std deviation of inter-frame intervals\n let jitter = 0;\n if (this.playbackFrameTimestamps.length >= 2) {\n const intervals: number[] = [];\n for (let i = 1; i < this.playbackFrameTimestamps.length; i++) {\n intervals.push(\n this.playbackFrameTimestamps[i] - this.playbackFrameTimestamps[i - 1],\n );\n }\n const mean = intervals.reduce((a, b) => a + b, 0) / intervals.length;\n const variance =\n intervals.reduce((sum, v) => sum + (v - mean) ** 2, 0) /\n intervals.length;\n jitter = Math.sqrt(variance);\n }\n\n logger.info(\n 'AnimationHandler',\n `Playback stats: fps=${fps}, lossRate=${lossRate.toFixed(1)}%, jitter=${jitter.toFixed(1)}ms`,\n );\n\n // Reset counters for next interval\n this.playbackFrameCount = 0;\n this.playbackFrameTimestamps = [];\n this.playbackGapCount = 0;\n }\n\n // ── Jitter Buffer ──\n\n /**\n * Insert a decoded frame into the jitter buffer.\n * Handles dedup, enforces max size, and drives the buffer state machine.\n * @internal\n */\n private bufferFrame(flame: Flame, seq: number): void {\n // Never allow frames older than what we've already rendered.\n if (this.lastRenderedFrameSeq >= 0 && seq <= this.lastRenderedFrameSeq) {\n logger.warn(\n 'AnimationHandler',\n `Jitter buffer: dropping stale frame seq=${seq} (lastRendered=${this.lastRenderedFrameSeq})`,\n );\n return;\n }\n\n // If we're already waiting for a newer sequence, this frame arrived too late.\n if (this.bufferNextSeq >= 0 && seq < this.bufferNextSeq) {\n logger.warn(\n 'AnimationHandler',\n `Jitter buffer: dropping late frame seq=${seq} (nextExpected=${this.bufferNextSeq})`,\n );\n return;\n }\n\n // Dedup — server sends each frame twice for resilience\n if (this.frameBuffer.has(seq)) {\n return;\n }\n\n this.frameBuffer.set(seq, { flame, seq, receivedAt: performance.now() });\n\n // Enforce max buffer size — drop oldest when full\n if (this.frameBuffer.size > AnimationHandler.BUFFER_MAX_SIZE) {\n let oldestSeq = Infinity;\n for (const k of this.frameBuffer.keys()) {\n if (k < oldestSeq) oldestSeq = k;\n }\n this.frameBuffer.delete(oldestSeq);\n logger.warn(\n 'AnimationHandler',\n `Jitter buffer: overflow, dropping seq=${oldestSeq} (nextExpected=${this.bufferNextSeq})`,\n );\n }\n\n // State machine transitions\n switch (this.bufferState) {\n case 'direct':\n this.bufferState = 'filling';\n if (this.bufferNextSeq < 0) {\n this.bufferNextSeq = seq;\n }\n logger.info(\n 'AnimationHandler',\n `Jitter buffer: filling (first frame seq=${seq})`,\n );\n if (this.frameBuffer.size >= AnimationHandler.BUFFER_INITIAL_FILL) {\n this.startBufferDrain();\n }\n break;\n case 'filling':\n if (this.frameBuffer.size >= AnimationHandler.BUFFER_INITIAL_FILL) {\n this.startBufferDrain();\n }\n break;\n case 'starved':\n this.startBufferDrain();\n break;\n case 'draining':\n // Already draining, frame will be picked up by drain loop\n break;\n }\n }\n\n /**\n * Drop buffered frames that are now too old to ever be rendered in-order.\n * @internal\n */\n private dropStaleBufferedFrames(): void {\n if (this.frameBuffer.size === 0) {\n return;\n }\n\n const minAllowedSeq = Math.max(\n this.bufferNextSeq,\n this.lastRenderedFrameSeq + 1,\n );\n if (minAllowedSeq < 0) {\n return;\n }\n\n let dropped = 0;\n for (const seq of Array.from(this.frameBuffer.keys())) {\n if (seq < minAllowedSeq) {\n this.frameBuffer.delete(seq);\n dropped++;\n }\n }\n\n if (dropped > 0) {\n logger.warn(\n 'AnimationHandler',\n `Jitter buffer: dropped ${dropped} stale frame(s) older than seq=${minAllowedSeq}`,\n );\n }\n }\n\n /**\n * Find the lowest buffered sequence at or after minSeq.\n * @internal\n */\n private findLowestBufferedSeqAtOrAfter(minSeq: number): number | null {\n let candidate = Infinity;\n for (const seq of this.frameBuffer.keys()) {\n if (seq >= minSeq && seq < candidate) {\n candidate = seq;\n }\n }\n return candidate === Infinity ? null : candidate;\n }\n\n /**\n * Begin draining the buffer at 25fps.\n * @internal\n */\n private startBufferDrain(): void {\n this.bufferState = 'draining';\n if (this.bufferNextSeq < 0) {\n let minSeq = Infinity;\n for (const k of this.frameBuffer.keys()) {\n if (k < minSeq) minSeq = k;\n }\n this.bufferNextSeq = minSeq;\n }\n logger.info(\n 'AnimationHandler',\n `Jitter buffer: draining (${this.frameBuffer.size} frames buffered)`,\n );\n this.bufferLastDrainTime = performance.now();\n this.drainBufferFrame();\n }\n\n /**\n * Drain one frame from the buffer and schedule the next drain.\n * Handles missing frames (skip-ahead after maxBufferDelayMs) and starvation.\n * @internal\n */\n private drainBufferFrame(): void {\n this.bufferDrainTimer = null;\n\n if (this.bufferState !== 'draining') {\n return;\n }\n\n this.dropStaleBufferedFrames();\n\n const frame = this.frameBuffer.get(this.bufferNextSeq);\n\n if (frame) {\n // Expected frame found — render it\n this.renderBufferedFrame(frame);\n this.frameBuffer.delete(this.bufferNextSeq);\n this.bufferNextSeq++;\n } else if (this.frameBuffer.size > 0) {\n // Expected frame missing — check if we should skip ahead to the next in-order frame.\n const nextSeq = this.findLowestBufferedSeqAtOrAfter(this.bufferNextSeq);\n\n if (nextSeq === null) {\n this.bufferState = 'starved';\n logger.warn(\n 'AnimationHandler',\n 'Jitter buffer: no in-order frames available, pausing drain',\n );\n return;\n }\n\n const nextFrame = this.frameBuffer.get(nextSeq)!;\n const waitTime = performance.now() - nextFrame.receivedAt;\n\n if (waitTime > this.config.maxBufferDelayMs) {\n // Missing frame didn't arrive in time — skip ahead to the next available sequence.\n const gap = Math.max(0, nextSeq - this.bufferNextSeq);\n this.playbackGapCount += gap;\n logger.warn(\n 'AnimationHandler',\n `Jitter buffer: skipping ${gap} frame(s) from seq ${this.bufferNextSeq} to ${nextSeq} after ${waitTime.toFixed(1)}ms`,\n );\n this.renderBufferedFrame(nextFrame);\n this.frameBuffer.delete(nextSeq);\n this.bufferNextSeq = nextSeq + 1;\n }\n // else: wait for the missing frame (render nothing this cycle)\n } else {\n // Buffer empty — starvation\n this.bufferState = 'starved';\n logger.warn('AnimationHandler', 'Jitter buffer: starved, pausing drain');\n return; // Don't schedule next drain\n }\n\n // Schedule next drain with drift correction\n const now = performance.now();\n const nextTarget =\n this.bufferLastDrainTime + AnimationHandler.BUFFER_FRAME_INTERVAL_MS;\n const delay = Math.max(0, nextTarget - now);\n this.bufferLastDrainTime = nextTarget;\n this.bufferDrainTimer = setTimeout(() => this.drainBufferFrame(), delay);\n }\n\n /**\n * Render a single frame from the buffer and collect playback stats.\n * @internal\n */\n private renderBufferedFrame(frame: BufferedFrame): void {\n if (\n this.lastRenderedFrameSeq >= 0 &&\n frame.seq <= this.lastRenderedFrameSeq\n ) {\n logger.warn(\n 'AnimationHandler',\n `Jitter buffer: refusing out-of-order render seq=${frame.seq} (lastRendered=${this.lastRenderedFrameSeq})`,\n );\n return;\n }\n\n this.renderer.renderFrame(frame.flame);\n this.lastRenderedFrameSeq = frame.seq;\n this.renderedFrameCount++;\n this.logRenderedFrame('buffer', frame.seq);\n\n // Playback stats\n this.playbackFrameTimestamps.push(performance.now());\n this.playbackFrameCount++;\n }\n\n /**\n * Flush the jitter buffer, stop the drain loop, and revert to direct mode.\n * @internal\n */\n private flushBuffer(): void {\n this.frameBuffer.clear();\n this.bufferState = 'direct';\n this.bufferNextSeq = -1;\n this.bufferLastDrainTime = 0;\n if (this.bufferDrainTimer !== null) {\n clearTimeout(this.bufferDrainTimer);\n this.bufferDrainTimer = null;\n }\n }\n\n /**\n * Play a single transition frame and schedule the next one.\n * @internal\n */\n private playTransitionFrame(): void {\n if (\n !this.isPlayingTransition ||\n this.transitionFrameIndex >= this.transitionFrames.length\n ) {\n // Transition complete\n const wasTransitioningToIdle = this.isTransitioningToIdle;\n this.isPlayingTransition = false;\n this.isTransitioningToIdle = false;\n this.transitionFrames = [];\n this.transitionFrameIndex = 0;\n if (this.transitionTimeoutId !== null) {\n clearTimeout(this.transitionTimeoutId);\n this.transitionTimeoutId = null;\n }\n logger.info('AnimationHandler', 'Transition playback complete');\n\n // If transitioning to idle, start idle animation\n if (wasTransitioningToIdle) {\n logger.info(\n 'AnimationHandler',\n 'Starting idle animation after transition',\n );\n this.renderer.renderFrame(undefined, true);\n this.logRenderedFrame('idle');\n }\n return;\n }\n\n if (!this.renderer.isReady()) {\n this.isPlayingTransition = false;\n this.isTransitioningToIdle = false;\n return;\n }\n\n const frame = this.transitionFrames[this.transitionFrameIndex];\n this.renderer.renderFrame(frame);\n this.logRenderedFrame('transition');\n this.transitionFrameIndex++;\n\n // Schedule next frame at 25fps (40ms)\n this.transitionTimeoutId = setTimeout(() => {\n if (this.isPlayingTransition) {\n this.playTransitionFrame();\n }\n }, 40);\n }\n\n /**\n * Emit a per-frame render log for debugging ordering issues.\n * @internal\n */\n private logRenderedFrame(\n source: 'direct' | 'buffer' | 'transition' | 'idle' | 'transition-fallback',\n seq?: number,\n isRecovered?: boolean,\n ): void {\n logger.info(\n 'AnimationHandler',\n `Rendered frame: source=${source}, seq=${seq ?? 'n/a'}${isRecovered ? ' [RECOVERED]' : ''}`,\n );\n }\n}\n"],"names":[],"mappings":"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"}
package/dist/index8.js CHANGED
@@ -160,7 +160,10 @@ function decodeAnimationMessage(data) {
160
160
  break;
161
161
  case 6:
162
162
  if (tag === 50) {
163
- message.serverResponseAnimation = decodeServerResponseAnimation(reader, reader.uint32());
163
+ message.serverResponseAnimation = decodeServerResponseAnimation(
164
+ reader,
165
+ reader.uint32()
166
+ );
164
167
  }
165
168
  break;
166
169
  default:
@@ -1 +1 @@
1
- {"version":3,"file":"index8.js","sources":["../src/proto/animation.ts"],"sourcesContent":["/**\n * Minimal protobuf definitions for animation data decoding.\n *\n * This is a stripped-down version containing only the types needed\n * to decode animation frames from the server response.\n *\n * Based on driveningress/v2/driveningress.proto\n *\n * @packageDocumentation\n */\n\nimport { BinaryReader } from '@bufbuild/protobuf/wire';\nimport { logger } from '../utils';\n\n/**\n * Flame - represents a single animation frame.\n * Contains FLAME model parameters for facial animation.\n * @internal\n */\nexport interface Flame {\n /** Translation parameters (3 floats: x, y, z) */\n translation: number[];\n /** Rotation parameters (quaternion or euler angles) */\n rotation: number[];\n /** Neck pose parameters */\n neckPose: number[];\n /** Jaw pose parameters */\n jawPose: number[];\n /** Eye pose parameters */\n eyePose: number[];\n /** Eyelid parameters */\n eyeLid: number[];\n /** Expression blend shape weights */\n expression: number[];\n}\n\n/**\n * FlameAnimation - contains keyframes.\n * @internal\n */\nexport interface FlameAnimation {\n keyframes: Flame[];\n}\n\n/**\n * ServerResponseAnimation - server response containing animation data.\n * @internal\n */\nexport interface ServerResponseAnimation {\n connectionId: string;\n reqId: string;\n end: boolean;\n animation?: FlameAnimation | undefined;\n}\n\n/**\n * AnimationMessage - top-level message wrapper.\n * @internal\n */\nexport interface AnimationMessage {\n type: number;\n serverResponseAnimation?: ServerResponseAnimation | undefined;\n}\n\n// ============== Decoders ==============\n\n/**\n * Decode a Flame message from protobuf.\n * @internal\n */\nfunction decodeFlame(reader: BinaryReader, length: number): Flame {\n const end = reader.pos + length;\n const flame: Flame = {\n translation: [],\n rotation: [],\n neckPose: [],\n jawPose: [],\n eyePose: [],\n eyeLid: [],\n expression: [],\n };\n\n while (reader.pos < end) {\n const tag = reader.uint32();\n switch (tag >>> 3) {\n case 1: // translation\n if (tag === 13) {\n flame.translation.push(reader.float());\n } else if (tag === 10) {\n const end2 = reader.uint32() + reader.pos;\n while (reader.pos < end2) {\n flame.translation.push(reader.float());\n }\n }\n break;\n case 2: // rotation\n if (tag === 21) {\n flame.rotation.push(reader.float());\n } else if (tag === 18) {\n const end2 = reader.uint32() + reader.pos;\n while (reader.pos < end2) {\n flame.rotation.push(reader.float());\n }\n }\n break;\n case 3: // neckPose\n if (tag === 29) {\n flame.neckPose.push(reader.float());\n } else if (tag === 26) {\n const end2 = reader.uint32() + reader.pos;\n while (reader.pos < end2) {\n flame.neckPose.push(reader.float());\n }\n }\n break;\n case 4: // jawPose\n if (tag === 37) {\n flame.jawPose.push(reader.float());\n } else if (tag === 34) {\n const end2 = reader.uint32() + reader.pos;\n while (reader.pos < end2) {\n flame.jawPose.push(reader.float());\n }\n }\n break;\n case 5: // eyePose\n if (tag === 45) {\n flame.eyePose.push(reader.float());\n } else if (tag === 42) {\n const end2 = reader.uint32() + reader.pos;\n while (reader.pos < end2) {\n flame.eyePose.push(reader.float());\n }\n }\n break;\n case 6: // eyeLid\n if (tag === 53) {\n flame.eyeLid.push(reader.float());\n } else if (tag === 50) {\n const end2 = reader.uint32() + reader.pos;\n while (reader.pos < end2) {\n flame.eyeLid.push(reader.float());\n }\n }\n break;\n case 7: // expression\n if (tag === 61) {\n flame.expression.push(reader.float());\n } else if (tag === 58) {\n const end2 = reader.uint32() + reader.pos;\n while (reader.pos < end2) {\n flame.expression.push(reader.float());\n }\n }\n break;\n default:\n reader.skip(tag & 7);\n }\n }\n\n return flame;\n}\n\n/**\n * Decode a FlameAnimation message from protobuf.\n * @internal\n */\nfunction decodeFlameAnimation(reader: BinaryReader, length: number): FlameAnimation {\n const end = reader.pos + length;\n const animation: FlameAnimation = { keyframes: [] };\n\n while (reader.pos < end) {\n const tag = reader.uint32();\n switch (tag >>> 3) {\n case 1: // keyframes\n if (tag === 10) {\n animation.keyframes.push(decodeFlame(reader, reader.uint32()));\n }\n break;\n default:\n reader.skip(tag & 7);\n }\n }\n\n return animation;\n}\n\n/**\n * Decode a ServerResponseAnimation message from protobuf.\n * @internal\n */\nfunction decodeServerResponseAnimation(reader: BinaryReader, length: number): ServerResponseAnimation {\n const end = reader.pos + length;\n const response: ServerResponseAnimation = {\n connectionId: \"\",\n reqId: \"\",\n end: false,\n animation: undefined,\n };\n\n while (reader.pos < end) {\n const tag = reader.uint32();\n switch (tag >>> 3) {\n case 1: // connectionId\n if (tag === 10) {\n response.connectionId = reader.string();\n }\n break;\n case 2: // reqId\n if (tag === 18) {\n response.reqId = reader.string();\n }\n break;\n case 3: // end\n if (tag === 24) {\n response.end = reader.bool();\n }\n break;\n case 4: // animation\n if (tag === 34) {\n response.animation = decodeFlameAnimation(reader, reader.uint32());\n }\n break;\n default:\n reader.skip(tag & 7);\n }\n }\n\n return response;\n}\n\n/**\n * Decode an AnimationMessage from protobuf bytes.\n * @param data - Raw protobuf bytes\n * @returns Decoded AnimationMessage\n * @internal\n */\nexport function decodeAnimationMessage(data: Uint8Array): AnimationMessage {\n const reader = new BinaryReader(data);\n const message: AnimationMessage = {\n type: 0,\n serverResponseAnimation: undefined,\n };\n\n while (reader.pos < reader.len) {\n const tag = reader.uint32();\n switch (tag >>> 3) {\n case 1: // type\n if (tag === 8) {\n message.type = reader.int32();\n }\n break;\n case 6: // serverResponseAnimation (field number 6 in the proto)\n if (tag === 50) {\n message.serverResponseAnimation = decodeServerResponseAnimation(reader, reader.uint32());\n }\n break;\n default:\n reader.skip(tag & 7);\n }\n }\n\n return message;\n}\n\n/**\n * Decode animation keyframes from protobuf data.\n * @param protobufData - Raw protobuf bytes\n * @returns Array of Flame keyframes, or null if decoding fails\n * @internal\n */\nexport function decodeAnimationKeyframes(protobufData: ArrayBuffer): Flame[] | null {\n try {\n const message = decodeAnimationMessage(new Uint8Array(protobufData));\n\n if (message.serverResponseAnimation?.animation?.keyframes) {\n return message.serverResponseAnimation.animation.keyframes;\n }\n\n return null;\n } catch (error) {\n logger.error('proto', 'Failed to decode animation keyframes:', error);\n return null;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index8.js","sources":["../src/proto/animation.ts"],"sourcesContent":["/**\n * Minimal protobuf definitions for animation data decoding.\n *\n * This is a stripped-down version containing only the types needed\n * to decode animation frames from the server response.\n *\n * Based on driveningress/v2/driveningress.proto\n *\n * @packageDocumentation\n */\n\nimport { BinaryReader } from '@bufbuild/protobuf/wire';\nimport { logger } from '../utils';\n\n/**\n * Flame - represents a single animation frame.\n * Contains FLAME model parameters for facial animation.\n * @internal\n */\nexport interface Flame {\n /** Translation parameters (3 floats: x, y, z) */\n translation: number[];\n /** Rotation parameters (quaternion or euler angles) */\n rotation: number[];\n /** Neck pose parameters */\n neckPose: number[];\n /** Jaw pose parameters */\n jawPose: number[];\n /** Eye pose parameters */\n eyePose: number[];\n /** Eyelid parameters */\n eyeLid: number[];\n /** Expression blend shape weights */\n expression: number[];\n}\n\n/**\n * FlameAnimation - contains keyframes.\n * @internal\n */\nexport interface FlameAnimation {\n keyframes: Flame[];\n}\n\n/**\n * ServerResponseAnimation - server response containing animation data.\n * @internal\n */\nexport interface ServerResponseAnimation {\n connectionId: string;\n reqId: string;\n end: boolean;\n animation?: FlameAnimation | undefined;\n}\n\n/**\n * AnimationMessage - top-level message wrapper.\n * @internal\n */\nexport interface AnimationMessage {\n type: number;\n serverResponseAnimation?: ServerResponseAnimation | undefined;\n}\n\n// ============== Decoders ==============\n\n/**\n * Decode a Flame message from protobuf.\n * @internal\n */\nfunction decodeFlame(reader: BinaryReader, length: number): Flame {\n const end = reader.pos + length;\n const flame: Flame = {\n translation: [],\n rotation: [],\n neckPose: [],\n jawPose: [],\n eyePose: [],\n eyeLid: [],\n expression: [],\n };\n\n while (reader.pos < end) {\n const tag = reader.uint32();\n switch (tag >>> 3) {\n case 1: // translation\n if (tag === 13) {\n flame.translation.push(reader.float());\n } else if (tag === 10) {\n const end2 = reader.uint32() + reader.pos;\n while (reader.pos < end2) {\n flame.translation.push(reader.float());\n }\n }\n break;\n case 2: // rotation\n if (tag === 21) {\n flame.rotation.push(reader.float());\n } else if (tag === 18) {\n const end2 = reader.uint32() + reader.pos;\n while (reader.pos < end2) {\n flame.rotation.push(reader.float());\n }\n }\n break;\n case 3: // neckPose\n if (tag === 29) {\n flame.neckPose.push(reader.float());\n } else if (tag === 26) {\n const end2 = reader.uint32() + reader.pos;\n while (reader.pos < end2) {\n flame.neckPose.push(reader.float());\n }\n }\n break;\n case 4: // jawPose\n if (tag === 37) {\n flame.jawPose.push(reader.float());\n } else if (tag === 34) {\n const end2 = reader.uint32() + reader.pos;\n while (reader.pos < end2) {\n flame.jawPose.push(reader.float());\n }\n }\n break;\n case 5: // eyePose\n if (tag === 45) {\n flame.eyePose.push(reader.float());\n } else if (tag === 42) {\n const end2 = reader.uint32() + reader.pos;\n while (reader.pos < end2) {\n flame.eyePose.push(reader.float());\n }\n }\n break;\n case 6: // eyeLid\n if (tag === 53) {\n flame.eyeLid.push(reader.float());\n } else if (tag === 50) {\n const end2 = reader.uint32() + reader.pos;\n while (reader.pos < end2) {\n flame.eyeLid.push(reader.float());\n }\n }\n break;\n case 7: // expression\n if (tag === 61) {\n flame.expression.push(reader.float());\n } else if (tag === 58) {\n const end2 = reader.uint32() + reader.pos;\n while (reader.pos < end2) {\n flame.expression.push(reader.float());\n }\n }\n break;\n default:\n reader.skip(tag & 7);\n }\n }\n\n return flame;\n}\n\n/**\n * Decode a FlameAnimation message from protobuf.\n * @internal\n */\nfunction decodeFlameAnimation(\n reader: BinaryReader,\n length: number,\n): FlameAnimation {\n const end = reader.pos + length;\n const animation: FlameAnimation = { keyframes: [] };\n\n while (reader.pos < end) {\n const tag = reader.uint32();\n switch (tag >>> 3) {\n case 1: // keyframes\n if (tag === 10) {\n animation.keyframes.push(decodeFlame(reader, reader.uint32()));\n }\n break;\n default:\n reader.skip(tag & 7);\n }\n }\n\n return animation;\n}\n\n/**\n * Decode a ServerResponseAnimation message from protobuf.\n * @internal\n */\nfunction decodeServerResponseAnimation(\n reader: BinaryReader,\n length: number,\n): ServerResponseAnimation {\n const end = reader.pos + length;\n const response: ServerResponseAnimation = {\n connectionId: '',\n reqId: '',\n end: false,\n animation: undefined,\n };\n\n while (reader.pos < end) {\n const tag = reader.uint32();\n switch (tag >>> 3) {\n case 1: // connectionId\n if (tag === 10) {\n response.connectionId = reader.string();\n }\n break;\n case 2: // reqId\n if (tag === 18) {\n response.reqId = reader.string();\n }\n break;\n case 3: // end\n if (tag === 24) {\n response.end = reader.bool();\n }\n break;\n case 4: // animation\n if (tag === 34) {\n response.animation = decodeFlameAnimation(reader, reader.uint32());\n }\n break;\n default:\n reader.skip(tag & 7);\n }\n }\n\n return response;\n}\n\n/**\n * Decode an AnimationMessage from protobuf bytes.\n * @param data - Raw protobuf bytes\n * @returns Decoded AnimationMessage\n * @internal\n */\nexport function decodeAnimationMessage(data: Uint8Array): AnimationMessage {\n const reader = new BinaryReader(data);\n const message: AnimationMessage = {\n type: 0,\n serverResponseAnimation: undefined,\n };\n\n while (reader.pos < reader.len) {\n const tag = reader.uint32();\n switch (tag >>> 3) {\n case 1: // type\n if (tag === 8) {\n message.type = reader.int32();\n }\n break;\n case 6: // serverResponseAnimation (field number 6 in the proto)\n if (tag === 50) {\n message.serverResponseAnimation = decodeServerResponseAnimation(\n reader,\n reader.uint32(),\n );\n }\n break;\n default:\n reader.skip(tag & 7);\n }\n }\n\n return message;\n}\n\n/**\n * Decode animation keyframes from protobuf data.\n * @param protobufData - Raw protobuf bytes\n * @returns Array of Flame keyframes, or null if decoding fails\n * @internal\n */\nexport function decodeAnimationKeyframes(\n protobufData: ArrayBuffer,\n): Flame[] | null {\n try {\n const message = decodeAnimationMessage(new Uint8Array(protobufData));\n\n if (message.serverResponseAnimation?.animation?.keyframes) {\n return message.serverResponseAnimation.animation.keyframes;\n }\n\n return null;\n } catch (error) {\n logger.error('proto', 'Failed to decode animation keyframes:', error);\n return null;\n }\n}\n"],"names":[],"mappings":"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"}
package/dist/index9.js CHANGED
@@ -44,7 +44,11 @@ class BaseProvider {
44
44
  try {
45
45
  handler(...args);
46
46
  } catch (error) {
47
- logger.error(this.name, `Error in event handler for ${event}:`, error);
47
+ logger.error(
48
+ this.name,
49
+ `Error in event handler for ${event}:`,
50
+ error
51
+ );
48
52
  }
49
53
  });
50
54
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index9.js","sources":["../src/providers/base/BaseProvider.ts"],"sourcesContent":["/**\n * Base Provider - Common implementation for RTC providers.\n *\n * This class provides common functionality that can be shared\n * across different provider implementations.\n *\n * @internal\n * @packageDocumentation\n */\n\nimport type { RTCProviderEvents, AnimationTrackCallbacks, AudioTrackCallbacks } from '../../core/types';\nimport { logger } from '../../utils';\n\n/**\n * Base class for RTC providers.\n * Provides common event handling and state management.\n */\nexport abstract class BaseProvider {\n /** Provider name identifier */\n abstract readonly name: string;\n\n /** @internal */\n protected connectionState: string = 'disconnected';\n /** @internal */\n protected eventHandlers: Map<string, Set<Function>> = new Map();\n\n /**\n * Connect to RTC server.\n * @param config - Connection configuration\n */\n abstract connect(config: import('../../types').RTCConnectionConfig): Promise<void>;\n\n /**\n * Disconnect from RTC server.\n */\n abstract disconnect(): Promise<void>;\n\n /**\n * Get current connection state.\n */\n abstract getConnectionState(): string;\n\n /**\n * Subscribe to animation track.\n * @param callbacks - Animation track callbacks\n * @internal\n */\n abstract subscribeAnimationTrack(callbacks: AnimationTrackCallbacks): Promise<void>;\n\n /**\n * Unsubscribe from animation track.\n * @internal\n */\n abstract unsubscribeAnimationTrack(): Promise<void>;\n\n /**\n * Subscribe to audio track.\n * @param callbacks - Audio track callbacks\n * @internal\n */\n abstract subscribeAudioTrack(callbacks: AudioTrackCallbacks): Promise<void>;\n\n /**\n * Unsubscribe from audio track.\n * @internal\n */\n abstract unsubscribeAudioTrack(): Promise<void>;\n\n /**\n * Publish local audio track.\n * @param track - MediaStreamTrack to publish\n */\n abstract publishAudioTrack(track: MediaStreamTrack): Promise<void>;\n\n /**\n * Unpublish audio track.\n */\n abstract unpublishAudioTrack(): Promise<void>;\n\n /**\n * Get the native RTC client object.\n * @returns The native client, or null if not connected\n */\n abstract getNativeClient(): unknown;\n\n /**\n * Add event listener.\n * @param event - Event name\n * @param handler - Event handler\n */\n on(event: string, handler: Function): void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler);\n }\n\n /**\n * Remove event listener.\n * @param event - Event name\n * @param handler - Event handler\n */\n off(event: string, handler: Function): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler);\n }\n }\n\n /**\n * Emit event to all listeners.\n * @param event - Event name\n * @param args - Event arguments\n * @internal\n */\n protected emit<K extends keyof RTCProviderEvents>(\n event: K,\n ...args: Parameters<RTCProviderEvents[K]>\n ): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (handler as any)(...args);\n } catch (error) {\n logger.error(this.name, `Error in event handler for ${event}:`, error);\n }\n });\n }\n }\n\n /**\n * Update connection state and emit event.\n * @param state - New connection state\n * @internal\n */\n protected setConnectionState(state: string): void {\n if (this.connectionState !== state) {\n const prevState = this.connectionState;\n this.connectionState = state;\n \n // Log connection state changes\n if (state === 'disconnected' || state === 'failed') {\n logger.error(this.name, `Connection state: ${prevState} -> ${state}`);\n } else if (state === 'reconnecting') {\n logger.warn(this.name, `Connection state: ${prevState} -> ${state}`);\n } else {\n logger.info(this.name, `Connection state: ${prevState} -> ${state}`);\n }\n \n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.emit('connection-state-changed', state as any);\n }\n }\n}\n"],"names":[],"mappings":";;;;AAiBO,MAAe,aAAa;AAAA,EAA5B;AAKK;AAAA,2CAA0B;AAE1B;AAAA,6DAAgD,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkE1D,GAAG,OAAe,SAAyB;AACzC,QAAI,CAAC,KAAK,cAAc,IAAI,KAAK,GAAG;AAClC,WAAK,cAAc,IAAI,OAAO,oBAAI,KAAK;AAAA,IACzC;AACA,SAAK,cAAc,IAAI,KAAK,EAAG,IAAI,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,OAAe,SAAyB;AAC1C,UAAM,WAAW,KAAK,cAAc,IAAI,KAAK;AAC7C,QAAI,UAAU;AACZ,eAAS,OAAO,OAAO;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,KACR,UACG,MACG;AACN,UAAM,WAAW,KAAK,cAAc,IAAI,KAAK;AAC7C,QAAI,UAAU;AACZ,eAAS,QAAQ,CAAC,YAAY;AAC5B,YAAI;AAED,kBAAgB,GAAG,IAAI;AAAA,QAC1B,SAAS,OAAO;AACd,iBAAO,MAAM,KAAK,MAAM,8BAA8B,KAAK,KAAK,KAAK;AAAA,QACvE;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,mBAAmB,OAAqB;AAChD,QAAI,KAAK,oBAAoB,OAAO;AAClC,YAAM,YAAY,KAAK;AACvB,WAAK,kBAAkB;AAGvB,UAAI,UAAU,kBAAkB,UAAU,UAAU;AAClD,eAAO,MAAM,KAAK,MAAM,qBAAqB,SAAS,OAAO,KAAK,EAAE;AAAA,MACtE,WAAW,UAAU,gBAAgB;AACnC,eAAO,KAAK,KAAK,MAAM,qBAAqB,SAAS,OAAO,KAAK,EAAE;AAAA,MACrE,OAAO;AACL,eAAO,KAAK,KAAK,MAAM,qBAAqB,SAAS,OAAO,KAAK,EAAE;AAAA,MACrE;AAGA,WAAK,KAAK,4BAA4B,KAAY;AAAA,IACpD;AAAA,EACF;AACF;"}
1
+ {"version":3,"file":"index9.js","sources":["../src/providers/base/BaseProvider.ts"],"sourcesContent":["/**\n * Base Provider - Common implementation for RTC providers.\n *\n * This class provides common functionality that can be shared\n * across different provider implementations.\n *\n * @internal\n * @packageDocumentation\n */\n\nimport type {\n RTCProviderEvents,\n AnimationTrackCallbacks,\n AudioTrackCallbacks,\n} from '../../core/types';\nimport type { RTCProviderEventHandler } from '../../core/RTCProvider';\nimport { logger } from '../../utils';\n\n/**\n * Base class for RTC providers.\n * Provides common event handling and state management.\n */\nexport abstract class BaseProvider {\n /** Provider name identifier */\n abstract readonly name: string;\n\n /** @internal */\n protected connectionState: string = 'disconnected';\n /** @internal */\n protected eventHandlers: Map<string, Set<RTCProviderEventHandler>> =\n new Map();\n\n /**\n * Pre-warm a future RTC connection if the provider supports it.\n */\n prepareConnection?(\n config: import('../../types').RTCPrepareConnectionConfig,\n ): Promise<void>;\n\n /**\n * Connect to RTC server.\n * @param config - Connection configuration\n */\n abstract connect(\n config: import('../../types').RTCConnectionConfig,\n ): Promise<void>;\n\n /**\n * Disconnect from RTC server.\n */\n abstract disconnect(): Promise<void>;\n\n /**\n * Get current connection state.\n */\n abstract getConnectionState(): string;\n\n /**\n * Subscribe to animation track.\n * @param callbacks - Animation track callbacks\n * @internal\n */\n abstract subscribeAnimationTrack(\n callbacks: AnimationTrackCallbacks,\n ): Promise<void>;\n\n /**\n * Unsubscribe from animation track.\n * @internal\n */\n abstract unsubscribeAnimationTrack(): Promise<void>;\n\n /**\n * Subscribe to audio track.\n * @param callbacks - Audio track callbacks\n * @internal\n */\n abstract subscribeAudioTrack(callbacks: AudioTrackCallbacks): Promise<void>;\n\n /**\n * Unsubscribe from audio track.\n * @internal\n */\n abstract unsubscribeAudioTrack(): Promise<void>;\n\n /**\n * Publish local audio track.\n * @param track - Optional MediaStreamTrack to publish\n */\n abstract publishAudioTrack(track?: MediaStreamTrack): Promise<void>;\n\n /**\n * Unpublish audio track.\n */\n abstract unpublishAudioTrack(): Promise<void>;\n\n /**\n * Get the native RTC client object.\n * @returns The native client, or null if not connected\n */\n abstract getNativeClient(): unknown;\n\n /**\n * Add event listener.\n * @param event - Event name\n * @param handler - Event handler\n */\n on(event: string, handler: RTCProviderEventHandler): void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler);\n }\n\n /**\n * Remove event listener.\n * @param event - Event name\n * @param handler - Event handler\n */\n off(event: string, handler: RTCProviderEventHandler): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler);\n }\n }\n\n /**\n * Emit event to all listeners.\n * @param event - Event name\n * @param args - Event arguments\n * @internal\n */\n protected emit<K extends keyof RTCProviderEvents>(\n event: K,\n ...args: Parameters<RTCProviderEvents[K]>\n ): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (handler as any)(...args);\n } catch (error) {\n logger.error(\n this.name,\n `Error in event handler for ${event}:`,\n error,\n );\n }\n });\n }\n }\n\n /**\n * Update connection state and emit event.\n * @param state - New connection state\n * @internal\n */\n protected setConnectionState(state: string): void {\n if (this.connectionState !== state) {\n const prevState = this.connectionState;\n this.connectionState = state;\n\n // Log connection state changes\n if (state === 'disconnected' || state === 'failed') {\n logger.error(this.name, `Connection state: ${prevState} -> ${state}`);\n } else if (state === 'reconnecting') {\n logger.warn(this.name, `Connection state: ${prevState} -> ${state}`);\n } else {\n logger.info(this.name, `Connection state: ${prevState} -> ${state}`);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.emit('connection-state-changed', state as any);\n }\n }\n}\n"],"names":[],"mappings":";;;;AAsBO,MAAe,aAAa;AAAA,EAA5B;AAKK;AAAA,2CAA0B;AAE1B;AAAA,6DACJ,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6EN,GAAG,OAAe,SAAwC;AACxD,QAAI,CAAC,KAAK,cAAc,IAAI,KAAK,GAAG;AAClC,WAAK,cAAc,IAAI,OAAO,oBAAI,KAAK;AAAA,IACzC;AACA,SAAK,cAAc,IAAI,KAAK,EAAG,IAAI,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,OAAe,SAAwC;AACzD,UAAM,WAAW,KAAK,cAAc,IAAI,KAAK;AAC7C,QAAI,UAAU;AACZ,eAAS,OAAO,OAAO;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,KACR,UACG,MACG;AACN,UAAM,WAAW,KAAK,cAAc,IAAI,KAAK;AAC7C,QAAI,UAAU;AACZ,eAAS,QAAQ,CAAC,YAAY;AAC5B,YAAI;AAED,kBAAgB,GAAG,IAAI;AAAA,QAC1B,SAAS,OAAO;AACd,iBAAO;AAAA,YACL,KAAK;AAAA,YACL,8BAA8B,KAAK;AAAA,YACnC;AAAA,UAAA;AAAA,QAEJ;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,mBAAmB,OAAqB;AAChD,QAAI,KAAK,oBAAoB,OAAO;AAClC,YAAM,YAAY,KAAK;AACvB,WAAK,kBAAkB;AAGvB,UAAI,UAAU,kBAAkB,UAAU,UAAU;AAClD,eAAO,MAAM,KAAK,MAAM,qBAAqB,SAAS,OAAO,KAAK,EAAE;AAAA,MACtE,WAAW,UAAU,gBAAgB;AACnC,eAAO,KAAK,KAAK,MAAM,qBAAqB,SAAS,OAAO,KAAK,EAAE;AAAA,MACrE,OAAO;AACL,eAAO,KAAK,KAAK,MAAM,qBAAqB,SAAS,OAAO,KAAK,EAAE;AAAA,MACrE;AAGA,WAAK,KAAK,4BAA4B,KAAY;AAAA,IACpD;AAAA,EACF;AACF;"}
@@ -1 +1 @@
1
- {"version":3,"file":"AgoraProvider.d.ts","sourceRoot":"","sources":["../../../src/providers/agora/AgoraProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAE,mBAAmB,EAAyB,MAAM,aAAa,CAAC;AAK9E,OAAO,KAAK,EACV,WAAW,EAKZ,MAAM,SAAS,CAAC;AAoBjB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,aAAc,SAAQ,YAAY;IAC7C,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,WAAW;gBA8BZ,QAAQ,GAAE,oBAAyB;IAI/C;;;OAGG;IACH,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAiCjC,OAAO,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAyOnD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAUjC,kBAAkB,IAAI,MAAM;IA4EtB,iBAAiB,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0B1D,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAU1C;;;;;;;;;;;;;;;;OAgBG;IACH,eAAe,IAAI,WAAW,GAAG,IAAI;CA6CtC"}
1
+ {"version":3,"file":"AgoraProvider.d.ts","sourceRoot":"","sources":["../../../src/providers/agora/AgoraProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAE,mBAAmB,EAAyB,MAAM,aAAa,CAAC;AAQ9E,OAAO,KAAK,EACV,WAAW,EAKZ,MAAM,SAAS,CAAC;AAiBjB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,aAAc,SAAQ,YAAY;IAC7C,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,WAAW;gBA8BZ,QAAQ,GAAE,oBAAyB;IAI/C;;;OAGG;IACH,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAiCjC,OAAO,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwQnD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAUjC,kBAAkB,IAAI,MAAM;IA8EtB,iBAAiB,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0B1D,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAU1C;;;;;;;;;;;;;;;;OAgBG;IACH,eAAe,IAAI,WAAW,GAAG,IAAI;CA6CtC"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/agora/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA6BH;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,eAAe,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/agora/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA+BH;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,eAAe,CAAC"}
@@ -1,12 +1,4 @@
1
- /**
2
- * Base Provider - Common implementation for RTC providers.
3
- *
4
- * This class provides common functionality that can be shared
5
- * across different provider implementations.
6
- *
7
- * @internal
8
- * @packageDocumentation
9
- */
1
+ import { RTCProviderEventHandler } from '../../core/RTCProvider';
10
2
  /**
11
3
  * Base class for RTC providers.
12
4
  * Provides common event handling and state management.
@@ -14,6 +6,10 @@
14
6
  export declare abstract class BaseProvider {
15
7
  /** Provider name identifier */
16
8
  abstract readonly name: string;
9
+ /**
10
+ * Pre-warm a future RTC connection if the provider supports it.
11
+ */
12
+ prepareConnection?(config: import('../../types').RTCPrepareConnectionConfig): Promise<void>;
17
13
  /**
18
14
  * Connect to RTC server.
19
15
  * @param config - Connection configuration
@@ -29,9 +25,9 @@ export declare abstract class BaseProvider {
29
25
  abstract getConnectionState(): string;
30
26
  /**
31
27
  * Publish local audio track.
32
- * @param track - MediaStreamTrack to publish
28
+ * @param track - Optional MediaStreamTrack to publish
33
29
  */
34
- abstract publishAudioTrack(track: MediaStreamTrack): Promise<void>;
30
+ abstract publishAudioTrack(track?: MediaStreamTrack): Promise<void>;
35
31
  /**
36
32
  * Unpublish audio track.
37
33
  */
@@ -46,12 +42,12 @@ export declare abstract class BaseProvider {
46
42
  * @param event - Event name
47
43
  * @param handler - Event handler
48
44
  */
49
- on(event: string, handler: Function): void;
45
+ on(event: string, handler: RTCProviderEventHandler): void;
50
46
  /**
51
47
  * Remove event listener.
52
48
  * @param event - Event name
53
49
  * @param handler - Event handler
54
50
  */
55
- off(event: string, handler: Function): void;
51
+ off(event: string, handler: RTCProviderEventHandler): void;
56
52
  }
57
53
  //# sourceMappingURL=BaseProvider.d.ts.map