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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index2.js CHANGED
@@ -29,6 +29,10 @@ class AvatarPlayer {
29
29
  __publicField(this, "lastConnectionConfig", null);
30
30
  /** @internal */
31
31
  __publicField(this, "isReconnecting", false);
32
+ /** @internal */
33
+ __publicField(this, "hasActiveAnimationSession", false);
34
+ /** @internal */
35
+ __publicField(this, "isTrackingPrimed", false);
32
36
  this.provider = provider;
33
37
  this.avatarView = avatarView;
34
38
  if ((options == null ? void 0 : options.logLevel) !== void 0) {
@@ -39,6 +43,8 @@ class AvatarPlayer {
39
43
  enableJitterBuffer: options == null ? void 0 : options.enableJitterBuffer,
40
44
  maxBufferDelayMs: options == null ? void 0 : options.maxBufferDelayMs,
41
45
  onStreamStalled: () => {
46
+ this.hasActiveAnimationSession = false;
47
+ this.isTrackingPrimed = false;
42
48
  this.emit("stalled");
43
49
  }
44
50
  });
@@ -315,9 +321,23 @@ class AvatarPlayer {
315
321
  async setupAnimationCallbacks() {
316
322
  const callbacks = {
317
323
  onAnimationData: (protobufData, metadata) => {
318
- if (metadata.isStart) {
319
- this.animationHandler.resetTracking();
320
- this.provider.playRemoteAudio();
324
+ if (!this.hasActiveAnimationSession) {
325
+ if (!this.isTrackingPrimed) {
326
+ this.animationHandler.resetTracking();
327
+ }
328
+ this.hasActiveAnimationSession = true;
329
+ this.isTrackingPrimed = false;
330
+ if (!metadata.isStart) {
331
+ logger.info(
332
+ "AvatarPlayer",
333
+ `Session start inferred from frame seq=${metadata.frameSeq ?? "n/a"}`
334
+ );
335
+ }
336
+ } else if (metadata.isStart) {
337
+ logger.info(
338
+ "AvatarPlayer",
339
+ `Ignoring duplicate session start frame seq=${metadata.frameSeq ?? "n/a"}`
340
+ );
321
341
  }
322
342
  this.animationHandler.handleAnimationData(
323
343
  protobufData,
@@ -326,7 +346,6 @@ class AvatarPlayer {
326
346
  );
327
347
  },
328
348
  onTransition: (protobufData, transitionFrameCount) => {
329
- this.provider.pauseRemoteAudio();
330
349
  this.animationHandler.handleTransitionData(protobufData, transitionFrameCount);
331
350
  },
332
351
  onTransitionEnd: (protobufData, transitionFrameCount) => {
@@ -337,6 +356,13 @@ class AvatarPlayer {
337
356
  onSessionEnd: () => {
338
357
  },
339
358
  onIdleStart: () => {
359
+ if (this.animationHandler.isInTransition()) {
360
+ logger.info("AvatarPlayer", "Ignoring idleStart while transition is active");
361
+ return;
362
+ }
363
+ this.hasActiveAnimationSession = false;
364
+ this.animationHandler.resetTracking();
365
+ this.isTrackingPrimed = true;
340
366
  this.animationHandler.startIdle();
341
367
  },
342
368
  onStreamStats: (stats) => {
@@ -360,6 +386,8 @@ class AvatarPlayer {
360
386
  });
361
387
  this.provider.on("disconnected", () => {
362
388
  this._isConnected = false;
389
+ this.hasActiveAnimationSession = false;
390
+ this.isTrackingPrimed = false;
363
391
  this.animationHandler.startIdle();
364
392
  });
365
393
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index2.js","sources":["../src/core/AvatarPlayer.ts"],"sourcesContent":["/**\n * AvatarPlayer - Unified entry point for RTC avatar streaming.\n *\n * This class provides a unified API that shields applications from\n * differences between different RTC providers (LiveKit, Agora, etc.)\n * and integrates seamlessly with @spatialwalk/avatarkit for rendering.\n *\n * @packageDocumentation\n */\n\nimport type { RTCProvider } from './RTCProvider';\nimport type { RTCConnectionConfig } from '../types';\nimport { AnimationHandler } from './AnimationHandler';\nimport type { AnimationTrackCallbacks, RTCProviderEvents } from './types';\nimport { configureLogger, logger, type LogLevel } from '../utils';\n\n// Import AvatarView type from avatarkit\nimport type { AvatarView, KeyframeData } from '@spatialwalk/avatarkit';\n\n/**\n * Options for configuring AvatarPlayer.\n */\nexport interface AvatarPlayerOptions {\n /**\n * Log level for SDK output.\n * - 'info': Show all logs (debug mode)\n * - 'warning': Show warnings and errors (default)\n * - 'error': Show only errors\n * - 'none': Disable all logs\n */\n logLevel?: LogLevel;\n\n /**\n * Enable jitter buffer for smoother animation 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 * Only used when enableJitterBuffer is true.\n * Default: 80 (2 frames at 25fps)\n */\n maxBufferDelayMs?: number;\n}\n\n/**\n * Unified Avatar Player.\n *\n * Main entry point for applications to interact with RTC avatar streaming.\n * Handles connection management, audio publishing, animation rendering,\n * and coordinates with avatarkit for display.\n *\n * @example\n * ```typescript\n * import { AvatarPlayer, LiveKitProvider } from '@spatialwalk/avatarkit-rtc';\n * import { AvatarView, Avatar } from '@spatialwalk/avatarkit';\n *\n * // Create avatar and view (from avatarkit)\n * const avatar = await Avatar.create(characterId);\n * const avatarView = new AvatarView(avatar, container);\n *\n * // Create player\n * const provider = new LiveKitProvider();\n * const player = new AvatarPlayer(provider, avatarView);\n *\n * // Connect\n * await player.connect({ url: 'wss://...', token: '...' });\n *\n * // Option 1: Use microphone (most common)\n * await player.startPublishing();\n * await player.stopPublishing();\n *\n * // Option 2: Use custom audio source\n * const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * await player.unpublishAudio();\n * stream.getTracks().forEach(t => t.stop());\n *\n * // Disconnect\n * await player.disconnect();\n * ```\n */\nexport class AvatarPlayer {\n /** @internal */\n private provider: RTCProvider;\n\n /** @internal */\n private avatarView: AvatarView;\n\n /** @internal */\n private animationHandler: AnimationHandler;\n\n /** @internal */\n private _isConnected = false;\n\n /** @internal */\n private localAudioTrack: MediaStreamTrack | null = null;\n\n /** @internal */\n private microphoneStream: MediaStream | null = null;\n\n /** @internal */\n private eventHandlers: Map<string, Set<Function>> = new Map();\n\n /** @internal */\n private lastConnectionConfig: RTCConnectionConfig | null = null;\n\n /** @internal */\n private isReconnecting = false;\n\n /**\n * Create a new AvatarPlayer instance.\n * @param provider - RTC provider implementation (e.g., LiveKitProvider)\n * @param avatarView - AvatarView instance from @spatialwalk/avatarkit\n * @param options - Optional configuration for transitions\n */\n constructor(\n provider: RTCProvider,\n avatarView: AvatarView,\n options?: AvatarPlayerOptions\n ) {\n this.provider = provider;\n this.avatarView = avatarView;\n\n // Configure logging if specified\n if (options?.logLevel !== undefined) {\n configureLogger({ level: options.logLevel });\n }\n\n // Create internal renderer adapter\n const renderer = this.createRendererAdapter();\n\n this.animationHandler = new AnimationHandler(renderer, {\n enableJitterBuffer: options?.enableJitterBuffer,\n maxBufferDelayMs: options?.maxBufferDelayMs,\n onStreamStalled: () => {\n // AnimationHandler already switches to idle state\n // Just notify external listeners, let them decide what to do (e.g., call reconnect())\n this.emit('stalled');\n },\n });\n\n // Setup provider event handlers\n this.setupProviderEvents();\n }\n\n /**\n * Check if currently connected to RTC server.\n */\n get isConnected(): boolean {\n return this._isConnected;\n }\n\n /**\n * Connect to RTC server.\n * @param config - Connection configuration (URL, token, room name, etc.)\n * @throws Error if already connected or connection fails\n */\n async connect(config: RTCConnectionConfig): Promise<void> {\n if (this._isConnected) {\n throw new Error('Already connected. Please disconnect first.');\n }\n\n // Save config for potential reconnection\n this.lastConnectionConfig = config;\n\n // Setup animation callbacks before connecting\n await this.setupAnimationCallbacks();\n await this.provider.connect(config);\n this._isConnected = true;\n }\n\n /**\n * Disconnect from RTC server.\n * Stops all tracks and cleans up resources.\n */\n async disconnect(): Promise<void> {\n if (!this._isConnected) {\n return;\n }\n\n // Stop microphone if active\n if (this.microphoneStream) {\n await this.stopPublishing();\n } else if (this.localAudioTrack) {\n // Stop custom audio track\n await this.unpublishAudio();\n }\n\n await this.provider.disconnect();\n this.animationHandler.dispose();\n this._isConnected = false;\n }\n\n /**\n * Publish an audio track to the RTC server.\n * \n * The audio source is controlled externally - you can pass any audio track:\n * - Microphone: `getUserMedia({ audio: true })`\n * - Browser audio: `audioElement.captureStream()`\n * - Web Audio API: `audioContext.createMediaStreamDestination().stream`\n * - Screen share audio: `getDisplayMedia({ audio: true })`\n * \n * @param track - MediaStreamTrack to publish (must be an audio track)\n * @throws Error if not connected or track is invalid\n * \n * @example\n * ```typescript\n * // Microphone\n * const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * \n * // Browser audio element\n * const audioEl = document.querySelector('audio');\n * const stream = audioEl.captureStream();\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * ```\n */\n async publishAudio(track: MediaStreamTrack): Promise<void> {\n if (!this._isConnected) {\n throw new Error('Not connected. Please call connect() first.');\n }\n\n if (track.kind !== 'audio') {\n throw new Error('Invalid track: expected audio track');\n }\n\n // Unpublish existing track if any\n if (this.localAudioTrack) {\n await this.unpublishAudio();\n }\n\n this.localAudioTrack = track;\n await this.provider.publishAudioTrack(track);\n }\n\n /**\n * Stop publishing audio.\n * Note: This does NOT stop the track - the caller is responsible for managing the track lifecycle.\n */\n async unpublishAudio(): Promise<void> {\n if (!this.localAudioTrack) {\n return;\n }\n\n await this.provider.unpublishAudioTrack();\n this.localAudioTrack = null;\n }\n\n /**\n * Start publishing microphone audio.\n * \n * This requests microphone permission and publishes it to the RTC session.\n * For custom audio sources (e.g., audio files), use `publishAudio(track)` instead.\n * \n * @throws Error if not connected, permission denied, or no microphone found\n * \n * @example\n * ```typescript\n * await player.startPublishing();\n * // ... later\n * await player.stopPublishing();\n * ```\n */\n async startPublishing(): Promise<void> {\n if (!this._isConnected) {\n throw new Error('Not connected. Please call connect() first.');\n }\n\n if (this.microphoneStream) {\n return; // Already using microphone\n }\n\n // Request microphone permission\n this.microphoneStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n const track = this.microphoneStream.getAudioTracks()[0];\n \n await this.publishAudio(track);\n }\n\n /**\n * Stop publishing microphone audio and release the microphone.\n * \n * This stops the microphone track and releases the device.\n * If you used `publishAudio()` with a custom track, use `unpublishAudio()` instead.\n */\n async stopPublishing(): Promise<void> {\n if (!this.microphoneStream) {\n return;\n }\n\n await this.unpublishAudio();\n\n // Stop and release microphone\n this.microphoneStream.getTracks().forEach((track) => track.stop());\n this.microphoneStream = null;\n }\n\n /**\n * Get current connection state.\n * @returns Connection state string (e.g., 'connected', 'disconnected')\n */\n getConnectionState(): string {\n return this.provider.getConnectionState();\n }\n\n /**\n * Get the native RTC client object.\n * \n * Returns the underlying RTC client for advanced use cases:\n * - LiveKit: Returns the Room instance\n * - Agora: Returns the IAgoraRTCClient instance\n * \n * @returns The native RTC client object, or null if not connected\n * \n * @example\n * ```typescript\n * // Access LiveKit Room\n * const room = player.getNativeClient() as Room;\n * console.log('Participants:', room?.remoteParticipants.size);\n * \n * // Access Agora Client\n * const client = player.getNativeClient() as IAgoraRTCClient;\n * console.log('State:', client?.connectionState);\n * ```\n */\n getNativeClient(): unknown {\n return this.provider.getNativeClient();\n }\n\n /**\n * Add event listener.\n * @param event - Event name ('connected', 'disconnected', 'error', 'stalled', etc.)\n * @param handler - Event handler function\n */\n on(event: string, handler: Function): void {\n if (event === 'stalled') {\n // Handle stalled event locally\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler);\n } else {\n // Forward other events to provider\n this.provider.on(event as any, handler as any);\n }\n }\n\n /**\n * Remove event listener.\n * @param event - Event name\n * @param handler - Event handler function (must be same reference as added)\n */\n off(event: string, handler: Function): void {\n if (event === 'stalled') {\n this.eventHandlers.get(event)?.delete(handler);\n } else {\n this.provider.off(event as any, handler as any);\n }\n }\n\n /**\n * Emit an event to local listeners.\n * @internal\n */\n private emit(event: string, ...args: unknown[]): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(...args);\n } catch (e) {\n logger.error('AvatarPlayer', `Error in ${event} handler:`, e);\n }\n });\n }\n }\n\n /**\n * Reconnect to RTC server using the last connection configuration.\n * Useful for recovering from connection issues or stream stalls.\n *\n * @example\n * ```typescript\n * player.on('stalled', async () => {\n * console.log('Stream stalled, attempting reconnection...');\n * await player.reconnect();\n * });\n * ```\n *\n * @throws Error if no previous connection config exists or reconnection fails\n */\n async reconnect(): Promise<void> {\n if (this.isReconnecting) {\n logger.warn('AvatarPlayer', 'Already attempting reconnection, skipping');\n return;\n }\n\n if (!this.lastConnectionConfig) {\n throw new Error('Cannot reconnect: no previous connection. Call connect() first.');\n }\n\n this.isReconnecting = true;\n logger.info('AvatarPlayer', 'Attempting reconnection...');\n\n try {\n // Disconnect first if connected\n if (this._isConnected) {\n await this.disconnect();\n }\n\n // Wait a short moment before reconnecting\n await new Promise((resolve) => setTimeout(resolve, 500));\n\n // Reconnect\n await this.connect(this.lastConnectionConfig);\n logger.info('AvatarPlayer', 'Reconnection successful');\n } catch (error) {\n logger.error('AvatarPlayer', 'Reconnection failed:', error);\n throw error;\n } finally {\n this.isReconnecting = false;\n }\n }\n\n /**\n * Create internal renderer adapter that bridges AnimationHandler to AvatarView.\n * @internal\n */\n private createRendererAdapter() {\n const avatarView = this.avatarView;\n\n return {\n renderFrame(flame: KeyframeData | undefined, startIdle?: boolean): void {\n if (startIdle) {\n // Enable idle rendering mode\n avatarView.renderFlame(undefined as unknown as KeyframeData, true);\n } else if (flame) {\n // Render the animation frame\n avatarView.renderFlame(flame);\n }\n },\n\n async generateTransitionFromIdle(\n targetFrame: KeyframeData,\n frameCount: number\n ): Promise<KeyframeData[]> {\n // Use avatarkit's generateTransition with from=undefined (current idle frame)\n return avatarView.generateTransition({\n to: targetFrame,\n frameCount,\n useLinear: true, // Linear interpolation for idle->speaking\n });\n },\n\n isReady(): boolean {\n return true; // AvatarView is always ready once created\n },\n };\n }\n\n /**\n * Setup animation track callbacks.\n * @internal\n */\n private async setupAnimationCallbacks(): Promise<void> {\n const callbacks: AnimationTrackCallbacks = {\n onAnimationData: (protobufData, metadata) => {\n // Reset tracking on session start\n if (metadata.isStart) {\n this.animationHandler.resetTracking();\n // Start audio playback on session start (fallback if transition didn't trigger it)\n this.provider.playRemoteAudio();\n }\n\n this.animationHandler.handleAnimationData(\n protobufData,\n metadata.frameSeq,\n metadata.isRecovered\n );\n },\n onTransition: (protobufData, transitionFrameCount) => {\n this.provider.pauseRemoteAudio();\n this.animationHandler.handleTransitionData(protobufData, transitionFrameCount);\n },\n onTransitionEnd: (protobufData, transitionFrameCount) => {\n this.animationHandler.handleTransitionToIdle(protobufData, transitionFrameCount);\n },\n onSessionStart: () => {\n // Session started - can be used for UI feedback\n },\n onSessionEnd: () => {\n // Session ended - can be used for UI feedback\n },\n onIdleStart: () => {\n this.animationHandler.startIdle();\n },\n onStreamStats: (stats) => {\n // Log packet loss/recovery stats\n if (stats.framesLost > 0 || stats.framesRecovered > 0 || stats.framesDropped > 0) {\n logger.warn(\n 'AvatarPlayer',\n `Stream stats: lost=${stats.framesLost}, recovered=${stats.framesRecovered}, dropped=${stats.framesDropped}, fps=${stats.framesPerSec}`\n );\n }\n },\n };\n\n await this.provider.subscribeAnimationTrack(callbacks);\n }\n\n /**\n * Setup provider event handlers.\n * @internal\n */\n private setupProviderEvents(): void {\n this.provider.on('connected', () => {\n this._isConnected = true;\n });\n\n this.provider.on('disconnected', () => {\n this._isConnected = false;\n // Return to idle animation when disconnected\n this.animationHandler.startIdle();\n });\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index2.js","sources":["../src/core/AvatarPlayer.ts"],"sourcesContent":["/**\n * AvatarPlayer - Unified entry point for RTC avatar streaming.\n *\n * This class provides a unified API that shields applications from\n * differences between different RTC providers (LiveKit, Agora, etc.)\n * and integrates seamlessly with @spatialwalk/avatarkit for rendering.\n *\n * @packageDocumentation\n */\n\nimport type { RTCProvider } from './RTCProvider';\nimport type { RTCConnectionConfig } from '../types';\nimport { AnimationHandler } from './AnimationHandler';\nimport type { AnimationTrackCallbacks, RTCProviderEvents } from './types';\nimport { configureLogger, logger, type LogLevel } from '../utils';\n\n// Import AvatarView type from avatarkit\nimport type { AvatarView, KeyframeData } from '@spatialwalk/avatarkit';\n\n/**\n * Options for configuring AvatarPlayer.\n */\nexport interface AvatarPlayerOptions {\n /**\n * Log level for SDK output.\n * - 'info': Show all logs (debug mode)\n * - 'warning': Show warnings and errors (default)\n * - 'error': Show only errors\n * - 'none': Disable all logs\n */\n logLevel?: LogLevel;\n\n /**\n * Enable jitter buffer for smoother animation 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 * Only used when enableJitterBuffer is true.\n * Default: 80 (2 frames at 25fps)\n */\n maxBufferDelayMs?: number;\n}\n\n/**\n * Unified Avatar Player.\n *\n * Main entry point for applications to interact with RTC avatar streaming.\n * Handles connection management, audio publishing, animation rendering,\n * and coordinates with avatarkit for display.\n *\n * @example\n * ```typescript\n * import { AvatarPlayer, LiveKitProvider } from '@spatialwalk/avatarkit-rtc';\n * import { AvatarView, Avatar } from '@spatialwalk/avatarkit';\n *\n * // Create avatar and view (from avatarkit)\n * const avatar = await Avatar.create(characterId);\n * const avatarView = new AvatarView(avatar, container);\n *\n * // Create player\n * const provider = new LiveKitProvider();\n * const player = new AvatarPlayer(provider, avatarView);\n *\n * // Connect\n * await player.connect({ url: 'wss://...', token: '...' });\n *\n * // Option 1: Use microphone (most common)\n * await player.startPublishing();\n * await player.stopPublishing();\n *\n * // Option 2: Use custom audio source\n * const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * await player.unpublishAudio();\n * stream.getTracks().forEach(t => t.stop());\n *\n * // Disconnect\n * await player.disconnect();\n * ```\n */\nexport class AvatarPlayer {\n /** @internal */\n private provider: RTCProvider;\n\n /** @internal */\n private avatarView: AvatarView;\n\n /** @internal */\n private animationHandler: AnimationHandler;\n\n /** @internal */\n private _isConnected = false;\n\n /** @internal */\n private localAudioTrack: MediaStreamTrack | null = null;\n\n /** @internal */\n private microphoneStream: MediaStream | null = null;\n\n /** @internal */\n private eventHandlers: Map<string, Set<Function>> = new Map();\n\n /** @internal */\n private lastConnectionConfig: RTCConnectionConfig | null = null;\n\n /** @internal */\n private isReconnecting = false;\n\n /** @internal */\n private hasActiveAnimationSession = false;\n\n /** @internal */\n private isTrackingPrimed = false;\n\n /**\n * Create a new AvatarPlayer instance.\n * @param provider - RTC provider implementation (e.g., LiveKitProvider)\n * @param avatarView - AvatarView instance from @spatialwalk/avatarkit\n * @param options - Optional configuration for transitions\n */\n constructor(\n provider: RTCProvider,\n avatarView: AvatarView,\n options?: AvatarPlayerOptions\n ) {\n this.provider = provider;\n this.avatarView = avatarView;\n\n // Configure logging if specified\n if (options?.logLevel !== undefined) {\n configureLogger({ level: options.logLevel });\n }\n\n // Create internal renderer adapter\n const renderer = this.createRendererAdapter();\n\n this.animationHandler = new AnimationHandler(renderer, {\n enableJitterBuffer: options?.enableJitterBuffer,\n maxBufferDelayMs: options?.maxBufferDelayMs,\n onStreamStalled: () => {\n // AnimationHandler already switches to idle state\n // Just notify external listeners, let them decide what to do (e.g., call reconnect())\n this.hasActiveAnimationSession = false;\n this.isTrackingPrimed = false;\n this.emit('stalled');\n },\n });\n\n // Setup provider event handlers\n this.setupProviderEvents();\n }\n\n /**\n * Check if currently connected to RTC server.\n */\n get isConnected(): boolean {\n return this._isConnected;\n }\n\n /**\n * Connect to RTC server.\n * @param config - Connection configuration (URL, token, room name, etc.)\n * @throws Error if already connected or connection fails\n */\n async connect(config: RTCConnectionConfig): Promise<void> {\n if (this._isConnected) {\n throw new Error('Already connected. Please disconnect first.');\n }\n\n // Save config for potential reconnection\n this.lastConnectionConfig = config;\n\n // Setup animation callbacks before connecting\n await this.setupAnimationCallbacks();\n await this.provider.connect(config);\n this._isConnected = true;\n }\n\n /**\n * Disconnect from RTC server.\n * Stops all tracks and cleans up resources.\n */\n async disconnect(): Promise<void> {\n if (!this._isConnected) {\n return;\n }\n\n // Stop microphone if active\n if (this.microphoneStream) {\n await this.stopPublishing();\n } else if (this.localAudioTrack) {\n // Stop custom audio track\n await this.unpublishAudio();\n }\n\n await this.provider.disconnect();\n this.animationHandler.dispose();\n this._isConnected = false;\n }\n\n /**\n * Publish an audio track to the RTC server.\n * \n * The audio source is controlled externally - you can pass any audio track:\n * - Microphone: `getUserMedia({ audio: true })`\n * - Browser audio: `audioElement.captureStream()`\n * - Web Audio API: `audioContext.createMediaStreamDestination().stream`\n * - Screen share audio: `getDisplayMedia({ audio: true })`\n * \n * @param track - MediaStreamTrack to publish (must be an audio track)\n * @throws Error if not connected or track is invalid\n * \n * @example\n * ```typescript\n * // Microphone\n * const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * \n * // Browser audio element\n * const audioEl = document.querySelector('audio');\n * const stream = audioEl.captureStream();\n * await player.publishAudio(stream.getAudioTracks()[0]);\n * ```\n */\n async publishAudio(track: MediaStreamTrack): Promise<void> {\n if (!this._isConnected) {\n throw new Error('Not connected. Please call connect() first.');\n }\n\n if (track.kind !== 'audio') {\n throw new Error('Invalid track: expected audio track');\n }\n\n // Unpublish existing track if any\n if (this.localAudioTrack) {\n await this.unpublishAudio();\n }\n\n this.localAudioTrack = track;\n await this.provider.publishAudioTrack(track);\n }\n\n /**\n * Stop publishing audio.\n * Note: This does NOT stop the track - the caller is responsible for managing the track lifecycle.\n */\n async unpublishAudio(): Promise<void> {\n if (!this.localAudioTrack) {\n return;\n }\n\n await this.provider.unpublishAudioTrack();\n this.localAudioTrack = null;\n }\n\n /**\n * Start publishing microphone audio.\n * \n * This requests microphone permission and publishes it to the RTC session.\n * For custom audio sources (e.g., audio files), use `publishAudio(track)` instead.\n * \n * @throws Error if not connected, permission denied, or no microphone found\n * \n * @example\n * ```typescript\n * await player.startPublishing();\n * // ... later\n * await player.stopPublishing();\n * ```\n */\n async startPublishing(): Promise<void> {\n if (!this._isConnected) {\n throw new Error('Not connected. Please call connect() first.');\n }\n\n if (this.microphoneStream) {\n return; // Already using microphone\n }\n\n // Request microphone permission\n this.microphoneStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n const track = this.microphoneStream.getAudioTracks()[0];\n \n await this.publishAudio(track);\n }\n\n /**\n * Stop publishing microphone audio and release the microphone.\n * \n * This stops the microphone track and releases the device.\n * If you used `publishAudio()` with a custom track, use `unpublishAudio()` instead.\n */\n async stopPublishing(): Promise<void> {\n if (!this.microphoneStream) {\n return;\n }\n\n await this.unpublishAudio();\n\n // Stop and release microphone\n this.microphoneStream.getTracks().forEach((track) => track.stop());\n this.microphoneStream = null;\n }\n\n /**\n * Get current connection state.\n * @returns Connection state string (e.g., 'connected', 'disconnected')\n */\n getConnectionState(): string {\n return this.provider.getConnectionState();\n }\n\n /**\n * Get the native RTC client object.\n * \n * Returns the underlying RTC client for advanced use cases:\n * - LiveKit: Returns the Room instance\n * - Agora: Returns the IAgoraRTCClient instance\n * \n * @returns The native RTC client object, or null if not connected\n * \n * @example\n * ```typescript\n * // Access LiveKit Room\n * const room = player.getNativeClient() as Room;\n * console.log('Participants:', room?.remoteParticipants.size);\n * \n * // Access Agora Client\n * const client = player.getNativeClient() as IAgoraRTCClient;\n * console.log('State:', client?.connectionState);\n * ```\n */\n getNativeClient(): unknown {\n return this.provider.getNativeClient();\n }\n\n /**\n * Add event listener.\n * @param event - Event name ('connected', 'disconnected', 'error', 'stalled', etc.)\n * @param handler - Event handler function\n */\n on(event: string, handler: Function): void {\n if (event === 'stalled') {\n // Handle stalled event locally\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler);\n } else {\n // Forward other events to provider\n this.provider.on(event as any, handler as any);\n }\n }\n\n /**\n * Remove event listener.\n * @param event - Event name\n * @param handler - Event handler function (must be same reference as added)\n */\n off(event: string, handler: Function): void {\n if (event === 'stalled') {\n this.eventHandlers.get(event)?.delete(handler);\n } else {\n this.provider.off(event as any, handler as any);\n }\n }\n\n /**\n * Emit an event to local listeners.\n * @internal\n */\n private emit(event: string, ...args: unknown[]): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(...args);\n } catch (e) {\n logger.error('AvatarPlayer', `Error in ${event} handler:`, e);\n }\n });\n }\n }\n\n /**\n * Reconnect to RTC server using the last connection configuration.\n * Useful for recovering from connection issues or stream stalls.\n *\n * @example\n * ```typescript\n * player.on('stalled', async () => {\n * console.log('Stream stalled, attempting reconnection...');\n * await player.reconnect();\n * });\n * ```\n *\n * @throws Error if no previous connection config exists or reconnection fails\n */\n async reconnect(): Promise<void> {\n if (this.isReconnecting) {\n logger.warn('AvatarPlayer', 'Already attempting reconnection, skipping');\n return;\n }\n\n if (!this.lastConnectionConfig) {\n throw new Error('Cannot reconnect: no previous connection. Call connect() first.');\n }\n\n this.isReconnecting = true;\n logger.info('AvatarPlayer', 'Attempting reconnection...');\n\n try {\n // Disconnect first if connected\n if (this._isConnected) {\n await this.disconnect();\n }\n\n // Wait a short moment before reconnecting\n await new Promise((resolve) => setTimeout(resolve, 500));\n\n // Reconnect\n await this.connect(this.lastConnectionConfig);\n logger.info('AvatarPlayer', 'Reconnection successful');\n } catch (error) {\n logger.error('AvatarPlayer', 'Reconnection failed:', error);\n throw error;\n } finally {\n this.isReconnecting = false;\n }\n }\n\n /**\n * Create internal renderer adapter that bridges AnimationHandler to AvatarView.\n * @internal\n */\n private createRendererAdapter() {\n const avatarView = this.avatarView;\n\n return {\n renderFrame(flame: KeyframeData | undefined, startIdle?: boolean): void {\n if (startIdle) {\n // Enable idle rendering mode\n avatarView.renderFlame(undefined as unknown as KeyframeData, true);\n } else if (flame) {\n // Render the animation frame\n avatarView.renderFlame(flame);\n }\n },\n\n async generateTransitionFromIdle(\n targetFrame: KeyframeData,\n frameCount: number\n ): Promise<KeyframeData[]> {\n // Use avatarkit's generateTransition with from=undefined (current idle frame)\n return avatarView.generateTransition({\n to: targetFrame,\n frameCount,\n useLinear: true, // Linear interpolation for idle->speaking\n });\n },\n\n isReady(): boolean {\n return true; // AvatarView is always ready once created\n },\n };\n }\n\n /**\n * Setup animation track callbacks.\n * @internal\n */\n private async setupAnimationCallbacks(): Promise<void> {\n const callbacks: AnimationTrackCallbacks = {\n onAnimationData: (protobufData, metadata) => {\n // Reset tracking only once per session to avoid duplicate start-packet resets.\n if (!this.hasActiveAnimationSession) {\n if (!this.isTrackingPrimed) {\n this.animationHandler.resetTracking();\n }\n\n this.hasActiveAnimationSession = true;\n this.isTrackingPrimed = false;\n\n if (!metadata.isStart) {\n logger.info(\n 'AvatarPlayer',\n `Session start inferred from frame seq=${metadata.frameSeq ?? 'n/a'}`\n );\n }\n } else if (metadata.isStart) {\n logger.info(\n 'AvatarPlayer',\n `Ignoring duplicate session start frame seq=${metadata.frameSeq ?? 'n/a'}`\n );\n }\n\n this.animationHandler.handleAnimationData(\n protobufData,\n metadata.frameSeq,\n metadata.isRecovered\n );\n },\n onTransition: (protobufData, transitionFrameCount) => {\n this.animationHandler.handleTransitionData(protobufData, transitionFrameCount);\n },\n onTransitionEnd: (protobufData, transitionFrameCount) => {\n this.animationHandler.handleTransitionToIdle(protobufData, transitionFrameCount);\n },\n onSessionStart: () => {\n // Session started - can be used for UI feedback\n },\n onSessionEnd: () => {\n // Session ended - can be used for UI feedback\n },\n onIdleStart: () => {\n // Idle packets can race with start-transition packets; avoid rendering idle mid-transition.\n if (this.animationHandler.isInTransition()) {\n logger.info('AvatarPlayer', 'Ignoring idleStart while transition is active');\n return;\n }\n\n this.hasActiveAnimationSession = false;\n this.animationHandler.resetTracking();\n this.isTrackingPrimed = true;\n this.animationHandler.startIdle();\n },\n onStreamStats: (stats) => {\n // Log packet loss/recovery stats\n if (stats.framesLost > 0 || stats.framesRecovered > 0 || stats.framesDropped > 0) {\n logger.warn(\n 'AvatarPlayer',\n `Stream stats: lost=${stats.framesLost}, recovered=${stats.framesRecovered}, dropped=${stats.framesDropped}, fps=${stats.framesPerSec}`\n );\n }\n },\n };\n\n await this.provider.subscribeAnimationTrack(callbacks);\n }\n\n /**\n * Setup provider event handlers.\n * @internal\n */\n private setupProviderEvents(): void {\n this.provider.on('connected', () => {\n this._isConnected = true;\n });\n\n this.provider.on('disconnected', () => {\n this._isConnected = false;\n this.hasActiveAnimationSession = false;\n this.isTrackingPrimed = false;\n // Return to idle animation when disconnected\n this.animationHandler.startIdle();\n });\n }\n}\n"],"names":[],"mappings":"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"}
package/dist/index3.js CHANGED
@@ -3,8 +3,8 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { BaseProvider } from "./index9.js";
5
5
  import { isLiveKitConfig } from "./index5.js";
6
- import { VP8Extractor } from "./index11.js";
7
- import { getInsertableStreamsMethod } from "./index12.js";
6
+ import { VP8Extractor } from "./index10.js";
7
+ import { getInsertableStreamsMethod } from "./index11.js";
8
8
  import { logger } from "./index7.js";
9
9
  const globalLiveKitProviderRegistry = /* @__PURE__ */ new Set();
10
10
  let rtcPeerConnectionPatched = false;
@@ -85,6 +85,18 @@ class LiveKitProvider extends BaseProvider {
85
85
  __publicField(this, "audioCallbacks", null);
86
86
  /** @internal */
87
87
  __publicField(this, "audioElements", /* @__PURE__ */ new Map());
88
+ /** @internal */
89
+ __publicField(this, "awaitingAudioUnlock", false);
90
+ /** @internal */
91
+ __publicField(this, "audioUnlockEventNames", [
92
+ "pointerdown",
93
+ "keydown",
94
+ "touchstart"
95
+ ]);
96
+ /** @internal */
97
+ __publicField(this, "handleAudioUnlockGesture", () => {
98
+ void this.ensureAudioPlaybackUnlocked("user-gesture");
99
+ });
88
100
  // Microphone publishing
89
101
  /** @internal */
90
102
  __publicField(this, "localAudioTrack", null);
@@ -269,6 +281,104 @@ class LiveKitProvider extends BaseProvider {
269
281
  this.vp8Extractors.delete(receiver);
270
282
  }
271
283
  }
284
+ /**
285
+ * Add one-time user gesture listeners to recover from autoplay blocking.
286
+ * @internal
287
+ */
288
+ addAudioUnlockListeners() {
289
+ if (typeof window === "undefined" || this.awaitingAudioUnlock) {
290
+ return;
291
+ }
292
+ this.awaitingAudioUnlock = true;
293
+ for (const eventName of this.audioUnlockEventNames) {
294
+ window.addEventListener(eventName, this.handleAudioUnlockGesture, {
295
+ capture: true,
296
+ passive: true
297
+ });
298
+ }
299
+ logger.warn("LiveKit", "Audio playback blocked by autoplay policy, waiting for user interaction");
300
+ }
301
+ /**
302
+ * Remove user gesture listeners after playback is unlocked.
303
+ * @internal
304
+ */
305
+ removeAudioUnlockListeners() {
306
+ if (typeof window === "undefined" || !this.awaitingAudioUnlock) {
307
+ return;
308
+ }
309
+ this.awaitingAudioUnlock = false;
310
+ for (const eventName of this.audioUnlockEventNames) {
311
+ window.removeEventListener(eventName, this.handleAudioUnlockGesture, true);
312
+ }
313
+ }
314
+ /**
315
+ * Detect browser autoplay blocking errors from audio play/start calls.
316
+ * @internal
317
+ */
318
+ isAutoplayBlockedError(error) {
319
+ if (typeof DOMException !== "undefined" && error instanceof DOMException) {
320
+ return error.name === "NotAllowedError";
321
+ }
322
+ if (error instanceof Error) {
323
+ return error.name === "NotAllowedError" || /didn't interact|user didn't interact|NotAllowedError/i.test(error.message);
324
+ }
325
+ return false;
326
+ }
327
+ /**
328
+ * Try to resume all attached remote audio elements.
329
+ * @returns true if at least one element is still blocked by autoplay policy
330
+ * @internal
331
+ */
332
+ async resumeAttachedAudioElements(reason) {
333
+ let hasAutoplayBlockedElement = false;
334
+ for (const [participantIdentity, audioElement] of this.audioElements) {
335
+ if (!audioElement.paused) {
336
+ continue;
337
+ }
338
+ try {
339
+ await audioElement.play();
340
+ } catch (error) {
341
+ if (this.isAutoplayBlockedError(error)) {
342
+ hasAutoplayBlockedElement = true;
343
+ }
344
+ logger.warn(
345
+ "LiveKit",
346
+ `Failed to resume remote audio element (${reason}) for ${participantIdentity}:`,
347
+ error
348
+ );
349
+ }
350
+ }
351
+ return hasAutoplayBlockedElement;
352
+ }
353
+ /**
354
+ * Ensure remote audio playback is unlocked and resumed.
355
+ * @internal
356
+ */
357
+ async ensureAudioPlaybackUnlocked(reason) {
358
+ if (!this.room) {
359
+ return;
360
+ }
361
+ const roomWithAudioControl = this.room;
362
+ if (!roomWithAudioControl.canPlaybackAudio && typeof roomWithAudioControl.startAudio === "function") {
363
+ try {
364
+ await roomWithAudioControl.startAudio();
365
+ logger.info("LiveKit", `Audio context resumed (${reason})`);
366
+ } catch (error) {
367
+ if (this.isAutoplayBlockedError(error)) {
368
+ this.addAudioUnlockListeners();
369
+ } else {
370
+ logger.warn("LiveKit", `Failed to call room.startAudio() (${reason}):`, error);
371
+ }
372
+ return;
373
+ }
374
+ }
375
+ const hasAutoplayBlockedElement = await this.resumeAttachedAudioElements(reason);
376
+ if (roomWithAudioControl.canPlaybackAudio && !hasAutoplayBlockedElement) {
377
+ this.removeAudioUnlockListeners();
378
+ } else {
379
+ this.addAudioUnlockListeners();
380
+ }
381
+ }
272
382
  async connect(config) {
273
383
  var _a;
274
384
  logger.info("LiveKit", "connect() called with config:", {
@@ -283,6 +393,7 @@ class LiveKitProvider extends BaseProvider {
283
393
  }
284
394
  const livekitConfig = config;
285
395
  logger.info("LiveKit", "Config validated, connecting to:", livekitConfig.url);
396
+ this.removeAudioUnlockListeners();
286
397
  const livekit = await this.loadSDK();
287
398
  logger.info("LiveKit", "SDK loaded successfully");
288
399
  const { Room } = livekit;
@@ -306,7 +417,6 @@ class LiveKitProvider extends BaseProvider {
306
417
  });
307
418
  });
308
419
  this.setConnectionState(this.mapLiveKitConnectionState(livekit, this.room.state));
309
- this.emit("connected");
310
420
  } catch (error) {
311
421
  logger.error("LiveKit", "Connection failed:", error);
312
422
  this.setConnectionState("failed");
@@ -325,6 +435,10 @@ class LiveKitProvider extends BaseProvider {
325
435
  room.on(RoomEvent.Connected, () => {
326
436
  this.setConnectionState("connected");
327
437
  this.emit("connected");
438
+ const roomWithAudioControl = room;
439
+ if (!roomWithAudioControl.canPlaybackAudio) {
440
+ this.addAudioUnlockListeners();
441
+ }
328
442
  });
329
443
  room.on(RoomEvent.ParticipantConnected, (participant) => {
330
444
  logger.info("LiveKit", `Participant connected: ${participant.identity}`);
@@ -344,6 +458,14 @@ class LiveKitProvider extends BaseProvider {
344
458
  this.setConnectionState("disconnected");
345
459
  this.emit("disconnected");
346
460
  });
461
+ room.on(RoomEvent.AudioPlaybackStatusChanged, (canPlaybackAudio) => {
462
+ logger.info("LiveKit", `Audio playback status changed: ${canPlaybackAudio ? "enabled" : "blocked"}`);
463
+ if (canPlaybackAudio) {
464
+ void this.ensureAudioPlaybackUnlocked("audio-playback-status-changed");
465
+ } else {
466
+ this.addAudioUnlockListeners();
467
+ }
468
+ });
347
469
  this.room.on(
348
470
  RoomEvent.TrackSubscribed,
349
471
  (track, publication, participant) => {
@@ -362,11 +484,16 @@ class LiveKitProvider extends BaseProvider {
362
484
  audioElement.autoplay = true;
363
485
  audioElement.controls = false;
364
486
  audioElement.style.display = "none";
487
+ const previousAudioElement = this.audioElements.get(participant.identity);
488
+ if (previousAudioElement && previousAudioElement !== audioElement) {
489
+ previousAudioElement.remove();
490
+ }
365
491
  if (typeof document !== "undefined") {
366
492
  document.body.appendChild(audioElement);
367
493
  logger.info("LiveKit", "Audio element appended to body");
368
494
  }
369
495
  this.audioElements.set(participant.identity, audioElement);
496
+ void this.ensureAudioPlaybackUnlocked("audio-track-subscribed");
370
497
  if (this.audioCallbacks) {
371
498
  (_b = (_a = this.audioCallbacks).onAudioReceived) == null ? void 0 : _b.call(_a, participant);
372
499
  }
@@ -477,7 +604,6 @@ class LiveKitProvider extends BaseProvider {
477
604
  async unsubscribeAudioTrack() {
478
605
  this.audioCallbacks = null;
479
606
  this.audioElements.forEach((el) => {
480
- el.pause();
481
607
  el.remove();
482
608
  });
483
609
  this.audioElements.clear();
@@ -524,27 +650,6 @@ class LiveKitProvider extends BaseProvider {
524
650
  this.localAudioTrack = null;
525
651
  this.isMicrophoneEnabled = false;
526
652
  }
527
- /**
528
- * Play remote audio (resume playback)
529
- */
530
- playRemoteAudio() {
531
- this.audioElements.forEach((audioElement) => {
532
- if (audioElement.paused) {
533
- audioElement.play().catch(() => {
534
- });
535
- }
536
- });
537
- }
538
- /**
539
- * Pause remote audio
540
- */
541
- pauseRemoteAudio() {
542
- this.audioElements.forEach((audioElement) => {
543
- if (!audioElement.paused) {
544
- audioElement.pause();
545
- }
546
- });
547
- }
548
653
  /**
549
654
  * Get the native LiveKit Room instance.
550
655
  *
@@ -570,8 +675,8 @@ class LiveKitProvider extends BaseProvider {
570
675
  * @internal
571
676
  */
572
677
  cleanup() {
678
+ this.removeAudioUnlockListeners();
573
679
  this.audioElements.forEach((el) => {
574
- el.pause();
575
680
  el.remove();
576
681
  });
577
682
  this.audioElements.clear();
@@ -1 +1 @@
1
- {"version":3,"file":"index3.js","sources":["../src/providers/livekit/LiveKitProvider.ts"],"sourcesContent":["/**\n * LiveKit Provider Implementation.\n *\n * This provider uses LiveKit's VP8 video track approach\n * to transport animation data via RTCRtpScriptTransform.\n *\n * @packageDocumentation\n */\n\nimport { BaseProvider } from '../base/BaseProvider';\nimport type { RTCConnectionConfig, LiveKitConnectionConfig } from '../../types';\nimport { isLiveKitConfig } from '../../types';\nimport type { AnimationTrackCallbacks, AudioTrackCallbacks } from '../../core/types';\nimport { VP8Extractor } from './VP8Extractor';\nimport { getInsertableStreamsMethod } from './utils';\nimport { logger } from '../../utils';\nimport type {\n LiveKitModule,\n LiveKitRoom,\n LiveKitLocalAudioTrack,\n} from './types';\n\n/**\n * Global registry for LiveKitProvider instances to receive early track events.\n * @internal\n */\nconst globalLiveKitProviderRegistry = new Set<LiveKitProvider>();\n\n/**\n * Flag to ensure we only patch RTCPeerConnection once.\n * @internal\n */\nlet rtcPeerConnectionPatched = false;\n\n/**\n * Patch RTCPeerConnection.prototype to intercept track events at the earliest possible moment.\n * This runs BEFORE any PeerConnection is created by LiveKit.\n * @internal\n */\nfunction patchRTCPeerConnection(): void {\n if (rtcPeerConnectionPatched) return;\n if (typeof RTCPeerConnection === 'undefined') return;\n\n rtcPeerConnectionPatched = true;\n\n const originalAddEventListener = RTCPeerConnection.prototype.addEventListener;\n\n RTCPeerConnection.prototype.addEventListener = function(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions\n ) {\n if (type === 'track') {\n const wrappedListener = function(this: RTCPeerConnection, event: Event) {\n const trackEvent = event as RTCTrackEvent;\n\n if ((trackEvent.track?.kind === 'audio' || trackEvent.track?.kind === 'video') && trackEvent.receiver) {\n for (const provider of globalLiveKitProviderRegistry) {\n provider.handleEarlyTrack(trackEvent.receiver, trackEvent.track);\n }\n }\n\n if (typeof listener === 'function') {\n listener.call(this, event);\n } else {\n listener.handleEvent(event);\n }\n };\n\n return originalAddEventListener.call(this, type, wrappedListener as EventListener, options);\n }\n\n return originalAddEventListener.call(this, type, listener, options);\n };\n\n // Also patch the ontrack setter\n const originalOnTrackDescriptor = Object.getOwnPropertyDescriptor(RTCPeerConnection.prototype, 'ontrack');\n if (originalOnTrackDescriptor) {\n Object.defineProperty(RTCPeerConnection.prototype, 'ontrack', {\n get: originalOnTrackDescriptor.get,\n set: function(handler: ((this: RTCPeerConnection, ev: RTCTrackEvent) => void) | null) {\n if (handler) {\n const wrappedHandler = function(this: RTCPeerConnection, event: RTCTrackEvent) {\n if ((event.track?.kind === 'audio' || event.track?.kind === 'video') && event.receiver) {\n for (const provider of globalLiveKitProviderRegistry) {\n provider.handleEarlyTrack(event.receiver, event.track);\n }\n }\n handler.call(this, event);\n };\n originalOnTrackDescriptor.set?.call(this, wrappedHandler);\n } else {\n originalOnTrackDescriptor.set?.call(this, handler);\n }\n },\n configurable: true,\n enumerable: true,\n });\n }\n}\n\n// Apply the patch immediately when this module loads\npatchRTCPeerConnection();\n\n/**\n * LiveKit Provider.\n *\n * Implements RTCProvider interface for LiveKit platform.\n * Uses RTCRtpScriptTransform to extract animation data from VP8 video tracks.\n *\n * @example\n * ```typescript\n * import { AvatarPlayer, LiveKitProvider } from '@spatialwalk/avatarkit-rtc';\n *\n * const provider = new LiveKitProvider();\n * const player = new AvatarPlayer(provider, renderer);\n *\n * await player.connect({\n * url: 'wss://your-livekit-server.com',\n * token: 'your-token',\n * });\n * ```\n */\nexport class LiveKitProvider extends BaseProvider {\n /** Provider name identifier */\n readonly name = 'livekit';\n\n /** @internal */\n private room: LiveKitRoom | null = null;\n /** @internal */\n private livekitSDK: LiveKitModule | null = null;\n\n // Animation track subscription\n /** @internal */\n private animationCallbacks: AnimationTrackCallbacks | null = null;\n /** @internal */\n private vp8Extractor: VP8Extractor | null = null;\n /** @internal */\n private vp8Extractors: Map<RTCRtpReceiver, VP8Extractor> = new Map();\n /** @internal */\n private transformedReceivers: Set<RTCRtpReceiver> = new Set();\n /** @internal */\n private receiverParticipantMap: Map<RTCRtpReceiver, { participant: unknown; trackName?: string }> = new Map();\n\n // Audio track subscription\n /** @internal */\n private audioCallbacks: AudioTrackCallbacks | null = null;\n /** @internal */\n private audioElements: Map<string, HTMLAudioElement> = new Map();\n\n // Microphone publishing\n /** @internal */\n private localAudioTrack: LiveKitLocalAudioTrack | null = null;\n /** @internal */\n private isMicrophoneEnabled = false;\n\n constructor() {\n super();\n // Register to receive early track events\n globalLiveKitProviderRegistry.add(this);\n }\n\n /**\n * Load LiveKit SDK dynamically.\n * @internal\n */\n private async loadSDK(): Promise<LiveKitModule> {\n if (this.livekitSDK) {\n return this.livekitSDK;\n }\n\n try {\n this.livekitSDK = await import('livekit-client');\n return this.livekitSDK;\n } catch (error) {\n throw new Error(\n '❌ Failed to load livekit-client.\\n' +\n 'Please ensure it is installed: pnpm add livekit-client'\n );\n }\n }\n\n /**\n * Map LiveKit connection state to string.\n * @internal\n */\n private mapLiveKitConnectionState(livekit: typeof import('livekit-client'), state: unknown): string {\n const { ConnectionState: LKConnectionState } = livekit;\n switch (state) {\n case LKConnectionState.Disconnected:\n return 'disconnected';\n case LKConnectionState.Connecting:\n return 'connecting';\n case LKConnectionState.Connected:\n return 'connected';\n case LKConnectionState.Reconnecting:\n return 'reconnecting';\n default:\n return 'failed';\n }\n }\n\n /**\n * Handle early track event from the global RTCPeerConnection patch.\n * This is called BEFORE LiveKit processes the track.\n * @internal\n */\n handleEarlyTrack(receiver: RTCRtpReceiver, track: MediaStreamTrack): void {\n if (!this.room) return;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const roomState = (this.room as any).state;\n if (roomState === 'disconnected' || roomState === 0) return;\n\n // Apply transform to video tracks - animation data is sent as VP8 video\n if (track.kind === 'video' && this.animationCallbacks) {\n this.applyAnimationReceiverTransform(receiver, track);\n }\n }\n\n /**\n * Find all RTCPeerConnection instances in the room\n * @internal\n */\n private findAllPeerConnections(root: unknown): RTCPeerConnection[] {\n if (typeof RTCPeerConnection === 'undefined') return [];\n if (!root || typeof root !== 'object') return [];\n\n const seen = new Set<unknown>();\n const queue: Array<{ v: unknown; d: number }> = [{ v: root, d: 0 }];\n const pcs: RTCPeerConnection[] = [];\n let steps = 0;\n\n while (queue.length && steps < 2000) {\n steps++;\n const { v, d } = queue.shift()!;\n if (!v || typeof v !== 'object') continue;\n if (seen.has(v)) continue;\n seen.add(v);\n\n if (v instanceof RTCPeerConnection) {\n pcs.push(v);\n continue;\n }\n if (d >= 6) continue;\n\n if (Array.isArray(v)) {\n for (const item of v) queue.push({ v: item, d: d + 1 });\n continue;\n }\n\n if (v instanceof Map) {\n for (const item of v.values()) queue.push({ v: item, d: d + 1 });\n continue;\n }\n\n if (v instanceof Set) {\n for (const item of v.values()) queue.push({ v: item, d: d + 1 });\n continue;\n }\n\n try {\n for (const key of Object.keys(v as object)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n queue.push({ v: (v as any)[key], d: d + 1 });\n }\n } catch {\n // ignore\n }\n }\n\n return pcs;\n }\n\n /**\n * Find video receiver by track ID\n * @internal\n */\n private findVideoReceiverByTrackId(trackId?: string): RTCRtpReceiver | null {\n if (!trackId) return null;\n const pcs = this.findAllPeerConnections(this.room);\n for (const pc of pcs) {\n try {\n const receivers = pc.getReceivers();\n for (const r of receivers) {\n if (r.track?.kind === 'video' && r.track.id === trackId) return r;\n }\n } catch {\n // ignore\n }\n }\n // fallback: any video receiver\n for (const pc of pcs) {\n try {\n const r = pc.getReceivers().find((x) => x.track?.kind === 'video');\n if (r) return r;\n } catch {\n // ignore\n }\n }\n return null;\n }\n\n /**\n * Find audio receiver by track ID\n * @internal\n */\n private findAudioReceiverByTrackId(trackId?: string): RTCRtpReceiver | null {\n if (!trackId) return null;\n const pcs = this.findAllPeerConnections(this.room);\n for (const pc of pcs) {\n try {\n const receivers = pc.getReceivers();\n for (const r of receivers) {\n if (r.track?.kind === 'audio' && r.track.id === trackId) return r;\n }\n } catch {\n // ignore\n }\n }\n // fallback: any audio receiver\n for (const pc of pcs) {\n try {\n const r = pc.getReceivers().find((x) => x.track?.kind === 'audio');\n if (r) return r;\n } catch {\n // ignore\n }\n }\n return null;\n }\n\n /**\n * Apply animation receiver transform on a video receiver\n * @internal\n */\n private applyAnimationReceiverTransform(receiver: RTCRtpReceiver, mediaTrack: MediaStreamTrack): void {\n if (mediaTrack.kind !== 'video') return;\n if (this.transformedReceivers.has(receiver)) return;\n\n const method = getInsertableStreamsMethod();\n if (method !== 'scriptTransform') return;\n if (receiver.transform) return;\n if (!this.animationCallbacks) return;\n\n this.transformedReceivers.add(receiver);\n\n try {\n const extractor = new VP8Extractor();\n this.vp8Extractors.set(receiver, extractor);\n this.vp8Extractor = extractor;\n \n extractor.initialize(receiver, this.animationCallbacks).catch((err) => {\n logger.error('LiveKit', 'Failed to initialize VP8Extractor:', err);\n this.transformedReceivers.delete(receiver);\n this.vp8Extractors.delete(receiver);\n });\n } catch (e) {\n logger.error('LiveKit', 'Failed to apply transform:', e);\n this.transformedReceivers.delete(receiver);\n this.vp8Extractors.delete(receiver);\n }\n }\n\n\n async connect(config: RTCConnectionConfig): Promise<void> {\n logger.info('LiveKit', 'connect() called with config:', {\n hasUrl: 'url' in config,\n hasRoomName: 'roomName' in config,\n hasToken: 'token' in config,\n config,\n });\n\n if (!isLiveKitConfig(config)) {\n logger.error('LiveKit', 'Config validation failed - missing url or roomName');\n throw new Error('LiveKitProvider requires url and roomName in connection config');\n }\n\n const livekitConfig: LiveKitConnectionConfig = config;\n logger.info('LiveKit', 'Config validated, connecting to:', livekitConfig.url);\n\n const livekit = await this.loadSDK();\n logger.info('LiveKit', 'SDK loaded successfully');\n const { Room } = livekit;\n\n this.room = new Room({\n videoCaptureDefaults: undefined,\n audioCaptureDefaults: undefined,\n });\n\n this.setConnectionState('connecting');\n\n // Setup event listeners\n this.setupEventListeners(livekit);\n\n try {\n logger.info('LiveKit', 'Attempting to connect to room...');\n await this.room.connect(livekitConfig.url, livekitConfig.token);\n logger.info('LiveKit', 'Room connected, state:', this.room.state);\n logger.info('LiveKit', 'Room name:', this.room.name);\n logger.info('LiveKit', 'Local participant:', this.room.localParticipant?.identity);\n logger.info('LiveKit', 'Remote participants:', this.room.remoteParticipants.size);\n \n // Log existing participants and their tracks\n this.room.remoteParticipants.forEach((participant: any, sid: string) => {\n logger.info('LiveKit', `Remote participant: ${participant.identity}, sid: ${sid}`);\n participant.trackPublications.forEach((pub: any, trackSid: string) => {\n logger.info('LiveKit', ` Track: ${pub.trackName}, kind: ${pub.kind}, subscribed: ${pub.isSubscribed}, sid: ${trackSid}`);\n });\n });\n\n this.setConnectionState(this.mapLiveKitConnectionState(livekit, this.room.state));\n this.emit('connected');\n } catch (error) {\n logger.error('LiveKit', 'Connection failed:', error);\n this.setConnectionState('failed');\n this.emit('error', error as Error);\n throw error;\n }\n }\n\n /**\n * Setup LiveKit room event listeners\n * @internal\n */\n private setupEventListeners(livekit: LiveKitModule): void {\n if (!this.room) return;\n \n const room = this.room;\n const { RoomEvent, Track } = livekit;\n\n // Connected\n room.on(RoomEvent.Connected, () => {\n this.setConnectionState('connected');\n this.emit('connected');\n });\n\n // Participant connected\n room.on(RoomEvent.ParticipantConnected, (participant: any) => {\n logger.info('LiveKit', `Participant connected: ${participant.identity}`);\n });\n\n // Participant disconnected\n room.on(RoomEvent.ParticipantDisconnected, (participant: any) => {\n logger.warn('LiveKit', `Participant disconnected: ${participant.identity}`);\n });\n\n // Track published\n room.on(RoomEvent.TrackPublished, (publication: any, participant: any) => {\n logger.info('LiveKit', 'TrackPublished:', {\n trackName: publication.trackName,\n kind: publication.kind,\n participant: participant.identity,\n });\n });\n\n // Disconnected\n room.on(RoomEvent.Disconnected, () => {\n this.cleanup();\n this.setConnectionState('disconnected');\n this.emit('disconnected');\n });\n\n // Track subscribed\n this.room.on(\n RoomEvent.TrackSubscribed,\n (track: any, publication: any, participant: any) => {\n logger.info('LiveKit', 'TrackSubscribed:', {\n kind: track.kind,\n trackName: publication.trackName,\n participant: participant.identity,\n });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mediaStreamTrack = (track as any).mediaStreamTrack as MediaStreamTrack | undefined;\n const trackName = publication.trackName;\n\n if (track.kind === Track.Kind.Audio) {\n logger.info('LiveKit', 'Audio track received, attaching...');\n // Audio track\n const audioElement = track.attach() as HTMLAudioElement;\n audioElement.id = `audio-${participant.identity}`;\n audioElement.autoplay = true;\n audioElement.controls = false;\n audioElement.style.display = 'none';\n \n if (typeof document !== 'undefined') {\n document.body.appendChild(audioElement);\n logger.info('LiveKit', 'Audio element appended to body');\n }\n \n this.audioElements.set(participant.identity, audioElement);\n\n if (this.audioCallbacks) {\n this.audioCallbacks.onAudioReceived?.(participant);\n }\n } else if (track.kind === Track.Kind.Video) {\n logger.info('LiveKit', 'Video track received, setting up animation transform...');\n // Video track - apply animation transform\n if (mediaStreamTrack) {\n const receiver = this.findVideoReceiverByTrackId(mediaStreamTrack.id);\n if (receiver) {\n this.receiverParticipantMap.set(receiver, { participant, trackName });\n \n // Apply transform if not already applied in handleEarlyTrack\n if (!receiver.transform || !this.transformedReceivers.has(receiver)) {\n this.applyAnimationReceiverTransform(receiver, mediaStreamTrack);\n }\n }\n }\n\n // Attach to hidden video element to trigger data flow\n const videoElement = track.attach() as HTMLVideoElement;\n videoElement.id = `video-animation-${participant.identity}`;\n videoElement.muted = true;\n videoElement.autoplay = true;\n videoElement.style.display = 'none';\n if (typeof document !== 'undefined') {\n document.body.appendChild(videoElement);\n videoElement.play().catch(() => {});\n }\n }\n }\n );\n\n // Track unsubscribed\n this.room.on(\n RoomEvent.TrackUnsubscribed,\n (track: any, _publication: any, participant: any) => {\n if (track.kind === Track.Kind.Audio) {\n track.detach().forEach((el: HTMLElement) => el.remove());\n this.audioElements.delete(participant.identity);\n \n if (this.audioCallbacks) {\n this.audioCallbacks.onAudioLost?.(participant);\n }\n }\n }\n );\n\n // Connection state changed\n room.on(RoomEvent.ConnectionStateChanged, (state: unknown) => {\n this.setConnectionState(this.mapLiveKitConnectionState(livekit, state));\n });\n }\n\n async disconnect(): Promise<void> {\n this.cleanup();\n if (this.room) {\n this.room.disconnect();\n this.room = null;\n }\n this.setConnectionState('disconnected');\n globalLiveKitProviderRegistry.delete(this);\n }\n\n getConnectionState(): string {\n if (!this.room) {\n return 'disconnected';\n }\n return this.connectionState;\n }\n\n /** @internal */\n async subscribeAnimationTrack(callbacks: AnimationTrackCallbacks): Promise<void> {\n this.animationCallbacks = callbacks;\n \n // If room is already connected, check for existing tracks\n if (this.room) {\n const livekit = await this.loadSDK();\n const { ConnectionState: LKConnectionState } = livekit;\n \n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const roomState = (this.room as any).state;\n \n if (roomState === LKConnectionState.Connected || roomState === 'connected') {\n // Check for existing video tracks that might be animation tracks\n const remoteParticipants = this.room.remoteParticipants.values();\n for (const participant of remoteParticipants) {\n // Get video track publications (handle different SDK versions)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const pubs = (participant as any).videoTrackPublications ?? (participant as any).trackPublications;\n if (pubs && typeof pubs.values === 'function') {\n for (const publication of pubs.values()) {\n if (publication.trackName?.includes('animation') && publication.track) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mediaStreamTrack = (publication.track as any).mediaStreamTrack as MediaStreamTrack | undefined;\n if (mediaStreamTrack) {\n const receiver = this.findVideoReceiverByTrackId(mediaStreamTrack.id);\n if (receiver) {\n this.applyAnimationReceiverTransform(receiver, mediaStreamTrack);\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n /** @internal */\n async unsubscribeAnimationTrack(): Promise<void> {\n this.animationCallbacks = null;\n \n // Dispose all extractors\n this.vp8Extractors.forEach((extractor) => {\n extractor.dispose();\n });\n this.vp8Extractors.clear();\n \n if (this.vp8Extractor) {\n this.vp8Extractor.dispose();\n this.vp8Extractor = null;\n }\n this.transformedReceivers.clear();\n this.receiverParticipantMap.clear();\n }\n\n /** @internal */\n async subscribeAudioTrack(callbacks: AudioTrackCallbacks): Promise<void> {\n this.audioCallbacks = callbacks;\n // Audio tracks are automatically handled in TrackSubscribed event\n }\n\n /** @internal */\n async unsubscribeAudioTrack(): Promise<void> {\n this.audioCallbacks = null;\n this.audioElements.forEach((el) => {\n el.pause();\n el.remove();\n });\n this.audioElements.clear();\n }\n\n async publishAudioTrack(track: MediaStreamTrack): Promise<void> {\n if (!this.room) {\n throw new Error('Not connected to room');\n }\n\n try {\n const livekit = await this.loadSDK();\n const { createLocalAudioTrack, Track: TrackEnum } = livekit;\n\n // If track is provided, use it; otherwise create from microphone\n if (track) {\n await this.room.localParticipant.publishTrack(track, {\n name: 'user-microphone',\n source: TrackEnum.Source.Microphone,\n red: false,\n });\n } else {\n // Create and publish microphone track\n const localTrack = await createLocalAudioTrack({\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n });\n \n this.localAudioTrack = localTrack;\n \n await this.room.localParticipant.publishTrack(localTrack, {\n name: 'user-microphone',\n source: TrackEnum.Source.Microphone,\n red: false,\n });\n }\n\n this.isMicrophoneEnabled = true;\n } catch (error) {\n logger.error('LiveKit', 'Failed to publish audio track:', error);\n this.isMicrophoneEnabled = false;\n throw error;\n }\n }\n\n async unpublishAudioTrack(): Promise<void> {\n if (!this.room || !this.localAudioTrack) {\n return;\n }\n\n await this.room.localParticipant.unpublishTrack(this.localAudioTrack);\n this.localAudioTrack.stop();\n this.localAudioTrack = null;\n this.isMicrophoneEnabled = false;\n }\n\n /**\n * Play remote audio (resume playback)\n */\n playRemoteAudio(): void {\n this.audioElements.forEach((audioElement) => {\n if (audioElement.paused) {\n audioElement.play().catch(() => {});\n }\n });\n }\n\n /**\n * Pause remote audio\n */\n pauseRemoteAudio(): void {\n this.audioElements.forEach((audioElement) => {\n if (!audioElement.paused) {\n audioElement.pause();\n }\n });\n }\n\n /**\n * Get the native LiveKit Room instance.\n * \n * Allows advanced users to access LiveKit-specific features\n * not exposed through the unified API.\n * \n * @returns The LiveKit Room instance, or null if not connected\n * \n * @example\n * ```typescript\n * const room = provider.getNativeClient();\n * if (room) {\n * // Access LiveKit-specific features\n * console.log('Participants:', room.remoteParticipants.size);\n * }\n * ```\n */\n getNativeClient(): LiveKitRoom | null {\n return this.room;\n }\n\n /**\n * Cleanup resources\n * @internal\n */\n private cleanup(): void {\n // Cleanup audio elements\n this.audioElements.forEach((el) => {\n el.pause();\n el.remove();\n });\n this.audioElements.clear();\n \n // Dispose all extractors\n this.vp8Extractors.forEach((extractor) => extractor.dispose());\n this.vp8Extractors.clear();\n this.vp8Extractor = null;\n \n this.transformedReceivers.clear();\n this.receiverParticipantMap.clear();\n\n // Stop local audio track\n if (this.localAudioTrack) {\n this.localAudioTrack.stop();\n this.localAudioTrack = null;\n }\n this.isMicrophoneEnabled = false;\n }\n}\n"],"names":["_a","_b"],"mappings":"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"}
1
+ {"version":3,"file":"index3.js","sources":["../src/providers/livekit/LiveKitProvider.ts"],"sourcesContent":["/**\n * LiveKit Provider Implementation.\n *\n * This provider uses LiveKit's VP8 video track approach\n * to transport animation data via RTCRtpScriptTransform.\n *\n * @packageDocumentation\n */\n\nimport { BaseProvider } from '../base/BaseProvider';\nimport type { RTCConnectionConfig, LiveKitConnectionConfig } from '../../types';\nimport { isLiveKitConfig } from '../../types';\nimport type { AnimationTrackCallbacks, AudioTrackCallbacks } from '../../core/types';\nimport { VP8Extractor } from './VP8Extractor';\nimport { getInsertableStreamsMethod } from './utils';\nimport { logger } from '../../utils';\nimport type {\n LiveKitModule,\n LiveKitRoom,\n LiveKitLocalAudioTrack,\n} from './types';\n\n/**\n * Global registry for LiveKitProvider instances to receive early track events.\n * @internal\n */\nconst globalLiveKitProviderRegistry = new Set<LiveKitProvider>();\n\n/**\n * Flag to ensure we only patch RTCPeerConnection once.\n * @internal\n */\nlet rtcPeerConnectionPatched = false;\n\n/**\n * Patch RTCPeerConnection.prototype to intercept track events at the earliest possible moment.\n * This runs BEFORE any PeerConnection is created by LiveKit.\n * @internal\n */\nfunction patchRTCPeerConnection(): void {\n if (rtcPeerConnectionPatched) return;\n if (typeof RTCPeerConnection === 'undefined') return;\n\n rtcPeerConnectionPatched = true;\n\n const originalAddEventListener = RTCPeerConnection.prototype.addEventListener;\n\n RTCPeerConnection.prototype.addEventListener = function(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions\n ) {\n if (type === 'track') {\n const wrappedListener = function(this: RTCPeerConnection, event: Event) {\n const trackEvent = event as RTCTrackEvent;\n\n if ((trackEvent.track?.kind === 'audio' || trackEvent.track?.kind === 'video') && trackEvent.receiver) {\n for (const provider of globalLiveKitProviderRegistry) {\n provider.handleEarlyTrack(trackEvent.receiver, trackEvent.track);\n }\n }\n\n if (typeof listener === 'function') {\n listener.call(this, event);\n } else {\n listener.handleEvent(event);\n }\n };\n\n return originalAddEventListener.call(this, type, wrappedListener as EventListener, options);\n }\n\n return originalAddEventListener.call(this, type, listener, options);\n };\n\n // Also patch the ontrack setter\n const originalOnTrackDescriptor = Object.getOwnPropertyDescriptor(RTCPeerConnection.prototype, 'ontrack');\n if (originalOnTrackDescriptor) {\n Object.defineProperty(RTCPeerConnection.prototype, 'ontrack', {\n get: originalOnTrackDescriptor.get,\n set: function(handler: ((this: RTCPeerConnection, ev: RTCTrackEvent) => void) | null) {\n if (handler) {\n const wrappedHandler = function(this: RTCPeerConnection, event: RTCTrackEvent) {\n if ((event.track?.kind === 'audio' || event.track?.kind === 'video') && event.receiver) {\n for (const provider of globalLiveKitProviderRegistry) {\n provider.handleEarlyTrack(event.receiver, event.track);\n }\n }\n handler.call(this, event);\n };\n originalOnTrackDescriptor.set?.call(this, wrappedHandler);\n } else {\n originalOnTrackDescriptor.set?.call(this, handler);\n }\n },\n configurable: true,\n enumerable: true,\n });\n }\n}\n\n// Apply the patch immediately when this module loads\npatchRTCPeerConnection();\n\n/**\n * LiveKit Provider.\n *\n * Implements RTCProvider interface for LiveKit platform.\n * Uses RTCRtpScriptTransform to extract animation data from VP8 video tracks.\n *\n * @example\n * ```typescript\n * import { AvatarPlayer, LiveKitProvider } from '@spatialwalk/avatarkit-rtc';\n *\n * const provider = new LiveKitProvider();\n * const player = new AvatarPlayer(provider, renderer);\n *\n * await player.connect({\n * url: 'wss://your-livekit-server.com',\n * token: 'your-token',\n * });\n * ```\n */\nexport class LiveKitProvider extends BaseProvider {\n /** Provider name identifier */\n readonly name = 'livekit';\n\n /** @internal */\n private room: LiveKitRoom | null = null;\n /** @internal */\n private livekitSDK: LiveKitModule | null = null;\n\n // Animation track subscription\n /** @internal */\n private animationCallbacks: AnimationTrackCallbacks | null = null;\n /** @internal */\n private vp8Extractor: VP8Extractor | null = null;\n /** @internal */\n private vp8Extractors: Map<RTCRtpReceiver, VP8Extractor> = new Map();\n /** @internal */\n private transformedReceivers: Set<RTCRtpReceiver> = new Set();\n /** @internal */\n private receiverParticipantMap: Map<RTCRtpReceiver, { participant: unknown; trackName?: string }> = new Map();\n\n // Audio track subscription\n /** @internal */\n private audioCallbacks: AudioTrackCallbacks | null = null;\n /** @internal */\n private audioElements: Map<string, HTMLAudioElement> = new Map();\n /** @internal */\n private awaitingAudioUnlock = false;\n /** @internal */\n private readonly audioUnlockEventNames: Array<keyof WindowEventMap> = [\n 'pointerdown',\n 'keydown',\n 'touchstart',\n ];\n /** @internal */\n private readonly handleAudioUnlockGesture = (): void => {\n void this.ensureAudioPlaybackUnlocked('user-gesture');\n };\n\n // Microphone publishing\n /** @internal */\n private localAudioTrack: LiveKitLocalAudioTrack | null = null;\n /** @internal */\n private isMicrophoneEnabled = false;\n\n constructor() {\n super();\n // Register to receive early track events\n globalLiveKitProviderRegistry.add(this);\n }\n\n /**\n * Load LiveKit SDK dynamically.\n * @internal\n */\n private async loadSDK(): Promise<LiveKitModule> {\n if (this.livekitSDK) {\n return this.livekitSDK;\n }\n\n try {\n this.livekitSDK = await import('livekit-client');\n return this.livekitSDK;\n } catch (error) {\n throw new Error(\n '❌ Failed to load livekit-client.\\n' +\n 'Please ensure it is installed: pnpm add livekit-client'\n );\n }\n }\n\n /**\n * Map LiveKit connection state to string.\n * @internal\n */\n private mapLiveKitConnectionState(livekit: typeof import('livekit-client'), state: unknown): string {\n const { ConnectionState: LKConnectionState } = livekit;\n switch (state) {\n case LKConnectionState.Disconnected:\n return 'disconnected';\n case LKConnectionState.Connecting:\n return 'connecting';\n case LKConnectionState.Connected:\n return 'connected';\n case LKConnectionState.Reconnecting:\n return 'reconnecting';\n default:\n return 'failed';\n }\n }\n\n /**\n * Handle early track event from the global RTCPeerConnection patch.\n * This is called BEFORE LiveKit processes the track.\n * @internal\n */\n handleEarlyTrack(receiver: RTCRtpReceiver, track: MediaStreamTrack): void {\n if (!this.room) return;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const roomState = (this.room as any).state;\n if (roomState === 'disconnected' || roomState === 0) return;\n\n // Apply transform to video tracks - animation data is sent as VP8 video\n if (track.kind === 'video' && this.animationCallbacks) {\n this.applyAnimationReceiverTransform(receiver, track);\n }\n }\n\n /**\n * Find all RTCPeerConnection instances in the room\n * @internal\n */\n private findAllPeerConnections(root: unknown): RTCPeerConnection[] {\n if (typeof RTCPeerConnection === 'undefined') return [];\n if (!root || typeof root !== 'object') return [];\n\n const seen = new Set<unknown>();\n const queue: Array<{ v: unknown; d: number }> = [{ v: root, d: 0 }];\n const pcs: RTCPeerConnection[] = [];\n let steps = 0;\n\n while (queue.length && steps < 2000) {\n steps++;\n const { v, d } = queue.shift()!;\n if (!v || typeof v !== 'object') continue;\n if (seen.has(v)) continue;\n seen.add(v);\n\n if (v instanceof RTCPeerConnection) {\n pcs.push(v);\n continue;\n }\n if (d >= 6) continue;\n\n if (Array.isArray(v)) {\n for (const item of v) queue.push({ v: item, d: d + 1 });\n continue;\n }\n\n if (v instanceof Map) {\n for (const item of v.values()) queue.push({ v: item, d: d + 1 });\n continue;\n }\n\n if (v instanceof Set) {\n for (const item of v.values()) queue.push({ v: item, d: d + 1 });\n continue;\n }\n\n try {\n for (const key of Object.keys(v as object)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n queue.push({ v: (v as any)[key], d: d + 1 });\n }\n } catch {\n // ignore\n }\n }\n\n return pcs;\n }\n\n /**\n * Find video receiver by track ID\n * @internal\n */\n private findVideoReceiverByTrackId(trackId?: string): RTCRtpReceiver | null {\n if (!trackId) return null;\n const pcs = this.findAllPeerConnections(this.room);\n for (const pc of pcs) {\n try {\n const receivers = pc.getReceivers();\n for (const r of receivers) {\n if (r.track?.kind === 'video' && r.track.id === trackId) return r;\n }\n } catch {\n // ignore\n }\n }\n // fallback: any video receiver\n for (const pc of pcs) {\n try {\n const r = pc.getReceivers().find((x) => x.track?.kind === 'video');\n if (r) return r;\n } catch {\n // ignore\n }\n }\n return null;\n }\n\n /**\n * Find audio receiver by track ID\n * @internal\n */\n private findAudioReceiverByTrackId(trackId?: string): RTCRtpReceiver | null {\n if (!trackId) return null;\n const pcs = this.findAllPeerConnections(this.room);\n for (const pc of pcs) {\n try {\n const receivers = pc.getReceivers();\n for (const r of receivers) {\n if (r.track?.kind === 'audio' && r.track.id === trackId) return r;\n }\n } catch {\n // ignore\n }\n }\n // fallback: any audio receiver\n for (const pc of pcs) {\n try {\n const r = pc.getReceivers().find((x) => x.track?.kind === 'audio');\n if (r) return r;\n } catch {\n // ignore\n }\n }\n return null;\n }\n\n /**\n * Apply animation receiver transform on a video receiver\n * @internal\n */\n private applyAnimationReceiverTransform(receiver: RTCRtpReceiver, mediaTrack: MediaStreamTrack): void {\n if (mediaTrack.kind !== 'video') return;\n if (this.transformedReceivers.has(receiver)) return;\n\n const method = getInsertableStreamsMethod();\n if (method !== 'scriptTransform') return;\n if (receiver.transform) return;\n if (!this.animationCallbacks) return;\n\n this.transformedReceivers.add(receiver);\n\n try {\n const extractor = new VP8Extractor();\n this.vp8Extractors.set(receiver, extractor);\n this.vp8Extractor = extractor;\n \n extractor.initialize(receiver, this.animationCallbacks).catch((err) => {\n logger.error('LiveKit', 'Failed to initialize VP8Extractor:', err);\n this.transformedReceivers.delete(receiver);\n this.vp8Extractors.delete(receiver);\n });\n } catch (e) {\n logger.error('LiveKit', 'Failed to apply transform:', e);\n this.transformedReceivers.delete(receiver);\n this.vp8Extractors.delete(receiver);\n }\n }\n\n /**\n * Add one-time user gesture listeners to recover from autoplay blocking.\n * @internal\n */\n private addAudioUnlockListeners(): void {\n if (typeof window === 'undefined' || this.awaitingAudioUnlock) {\n return;\n }\n\n this.awaitingAudioUnlock = true;\n for (const eventName of this.audioUnlockEventNames) {\n window.addEventListener(eventName, this.handleAudioUnlockGesture, {\n capture: true,\n passive: true,\n });\n }\n\n logger.warn('LiveKit', 'Audio playback blocked by autoplay policy, waiting for user interaction');\n }\n\n /**\n * Remove user gesture listeners after playback is unlocked.\n * @internal\n */\n private removeAudioUnlockListeners(): void {\n if (typeof window === 'undefined' || !this.awaitingAudioUnlock) {\n return;\n }\n\n this.awaitingAudioUnlock = false;\n for (const eventName of this.audioUnlockEventNames) {\n window.removeEventListener(eventName, this.handleAudioUnlockGesture, true);\n }\n }\n\n /**\n * Detect browser autoplay blocking errors from audio play/start calls.\n * @internal\n */\n private isAutoplayBlockedError(error: unknown): boolean {\n if (typeof DOMException !== 'undefined' && error instanceof DOMException) {\n return error.name === 'NotAllowedError';\n }\n\n if (error instanceof Error) {\n return (\n error.name === 'NotAllowedError'\n || /didn't interact|user didn't interact|NotAllowedError/i.test(error.message)\n );\n }\n\n return false;\n }\n\n /**\n * Try to resume all attached remote audio elements.\n * @returns true if at least one element is still blocked by autoplay policy\n * @internal\n */\n private async resumeAttachedAudioElements(reason: string): Promise<boolean> {\n let hasAutoplayBlockedElement = false;\n\n for (const [participantIdentity, audioElement] of this.audioElements) {\n if (!audioElement.paused) {\n continue;\n }\n\n try {\n await audioElement.play();\n } catch (error) {\n if (this.isAutoplayBlockedError(error)) {\n hasAutoplayBlockedElement = true;\n }\n logger.warn(\n 'LiveKit',\n `Failed to resume remote audio element (${reason}) for ${participantIdentity}:`,\n error\n );\n }\n }\n\n return hasAutoplayBlockedElement;\n }\n\n /**\n * Ensure remote audio playback is unlocked and resumed.\n * @internal\n */\n private async ensureAudioPlaybackUnlocked(reason: string): Promise<void> {\n if (!this.room) {\n return;\n }\n\n const roomWithAudioControl = this.room as LiveKitRoom & {\n startAudio?: () => Promise<void>;\n canPlaybackAudio: boolean;\n };\n\n if (!roomWithAudioControl.canPlaybackAudio && typeof roomWithAudioControl.startAudio === 'function') {\n try {\n await roomWithAudioControl.startAudio();\n logger.info('LiveKit', `Audio context resumed (${reason})`);\n } catch (error) {\n if (this.isAutoplayBlockedError(error)) {\n this.addAudioUnlockListeners();\n } else {\n logger.warn('LiveKit', `Failed to call room.startAudio() (${reason}):`, error);\n }\n return;\n }\n }\n\n const hasAutoplayBlockedElement = await this.resumeAttachedAudioElements(reason);\n if (roomWithAudioControl.canPlaybackAudio && !hasAutoplayBlockedElement) {\n this.removeAudioUnlockListeners();\n } else {\n this.addAudioUnlockListeners();\n }\n }\n\n\n async connect(config: RTCConnectionConfig): Promise<void> {\n logger.info('LiveKit', 'connect() called with config:', {\n hasUrl: 'url' in config,\n hasRoomName: 'roomName' in config,\n hasToken: 'token' in config,\n config,\n });\n\n if (!isLiveKitConfig(config)) {\n logger.error('LiveKit', 'Config validation failed - missing url or roomName');\n throw new Error('LiveKitProvider requires url and roomName in connection config');\n }\n\n const livekitConfig: LiveKitConnectionConfig = config;\n logger.info('LiveKit', 'Config validated, connecting to:', livekitConfig.url);\n\n this.removeAudioUnlockListeners();\n\n const livekit = await this.loadSDK();\n logger.info('LiveKit', 'SDK loaded successfully');\n const { Room } = livekit;\n\n this.room = new Room({\n videoCaptureDefaults: undefined,\n audioCaptureDefaults: undefined,\n });\n\n this.setConnectionState('connecting');\n\n // Setup event listeners\n this.setupEventListeners(livekit);\n\n try {\n logger.info('LiveKit', 'Attempting to connect to room...');\n await this.room.connect(livekitConfig.url, livekitConfig.token);\n logger.info('LiveKit', 'Room connected, state:', this.room.state);\n logger.info('LiveKit', 'Room name:', this.room.name);\n logger.info('LiveKit', 'Local participant:', this.room.localParticipant?.identity);\n logger.info('LiveKit', 'Remote participants:', this.room.remoteParticipants.size);\n \n // Log existing participants and their tracks\n this.room.remoteParticipants.forEach((participant: any, sid: string) => {\n logger.info('LiveKit', `Remote participant: ${participant.identity}, sid: ${sid}`);\n participant.trackPublications.forEach((pub: any, trackSid: string) => {\n logger.info('LiveKit', ` Track: ${pub.trackName}, kind: ${pub.kind}, subscribed: ${pub.isSubscribed}, sid: ${trackSid}`);\n });\n });\n\n this.setConnectionState(this.mapLiveKitConnectionState(livekit, this.room.state));\n } catch (error) {\n logger.error('LiveKit', 'Connection failed:', error);\n this.setConnectionState('failed');\n this.emit('error', error as Error);\n throw error;\n }\n }\n\n /**\n * Setup LiveKit room event listeners\n * @internal\n */\n private setupEventListeners(livekit: LiveKitModule): void {\n if (!this.room) return;\n \n const room = this.room;\n const { RoomEvent, Track } = livekit;\n\n // Connected\n room.on(RoomEvent.Connected, () => {\n this.setConnectionState('connected');\n this.emit('connected');\n\n const roomWithAudioControl = room as LiveKitRoom & { canPlaybackAudio: boolean };\n if (!roomWithAudioControl.canPlaybackAudio) {\n this.addAudioUnlockListeners();\n }\n });\n\n // Participant connected\n room.on(RoomEvent.ParticipantConnected, (participant: any) => {\n logger.info('LiveKit', `Participant connected: ${participant.identity}`);\n });\n\n // Participant disconnected\n room.on(RoomEvent.ParticipantDisconnected, (participant: any) => {\n logger.warn('LiveKit', `Participant disconnected: ${participant.identity}`);\n });\n\n // Track published\n room.on(RoomEvent.TrackPublished, (publication: any, participant: any) => {\n logger.info('LiveKit', 'TrackPublished:', {\n trackName: publication.trackName,\n kind: publication.kind,\n participant: participant.identity,\n });\n });\n\n // Disconnected\n room.on(RoomEvent.Disconnected, () => {\n this.cleanup();\n this.setConnectionState('disconnected');\n this.emit('disconnected');\n });\n\n room.on(RoomEvent.AudioPlaybackStatusChanged, (canPlaybackAudio: boolean) => {\n logger.info('LiveKit', `Audio playback status changed: ${canPlaybackAudio ? 'enabled' : 'blocked'}`);\n\n if (canPlaybackAudio) {\n void this.ensureAudioPlaybackUnlocked('audio-playback-status-changed');\n } else {\n this.addAudioUnlockListeners();\n }\n });\n\n // Track subscribed\n this.room.on(\n RoomEvent.TrackSubscribed,\n (track: any, publication: any, participant: any) => {\n logger.info('LiveKit', 'TrackSubscribed:', {\n kind: track.kind,\n trackName: publication.trackName,\n participant: participant.identity,\n });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mediaStreamTrack = (track as any).mediaStreamTrack as MediaStreamTrack | undefined;\n const trackName = publication.trackName;\n\n if (track.kind === Track.Kind.Audio) {\n logger.info('LiveKit', 'Audio track received, attaching...');\n // Audio track\n const audioElement = track.attach() as HTMLAudioElement;\n audioElement.id = `audio-${participant.identity}`;\n audioElement.autoplay = true;\n audioElement.controls = false;\n audioElement.style.display = 'none';\n\n const previousAudioElement = this.audioElements.get(participant.identity);\n if (previousAudioElement && previousAudioElement !== audioElement) {\n previousAudioElement.remove();\n }\n \n if (typeof document !== 'undefined') {\n document.body.appendChild(audioElement);\n logger.info('LiveKit', 'Audio element appended to body');\n }\n \n this.audioElements.set(participant.identity, audioElement);\n void this.ensureAudioPlaybackUnlocked('audio-track-subscribed');\n\n if (this.audioCallbacks) {\n this.audioCallbacks.onAudioReceived?.(participant);\n }\n } else if (track.kind === Track.Kind.Video) {\n logger.info('LiveKit', 'Video track received, setting up animation transform...');\n // Video track - apply animation transform\n if (mediaStreamTrack) {\n const receiver = this.findVideoReceiverByTrackId(mediaStreamTrack.id);\n if (receiver) {\n this.receiverParticipantMap.set(receiver, { participant, trackName });\n \n // Apply transform if not already applied in handleEarlyTrack\n if (!receiver.transform || !this.transformedReceivers.has(receiver)) {\n this.applyAnimationReceiverTransform(receiver, mediaStreamTrack);\n }\n }\n }\n\n // Attach to hidden video element to trigger data flow\n const videoElement = track.attach() as HTMLVideoElement;\n videoElement.id = `video-animation-${participant.identity}`;\n videoElement.muted = true;\n videoElement.autoplay = true;\n videoElement.style.display = 'none';\n if (typeof document !== 'undefined') {\n document.body.appendChild(videoElement);\n videoElement.play().catch(() => {});\n }\n }\n }\n );\n\n // Track unsubscribed\n this.room.on(\n RoomEvent.TrackUnsubscribed,\n (track: any, _publication: any, participant: any) => {\n if (track.kind === Track.Kind.Audio) {\n track.detach().forEach((el: HTMLElement) => el.remove());\n this.audioElements.delete(participant.identity);\n \n if (this.audioCallbacks) {\n this.audioCallbacks.onAudioLost?.(participant);\n }\n }\n }\n );\n\n // Connection state changed\n room.on(RoomEvent.ConnectionStateChanged, (state: unknown) => {\n this.setConnectionState(this.mapLiveKitConnectionState(livekit, state));\n });\n }\n\n async disconnect(): Promise<void> {\n this.cleanup();\n if (this.room) {\n this.room.disconnect();\n this.room = null;\n }\n this.setConnectionState('disconnected');\n globalLiveKitProviderRegistry.delete(this);\n }\n\n getConnectionState(): string {\n if (!this.room) {\n return 'disconnected';\n }\n return this.connectionState;\n }\n\n /** @internal */\n async subscribeAnimationTrack(callbacks: AnimationTrackCallbacks): Promise<void> {\n this.animationCallbacks = callbacks;\n \n // If room is already connected, check for existing tracks\n if (this.room) {\n const livekit = await this.loadSDK();\n const { ConnectionState: LKConnectionState } = livekit;\n \n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const roomState = (this.room as any).state;\n \n if (roomState === LKConnectionState.Connected || roomState === 'connected') {\n // Check for existing video tracks that might be animation tracks\n const remoteParticipants = this.room.remoteParticipants.values();\n for (const participant of remoteParticipants) {\n // Get video track publications (handle different SDK versions)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const pubs = (participant as any).videoTrackPublications ?? (participant as any).trackPublications;\n if (pubs && typeof pubs.values === 'function') {\n for (const publication of pubs.values()) {\n if (publication.trackName?.includes('animation') && publication.track) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mediaStreamTrack = (publication.track as any).mediaStreamTrack as MediaStreamTrack | undefined;\n if (mediaStreamTrack) {\n const receiver = this.findVideoReceiverByTrackId(mediaStreamTrack.id);\n if (receiver) {\n this.applyAnimationReceiverTransform(receiver, mediaStreamTrack);\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n /** @internal */\n async unsubscribeAnimationTrack(): Promise<void> {\n this.animationCallbacks = null;\n \n // Dispose all extractors\n this.vp8Extractors.forEach((extractor) => {\n extractor.dispose();\n });\n this.vp8Extractors.clear();\n \n if (this.vp8Extractor) {\n this.vp8Extractor.dispose();\n this.vp8Extractor = null;\n }\n this.transformedReceivers.clear();\n this.receiverParticipantMap.clear();\n }\n\n /** @internal */\n async subscribeAudioTrack(callbacks: AudioTrackCallbacks): Promise<void> {\n this.audioCallbacks = callbacks;\n // Audio tracks are automatically handled in TrackSubscribed event\n }\n\n /** @internal */\n async unsubscribeAudioTrack(): Promise<void> {\n this.audioCallbacks = null;\n this.audioElements.forEach((el) => {\n el.remove();\n });\n this.audioElements.clear();\n }\n\n async publishAudioTrack(track: MediaStreamTrack): Promise<void> {\n if (!this.room) {\n throw new Error('Not connected to room');\n }\n\n try {\n const livekit = await this.loadSDK();\n const { createLocalAudioTrack, Track: TrackEnum } = livekit;\n\n // If track is provided, use it; otherwise create from microphone\n if (track) {\n await this.room.localParticipant.publishTrack(track, {\n name: 'user-microphone',\n source: TrackEnum.Source.Microphone,\n red: false,\n });\n } else {\n // Create and publish microphone track\n const localTrack = await createLocalAudioTrack({\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n });\n \n this.localAudioTrack = localTrack;\n \n await this.room.localParticipant.publishTrack(localTrack, {\n name: 'user-microphone',\n source: TrackEnum.Source.Microphone,\n red: false,\n });\n }\n\n this.isMicrophoneEnabled = true;\n } catch (error) {\n logger.error('LiveKit', 'Failed to publish audio track:', error);\n this.isMicrophoneEnabled = false;\n throw error;\n }\n }\n\n async unpublishAudioTrack(): Promise<void> {\n if (!this.room || !this.localAudioTrack) {\n return;\n }\n\n await this.room.localParticipant.unpublishTrack(this.localAudioTrack);\n this.localAudioTrack.stop();\n this.localAudioTrack = null;\n this.isMicrophoneEnabled = false;\n }\n\n /**\n * Get the native LiveKit Room instance.\n * \n * Allows advanced users to access LiveKit-specific features\n * not exposed through the unified API.\n * \n * @returns The LiveKit Room instance, or null if not connected\n * \n * @example\n * ```typescript\n * const room = provider.getNativeClient();\n * if (room) {\n * // Access LiveKit-specific features\n * console.log('Participants:', room.remoteParticipants.size);\n * }\n * ```\n */\n getNativeClient(): LiveKitRoom | null {\n return this.room;\n }\n\n /**\n * Cleanup resources\n * @internal\n */\n private cleanup(): void {\n this.removeAudioUnlockListeners();\n\n // Cleanup audio elements\n this.audioElements.forEach((el) => {\n el.remove();\n });\n this.audioElements.clear();\n \n // Dispose all extractors\n this.vp8Extractors.forEach((extractor) => extractor.dispose());\n this.vp8Extractors.clear();\n this.vp8Extractor = null;\n \n this.transformedReceivers.clear();\n this.receiverParticipantMap.clear();\n\n // Stop local audio track\n if (this.localAudioTrack) {\n this.localAudioTrack.stop();\n this.localAudioTrack = null;\n }\n this.isMicrophoneEnabled = false;\n }\n}\n"],"names":["_a","_b"],"mappings":"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"}