@spatialwalk/avatarkit-rtc 1.0.0-beta.4 → 1.0.0-beta.5
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/core/AvatarPlayer.d.ts +13 -0
- package/dist/core/AvatarPlayer.d.ts.map +1 -1
- package/dist/core/RTCProvider.d.ts +2 -3
- package/dist/core/RTCProvider.d.ts.map +1 -1
- package/dist/index10.js +346 -64
- package/dist/index10.js.map +1 -1
- package/dist/index11.js +104 -14
- package/dist/index11.js.map +1 -1
- package/dist/index12.js +14 -386
- package/dist/index12.js.map +1 -1
- package/dist/index2.js +2 -0
- package/dist/index2.js.map +1 -1
- package/dist/index3.js +6 -2
- package/dist/index3.js.map +1 -1
- package/dist/index4.js +5 -1
- package/dist/index4.js.map +1 -1
- package/dist/index6.js +246 -5
- package/dist/index6.js.map +1 -1
- package/dist/index9.js.map +1 -1
- package/dist/providers/agora/AgoraProvider.d.ts +0 -5
- package/dist/providers/agora/AgoraProvider.d.ts.map +1 -1
- package/dist/providers/base/BaseProvider.d.ts +55 -1
- package/dist/providers/base/BaseProvider.d.ts.map +1 -1
- package/dist/providers/livekit/LiveKitProvider.d.ts +0 -5
- package/dist/providers/livekit/LiveKitProvider.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index2.js.map
CHANGED
|
@@ -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/**\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 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 /**\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":";;;;;AAqFO,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkCxB,YACE,UACA,YACA,SACA;AApCM;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA,wCAAe;AAGf;AAAA,2CAA2C;AAG3C;AAAA,4CAAuC;AAGvC;AAAA,6DAAgD,IAAA;AAGhD;AAAA,gDAAmD;AAGnD;AAAA,0CAAiB;AAavB,SAAK,WAAW;AAChB,SAAK,aAAa;AAGlB,SAAI,mCAAS,cAAa,QAAW;AACnC,sBAAgB,EAAE,OAAO,QAAQ,SAAA,CAAU;AAAA,IAC7C;AAGA,UAAM,WAAW,KAAK,sBAAA;AAEtB,SAAK,mBAAmB,IAAI,iBAAiB,UAAU;AAAA,MACrD,oBAAoB,mCAAS;AAAA,MAC7B,kBAAkB,mCAAS;AAAA,MAC3B,iBAAiB,MAAM;AAGrB,aAAK,KAAK,SAAS;AAAA,MACrB;AAAA,IAAA,CACD;AAGD,SAAK,oBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,cAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAQ,QAA4C;AACxD,QAAI,KAAK,cAAc;AACrB,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAGA,SAAK,uBAAuB;AAG5B,UAAM,KAAK,wBAAA;AACX,UAAM,KAAK,SAAS,QAAQ,MAAM;AAClC,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,aAA4B;AAChC,QAAI,CAAC,KAAK,cAAc;AACtB;AAAA,IACF;AAGA,QAAI,KAAK,kBAAkB;AACzB,YAAM,KAAK,eAAA;AAAA,IACb,WAAW,KAAK,iBAAiB;AAE/B,YAAM,KAAK,eAAA;AAAA,IACb;AAEA,UAAM,KAAK,SAAS,WAAA;AACpB,SAAK,iBAAiB,QAAA;AACtB,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0BA,MAAM,aAAa,OAAwC;AACzD,QAAI,CAAC,KAAK,cAAc;AACtB,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,QAAI,MAAM,SAAS,SAAS;AAC1B,YAAM,IAAI,MAAM,qCAAqC;AAAA,IACvD;AAGA,QAAI,KAAK,iBAAiB;AACxB,YAAM,KAAK,eAAA;AAAA,IACb;AAEA,SAAK,kBAAkB;AACvB,UAAM,KAAK,SAAS,kBAAkB,KAAK;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,iBAAgC;AACpC,QAAI,CAAC,KAAK,iBAAiB;AACzB;AAAA,IACF;AAEA,UAAM,KAAK,SAAS,oBAAA;AACpB,SAAK,kBAAkB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,kBAAiC;AACrC,QAAI,CAAC,KAAK,cAAc;AACtB,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,QAAI,KAAK,kBAAkB;AACzB;AAAA,IACF;AAGA,SAAK,mBAAmB,MAAM,UAAU,aAAa,aAAa,EAAE,OAAO,MAAM;AACjF,UAAM,QAAQ,KAAK,iBAAiB,eAAA,EAAiB,CAAC;AAEtD,UAAM,KAAK,aAAa,KAAK;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,iBAAgC;AACpC,QAAI,CAAC,KAAK,kBAAkB;AAC1B;AAAA,IACF;AAEA,UAAM,KAAK,eAAA;AAGX,SAAK,iBAAiB,YAAY,QAAQ,CAAC,UAAU,MAAM,MAAM;AACjE,SAAK,mBAAmB;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,qBAA6B;AAC3B,WAAO,KAAK,SAAS,mBAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBA,kBAA2B;AACzB,WAAO,KAAK,SAAS,gBAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,GAAG,OAAe,SAAyB;AACzC,QAAI,UAAU,WAAW;AAEvB,UAAI,CAAC,KAAK,cAAc,IAAI,KAAK,GAAG;AAClC,aAAK,cAAc,IAAI,OAAO,oBAAI,KAAK;AAAA,MACzC;AACA,WAAK,cAAc,IAAI,KAAK,EAAG,IAAI,OAAO;AAAA,IAC5C,OAAO;AAEL,WAAK,SAAS,GAAG,OAAc,OAAc;AAAA,IAC/C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,OAAe,SAAyB;;AAC1C,QAAI,UAAU,WAAW;AACvB,iBAAK,cAAc,IAAI,KAAK,MAA5B,mBAA+B,OAAO;AAAA,IACxC,OAAO;AACL,WAAK,SAAS,IAAI,OAAc,OAAc;AAAA,IAChD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,KAAK,UAAkB,MAAuB;AACpD,UAAM,WAAW,KAAK,cAAc,IAAI,KAAK;AAC7C,QAAI,UAAU;AACZ,eAAS,QAAQ,CAAC,YAAY;AAC5B,YAAI;AACF,kBAAQ,GAAG,IAAI;AAAA,QACjB,SAAS,GAAG;AACV,iBAAO,MAAM,gBAAgB,YAAY,KAAK,aAAa,CAAC;AAAA,QAC9D;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,YAA2B;AAC/B,QAAI,KAAK,gBAAgB;AACvB,aAAO,KAAK,gBAAgB,2CAA2C;AACvE;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,sBAAsB;AAC9B,YAAM,IAAI,MAAM,iEAAiE;AAAA,IACnF;AAEA,SAAK,iBAAiB;AACtB,WAAO,KAAK,gBAAgB,4BAA4B;AAExD,QAAI;AAEF,UAAI,KAAK,cAAc;AACrB,cAAM,KAAK,WAAA;AAAA,MACb;AAGA,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAG,CAAC;AAGvD,YAAM,KAAK,QAAQ,KAAK,oBAAoB;AAC5C,aAAO,KAAK,gBAAgB,yBAAyB;AAAA,IACvD,SAAS,OAAO;AACd,aAAO,MAAM,gBAAgB,wBAAwB,KAAK;AAC1D,YAAM;AAAA,IACR,UAAA;AACE,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,wBAAwB;AAC9B,UAAM,aAAa,KAAK;AAExB,WAAO;AAAA,MACL,YAAY,OAAiC,WAA2B;AACtE,YAAI,WAAW;AAEb,qBAAW,YAAY,QAAsC,IAAI;AAAA,QACnE,WAAW,OAAO;AAEhB,qBAAW,YAAY,KAAK;AAAA,QAC9B;AAAA,MACF;AAAA,MAEA,MAAM,2BACJ,aACA,YACyB;AAEzB,eAAO,WAAW,mBAAmB;AAAA,UACnC,IAAI;AAAA,UACJ;AAAA,UACA,WAAW;AAAA;AAAA,QAAA,CACZ;AAAA,MACH;AAAA,MAEA,UAAmB;AACjB,eAAO;AAAA,MACT;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,0BAAyC;AACrD,UAAM,YAAqC;AAAA,MACzC,iBAAiB,CAAC,cAAc,aAAa;AAE3C,YAAI,SAAS,SAAS;AACpB,eAAK,iBAAiB,cAAA;AAEtB,eAAK,SAAS,gBAAA;AAAA,QAChB;AAEA,aAAK,iBAAiB;AAAA,UACpB;AAAA,UACA,SAAS;AAAA,UACT,SAAS;AAAA,QAAA;AAAA,MAEb;AAAA,MACA,cAAc,CAAC,cAAc,yBAAyB;AACpD,aAAK,SAAS,iBAAA;AACd,aAAK,iBAAiB,qBAAqB,cAAc,oBAAoB;AAAA,MAC/E;AAAA,MACA,iBAAiB,CAAC,cAAc,yBAAyB;AACvD,aAAK,iBAAiB,uBAAuB,cAAc,oBAAoB;AAAA,MACjF;AAAA,MACA,gBAAgB,MAAM;AAAA,MAEtB;AAAA,MACA,cAAc,MAAM;AAAA,MAEpB;AAAA,MACA,aAAa,MAAM;AACjB,aAAK,iBAAiB,UAAA;AAAA,MACxB;AAAA,MACA,eAAe,CAAC,UAAU;AAExB,YAAI,MAAM,aAAa,KAAK,MAAM,kBAAkB,KAAK,MAAM,gBAAgB,GAAG;AAChF,iBAAO;AAAA,YACL;AAAA,YACA,sBAAsB,MAAM,UAAU,eAAe,MAAM,eAAe,aAAa,MAAM,aAAa,SAAS,MAAM,YAAY;AAAA,UAAA;AAAA,QAEzI;AAAA,MACF;AAAA,IAAA;AAGF,UAAM,KAAK,SAAS,wBAAwB,SAAS;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,sBAA4B;AAClC,SAAK,SAAS,GAAG,aAAa,MAAM;AAClC,WAAK,eAAe;AAAA,IACtB,CAAC;AAED,SAAK,SAAS,GAAG,gBAAgB,MAAM;AACrC,WAAK,eAAe;AAEpB,WAAK,iBAAiB,UAAA;AAAA,IACxB,CAAC;AAAA,EACH;AACF;"}
|
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 "./
|
|
7
|
-
import { getInsertableStreamsMethod } from "./
|
|
6
|
+
import { VP8Extractor } from "./index11.js";
|
|
7
|
+
import { getInsertableStreamsMethod } from "./index12.js";
|
|
8
8
|
import { logger } from "./index7.js";
|
|
9
9
|
const globalLiveKitProviderRegistry = /* @__PURE__ */ new Set();
|
|
10
10
|
let rtcPeerConnectionPatched = false;
|
|
@@ -426,6 +426,7 @@ class LiveKitProvider extends BaseProvider {
|
|
|
426
426
|
}
|
|
427
427
|
return this.connectionState;
|
|
428
428
|
}
|
|
429
|
+
/** @internal */
|
|
429
430
|
async subscribeAnimationTrack(callbacks) {
|
|
430
431
|
var _a;
|
|
431
432
|
this.animationCallbacks = callbacks;
|
|
@@ -454,6 +455,7 @@ class LiveKitProvider extends BaseProvider {
|
|
|
454
455
|
}
|
|
455
456
|
}
|
|
456
457
|
}
|
|
458
|
+
/** @internal */
|
|
457
459
|
async unsubscribeAnimationTrack() {
|
|
458
460
|
this.animationCallbacks = null;
|
|
459
461
|
this.vp8Extractors.forEach((extractor) => {
|
|
@@ -467,9 +469,11 @@ class LiveKitProvider extends BaseProvider {
|
|
|
467
469
|
this.transformedReceivers.clear();
|
|
468
470
|
this.receiverParticipantMap.clear();
|
|
469
471
|
}
|
|
472
|
+
/** @internal */
|
|
470
473
|
async subscribeAudioTrack(callbacks) {
|
|
471
474
|
this.audioCallbacks = callbacks;
|
|
472
475
|
}
|
|
476
|
+
/** @internal */
|
|
473
477
|
async unsubscribeAudioTrack() {
|
|
474
478
|
this.audioCallbacks = null;
|
|
475
479
|
this.audioElements.forEach((el) => {
|
package/dist/index3.js.map
CHANGED
|
@@ -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 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 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 async subscribeAudioTrack(callbacks: AudioTrackCallbacks): Promise<void> {\n this.audioCallbacks = callbacks;\n // Audio tracks are automatically handled in TrackSubscribed event\n }\n\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\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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"}
|
package/dist/index4.js
CHANGED
|
@@ -3,7 +3,7 @@ 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 { isAgoraConfig, ConnectionState } from "./index5.js";
|
|
6
|
-
import { SEIExtractor } from "./
|
|
6
|
+
import { SEIExtractor } from "./index10.js";
|
|
7
7
|
import { logger } from "./index7.js";
|
|
8
8
|
class AgoraProvider extends BaseProvider {
|
|
9
9
|
constructor(_options = {}) {
|
|
@@ -263,6 +263,7 @@ class AgoraProvider extends BaseProvider {
|
|
|
263
263
|
}
|
|
264
264
|
return this.connectionState;
|
|
265
265
|
}
|
|
266
|
+
/** @internal */
|
|
266
267
|
async subscribeAnimationTrack(callbacks) {
|
|
267
268
|
this._animationCallbacks = callbacks;
|
|
268
269
|
this.seiExtractor = new SEIExtractor();
|
|
@@ -283,6 +284,7 @@ class AgoraProvider extends BaseProvider {
|
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
}
|
|
287
|
+
/** @internal */
|
|
286
288
|
async unsubscribeAnimationTrack() {
|
|
287
289
|
this._animationCallbacks = null;
|
|
288
290
|
if (this.seiExtractor) {
|
|
@@ -301,9 +303,11 @@ class AgoraProvider extends BaseProvider {
|
|
|
301
303
|
}
|
|
302
304
|
this.remoteUsers.clear();
|
|
303
305
|
}
|
|
306
|
+
/** @internal */
|
|
304
307
|
async subscribeAudioTrack(callbacks) {
|
|
305
308
|
this.audioCallbacks = callbacks;
|
|
306
309
|
}
|
|
310
|
+
/** @internal */
|
|
307
311
|
async unsubscribeAudioTrack() {
|
|
308
312
|
this.audioCallbacks = null;
|
|
309
313
|
this.remoteAudioTracks.forEach((track) => {
|
package/dist/index4.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index4.js","sources":["../src/providers/agora/AgoraProvider.ts"],"sourcesContent":["/**\n * Agora Provider Implementation.\n *\n * This provider uses Agora's H.264 SEI approach\n * to transport animation data.\n *\n * Key differences from LiveKit:\n * - Uses native SEI events instead of RTCRtpScriptTransform\n * - No ALR (Application-Level Redundancy) needed - Agora handles reliability\n * - Simpler data extraction - uses SEI header parsing\n *\n * @packageDocumentation\n */\n\nimport { BaseProvider } from '../base/BaseProvider';\nimport type { RTCConnectionConfig, AgoraConnectionConfig } from '../../types';\nimport { isAgoraConfig, ConnectionState } from '../../types';\nimport type { AnimationTrackCallbacks, AudioTrackCallbacks } from '../../core/types';\nimport { SEIExtractor } from './SEIExtractor';\nimport { logger } from '../../utils';\nimport type {\n AgoraClient,\n AgoraLocalAudioTrack,\n AgoraRemoteAudioTrack,\n AgoraRemoteVideoTrack,\n AgoraUID,\n} from './types';\n\n/**\n * Agora Provider options.\n * @internal Reserved for future use\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface AgoraProviderOptions {\n // Reserved for future configuration options\n}\n\n/**\n * Remote user info with tracks\n * @internal\n */\ninterface RemoteUserInfo {\n videoTrack?: AgoraRemoteVideoTrack;\n audioTrack?: AgoraRemoteAudioTrack;\n}\n\n/**\n * Agora Provider.\n *\n * Implements RTCProvider interface for Agora platform.\n * Uses native SEI events to receive animation data from H.264 video tracks.\n *\n * @example\n * ```typescript\n * import { AvatarPlayer } from '@spatialwalk/avatarkit-rtc';\n * import { AgoraProvider } from '@spatialwalk/avatarkit-rtc/providers/agora';\n *\n * const provider = new AgoraProvider();\n * const player = new AvatarPlayer(provider, renderer);\n *\n * await player.connect({\n * appId: 'your-agora-app-id',\n * channel: 'your-channel',\n * token: 'your-token',\n * });\n * ```\n */\nexport class AgoraProvider extends BaseProvider {\n /** Provider name identifier */\n readonly name = 'agora';\n\n /** @internal */\n private client: AgoraClient | null = null;\n /** @internal Dynamic SDK - type is any due to dynamic import */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private agoraSDK: any = null;\n\n // Animation track subscription (stored for cleanup, actual callbacks handled by SEIExtractor)\n /** @internal */\n private _animationCallbacks: AnimationTrackCallbacks | null = null;\n /** @internal */\n private seiExtractor: SEIExtractor | null = null;\n /** @internal */\n private remoteUsers: Map<AgoraUID, RemoteUserInfo> = new Map();\n /** @internal */\n private videoContainers: Map<AgoraUID, HTMLDivElement> = new Map();\n\n // Audio track subscription\n /** @internal */\n private audioCallbacks: AudioTrackCallbacks | null = null;\n /** @internal */\n private localAudioTrack: AgoraLocalAudioTrack | null = null;\n /** @internal */\n private remoteAudioTracks: Map<AgoraUID, AgoraRemoteAudioTrack> = new Map();\n\n // Debug mode\n /** @internal */\n private debugLogging = false;\n\n constructor(_options: AgoraProviderOptions = {}) {\n super();\n }\n\n /**\n * Enable or disable debug logging.\n * @param enabled - Whether to enable debug logging\n */\n setDebugLogging(enabled: boolean): void {\n this.debugLogging = enabled;\n if (this.seiExtractor) {\n this.seiExtractor.setDebugLogging(enabled);\n }\n }\n\n /**\n * Load Agora SDK dynamically.\n * @internal\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private async loadSDK(): Promise<any> {\n if (this.agoraSDK) {\n return this.agoraSDK;\n }\n\n try {\n // Direct dynamic import - Vite will handle this properly with optimizeDeps\n // Using 'any' type due to dynamic import type inference limitations\n const sdk = await import('agora-rtc-sdk-ng');\n // Agora SDK exports default as the main AgoraRTC object\n this.agoraSDK = sdk.default ?? sdk;\n return this.agoraSDK;\n } catch (error) {\n logger.error('Agora', 'Failed to load SDK:', error);\n throw new Error(\n '❌ Failed to load agora-rtc-sdk-ng.\\n' +\n 'Please ensure it is installed: pnpm add agora-rtc-sdk-ng'\n );\n }\n }\n\n async connect(config: RTCConnectionConfig): Promise<void> {\n if (!isAgoraConfig(config)) {\n throw new Error('AgoraProvider requires appId and channel in connection config');\n }\n\n const agoraConfig: AgoraConnectionConfig = config;\n\n const AgoraRTC = await this.loadSDK();\n\n // Enable SEI reception (required for animation data)\n AgoraRTC.setParameter('ENABLE_VIDEO_SEI', true);\n\n this.client = AgoraRTC.createClient({ \n mode: 'rtc', \n codec: 'h264' // Required for SEI support\n });\n\n this.setConnectionState('connecting');\n\n // Setup event listeners\n this.setupEventListeners(AgoraRTC);\n\n try {\n await this.client.join(\n agoraConfig.appId,\n agoraConfig.channel,\n agoraConfig.token || null,\n agoraConfig.uid ?? null\n );\n \n this.setConnectionState('connected');\n this.emit('connected');\n } catch (error) {\n this.setConnectionState('failed');\n this.emit('error', error as Error);\n throw error;\n }\n }\n\n /**\n * Setup Agora client event listeners\n * @internal\n */\n private setupEventListeners(_AgoraRTC: typeof import('agora-rtc-sdk-ng')): void {\n // Connection state changed\n this.client.on('connection-state-change', (curState: string, _revState: string) => {\n const mapConnectionState = (state: string): ConnectionState => {\n switch (state) {\n case 'DISCONNECTED':\n case 'DISCONNECTING':\n return ConnectionState.Disconnected;\n case 'CONNECTING':\n return ConnectionState.Connecting;\n case 'CONNECTED':\n return ConnectionState.Connected;\n case 'RECONNECTING':\n return ConnectionState.Reconnecting;\n default:\n return ConnectionState.Failed;\n }\n };\n \n this.setConnectionState(mapConnectionState(curState));\n \n if (curState === 'CONNECTED') {\n this.emit('connected');\n } else if (curState === 'DISCONNECTED') {\n this.cleanup();\n this.emit('disconnected');\n }\n });\n\n // User published (remote user joined and published tracks)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.client.on('user-published', async (user: any, mediaType: 'audio' | 'video') => {\n if (this.debugLogging) {\n logger.info('Agora', `User published: ${user.uid}, mediaType=${mediaType}`);\n }\n\n try {\n // Subscribe to the track\n await this.client.subscribe(user, mediaType);\n\n if (mediaType === 'video') {\n if (user.videoTrack) {\n this.handleVideoTrack(user, user.videoTrack);\n } else {\n logger.warn('Agora', `Video track is null after subscribe for user ${user.uid}`);\n }\n } else if (mediaType === 'audio') {\n if (user.audioTrack) {\n this.handleAudioTrack(user, user.audioTrack);\n } else {\n logger.warn('Agora', `Audio track is null after subscribe for user ${user.uid}`);\n }\n }\n } catch (error) {\n logger.error('Agora', `Failed to subscribe to ${mediaType} from ${user.uid}:`, error);\n this.emit('error', error as Error);\n }\n });\n\n // User unpublished (remote user stopped publishing)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.client.on('user-unpublished', (user: any, mediaType: 'audio' | 'video') => {\n if (this.debugLogging) {\n logger.info('Agora', `User unpublished: ${user.uid}, mediaType=${mediaType}`);\n }\n\n if (mediaType === 'video') {\n this.removeVideoContainer(user.uid);\n this.remoteUsers.delete(user.uid);\n } else if (mediaType === 'audio') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const track = this.remoteAudioTracks.get(user.uid) as any;\n if (track) {\n track.stop();\n this.remoteAudioTracks.delete(user.uid);\n }\n \n if (this.audioCallbacks) {\n this.audioCallbacks.onAudioLost?.(user);\n }\n }\n });\n\n // User joined\n this.client.on('user-joined', (user: any) => {\n logger.info('Agora', `User joined: ${user.uid}`);\n });\n\n // User left\n this.client.on('user-left', (user: any, reason: string) => {\n logger.info('Agora', `User left: ${user.uid}, reason: ${reason}`);\n this.remoteUsers.delete(user.uid);\n const audioTrack = this.remoteAudioTracks.get(user.uid);\n if (audioTrack) {\n audioTrack.stop();\n this.remoteAudioTracks.delete(user.uid);\n }\n });\n\n // Error\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.client.on('exception', (error: any) => {\n logger.error('Agora', 'Exception:', error);\n this.emit('error', new Error(error.msg || String(error)));\n });\n }\n\n /**\n * Handle video track from remote user.\n * Sets up SEI event listeners and plays to hidden container.\n * @internal\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private handleVideoTrack(user: any, track: any): void {\n if (this.debugLogging) {\n logger.info('Agora', `Handling video track from ${user.uid}`);\n }\n\n // Listen for SEI data (animation frames)\n // Support both event names for SDK compatibility\n const seiHandler = (seiData: Uint8Array) => {\n if (this.seiExtractor) {\n this.seiExtractor.handleSEIData(seiData);\n }\n };\n\n // Try primary event name\n track.on('sei-received', seiHandler);\n // Also try alternative event name used in some SDK versions\n track.on('video-sei-received', seiHandler);\n\n // Play video to hidden element to ensure data flows\n // This is required for SEI events to be received\n const container = document.createElement('div');\n container.style.display = 'none';\n container.style.position = 'absolute';\n container.style.left = '-9999px';\n container.id = `agora-video-${user.uid}`;\n document.body.appendChild(container);\n this.videoContainers.set(user.uid, container);\n track.play(container);\n\n if (this.debugLogging) {\n logger.info('Agora', `Video track playing, SEI listeners attached for user ${user.uid}`);\n }\n\n // Store track reference\n const existingUser = this.remoteUsers.get(user.uid);\n if (existingUser) {\n existingUser.videoTrack = track;\n } else {\n this.remoteUsers.set(user.uid, { videoTrack: track });\n }\n }\n\n /**\n * Handle audio track from remote user.\n * @internal\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private handleAudioTrack(user: any, track: any): void {\n if (this.debugLogging) {\n logger.info('Agora', `Handling audio track from ${user.uid}`);\n }\n\n // Play audio\n track.play();\n this.remoteAudioTracks.set(user.uid, track);\n\n if (this.debugLogging) {\n logger.info('Agora', `Audio track playing for user ${user.uid}`);\n }\n\n if (this.audioCallbacks) {\n this.audioCallbacks.onAudioReceived?.(user);\n }\n }\n\n /**\n * Remove video container for a user.\n * @internal\n */\n private removeVideoContainer(uid: number): void {\n const container = this.videoContainers.get(uid);\n if (container) {\n container.remove();\n this.videoContainers.delete(uid);\n }\n }\n\n async disconnect(): Promise<void> {\n this.cleanup();\n if (this.client) {\n await this.client.leave();\n this.client = null;\n }\n this.setConnectionState('disconnected');\n this.emit('disconnected');\n }\n\n getConnectionState(): string {\n if (!this.client) {\n return 'disconnected';\n }\n return this.connectionState;\n }\n\n async subscribeAnimationTrack(callbacks: AnimationTrackCallbacks): Promise<void> {\n this._animationCallbacks = callbacks;\n \n // Create SEI extractor\n this.seiExtractor = new SEIExtractor();\n this.seiExtractor.setDebugLogging(this.debugLogging);\n this.seiExtractor.initialize(callbacks);\n \n // If already connected, check for existing remote video tracks\n if (this.client) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const remoteUsers = (this.client as any).remoteUsers;\n for (const user of remoteUsers) {\n if (user.videoTrack) {\n // Subscribe to SEI events with both event names for compatibility\n const seiHandler = (seiData: Uint8Array) => {\n if (this.seiExtractor) {\n this.seiExtractor.handleSEIData(seiData);\n }\n };\n user.videoTrack.on('sei-received', seiHandler);\n user.videoTrack.on('video-sei-received', seiHandler);\n }\n }\n }\n }\n\n async unsubscribeAnimationTrack(): Promise<void> {\n this._animationCallbacks = null;\n if (this.seiExtractor) {\n this.seiExtractor.dispose();\n this.seiExtractor = null;\n }\n \n // Remove SEI listeners from remote tracks\n for (const [uid, user] of this.remoteUsers) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const videoTrack = user.videoTrack as any;\n if (videoTrack) {\n videoTrack.off('sei-received');\n videoTrack.off('video-sei-received');\n }\n }\n \n // Remove video containers\n for (const [uid] of this.videoContainers) {\n this.removeVideoContainer(uid as number);\n }\n \n this.remoteUsers.clear();\n }\n\n async subscribeAudioTrack(callbacks: AudioTrackCallbacks): Promise<void> {\n this.audioCallbacks = callbacks;\n // Audio tracks are automatically handled in user-published event\n }\n\n async unsubscribeAudioTrack(): Promise<void> {\n this.audioCallbacks = null;\n this.remoteAudioTracks.forEach((track) => {\n track.stop();\n });\n this.remoteAudioTracks.clear();\n }\n\n async publishAudioTrack(track?: MediaStreamTrack): Promise<void> {\n if (!this.client) {\n throw new Error('Not connected to channel');\n }\n\n const AgoraRTC = await this.loadSDK();\n\n if (track) {\n // Create local audio track from provided MediaStreamTrack\n this.localAudioTrack = AgoraRTC.createCustomAudioTrack({\n mediaStreamTrack: track,\n });\n } else {\n // Create local audio track from microphone\n this.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack({\n encoderConfig: 'music_standard',\n AEC: true,\n ANS: true,\n AGC: true,\n });\n }\n\n // Publish the track\n await this.client.publish(this.localAudioTrack);\n }\n\n async unpublishAudioTrack(): Promise<void> {\n if (!this.client || !this.localAudioTrack) {\n return;\n }\n\n await this.client.unpublish(this.localAudioTrack);\n this.localAudioTrack.close();\n this.localAudioTrack = null;\n }\n\n /**\n * Play remote audio (resume playback)\n */\n playRemoteAudio(): void {\n this.remoteAudioTracks.forEach((track) => {\n track.play();\n });\n }\n\n /**\n * Pause remote audio\n */\n pauseRemoteAudio(): void {\n this.remoteAudioTracks.forEach((track) => {\n track.stop();\n });\n }\n\n /**\n * Get the native Agora RTC Client instance.\n * \n * Allows advanced users to access Agora-specific features\n * not exposed through the unified API.\n * \n * @returns The Agora IAgoraRTCClient instance, or null if not connected\n * \n * @example\n * ```typescript\n * const client = provider.getNativeClient();\n * if (client) {\n * // Access Agora-specific features\n * console.log('Connection state:', client.connectionState);\n * }\n * ```\n */\n getNativeClient(): AgoraClient | null {\n return this.client;\n }\n\n /**\n * Cleanup resources\n * @internal\n */\n private cleanup(): void {\n // Cleanup remote audio tracks\n this.remoteAudioTracks.forEach((track) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (track as any).stop();\n });\n this.remoteAudioTracks.clear();\n \n // Cleanup remote video tracks and SEI listeners\n for (const [uid, user] of this.remoteUsers) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const videoTrack = user.videoTrack as any;\n if (videoTrack) {\n videoTrack.off('sei-received');\n videoTrack.off('video-sei-received');\n }\n }\n this.remoteUsers.clear();\n \n // Cleanup video containers\n for (const [uid] of this.videoContainers) {\n this.removeVideoContainer(uid as number);\n }\n \n // Cleanup SEI extractor\n if (this.seiExtractor) {\n this.seiExtractor.dispose();\n this.seiExtractor = null;\n }\n \n // Cleanup local audio track\n if (this.localAudioTrack) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this.localAudioTrack as any).close();\n this.localAudioTrack = null;\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index4.js","sources":["../src/providers/agora/AgoraProvider.ts"],"sourcesContent":["/**\n * Agora Provider Implementation.\n *\n * This provider uses Agora's H.264 SEI approach\n * to transport animation data.\n *\n * Key differences from LiveKit:\n * - Uses native SEI events instead of RTCRtpScriptTransform\n * - No ALR (Application-Level Redundancy) needed - Agora handles reliability\n * - Simpler data extraction - uses SEI header parsing\n *\n * @packageDocumentation\n */\n\nimport { BaseProvider } from '../base/BaseProvider';\nimport type { RTCConnectionConfig, AgoraConnectionConfig } from '../../types';\nimport { isAgoraConfig, ConnectionState } from '../../types';\nimport type { AnimationTrackCallbacks, AudioTrackCallbacks } from '../../core/types';\nimport { SEIExtractor } from './SEIExtractor';\nimport { logger } from '../../utils';\nimport type {\n AgoraClient,\n AgoraLocalAudioTrack,\n AgoraRemoteAudioTrack,\n AgoraRemoteVideoTrack,\n AgoraUID,\n} from './types';\n\n/**\n * Agora Provider options.\n * @internal Reserved for future use\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface AgoraProviderOptions {\n // Reserved for future configuration options\n}\n\n/**\n * Remote user info with tracks\n * @internal\n */\ninterface RemoteUserInfo {\n videoTrack?: AgoraRemoteVideoTrack;\n audioTrack?: AgoraRemoteAudioTrack;\n}\n\n/**\n * Agora Provider.\n *\n * Implements RTCProvider interface for Agora platform.\n * Uses native SEI events to receive animation data from H.264 video tracks.\n *\n * @example\n * ```typescript\n * import { AvatarPlayer } from '@spatialwalk/avatarkit-rtc';\n * import { AgoraProvider } from '@spatialwalk/avatarkit-rtc/providers/agora';\n *\n * const provider = new AgoraProvider();\n * const player = new AvatarPlayer(provider, renderer);\n *\n * await player.connect({\n * appId: 'your-agora-app-id',\n * channel: 'your-channel',\n * token: 'your-token',\n * });\n * ```\n */\nexport class AgoraProvider extends BaseProvider {\n /** Provider name identifier */\n readonly name = 'agora';\n\n /** @internal */\n private client: AgoraClient | null = null;\n /** @internal Dynamic SDK - type is any due to dynamic import */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private agoraSDK: any = null;\n\n // Animation track subscription (stored for cleanup, actual callbacks handled by SEIExtractor)\n /** @internal */\n private _animationCallbacks: AnimationTrackCallbacks | null = null;\n /** @internal */\n private seiExtractor: SEIExtractor | null = null;\n /** @internal */\n private remoteUsers: Map<AgoraUID, RemoteUserInfo> = new Map();\n /** @internal */\n private videoContainers: Map<AgoraUID, HTMLDivElement> = new Map();\n\n // Audio track subscription\n /** @internal */\n private audioCallbacks: AudioTrackCallbacks | null = null;\n /** @internal */\n private localAudioTrack: AgoraLocalAudioTrack | null = null;\n /** @internal */\n private remoteAudioTracks: Map<AgoraUID, AgoraRemoteAudioTrack> = new Map();\n\n // Debug mode\n /** @internal */\n private debugLogging = false;\n\n constructor(_options: AgoraProviderOptions = {}) {\n super();\n }\n\n /**\n * Enable or disable debug logging.\n * @param enabled - Whether to enable debug logging\n */\n setDebugLogging(enabled: boolean): void {\n this.debugLogging = enabled;\n if (this.seiExtractor) {\n this.seiExtractor.setDebugLogging(enabled);\n }\n }\n\n /**\n * Load Agora SDK dynamically.\n * @internal\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private async loadSDK(): Promise<any> {\n if (this.agoraSDK) {\n return this.agoraSDK;\n }\n\n try {\n // Direct dynamic import - Vite will handle this properly with optimizeDeps\n // Using 'any' type due to dynamic import type inference limitations\n const sdk = await import('agora-rtc-sdk-ng');\n // Agora SDK exports default as the main AgoraRTC object\n this.agoraSDK = sdk.default ?? sdk;\n return this.agoraSDK;\n } catch (error) {\n logger.error('Agora', 'Failed to load SDK:', error);\n throw new Error(\n '❌ Failed to load agora-rtc-sdk-ng.\\n' +\n 'Please ensure it is installed: pnpm add agora-rtc-sdk-ng'\n );\n }\n }\n\n async connect(config: RTCConnectionConfig): Promise<void> {\n if (!isAgoraConfig(config)) {\n throw new Error('AgoraProvider requires appId and channel in connection config');\n }\n\n const agoraConfig: AgoraConnectionConfig = config;\n\n const AgoraRTC = await this.loadSDK();\n\n // Enable SEI reception (required for animation data)\n AgoraRTC.setParameter('ENABLE_VIDEO_SEI', true);\n\n this.client = AgoraRTC.createClient({ \n mode: 'rtc', \n codec: 'h264' // Required for SEI support\n });\n\n this.setConnectionState('connecting');\n\n // Setup event listeners\n this.setupEventListeners(AgoraRTC);\n\n try {\n await this.client.join(\n agoraConfig.appId,\n agoraConfig.channel,\n agoraConfig.token || null,\n agoraConfig.uid ?? null\n );\n \n this.setConnectionState('connected');\n this.emit('connected');\n } catch (error) {\n this.setConnectionState('failed');\n this.emit('error', error as Error);\n throw error;\n }\n }\n\n /**\n * Setup Agora client event listeners\n * @internal\n */\n private setupEventListeners(_AgoraRTC: typeof import('agora-rtc-sdk-ng')): void {\n // Connection state changed\n this.client.on('connection-state-change', (curState: string, _revState: string) => {\n const mapConnectionState = (state: string): ConnectionState => {\n switch (state) {\n case 'DISCONNECTED':\n case 'DISCONNECTING':\n return ConnectionState.Disconnected;\n case 'CONNECTING':\n return ConnectionState.Connecting;\n case 'CONNECTED':\n return ConnectionState.Connected;\n case 'RECONNECTING':\n return ConnectionState.Reconnecting;\n default:\n return ConnectionState.Failed;\n }\n };\n \n this.setConnectionState(mapConnectionState(curState));\n \n if (curState === 'CONNECTED') {\n this.emit('connected');\n } else if (curState === 'DISCONNECTED') {\n this.cleanup();\n this.emit('disconnected');\n }\n });\n\n // User published (remote user joined and published tracks)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.client.on('user-published', async (user: any, mediaType: 'audio' | 'video') => {\n if (this.debugLogging) {\n logger.info('Agora', `User published: ${user.uid}, mediaType=${mediaType}`);\n }\n\n try {\n // Subscribe to the track\n await this.client.subscribe(user, mediaType);\n\n if (mediaType === 'video') {\n if (user.videoTrack) {\n this.handleVideoTrack(user, user.videoTrack);\n } else {\n logger.warn('Agora', `Video track is null after subscribe for user ${user.uid}`);\n }\n } else if (mediaType === 'audio') {\n if (user.audioTrack) {\n this.handleAudioTrack(user, user.audioTrack);\n } else {\n logger.warn('Agora', `Audio track is null after subscribe for user ${user.uid}`);\n }\n }\n } catch (error) {\n logger.error('Agora', `Failed to subscribe to ${mediaType} from ${user.uid}:`, error);\n this.emit('error', error as Error);\n }\n });\n\n // User unpublished (remote user stopped publishing)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.client.on('user-unpublished', (user: any, mediaType: 'audio' | 'video') => {\n if (this.debugLogging) {\n logger.info('Agora', `User unpublished: ${user.uid}, mediaType=${mediaType}`);\n }\n\n if (mediaType === 'video') {\n this.removeVideoContainer(user.uid);\n this.remoteUsers.delete(user.uid);\n } else if (mediaType === 'audio') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const track = this.remoteAudioTracks.get(user.uid) as any;\n if (track) {\n track.stop();\n this.remoteAudioTracks.delete(user.uid);\n }\n \n if (this.audioCallbacks) {\n this.audioCallbacks.onAudioLost?.(user);\n }\n }\n });\n\n // User joined\n this.client.on('user-joined', (user: any) => {\n logger.info('Agora', `User joined: ${user.uid}`);\n });\n\n // User left\n this.client.on('user-left', (user: any, reason: string) => {\n logger.info('Agora', `User left: ${user.uid}, reason: ${reason}`);\n this.remoteUsers.delete(user.uid);\n const audioTrack = this.remoteAudioTracks.get(user.uid);\n if (audioTrack) {\n audioTrack.stop();\n this.remoteAudioTracks.delete(user.uid);\n }\n });\n\n // Error\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.client.on('exception', (error: any) => {\n logger.error('Agora', 'Exception:', error);\n this.emit('error', new Error(error.msg || String(error)));\n });\n }\n\n /**\n * Handle video track from remote user.\n * Sets up SEI event listeners and plays to hidden container.\n * @internal\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private handleVideoTrack(user: any, track: any): void {\n if (this.debugLogging) {\n logger.info('Agora', `Handling video track from ${user.uid}`);\n }\n\n // Listen for SEI data (animation frames)\n // Support both event names for SDK compatibility\n const seiHandler = (seiData: Uint8Array) => {\n if (this.seiExtractor) {\n this.seiExtractor.handleSEIData(seiData);\n }\n };\n\n // Try primary event name\n track.on('sei-received', seiHandler);\n // Also try alternative event name used in some SDK versions\n track.on('video-sei-received', seiHandler);\n\n // Play video to hidden element to ensure data flows\n // This is required for SEI events to be received\n const container = document.createElement('div');\n container.style.display = 'none';\n container.style.position = 'absolute';\n container.style.left = '-9999px';\n container.id = `agora-video-${user.uid}`;\n document.body.appendChild(container);\n this.videoContainers.set(user.uid, container);\n track.play(container);\n\n if (this.debugLogging) {\n logger.info('Agora', `Video track playing, SEI listeners attached for user ${user.uid}`);\n }\n\n // Store track reference\n const existingUser = this.remoteUsers.get(user.uid);\n if (existingUser) {\n existingUser.videoTrack = track;\n } else {\n this.remoteUsers.set(user.uid, { videoTrack: track });\n }\n }\n\n /**\n * Handle audio track from remote user.\n * @internal\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private handleAudioTrack(user: any, track: any): void {\n if (this.debugLogging) {\n logger.info('Agora', `Handling audio track from ${user.uid}`);\n }\n\n // Play audio\n track.play();\n this.remoteAudioTracks.set(user.uid, track);\n\n if (this.debugLogging) {\n logger.info('Agora', `Audio track playing for user ${user.uid}`);\n }\n\n if (this.audioCallbacks) {\n this.audioCallbacks.onAudioReceived?.(user);\n }\n }\n\n /**\n * Remove video container for a user.\n * @internal\n */\n private removeVideoContainer(uid: number): void {\n const container = this.videoContainers.get(uid);\n if (container) {\n container.remove();\n this.videoContainers.delete(uid);\n }\n }\n\n async disconnect(): Promise<void> {\n this.cleanup();\n if (this.client) {\n await this.client.leave();\n this.client = null;\n }\n this.setConnectionState('disconnected');\n this.emit('disconnected');\n }\n\n getConnectionState(): string {\n if (!this.client) {\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 // Create SEI extractor\n this.seiExtractor = new SEIExtractor();\n this.seiExtractor.setDebugLogging(this.debugLogging);\n this.seiExtractor.initialize(callbacks);\n \n // If already connected, check for existing remote video tracks\n if (this.client) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const remoteUsers = (this.client as any).remoteUsers;\n for (const user of remoteUsers) {\n if (user.videoTrack) {\n // Subscribe to SEI events with both event names for compatibility\n const seiHandler = (seiData: Uint8Array) => {\n if (this.seiExtractor) {\n this.seiExtractor.handleSEIData(seiData);\n }\n };\n user.videoTrack.on('sei-received', seiHandler);\n user.videoTrack.on('video-sei-received', seiHandler);\n }\n }\n }\n }\n\n /** @internal */\n async unsubscribeAnimationTrack(): Promise<void> {\n this._animationCallbacks = null;\n if (this.seiExtractor) {\n this.seiExtractor.dispose();\n this.seiExtractor = null;\n }\n \n // Remove SEI listeners from remote tracks\n for (const [uid, user] of this.remoteUsers) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const videoTrack = user.videoTrack as any;\n if (videoTrack) {\n videoTrack.off('sei-received');\n videoTrack.off('video-sei-received');\n }\n }\n \n // Remove video containers\n for (const [uid] of this.videoContainers) {\n this.removeVideoContainer(uid as number);\n }\n \n this.remoteUsers.clear();\n }\n\n /** @internal */\n async subscribeAudioTrack(callbacks: AudioTrackCallbacks): Promise<void> {\n this.audioCallbacks = callbacks;\n // Audio tracks are automatically handled in user-published event\n }\n\n /** @internal */\n async unsubscribeAudioTrack(): Promise<void> {\n this.audioCallbacks = null;\n this.remoteAudioTracks.forEach((track) => {\n track.stop();\n });\n this.remoteAudioTracks.clear();\n }\n\n async publishAudioTrack(track?: MediaStreamTrack): Promise<void> {\n if (!this.client) {\n throw new Error('Not connected to channel');\n }\n\n const AgoraRTC = await this.loadSDK();\n\n if (track) {\n // Create local audio track from provided MediaStreamTrack\n this.localAudioTrack = AgoraRTC.createCustomAudioTrack({\n mediaStreamTrack: track,\n });\n } else {\n // Create local audio track from microphone\n this.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack({\n encoderConfig: 'music_standard',\n AEC: true,\n ANS: true,\n AGC: true,\n });\n }\n\n // Publish the track\n await this.client.publish(this.localAudioTrack);\n }\n\n async unpublishAudioTrack(): Promise<void> {\n if (!this.client || !this.localAudioTrack) {\n return;\n }\n\n await this.client.unpublish(this.localAudioTrack);\n this.localAudioTrack.close();\n this.localAudioTrack = null;\n }\n\n /**\n * Play remote audio (resume playback)\n */\n playRemoteAudio(): void {\n this.remoteAudioTracks.forEach((track) => {\n track.play();\n });\n }\n\n /**\n * Pause remote audio\n */\n pauseRemoteAudio(): void {\n this.remoteAudioTracks.forEach((track) => {\n track.stop();\n });\n }\n\n /**\n * Get the native Agora RTC Client instance.\n * \n * Allows advanced users to access Agora-specific features\n * not exposed through the unified API.\n * \n * @returns The Agora IAgoraRTCClient instance, or null if not connected\n * \n * @example\n * ```typescript\n * const client = provider.getNativeClient();\n * if (client) {\n * // Access Agora-specific features\n * console.log('Connection state:', client.connectionState);\n * }\n * ```\n */\n getNativeClient(): AgoraClient | null {\n return this.client;\n }\n\n /**\n * Cleanup resources\n * @internal\n */\n private cleanup(): void {\n // Cleanup remote audio tracks\n this.remoteAudioTracks.forEach((track) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (track as any).stop();\n });\n this.remoteAudioTracks.clear();\n \n // Cleanup remote video tracks and SEI listeners\n for (const [uid, user] of this.remoteUsers) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const videoTrack = user.videoTrack as any;\n if (videoTrack) {\n videoTrack.off('sei-received');\n videoTrack.off('video-sei-received');\n }\n }\n this.remoteUsers.clear();\n \n // Cleanup video containers\n for (const [uid] of this.videoContainers) {\n this.removeVideoContainer(uid as number);\n }\n \n // Cleanup SEI extractor\n if (this.seiExtractor) {\n this.seiExtractor.dispose();\n this.seiExtractor = null;\n }\n \n // Cleanup local audio track\n if (this.localAudioTrack) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this.localAudioTrack as any).close();\n this.localAudioTrack = null;\n }\n }\n}\n"],"names":[],"mappings":"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"}
|