@spatialwalk/avatarkit-rtc 1.0.0-beta.1 → 1.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +2 -410
  2. package/dist/assets/animation-worker-DOGeTjF0.js.map +1 -0
  3. package/dist/core/AvatarPlayer.d.ts +96 -12
  4. package/dist/core/AvatarPlayer.d.ts.map +1 -1
  5. package/dist/core/RTCProvider.d.ts +12 -16
  6. package/dist/core/RTCProvider.d.ts.map +1 -1
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +3 -2
  10. package/dist/index10.js +91 -39
  11. package/dist/index10.js.map +1 -1
  12. package/dist/index11.js +14 -386
  13. package/dist/index11.js.map +1 -1
  14. package/dist/index12.js +350 -64
  15. package/dist/index12.js.map +1 -1
  16. package/dist/index13.js +44 -14
  17. package/dist/index13.js.map +1 -1
  18. package/dist/index14.js +25 -44
  19. package/dist/index14.js.map +1 -1
  20. package/dist/index2.js +335 -46
  21. package/dist/index2.js.map +1 -1
  22. package/dist/index3.js +265 -54
  23. package/dist/index3.js.map +1 -1
  24. package/dist/index4.js +105 -86
  25. package/dist/index4.js.map +1 -1
  26. package/dist/index5.js +6 -2
  27. package/dist/index5.js.map +1 -1
  28. package/dist/index6.js +603 -39
  29. package/dist/index6.js.map +1 -1
  30. package/dist/index8.js +128 -167
  31. package/dist/index8.js.map +1 -1
  32. package/dist/index9.js +65 -164
  33. package/dist/index9.js.map +1 -1
  34. package/dist/providers/agora/AgoraProvider.d.ts +0 -13
  35. package/dist/providers/agora/AgoraProvider.d.ts.map +1 -1
  36. package/dist/providers/agora/index.d.ts +1 -5
  37. package/dist/providers/agora/index.d.ts.map +1 -1
  38. package/dist/providers/agora/types.d.ts.map +1 -1
  39. package/dist/providers/base/BaseProvider.d.ts +50 -8
  40. package/dist/providers/base/BaseProvider.d.ts.map +1 -1
  41. package/dist/providers/livekit/LiveKitProvider.d.ts +4 -15
  42. package/dist/providers/livekit/LiveKitProvider.d.ts.map +1 -1
  43. package/dist/providers/livekit/animation-worker.d.ts.map +1 -1
  44. package/dist/providers/livekit/index.d.ts +1 -5
  45. package/dist/providers/livekit/index.d.ts.map +1 -1
  46. package/dist/types/index.d.ts +21 -0
  47. package/dist/types/index.d.ts.map +1 -1
  48. package/dist/utils/index.d.ts +2 -0
  49. package/dist/utils/index.d.ts.map +1 -1
  50. package/dist/utils/telemetry.d.ts +22 -0
  51. package/dist/utils/telemetry.d.ts.map +1 -0
  52. package/package.json +21 -12
  53. package/dist/assets/animation-worker-CUXZycUw.js.map +0 -1
  54. package/dist/index15.js +0 -29
  55. package/dist/index15.js.map +0 -1
  56. package/dist/index16.js +0 -144
  57. package/dist/index16.js.map +0 -1
  58. package/dist/index17.js +0 -106
  59. package/dist/index17.js.map +0 -1
  60. package/dist/index18.js +0 -28
  61. package/dist/index18.js.map +0 -1
  62. package/dist/proto/animation.d.ts +0 -12
  63. package/dist/proto/animation.d.ts.map +0 -1
@@ -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, 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 and start\n * await player.connect({ url: 'wss://...', token: '...' });\n * await player.startPublishing(); // Start microphone\n *\n * // Disconnect when done\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 mediaStream: MediaStream | null = null;\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\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 // 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 publishing if active\n if (this.localAudioTrack) {\n await this.stopPublishing();\n }\n\n await this.provider.disconnect();\n this.animationHandler.dispose();\n this._isConnected = false;\n }\n\n /**\n * Start publishing microphone audio to the RTC server.\n * Requests microphone permission and starts publishing.\n * @throws Error if permission denied or no microphone found\n */\n async startPublishing(): Promise<void> {\n if (this.localAudioTrack) {\n return; // Already publishing\n }\n\n // Request microphone permission\n this.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n this.localAudioTrack = this.mediaStream.getAudioTracks()[0];\n\n await this.provider.publishAudioTrack(this.localAudioTrack);\n }\n\n /**\n * Stop publishing microphone audio.\n */\n async stopPublishing(): Promise<void> {\n if (!this.localAudioTrack) {\n return;\n }\n\n await this.provider.unpublishAudioTrack();\n\n // Stop and cleanup tracks\n this.localAudioTrack.stop();\n this.localAudioTrack = null;\n\n if (this.mediaStream) {\n this.mediaStream.getTracks().forEach((track) => track.stop());\n this.mediaStream = null;\n }\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', etc.)\n * @param handler - Event handler function\n */\n on<K extends keyof RTCProviderEvents>(\n event: K,\n handler: RTCProviderEvents[K]\n ): void {\n this.provider.on(event, handler);\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<K extends keyof RTCProviderEvents>(\n event: K,\n handler: RTCProviderEvents[K]\n ): void {\n this.provider.off(event, handler);\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: () => {\n // Stats can be forwarded to application layer if needed\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('disconnected', () => {\n this._isConnected = false;\n // Return to idle animation when disconnected\n this.animationHandler.startIdle();\n });\n }\n}\n"],"names":[],"mappings":";;;;;AA6DO,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBxB,YACE,UACA,YACA,SACA;AA3BM;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA,wCAAe;AAGf;AAAA,2CAA2C;AAG3C;AAAA,uCAAkC;AAaxC,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,QAAQ;AAGrD,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,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,iBAAiB;AACxB,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,EAOA,MAAM,kBAAiC;AACrC,QAAI,KAAK,iBAAiB;AACxB;AAAA,IACF;AAGA,SAAK,cAAc,MAAM,UAAU,aAAa,aAAa,EAAE,OAAO,MAAM;AAC5E,SAAK,kBAAkB,KAAK,YAAY,eAAA,EAAiB,CAAC;AAE1D,UAAM,KAAK,SAAS,kBAAkB,KAAK,eAAe;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAgC;AACpC,QAAI,CAAC,KAAK,iBAAiB;AACzB;AAAA,IACF;AAEA,UAAM,KAAK,SAAS,oBAAA;AAGpB,SAAK,gBAAgB,KAAA;AACrB,SAAK,kBAAkB;AAEvB,QAAI,KAAK,aAAa;AACpB,WAAK,YAAY,YAAY,QAAQ,CAAC,UAAU,MAAM,MAAM;AAC5D,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;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,GACE,OACA,SACM;AACN,SAAK,SAAS,GAAG,OAAO,OAAO;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IACE,OACA,SACM;AACN,SAAK,SAAS,IAAI,OAAO,OAAO;AAAA,EAClC;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,MAAM;AAAA,MAErB;AAAA,IAAA;AAGF,UAAM,KAAK,SAAS,wBAAwB,SAAS;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,sBAA4B;AAClC,SAAK,SAAS,GAAG,gBAAgB,MAAM;AACrC,WAAK,eAAe;AAEpB,WAAK,iBAAiB,UAAA;AAAA,IACxB,CAAC;AAAA,EACH;AACF;"}
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, RTCPrepareConnectionConfig } from '../types';\nimport { AnimationHandler } from './AnimationHandler';\nimport type { AnimationTrackCallbacks } from './types';\nimport { configureLogger, logger, type LogLevel, initializeTelemetry, trackEvent, flushTelemetry } from '../utils';\n\n// Import AvatarView type from avatarkit\nimport type { AvatarView } 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<(...args: unknown[]) => void>> =\n new Map();\n\n /** @internal */\n private lastConnectionConfig: RTCConnectionConfig | null = null;\n\n /** @internal */\n private isReconnecting = false;\n\n /** @internal */\n private hasActiveAnimationSession = false;\n\n /** @internal */\n private isTrackingPrimed = false;\n\n /** @internal */\n private connectStartTime = 0;\n\n /** @internal */\n private sessionStartTime = 0;\n\n /** @internal */\n private stallCount = 0;\n\n /** @internal */\n private reconnectCount = 0;\n\n /** @internal */\n private conversationCount = 0;\n\n /** @internal */\n private providerName = '';\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 this.providerName = provider.name;\n\n // Configure logging if specified\n if (options?.logLevel !== undefined) {\n configureLogger({ level: options.logLevel });\n }\n\n // Initialize telemetry (piggybacks on avatarkit's PostHog instance)\n initializeTelemetry({ sdkVersion: __RTC_SDK_VERSION__ });\n logger.info('AvatarPlayer', `SDK version: ${__RTC_SDK_VERSION__}`);\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 providerName: this.providerName,\n onStreamStalled: () => {\n // AnimationHandler already switches to idle state\n // Just notify external listeners, let them decide what to do (e.g., call reconnect())\n this.hasActiveAnimationSession = false;\n this.isTrackingPrimed = false;\n this.stallCount++;\n trackEvent('rtc_stream_stalled', 'warning', {\n provider: this.providerName,\n session_elapsed: this.sessionStartTime ? Date.now() - this.sessionStartTime : 0,\n });\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 this.connectStartTime = performance.now();\n\n trackEvent('rtc_connect_start', 'info', {\n provider: this.providerName,\n });\n\n try {\n // Setup animation callbacks before connecting\n await this.setupAnimationCallbacks();\n await this.provider.connect(config);\n this._isConnected = true;\n this.sessionStartTime = Date.now();\n this.stallCount = 0;\n this.reconnectCount = 0;\n this.conversationCount = 0;\n\n trackEvent('rtc_connect_success', 'info', {\n provider: this.providerName,\n duration: Math.round(performance.now() - this.connectStartTime),\n });\n } catch (error) {\n trackEvent('rtc_connect_failed', 'error', {\n provider: this.providerName,\n reason: error instanceof Error ? error.message : String(error),\n duration: Math.round(performance.now() - this.connectStartTime),\n });\n throw error;\n }\n }\n\n /**\n * Pre-warm a future RTC connection when the provider supports it.\n * This is best-effort and does not change the connected state.\n */\n async prepareConnection(config: RTCPrepareConnectionConfig): Promise<void> {\n await this.provider.prepareConnection?.(config);\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 // Collect session summary before teardown\n const sessionSummary = this.animationHandler.getSessionSummary();\n\n await this.provider.disconnect();\n this.animationHandler.dispose();\n this._isConnected = false;\n\n const sessionDuration = this.sessionStartTime ? Date.now() - this.sessionStartTime : 0;\n\n trackEvent('rtc_session_summary', 'info', {\n provider: this.providerName,\n total_duration_ms: sessionDuration,\n total_frames: sessionSummary.totalFrames,\n total_lost: sessionSummary.totalLost,\n total_recovered: sessionSummary.totalRecovered,\n total_dropped: sessionSummary.totalDropped,\n avg_fps: sessionSummary.avgFps,\n stall_count: this.stallCount,\n reconnect_count: this.reconnectCount,\n conversation_count: this.conversationCount,\n });\n\n trackEvent('rtc_disconnected', 'info', {\n provider: this.providerName,\n session_duration: sessionDuration,\n });\n\n flushTelemetry();\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 try {\n await this.provider.publishAudioTrack(track);\n } catch (error) {\n this.localAudioTrack = null;\n trackEvent('rtc_audio_publish_failed', 'error', {\n provider: this.providerName,\n description: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\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({\n audio: true,\n });\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: (...args: unknown[]) => void): 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, handler);\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: (...args: unknown[]) => void): void {\n if (event === 'stalled') {\n this.eventHandlers.get(event)?.delete(handler);\n } else {\n this.provider.off(event, handler);\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(\n 'Cannot reconnect: no previous connection. Call connect() first.',\n );\n }\n\n this.isReconnecting = true;\n this.reconnectCount++;\n logger.info('AvatarPlayer', 'Attempting reconnection...');\n\n const reconnectStart = performance.now();\n trackEvent('rtc_reconnect_start', 'info', { provider: this.providerName });\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\n trackEvent('rtc_reconnect_success', 'info', {\n provider: this.providerName,\n duration: Math.round(performance.now() - reconnectStart),\n });\n } catch (error) {\n logger.error('AvatarPlayer', 'Reconnection failed:', error);\n trackEvent('rtc_reconnect_failed', 'error', {\n provider: this.providerName,\n reason: error instanceof Error ? error.message : String(error),\n });\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 renderFromProtobuf(protobufData: ArrayBuffer): void {\n avatarView.renderFromProtobuf(protobufData);\n },\n\n renderFrame(frame: unknown, startIdle?: boolean): void {\n const view = avatarView as any;\n if (startIdle) {\n view.renderFlame(undefined, true);\n } else if (frame) {\n view.renderFlame(frame);\n }\n },\n\n async generateTransitionFromProtobuf(\n protobufData: ArrayBuffer,\n frameCount: number,\n ): Promise<unknown[]> {\n return avatarView.generateTransitionFromProtobuf(protobufData, frameCount);\n },\n\n isReady(): boolean {\n return true;\n },\n };\n }\n\n /**\n * Setup animation track callbacks.\n * @internal\n */\n private async setupAnimationCallbacks(): Promise<void> {\n const callbacks: AnimationTrackCallbacks = {\n onAnimationData: (protobufData, metadata) => {\n // Reset tracking only once per session to avoid duplicate start-packet resets.\n if (!this.hasActiveAnimationSession) {\n if (!this.isTrackingPrimed) {\n this.animationHandler.resetTracking();\n }\n\n this.hasActiveAnimationSession = true;\n this.isTrackingPrimed = false;\n\n if (!metadata.isStart) {\n logger.info(\n 'AvatarPlayer',\n `Session start inferred from frame seq=${metadata.frameSeq ?? 'n/a'}`,\n );\n }\n } else if (metadata.isStart) {\n logger.info(\n 'AvatarPlayer',\n `Ignoring duplicate session start frame seq=${metadata.frameSeq ?? 'n/a'}`,\n );\n }\n\n this.animationHandler.handleAnimationData(\n protobufData,\n metadata.frameSeq,\n metadata.isRecovered,\n );\n },\n onTransition: (protobufData, transitionFrameCount) => {\n this.conversationCount++;\n this.animationHandler.handleTransitionData(\n protobufData,\n transitionFrameCount,\n );\n },\n onTransitionEnd: (protobufData, transitionFrameCount) => {\n this.animationHandler.handleTransitionToIdle(\n protobufData,\n transitionFrameCount,\n );\n },\n onSessionStart: () => {\n // Session started - can be used for UI feedback\n },\n onSessionEnd: () => {\n // Session ended - can be used for UI feedback\n },\n onIdleStart: () => {\n // Idle packets can race with start-transition packets; avoid rendering idle mid-transition.\n if (this.animationHandler.isInTransition()) {\n // Keep session bookkeeping in sync even if visual idle is deferred.\n this.hasActiveAnimationSession = false;\n this.isTrackingPrimed = false;\n logger.info(\n 'AvatarPlayer',\n 'Deferring idleStart while transition is active',\n );\n return;\n }\n\n this.hasActiveAnimationSession = false;\n this.animationHandler.resetTracking();\n this.isTrackingPrimed = true;\n this.animationHandler.startIdle();\n },\n onStreamStats: (stats) => {\n // Log packet loss/recovery stats\n if (\n stats.framesLost > 0 ||\n stats.framesRecovered > 0 ||\n stats.framesDropped > 0\n ) {\n logger.warn(\n 'AvatarPlayer',\n `Stream stats: lost=${stats.framesLost}, recovered=${stats.framesRecovered}, dropped=${stats.framesDropped}, fps=${stats.framesPerSec}`,\n );\n }\n },\n };\n\n await this.provider.subscribeAnimationTrack(callbacks);\n }\n\n /**\n * Setup provider event handlers.\n * @internal\n */\n private setupProviderEvents(): void {\n this.provider.on('connected', () => {\n this._isConnected = true;\n });\n\n this.provider.on('disconnected', () => {\n this._isConnected = false;\n this.hasActiveAnimationSession = false;\n this.isTrackingPrimed = false;\n // Return to idle animation when disconnected\n this.animationHandler.startIdle();\n });\n\n this.provider.on('error', (error: unknown) => {\n trackEvent('rtc_error', 'error', {\n provider: this.providerName,\n description: error instanceof Error ? error.message : String(error),\n });\n });\n }\n}\n"],"names":[],"mappings":"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"}
package/dist/index3.js CHANGED
@@ -1,10 +1,10 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { BaseProvider } from "./index10.js";
5
- import { isLiveKitConfig } from "./index5.js";
6
- import { VP8Extractor } from "./index12.js";
7
- import { getInsertableStreamsMethod } from "./index13.js";
4
+ import { BaseProvider } from "./index9.js";
5
+ import { isLiveKitPrepareConfig, isLiveKitConfig } from "./index5.js";
6
+ import { VP8Extractor } from "./index10.js";
7
+ import { getInsertableStreamsMethod } from "./index11.js";
8
8
  import { logger } from "./index7.js";
9
9
  const globalLiveKitProviderRegistry = /* @__PURE__ */ new Set();
10
10
  let rtcPeerConnectionPatched = false;
@@ -29,11 +29,19 @@ function patchRTCPeerConnection() {
29
29
  listener.handleEvent(event);
30
30
  }
31
31
  };
32
- return originalAddEventListener.call(this, type, wrappedListener, options);
32
+ return originalAddEventListener.call(
33
+ this,
34
+ type,
35
+ wrappedListener,
36
+ options
37
+ );
33
38
  }
34
39
  return originalAddEventListener.call(this, type, listener, options);
35
40
  };
36
- const originalOnTrackDescriptor = Object.getOwnPropertyDescriptor(RTCPeerConnection.prototype, "ontrack");
41
+ const originalOnTrackDescriptor = Object.getOwnPropertyDescriptor(
42
+ RTCPeerConnection.prototype,
43
+ "ontrack"
44
+ );
37
45
  if (originalOnTrackDescriptor) {
38
46
  Object.defineProperty(RTCPeerConnection.prototype, "ontrack", {
39
47
  get: originalOnTrackDescriptor.get,
@@ -85,6 +93,18 @@ class LiveKitProvider extends BaseProvider {
85
93
  __publicField(this, "audioCallbacks", null);
86
94
  /** @internal */
87
95
  __publicField(this, "audioElements", /* @__PURE__ */ new Map());
96
+ /** @internal */
97
+ __publicField(this, "awaitingAudioUnlock", false);
98
+ /** @internal */
99
+ __publicField(this, "audioUnlockEventNames", [
100
+ "pointerdown",
101
+ "keydown",
102
+ "touchstart"
103
+ ]);
104
+ /** @internal */
105
+ __publicField(this, "handleAudioUnlockGesture", () => {
106
+ void this.ensureAudioPlaybackUnlocked("user-gesture");
107
+ });
88
108
  // Microphone publishing
89
109
  /** @internal */
90
110
  __publicField(this, "localAudioTrack", null);
@@ -92,6 +112,13 @@ class LiveKitProvider extends BaseProvider {
92
112
  __publicField(this, "isMicrophoneEnabled", false);
93
113
  globalLiveKitProviderRegistry.add(this);
94
114
  }
115
+ createRoom(livekit) {
116
+ const { Room } = livekit;
117
+ return new Room({
118
+ videoCaptureDefaults: void 0,
119
+ audioCaptureDefaults: void 0
120
+ });
121
+ }
95
122
  /**
96
123
  * Load LiveKit SDK dynamically.
97
124
  * @internal
@@ -103,12 +130,31 @@ class LiveKitProvider extends BaseProvider {
103
130
  try {
104
131
  this.livekitSDK = await import("livekit-client");
105
132
  return this.livekitSDK;
106
- } catch (error) {
133
+ } catch {
107
134
  throw new Error(
108
135
  "❌ Failed to load livekit-client.\nPlease ensure it is installed: pnpm add livekit-client"
109
136
  );
110
137
  }
111
138
  }
139
+ async prepareConnection(config) {
140
+ logger.info("LiveKit", "prepareConnection() called with config:", {
141
+ hasUrl: "url" in config,
142
+ hasToken: "token" in config
143
+ });
144
+ if (!isLiveKitPrepareConfig(config)) {
145
+ throw new Error("LiveKitProvider requires url in prepareConnection config");
146
+ }
147
+ const livekit = await this.loadSDK();
148
+ if (!this.room) {
149
+ this.room = this.createRoom(livekit);
150
+ }
151
+ try {
152
+ await this.room.prepareConnection(config.url, config.token);
153
+ logger.info("LiveKit", "prepareConnection() completed");
154
+ } catch (error) {
155
+ logger.warn("LiveKit", "prepareConnection() failed (non-fatal):", error);
156
+ }
157
+ }
112
158
  /**
113
159
  * Map LiveKit connection state to string.
114
160
  * @internal
@@ -269,6 +315,117 @@ class LiveKitProvider extends BaseProvider {
269
315
  this.vp8Extractors.delete(receiver);
270
316
  }
271
317
  }
318
+ /**
319
+ * Add one-time user gesture listeners to recover from autoplay blocking.
320
+ * @internal
321
+ */
322
+ addAudioUnlockListeners() {
323
+ if (typeof window === "undefined" || this.awaitingAudioUnlock) {
324
+ return;
325
+ }
326
+ this.awaitingAudioUnlock = true;
327
+ for (const eventName of this.audioUnlockEventNames) {
328
+ window.addEventListener(eventName, this.handleAudioUnlockGesture, {
329
+ capture: true,
330
+ passive: true
331
+ });
332
+ }
333
+ logger.warn(
334
+ "LiveKit",
335
+ "Audio playback blocked by autoplay policy, waiting for user interaction"
336
+ );
337
+ }
338
+ /**
339
+ * Remove user gesture listeners after playback is unlocked.
340
+ * @internal
341
+ */
342
+ removeAudioUnlockListeners() {
343
+ if (typeof window === "undefined" || !this.awaitingAudioUnlock) {
344
+ return;
345
+ }
346
+ this.awaitingAudioUnlock = false;
347
+ for (const eventName of this.audioUnlockEventNames) {
348
+ window.removeEventListener(
349
+ eventName,
350
+ this.handleAudioUnlockGesture,
351
+ true
352
+ );
353
+ }
354
+ }
355
+ /**
356
+ * Detect browser autoplay blocking errors from audio play/start calls.
357
+ * @internal
358
+ */
359
+ isAutoplayBlockedError(error) {
360
+ if (typeof DOMException !== "undefined" && error instanceof DOMException) {
361
+ return error.name === "NotAllowedError";
362
+ }
363
+ if (error instanceof Error) {
364
+ return error.name === "NotAllowedError" || /didn't interact|user didn't interact|NotAllowedError/i.test(
365
+ error.message
366
+ );
367
+ }
368
+ return false;
369
+ }
370
+ /**
371
+ * Try to resume all attached remote audio elements.
372
+ * @returns true if at least one element is still blocked by autoplay policy
373
+ * @internal
374
+ */
375
+ async resumeAttachedAudioElements(reason) {
376
+ let hasAutoplayBlockedElement = false;
377
+ for (const [participantIdentity, audioElement] of this.audioElements) {
378
+ if (!audioElement.paused) {
379
+ continue;
380
+ }
381
+ try {
382
+ await audioElement.play();
383
+ } catch (error) {
384
+ if (this.isAutoplayBlockedError(error)) {
385
+ hasAutoplayBlockedElement = true;
386
+ }
387
+ logger.warn(
388
+ "LiveKit",
389
+ `Failed to resume remote audio element (${reason}) for ${participantIdentity}:`,
390
+ error
391
+ );
392
+ }
393
+ }
394
+ return hasAutoplayBlockedElement;
395
+ }
396
+ /**
397
+ * Ensure remote audio playback is unlocked and resumed.
398
+ * @internal
399
+ */
400
+ async ensureAudioPlaybackUnlocked(reason) {
401
+ if (!this.room) {
402
+ return;
403
+ }
404
+ const roomWithAudioControl = this.room;
405
+ if (!roomWithAudioControl.canPlaybackAudio && typeof roomWithAudioControl.startAudio === "function") {
406
+ try {
407
+ await roomWithAudioControl.startAudio();
408
+ logger.info("LiveKit", `Audio context resumed (${reason})`);
409
+ } catch (error) {
410
+ if (this.isAutoplayBlockedError(error)) {
411
+ this.addAudioUnlockListeners();
412
+ } else {
413
+ logger.warn(
414
+ "LiveKit",
415
+ `Failed to call room.startAudio() (${reason}):`,
416
+ error
417
+ );
418
+ }
419
+ return;
420
+ }
421
+ }
422
+ const hasAutoplayBlockedElement = await this.resumeAttachedAudioElements(reason);
423
+ if (roomWithAudioControl.canPlaybackAudio && !hasAutoplayBlockedElement) {
424
+ this.removeAudioUnlockListeners();
425
+ } else {
426
+ this.addAudioUnlockListeners();
427
+ }
428
+ }
272
429
  async connect(config) {
273
430
  var _a;
274
431
  logger.info("LiveKit", "connect() called with config:", {
@@ -278,18 +435,26 @@ class LiveKitProvider extends BaseProvider {
278
435
  config
279
436
  });
280
437
  if (!isLiveKitConfig(config)) {
281
- logger.error("LiveKit", "Config validation failed - missing url or roomName");
282
- throw new Error("LiveKitProvider requires url and roomName in connection config");
438
+ logger.error(
439
+ "LiveKit",
440
+ "Config validation failed - missing url or token"
441
+ );
442
+ throw new Error(
443
+ "LiveKitProvider requires url and token in connection config"
444
+ );
283
445
  }
284
446
  const livekitConfig = config;
285
- logger.info("LiveKit", "Config validated, connecting to:", livekitConfig.url);
447
+ logger.info(
448
+ "LiveKit",
449
+ "Config validated, connecting to:",
450
+ livekitConfig.url
451
+ );
452
+ this.removeAudioUnlockListeners();
286
453
  const livekit = await this.loadSDK();
287
454
  logger.info("LiveKit", "SDK loaded successfully");
288
- const { Room } = livekit;
289
- this.room = new Room({
290
- videoCaptureDefaults: void 0,
291
- audioCaptureDefaults: void 0
292
- });
455
+ if (!this.room) {
456
+ this.room = this.createRoom(livekit);
457
+ }
293
458
  this.setConnectionState("connecting");
294
459
  this.setupEventListeners(livekit);
295
460
  try {
@@ -297,16 +462,31 @@ class LiveKitProvider extends BaseProvider {
297
462
  await this.room.connect(livekitConfig.url, livekitConfig.token);
298
463
  logger.info("LiveKit", "Room connected, state:", this.room.state);
299
464
  logger.info("LiveKit", "Room name:", this.room.name);
300
- logger.info("LiveKit", "Local participant:", (_a = this.room.localParticipant) == null ? void 0 : _a.identity);
301
- logger.info("LiveKit", "Remote participants:", this.room.remoteParticipants.size);
465
+ logger.info(
466
+ "LiveKit",
467
+ "Local participant:",
468
+ (_a = this.room.localParticipant) == null ? void 0 : _a.identity
469
+ );
470
+ logger.info(
471
+ "LiveKit",
472
+ "Remote participants:",
473
+ this.room.remoteParticipants.size
474
+ );
302
475
  this.room.remoteParticipants.forEach((participant, sid) => {
303
- logger.info("LiveKit", `Remote participant: ${participant.identity}, sid: ${sid}`);
476
+ logger.info(
477
+ "LiveKit",
478
+ `Remote participant: ${participant.identity}, sid: ${sid}`
479
+ );
304
480
  participant.trackPublications.forEach((pub, trackSid) => {
305
- logger.info("LiveKit", ` Track: ${pub.trackName}, kind: ${pub.kind}, subscribed: ${pub.isSubscribed}, sid: ${trackSid}`);
481
+ logger.info(
482
+ "LiveKit",
483
+ ` Track: ${pub.trackName}, kind: ${pub.kind}, subscribed: ${pub.isSubscribed}, sid: ${trackSid}`
484
+ );
306
485
  });
307
486
  });
308
- this.setConnectionState(this.mapLiveKitConnectionState(livekit, this.room.state));
309
- this.emit("connected");
487
+ this.setConnectionState(
488
+ this.mapLiveKitConnectionState(livekit, this.room.state)
489
+ );
310
490
  } catch (error) {
311
491
  logger.error("LiveKit", "Connection failed:", error);
312
492
  this.setConnectionState("failed");
@@ -325,9 +505,19 @@ class LiveKitProvider extends BaseProvider {
325
505
  room.on(RoomEvent.Connected, () => {
326
506
  this.setConnectionState("connected");
327
507
  this.emit("connected");
508
+ const roomWithAudioControl = room;
509
+ if (!roomWithAudioControl.canPlaybackAudio) {
510
+ this.addAudioUnlockListeners();
511
+ }
328
512
  });
329
513
  room.on(RoomEvent.ParticipantConnected, (participant) => {
330
- logger.info("LiveKit", `ParticipantConnected: ${participant.identity}`);
514
+ logger.info("LiveKit", `Participant connected: ${participant.identity}`);
515
+ });
516
+ room.on(RoomEvent.ParticipantDisconnected, (participant) => {
517
+ logger.warn(
518
+ "LiveKit",
519
+ `Participant disconnected: ${participant.identity}`
520
+ );
331
521
  });
332
522
  room.on(RoomEvent.TrackPublished, (publication, participant) => {
333
523
  logger.info("LiveKit", "TrackPublished:", {
@@ -341,6 +531,22 @@ class LiveKitProvider extends BaseProvider {
341
531
  this.setConnectionState("disconnected");
342
532
  this.emit("disconnected");
343
533
  });
534
+ room.on(
535
+ RoomEvent.AudioPlaybackStatusChanged,
536
+ (canPlaybackAudio) => {
537
+ logger.info(
538
+ "LiveKit",
539
+ `Audio playback status changed: ${canPlaybackAudio ? "enabled" : "blocked"}`
540
+ );
541
+ if (canPlaybackAudio) {
542
+ void this.ensureAudioPlaybackUnlocked(
543
+ "audio-playback-status-changed"
544
+ );
545
+ } else {
546
+ this.addAudioUnlockListeners();
547
+ }
548
+ }
549
+ );
344
550
  this.room.on(
345
551
  RoomEvent.TrackSubscribed,
346
552
  (track, publication, participant) => {
@@ -359,22 +565,40 @@ class LiveKitProvider extends BaseProvider {
359
565
  audioElement.autoplay = true;
360
566
  audioElement.controls = false;
361
567
  audioElement.style.display = "none";
568
+ const previousAudioElement = this.audioElements.get(
569
+ participant.identity
570
+ );
571
+ if (previousAudioElement && previousAudioElement !== audioElement) {
572
+ previousAudioElement.remove();
573
+ }
362
574
  if (typeof document !== "undefined") {
363
575
  document.body.appendChild(audioElement);
364
576
  logger.info("LiveKit", "Audio element appended to body");
365
577
  }
366
578
  this.audioElements.set(participant.identity, audioElement);
579
+ void this.ensureAudioPlaybackUnlocked("audio-track-subscribed");
367
580
  if (this.audioCallbacks) {
368
581
  (_b = (_a = this.audioCallbacks).onAudioReceived) == null ? void 0 : _b.call(_a, participant);
369
582
  }
370
583
  } else if (track.kind === Track.Kind.Video) {
371
- logger.info("LiveKit", "Video track received, setting up animation transform...");
584
+ logger.info(
585
+ "LiveKit",
586
+ "Video track received, setting up animation transform..."
587
+ );
372
588
  if (mediaStreamTrack) {
373
- const receiver = this.findVideoReceiverByTrackId(mediaStreamTrack.id);
589
+ const receiver = this.findVideoReceiverByTrackId(
590
+ mediaStreamTrack.id
591
+ );
374
592
  if (receiver) {
375
- this.receiverParticipantMap.set(receiver, { participant, trackName });
593
+ this.receiverParticipantMap.set(receiver, {
594
+ participant,
595
+ trackName
596
+ });
376
597
  if (!receiver.transform || !this.transformedReceivers.has(receiver)) {
377
- this.applyAnimationReceiverTransform(receiver, mediaStreamTrack);
598
+ this.applyAnimationReceiverTransform(
599
+ receiver,
600
+ mediaStreamTrack
601
+ );
378
602
  }
379
603
  }
380
604
  }
@@ -423,6 +647,7 @@ class LiveKitProvider extends BaseProvider {
423
647
  }
424
648
  return this.connectionState;
425
649
  }
650
+ /** @internal */
426
651
  async subscribeAnimationTrack(callbacks) {
427
652
  var _a;
428
653
  this.animationCallbacks = callbacks;
@@ -439,9 +664,14 @@ class LiveKitProvider extends BaseProvider {
439
664
  if (((_a = publication.trackName) == null ? void 0 : _a.includes("animation")) && publication.track) {
440
665
  const mediaStreamTrack = publication.track.mediaStreamTrack;
441
666
  if (mediaStreamTrack) {
442
- const receiver = this.findVideoReceiverByTrackId(mediaStreamTrack.id);
667
+ const receiver = this.findVideoReceiverByTrackId(
668
+ mediaStreamTrack.id
669
+ );
443
670
  if (receiver) {
444
- this.applyAnimationReceiverTransform(receiver, mediaStreamTrack);
671
+ this.applyAnimationReceiverTransform(
672
+ receiver,
673
+ mediaStreamTrack
674
+ );
445
675
  }
446
676
  }
447
677
  }
@@ -451,6 +681,7 @@ class LiveKitProvider extends BaseProvider {
451
681
  }
452
682
  }
453
683
  }
684
+ /** @internal */
454
685
  async unsubscribeAnimationTrack() {
455
686
  this.animationCallbacks = null;
456
687
  this.vp8Extractors.forEach((extractor) => {
@@ -464,13 +695,14 @@ class LiveKitProvider extends BaseProvider {
464
695
  this.transformedReceivers.clear();
465
696
  this.receiverParticipantMap.clear();
466
697
  }
698
+ /** @internal */
467
699
  async subscribeAudioTrack(callbacks) {
468
700
  this.audioCallbacks = callbacks;
469
701
  }
702
+ /** @internal */
470
703
  async unsubscribeAudioTrack() {
471
704
  this.audioCallbacks = null;
472
705
  this.audioElements.forEach((el) => {
473
- el.pause();
474
706
  el.remove();
475
707
  });
476
708
  this.audioElements.clear();
@@ -517,35 +749,14 @@ class LiveKitProvider extends BaseProvider {
517
749
  this.localAudioTrack = null;
518
750
  this.isMicrophoneEnabled = false;
519
751
  }
520
- /**
521
- * Play remote audio (resume playback)
522
- */
523
- playRemoteAudio() {
524
- this.audioElements.forEach((audioElement) => {
525
- if (audioElement.paused) {
526
- audioElement.play().catch(() => {
527
- });
528
- }
529
- });
530
- }
531
- /**
532
- * Pause remote audio
533
- */
534
- pauseRemoteAudio() {
535
- this.audioElements.forEach((audioElement) => {
536
- if (!audioElement.paused) {
537
- audioElement.pause();
538
- }
539
- });
540
- }
541
752
  /**
542
753
  * Get the native LiveKit Room instance.
543
- *
754
+ *
544
755
  * Allows advanced users to access LiveKit-specific features
545
756
  * not exposed through the unified API.
546
- *
757
+ *
547
758
  * @returns The LiveKit Room instance, or null if not connected
548
- *
759
+ *
549
760
  * @example
550
761
  * ```typescript
551
762
  * const room = provider.getNativeClient();
@@ -563,8 +774,8 @@ class LiveKitProvider extends BaseProvider {
563
774
  * @internal
564
775
  */
565
776
  cleanup() {
777
+ this.removeAudioUnlockListeners();
566
778
  this.audioElements.forEach((el) => {
567
- el.pause();
568
779
  el.remove();
569
780
  });
570
781
  this.audioElements.clear();