vg-x07df 0.1.0

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 (140) hide show
  1. package/.azure-pipelines/publish-public.yml +37 -0
  2. package/.azure-pipelines/publish.yml +39 -0
  3. package/.changeset/README.md +8 -0
  4. package/.changeset/config.json +11 -0
  5. package/AUTO_JOIN_GUIDE.md +411 -0
  6. package/README.md +215 -0
  7. package/Screenshot 2025-09-24 at 14.34.48.png +0 -0
  8. package/Screenshot 2025-10-04 at 12.58.54.png +0 -0
  9. package/biome.json +48 -0
  10. package/examples/demo/.env.example +19 -0
  11. package/examples/demo/CHANGELOG.md +22 -0
  12. package/examples/demo/README.md +72 -0
  13. package/examples/demo/eslint.config.js +23 -0
  14. package/examples/demo/index.html +13 -0
  15. package/examples/demo/package.json +34 -0
  16. package/examples/demo/pnpm-lock.yaml +2098 -0
  17. package/examples/demo/pnpm-workspace.yaml +1 -0
  18. package/examples/demo/public/vite.svg +1 -0
  19. package/examples/demo/src/App.css +52 -0
  20. package/examples/demo/src/App.tsx +176 -0
  21. package/examples/demo/src/assets/react.svg +1 -0
  22. package/examples/demo/src/components/auth/LoginForm.css +144 -0
  23. package/examples/demo/src/components/auth/LoginForm.tsx +80 -0
  24. package/examples/demo/src/components/calling/AutoJoinSettings.tsx +213 -0
  25. package/examples/demo/src/components/calling/AutoJoinStatus.tsx +72 -0
  26. package/examples/demo/src/components/calling/CallInitiator.css +258 -0
  27. package/examples/demo/src/components/calling/CallInitiator.tsx +142 -0
  28. package/examples/demo/src/components/calling/CallNotifications.css +119 -0
  29. package/examples/demo/src/components/calling/CallNotifications.tsx +108 -0
  30. package/examples/demo/src/components/calling/IncomingCallModal.css +192 -0
  31. package/examples/demo/src/components/calling/IncomingCallModal.tsx +78 -0
  32. package/examples/demo/src/components/calling/MinimizedCall.css +156 -0
  33. package/examples/demo/src/components/calling/MinimizedCall.tsx +78 -0
  34. package/examples/demo/src/components/conference/ConferenceHeader.css +265 -0
  35. package/examples/demo/src/components/conference/ConferenceHeader.tsx +78 -0
  36. package/examples/demo/src/components/conference/EnhancedControlBar.css +356 -0
  37. package/examples/demo/src/components/conference/EnhancedControlBar.tsx +262 -0
  38. package/examples/demo/src/components/conference/PaginationControls.css +67 -0
  39. package/examples/demo/src/components/conference/PaginationControls.tsx +64 -0
  40. package/examples/demo/src/components/conference/ParticipantGrid.css +153 -0
  41. package/examples/demo/src/components/conference/ParticipantGrid.tsx +87 -0
  42. package/examples/demo/src/components/conference/ParticipantTile.css +210 -0
  43. package/examples/demo/src/components/conference/ParticipantTile.tsx +114 -0
  44. package/examples/demo/src/components/conference/VideoConference.css +214 -0
  45. package/examples/demo/src/components/conference/VideoConference.tsx +93 -0
  46. package/examples/demo/src/contexts/AuthContext.tsx +105 -0
  47. package/examples/demo/src/hooks/useAuth.ts +5 -0
  48. package/examples/demo/src/hooks/useCallTimer.ts +42 -0
  49. package/examples/demo/src/index.css +68 -0
  50. package/examples/demo/src/main.tsx +10 -0
  51. package/examples/demo/src/services/auth.service.ts +153 -0
  52. package/examples/demo/src/types/auth.types.ts +31 -0
  53. package/examples/demo/tsconfig.app.json +28 -0
  54. package/examples/demo/tsconfig.json +7 -0
  55. package/examples/demo/tsconfig.node.json +26 -0
  56. package/examples/demo/vite.config.ts +15 -0
  57. package/images/callpad-without-ai.png +0 -0
  58. package/package.json +28 -0
  59. package/packages/sdk/CHANGELOG.md +33 -0
  60. package/packages/sdk/LICENSE +21 -0
  61. package/packages/sdk/README.md +97 -0
  62. package/packages/sdk/documentation.md +1132 -0
  63. package/packages/sdk/openapi-ts.config.ts +7 -0
  64. package/packages/sdk/package.json +88 -0
  65. package/packages/sdk/src/core/auth.manager.ts +52 -0
  66. package/packages/sdk/src/core/events/event-bus.ts +301 -0
  67. package/packages/sdk/src/core/events/index.ts +8 -0
  68. package/packages/sdk/src/core/events/types.ts +165 -0
  69. package/packages/sdk/src/core/index.ts +3 -0
  70. package/packages/sdk/src/core/signal/api.config.ts +49 -0
  71. package/packages/sdk/src/core/signal/index.ts +16 -0
  72. package/packages/sdk/src/core/signal/signal.client.ts +101 -0
  73. package/packages/sdk/src/core/signal/types.ts +110 -0
  74. package/packages/sdk/src/core/socketio/handlers/base.handler.ts +212 -0
  75. package/packages/sdk/src/core/socketio/handlers/call-accepted.handler.ts +34 -0
  76. package/packages/sdk/src/core/socketio/handlers/call-canceled.handler.ts +34 -0
  77. package/packages/sdk/src/core/socketio/handlers/call-declined.handler.ts +29 -0
  78. package/packages/sdk/src/core/socketio/handlers/call-ended.handler.ts +40 -0
  79. package/packages/sdk/src/core/socketio/handlers/call-incoming.handler.ts +72 -0
  80. package/packages/sdk/src/core/socketio/handlers/call-join-info.handler.ts +181 -0
  81. package/packages/sdk/src/core/socketio/handlers/call-participant-joined.handler.ts +42 -0
  82. package/packages/sdk/src/core/socketio/handlers/call-participant-joining.handler.ts +42 -0
  83. package/packages/sdk/src/core/socketio/handlers/call-timeout.handler.ts +31 -0
  84. package/packages/sdk/src/core/socketio/handlers/handler.registry.ts +62 -0
  85. package/packages/sdk/src/core/socketio/handlers/index.ts +21 -0
  86. package/packages/sdk/src/core/socketio/handlers/participant-left.handler.ts +37 -0
  87. package/packages/sdk/src/core/socketio/handlers/schema.ts +130 -0
  88. package/packages/sdk/src/core/socketio/index.ts +5 -0
  89. package/packages/sdk/src/core/socketio/socket.manager.ts +187 -0
  90. package/packages/sdk/src/core/socketio/types.ts +14 -0
  91. package/packages/sdk/src/core/types.ts +23 -0
  92. package/packages/sdk/src/generated/api/core/ApiError.ts +21 -0
  93. package/packages/sdk/src/generated/api/core/ApiRequestOptions.ts +13 -0
  94. package/packages/sdk/src/generated/api/core/ApiResult.ts +7 -0
  95. package/packages/sdk/src/generated/api/core/CancelablePromise.ts +126 -0
  96. package/packages/sdk/src/generated/api/core/OpenAPI.ts +55 -0
  97. package/packages/sdk/src/generated/api/core/request.ts +339 -0
  98. package/packages/sdk/src/generated/api/index.ts +5 -0
  99. package/packages/sdk/src/generated/api/models.ts +219 -0
  100. package/packages/sdk/src/generated/api/services.ts +225 -0
  101. package/packages/sdk/src/hooks/index.ts +21 -0
  102. package/packages/sdk/src/hooks/useAutoJoin.ts +66 -0
  103. package/packages/sdk/src/hooks/useCallActions.ts +28 -0
  104. package/packages/sdk/src/hooks/useCallQuality.ts +416 -0
  105. package/packages/sdk/src/hooks/useCallState.ts +23 -0
  106. package/packages/sdk/src/hooks/useConnection.ts +15 -0
  107. package/packages/sdk/src/hooks/useDevices.ts +296 -0
  108. package/packages/sdk/src/hooks/useErrorRecovery.ts +299 -0
  109. package/packages/sdk/src/hooks/useErrors.ts +84 -0
  110. package/packages/sdk/src/hooks/useEvent.ts +188 -0
  111. package/packages/sdk/src/hooks/useMediaControls.ts +215 -0
  112. package/packages/sdk/src/hooks/useParticipantStatus.ts +318 -0
  113. package/packages/sdk/src/hooks/useParticipants.ts +111 -0
  114. package/packages/sdk/src/index.ts +66 -0
  115. package/packages/sdk/src/livekit/constants.ts +76 -0
  116. package/packages/sdk/src/livekit/device.manager.ts +172 -0
  117. package/packages/sdk/src/livekit/error-classifier.ts +155 -0
  118. package/packages/sdk/src/livekit/events/eventBridge.ts +371 -0
  119. package/packages/sdk/src/livekit/events/trackRegistry.ts +114 -0
  120. package/packages/sdk/src/livekit/index.ts +49 -0
  121. package/packages/sdk/src/livekit/livekit.service.ts +110 -0
  122. package/packages/sdk/src/livekit/media.controls.ts +315 -0
  123. package/packages/sdk/src/livekit/room.manager.ts +79 -0
  124. package/packages/sdk/src/livekit/track.utils.ts +230 -0
  125. package/packages/sdk/src/livekit/types.ts +135 -0
  126. package/packages/sdk/src/provider/RtcProvider.tsx +78 -0
  127. package/packages/sdk/src/services/call-actions.ts +260 -0
  128. package/packages/sdk/src/services/error-recovery.ts +461 -0
  129. package/packages/sdk/src/services/index.ts +2 -0
  130. package/packages/sdk/src/services/sdk-builder.ts +104 -0
  131. package/packages/sdk/src/state/errors.ts +163 -0
  132. package/packages/sdk/src/state/selectors.ts +28 -0
  133. package/packages/sdk/src/state/store.ts +36 -0
  134. package/packages/sdk/src/state/types.ts +151 -0
  135. package/packages/sdk/src/utils/logger.ts +183 -0
  136. package/packages/sdk/tsconfig.json +49 -0
  137. package/packages/sdk/tsup.config.ts +51 -0
  138. package/pnpm-workspace.yaml +4 -0
  139. package/tsconfig.base.json +19 -0
  140. package/turbo.json +34 -0
@@ -0,0 +1,371 @@
1
+ import {
2
+ ConnectionQuality,
3
+ LocalParticipant,
4
+ type Participant,
5
+ type Room,
6
+ RoomEvent,
7
+ Track,
8
+ type TrackPublication,
9
+ } from "livekit-client";
10
+ import { SdkEventType, eventBus } from "../../core/events";
11
+ import { rtcStore } from "../../state/store";
12
+ import { trackRegistry } from "./trackRegistry";
13
+
14
+ export interface EventBridgeOptions {
15
+ log?: (
16
+ lvl: "debug" | "info" | "warn" | "error",
17
+ msg: string,
18
+ extra?: any
19
+ ) => void;
20
+ }
21
+
22
+ export class LiveKitEventBridge {
23
+ private room: Room;
24
+ private opts: EventBridgeOptions;
25
+
26
+ constructor(room: Room, opts: EventBridgeOptions = {}) {
27
+ this.room = room;
28
+ this.opts = opts;
29
+ this.setupEventListeners();
30
+ }
31
+
32
+ private setupEventListeners(): void {
33
+ this.room
34
+ // Connection events
35
+ .on(RoomEvent.Connected, this.handleConnected)
36
+ .on(RoomEvent.Disconnected, this.handleDisconnected)
37
+ .on(RoomEvent.Reconnecting, this.handleReconnecting)
38
+ .on(RoomEvent.Reconnected, this.handleReconnected)
39
+
40
+ // Participant events
41
+ .on(RoomEvent.ParticipantConnected, this.handleParticipantConnected)
42
+ .on(RoomEvent.ParticipantDisconnected, this.handleParticipantDisconnected)
43
+
44
+ // Track events
45
+ .on(RoomEvent.TrackSubscribed, this.handleTrackSubscribed)
46
+ .on(RoomEvent.TrackUnsubscribed, this.handleTrackUnsubscribed)
47
+ .on(RoomEvent.TrackMuted, this.handleTrackMuted)
48
+ .on(RoomEvent.TrackUnmuted, this.handleTrackUnmuted)
49
+
50
+ // Media events
51
+ .on(RoomEvent.ActiveSpeakersChanged, this.handleActiveSpeakersChanged)
52
+ .on(
53
+ RoomEvent.ConnectionQualityChanged,
54
+ this.handleConnectionQualityChanged
55
+ );
56
+ }
57
+
58
+ private handleConnected = (): void => {
59
+ this.opts.log?.("info", "LiveKit room connected");
60
+
61
+ rtcStore.getState().patch((state) => {
62
+ state.connection.connected = true;
63
+ state.connection.reconnecting = false;
64
+ });
65
+
66
+ // Sync all existing participants (including local)
67
+ this.syncAllParticipants();
68
+ };
69
+
70
+ private handleDisconnected = (): void => {
71
+ this.opts.log?.("info", "LiveKit room disconnected");
72
+
73
+ rtcStore.getState().patch((state) => {
74
+ state.connection.connected = false;
75
+ state.connection.reconnecting = false;
76
+ });
77
+ };
78
+
79
+ private handleReconnecting = (): void => {
80
+ this.opts.log?.("info", "LiveKit room reconnecting");
81
+
82
+ rtcStore.getState().patch((state) => {
83
+ state.connection.reconnecting = true;
84
+ });
85
+ };
86
+
87
+ private handleReconnected = (): void => {
88
+ this.opts.log?.("info", "LiveKit room reconnected");
89
+
90
+ rtcStore.getState().patch((state) => {
91
+ state.connection.connected = true;
92
+ state.connection.reconnecting = false;
93
+ });
94
+ };
95
+
96
+ private handleParticipantConnected = (participant: Participant): void => {
97
+ const pid = participant.identity;
98
+ this.opts.log?.("info", "Participant connected", { pid });
99
+
100
+ rtcStore.getState().patch((state) => {
101
+ // Create or update participant in unified state
102
+ if (!state.room.participants[pid]) {
103
+ state.room.participants[pid] = {
104
+ id: pid,
105
+ firstName: participant.name || "Unknown",
106
+ role: "MEMBER",
107
+ callState: "JOINED",
108
+ joinedAt: Date.now(),
109
+ audioEnabled: true,
110
+ videoEnabled: true,
111
+ isSpeaking: false,
112
+ };
113
+ this.opts.log?.("debug", "Created participant", { pid });
114
+ } else {
115
+ // Update existing participant
116
+ state.room.participants[pid].callState = "JOINED";
117
+ if (!state.room.participants[pid].joinedAt) {
118
+ state.room.participants[pid].joinedAt = Date.now();
119
+ }
120
+ }
121
+ });
122
+ };
123
+
124
+ private handleParticipantDisconnected = (participant: Participant): void => {
125
+ const pid = participant.identity;
126
+ this.opts.log?.("info", "Participant disconnected", { pid });
127
+
128
+ // Simple strategy: don't immediately mark as left, rely on call.ended
129
+ // This handles transient disconnects gracefully
130
+
131
+ rtcStore.getState().patch((state) => {
132
+ // Clean up tracks for this participant
133
+ trackRegistry.removeByParticipant(pid);
134
+
135
+ // Keep profile and presence for history - UI can filter as needed
136
+ });
137
+ };
138
+
139
+ private handleTrackSubscribed = (
140
+ track: Track,
141
+ publication: TrackPublication,
142
+ participant: Participant
143
+ ): void => {
144
+ const pid = participant.identity;
145
+ const trackSid = publication.trackSid;
146
+
147
+ this.opts.log?.("debug", "Track subscribed", {
148
+ pid,
149
+ trackSid,
150
+ kind: track.kind,
151
+ source: publication.source,
152
+ });
153
+
154
+ // Add to track registry
155
+ trackRegistry.add(trackSid, pid, track.kind, publication.source);
156
+
157
+ // Update participant mute states (for backward compatibility)
158
+ this.updateParticipantMuteState(participant);
159
+ };
160
+
161
+ private handleTrackUnsubscribed = (
162
+ track: Track,
163
+ publication: TrackPublication,
164
+ participant: Participant
165
+ ): void => {
166
+ const pid = participant.identity;
167
+ const trackSid = publication.trackSid;
168
+
169
+ this.opts.log?.("debug", "Track unsubscribed", {
170
+ pid,
171
+ trackSid,
172
+ kind: track.kind,
173
+ });
174
+
175
+ // Remove from track registry
176
+ trackRegistry.remove(trackSid);
177
+
178
+ // Update participant mute states (for backward compatibility)
179
+ this.updateParticipantMuteState(participant);
180
+ };
181
+
182
+ private handleTrackMuted = (
183
+ publication: TrackPublication,
184
+ participant: Participant
185
+ ): void => {
186
+ const pid = participant.identity;
187
+ const trackSid = publication.trackSid;
188
+
189
+ this.opts.log?.("debug", "Track muted", {
190
+ pid,
191
+ trackSid,
192
+ kind: publication.kind,
193
+ source: publication.source,
194
+ });
195
+
196
+ // Update participant mute states
197
+ this.updateParticipantMuteState(participant);
198
+
199
+ // Update local state if it's our own track
200
+ if (participant.isLocal) {
201
+ rtcStore.getState().patch((state) => {
202
+ if (publication.source === Track.Source.Microphone) {
203
+ state.local.audioEnabled = false;
204
+ } else if (publication.source === Track.Source.Camera) {
205
+ state.local.videoEnabled = false;
206
+ } else if (publication.source === Track.Source.ScreenShare) {
207
+ state.local.screenEnabled = false;
208
+ }
209
+ });
210
+ }
211
+ };
212
+
213
+ private handleTrackUnmuted = (
214
+ publication: TrackPublication,
215
+ participant: Participant
216
+ ): void => {
217
+ const pid = participant.identity;
218
+ const trackSid = publication.trackSid;
219
+
220
+ this.opts.log?.("debug", "Track unmuted", {
221
+ pid,
222
+ trackSid,
223
+ kind: publication.kind,
224
+ source: publication.source,
225
+ });
226
+
227
+ // Update participant mute states
228
+ this.updateParticipantMuteState(participant);
229
+
230
+ // Update local state if it's our own track
231
+ if (participant.isLocal) {
232
+ rtcStore.getState().patch((state) => {
233
+ if (publication.source === Track.Source.Microphone) {
234
+ state.local.audioEnabled = true;
235
+ } else if (publication.source === Track.Source.Camera) {
236
+ state.local.videoEnabled = true;
237
+ } else if (publication.source === Track.Source.ScreenShare) {
238
+ state.local.screenEnabled = true;
239
+ }
240
+ });
241
+ }
242
+ };
243
+
244
+ private handleActiveSpeakersChanged = (speakers: Participant[]): void => {
245
+ const speakerIds = new Set(speakers.map((s) => s.identity));
246
+
247
+ this.opts.log?.("debug", "Active speakers changed", {
248
+ speakers: Array.from(speakerIds),
249
+ });
250
+
251
+ rtcStore.getState().patch((state) => {
252
+ // Update speaking state for all participants
253
+ for (const [pid, participant] of Object.entries(
254
+ state.room.participants
255
+ )) {
256
+ participant.isSpeaking = speakerIds.has(pid);
257
+ }
258
+ });
259
+ };
260
+
261
+ private handleConnectionQualityChanged = (
262
+ quality: ConnectionQuality,
263
+ participant: Participant
264
+ ): void => {
265
+ const pid = participant.identity;
266
+ const qualityLabel = this.mapConnectionQuality(quality);
267
+
268
+ this.opts.log?.("debug", "Connection quality changed", {
269
+ pid,
270
+ quality: qualityLabel,
271
+ });
272
+
273
+ rtcStore.getState().patch((state) => {
274
+ // Update participant connection quality in unified state
275
+ if (state.room.participants[pid]) {
276
+ state.room.participants[pid].connectionQuality = qualityLabel;
277
+ }
278
+
279
+ // Update local connection quality if it's the local participant
280
+ if (participant.isLocal) {
281
+ state.connection.quality = qualityLabel;
282
+ }
283
+ });
284
+
285
+ // Emit SDK event for connection quality change
286
+ eventBus.emit(
287
+ SdkEventType.CONNECTION_QUALITY_CHANGED,
288
+ {
289
+ participantId: pid,
290
+ quality: qualityLabel,
291
+ timestamp: Date.now(),
292
+ },
293
+ "livekit"
294
+ );
295
+ };
296
+
297
+ private syncAllParticipants(): void {
298
+ const allParticipants = [
299
+ this.room.localParticipant,
300
+ ...Array.from(this.room.remoteParticipants.values()),
301
+ ];
302
+
303
+ for (const participant of allParticipants) {
304
+ this.handleParticipantConnected(participant);
305
+ }
306
+ }
307
+
308
+ private updateParticipantMuteState(participant: Participant): void {
309
+ // Media mute state will be tracked via track subscription/unsubscription events
310
+ // No need to maintain separate mute state in the new architecture
311
+ }
312
+
313
+ private getAudioMutedState(participant: Participant): boolean {
314
+ const audioPublication = participant.getTrackPublication(
315
+ Track.Source.Microphone
316
+ );
317
+ return (
318
+ !audioPublication ||
319
+ audioPublication.isMuted ||
320
+ !audioPublication.isEnabled
321
+ );
322
+ }
323
+
324
+ private getVideoMutedState(participant: Participant): boolean {
325
+ const videoPublication = participant.getTrackPublication(
326
+ Track.Source.Camera
327
+ );
328
+ return (
329
+ !videoPublication ||
330
+ videoPublication.isMuted ||
331
+ !videoPublication.isEnabled
332
+ );
333
+ }
334
+
335
+ private mapConnectionQuality(
336
+ quality: ConnectionQuality
337
+ ): "excellent" | "good" | "poor" | "lost" {
338
+ switch (quality) {
339
+ case ConnectionQuality.Excellent:
340
+ return "excellent";
341
+ case ConnectionQuality.Good:
342
+ return "good";
343
+ case ConnectionQuality.Poor:
344
+ return "poor";
345
+ default:
346
+ return "lost";
347
+ }
348
+ }
349
+
350
+ destroy(): void {
351
+ this.room
352
+ .off(RoomEvent.Connected, this.handleConnected)
353
+ .off(RoomEvent.Disconnected, this.handleDisconnected)
354
+ .off(RoomEvent.Reconnecting, this.handleReconnecting)
355
+ .off(RoomEvent.Reconnected, this.handleReconnected)
356
+ .off(RoomEvent.ParticipantConnected, this.handleParticipantConnected)
357
+ .off(
358
+ RoomEvent.ParticipantDisconnected,
359
+ this.handleParticipantDisconnected
360
+ )
361
+ .off(RoomEvent.TrackSubscribed, this.handleTrackSubscribed)
362
+ .off(RoomEvent.TrackUnsubscribed, this.handleTrackUnsubscribed)
363
+ .off(RoomEvent.TrackMuted, this.handleTrackMuted)
364
+ .off(RoomEvent.TrackUnmuted, this.handleTrackUnmuted)
365
+ .off(RoomEvent.ActiveSpeakersChanged, this.handleActiveSpeakersChanged)
366
+ .off(
367
+ RoomEvent.ConnectionQualityChanged,
368
+ this.handleConnectionQualityChanged
369
+ );
370
+ }
371
+ }
@@ -0,0 +1,114 @@
1
+ import { Track } from "livekit-client";
2
+
3
+ interface TrackState {
4
+ sid: string;
5
+ participantId: string;
6
+ kind: "audio" | "video" | "screen";
7
+ }
8
+
9
+ export class TrackRegistry {
10
+ private tracks: Record<string, TrackState> = {};
11
+
12
+ /**
13
+ * Add or update a track in the registry
14
+ * Idempotent: safe to call multiple times with same params
15
+ */
16
+ add(
17
+ sid: string,
18
+ participantId: string,
19
+ kind: Track.Kind,
20
+ source?: Track.Source
21
+ ): void {
22
+ this.tracks[sid] = {
23
+ sid,
24
+ participantId,
25
+ kind: source ? this.mapTrackSource(source) : this.mapTrackKind(kind),
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Remove a track from the registry
31
+ * Idempotent: ignores unknown sids
32
+ */
33
+ remove(sid: string): void {
34
+ delete this.tracks[sid];
35
+ }
36
+
37
+ /**
38
+ * Remove all tracks for a specific participant
39
+ * Used when participant disconnects
40
+ */
41
+ removeByParticipant(participantId: string): void {
42
+ const tracksToRemove: string[] = [];
43
+
44
+ for (const [sid, track] of Object.entries(this.tracks)) {
45
+ if (track.participantId === participantId) {
46
+ tracksToRemove.push(sid);
47
+ }
48
+ }
49
+
50
+ for (const sid of tracksToRemove) {
51
+ delete this.tracks[sid];
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Get all tracks for a specific participant
57
+ */
58
+ getByParticipant(participantId: string): TrackState[] {
59
+ return Object.values(this.tracks).filter(
60
+ (track) => track.participantId === participantId
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Get all tracks
66
+ */
67
+ getAll(): TrackState[] {
68
+ return Object.values(this.tracks);
69
+ }
70
+
71
+ /**
72
+ * Clear all tracks
73
+ */
74
+ clear(): void {
75
+ this.tracks = {};
76
+ }
77
+
78
+ /**
79
+ * Map LiveKit Track.Kind to our TrackState kind
80
+ * Note: This is deprecated - use mapTrackSource for better accuracy
81
+ */
82
+ private mapTrackKind(kind: Track.Kind): "audio" | "video" | "screen" {
83
+ switch (kind) {
84
+ case Track.Kind.Audio:
85
+ return "audio";
86
+ case Track.Kind.Video:
87
+ return "video";
88
+ default:
89
+ // Fallback for unknown kinds
90
+ return "video";
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Map LiveKit Track.Source to our TrackState kind (more accurate)
96
+ */
97
+ private mapTrackSource(source: Track.Source): "audio" | "video" | "screen" {
98
+ switch (source) {
99
+ case Track.Source.Microphone:
100
+ return "audio";
101
+ case Track.Source.Camera:
102
+ return "video";
103
+ case Track.Source.ScreenShare:
104
+ case Track.Source.ScreenShareAudio:
105
+ return "screen";
106
+ default:
107
+ // Fallback to generic video for unknown sources
108
+ return "video";
109
+ }
110
+ }
111
+ }
112
+
113
+ // Singleton instance
114
+ export const trackRegistry = new TrackRegistry();
@@ -0,0 +1,49 @@
1
+ // Internal LiveKit exports for SDK
2
+ export * from "./types";
3
+ export * from "./constants";
4
+ export { LiveKitService } from "./livekit.service";
5
+ export { LiveKitEventBridge } from "./events/eventBridge";
6
+ export { trackRegistry } from "./events/trackRegistry";
7
+ export type { MediaErrorInfo } from "./error-classifier";
8
+ export {
9
+ classifyMediaError,
10
+ MediaDeviceError,
11
+ MediaPermissionError,
12
+ MediaNotFoundError,
13
+ MediaInUseError,
14
+ MediaUnknownError,
15
+ } from "./error-classifier";
16
+
17
+ // Additional exports for subpath consumers
18
+ export { DeviceManager } from "./device.manager";
19
+ export { MediaControls } from "./media.controls";
20
+ export { RoomManager } from "./room.manager";
21
+
22
+ // Track utilities
23
+ export * from "./track.utils";
24
+
25
+ // Re-export essential LiveKit client types that consumers might need
26
+ export type {
27
+ Room,
28
+ RoomOptions,
29
+ Participant,
30
+ LocalParticipant,
31
+ RemoteParticipant,
32
+ Track,
33
+ LocalTrack,
34
+ RemoteTrack,
35
+ AudioTrack,
36
+ VideoTrack,
37
+ TrackPublication,
38
+ LocalTrackPublication,
39
+ RemoteTrackPublication,
40
+ ConnectionQuality,
41
+ ConnectionState,
42
+ ParticipantEvent,
43
+ RoomEvent,
44
+ TrackEvent,
45
+ // Additional utility types
46
+ ReconnectPolicy,
47
+ // Newer types that might be useful
48
+ DataPacket_Kind,
49
+ } from "livekit-client";
@@ -0,0 +1,110 @@
1
+ import type { Room, RoomOptions } from "livekit-client";
2
+ import { DeviceManager } from "./device.manager";
3
+ import { LiveKitEventBridge } from "./events/eventBridge";
4
+ import { MediaControls } from "./media.controls";
5
+ import { RoomManager } from "./room.manager";
6
+ import type { LiveKitServiceOptions } from "./types";
7
+
8
+ export class LiveKitService {
9
+ private roomManager: RoomManager;
10
+ private eventBridge: LiveKitEventBridge | undefined;
11
+ private mediaControls: MediaControls | undefined;
12
+ private deviceManager: DeviceManager | undefined;
13
+ private options: LiveKitServiceOptions;
14
+
15
+ constructor(options: LiveKitServiceOptions = { log: undefined }) {
16
+ this.options = options;
17
+ this.roomManager = new RoomManager();
18
+ }
19
+
20
+ async joinRoom(token: string, url: string): Promise<void> {
21
+ if (!url) {
22
+ const error = new Error("LiveKit URL is required");
23
+ this.options.log?.("error", "LiveKit URL missing", { token, url });
24
+ throw error;
25
+ }
26
+
27
+ try {
28
+ this.options.log?.("info", "Joining LiveKit room", { url });
29
+ await this.roomManager.connect({ url, token });
30
+
31
+ // Initialize managers after a successful connection
32
+ const eventBridgeOptions: {
33
+ log?: (
34
+ lvl: "debug" | "info" | "warn" | "error",
35
+ msg: string,
36
+ extra?: any
37
+ ) => void;
38
+ } = {};
39
+ if (this.options.log) {
40
+ eventBridgeOptions.log = this.options.log;
41
+ }
42
+ this.eventBridge = new LiveKitEventBridge(this.room, eventBridgeOptions);
43
+ this.mediaControls = new MediaControls(
44
+ this.room.localParticipant,
45
+ this.room
46
+ );
47
+ this.deviceManager = new DeviceManager(this.room);
48
+
49
+ // Enumerate devices after connection
50
+ try {
51
+ await this.deviceManager.enumerateDevices();
52
+ this.options.log?.("info", "Device enumeration completed");
53
+ } catch (error) {
54
+ this.options.log?.("warn", "Failed to enumerate devices", error);
55
+ // Don't fail the connection if device enumeration fails
56
+ }
57
+
58
+ this.options.log?.("info", "Successfully joined LiveKit room");
59
+ } catch (error) {
60
+ this.options.log?.("error", "Failed to join LiveKit room", error);
61
+ throw error;
62
+ }
63
+ }
64
+
65
+ async disconnect(): Promise<void> {
66
+ try {
67
+ this.options.log?.("info", "Disconnecting from LiveKit room");
68
+
69
+ // Cleanup managers first
70
+ this.eventBridge?.destroy();
71
+ this.eventBridge = undefined;
72
+ this.deviceManager?.destroy();
73
+ this.deviceManager = undefined;
74
+ this.mediaControls = undefined;
75
+
76
+ await this.roomManager.disconnect();
77
+ this.options.log?.("info", "Successfully disconnected from LiveKit room");
78
+ } catch (error) {
79
+ this.options.log?.("error", "Error during LiveKit disconnect", error);
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ get room(): Room {
85
+ return this.roomManager.room;
86
+ }
87
+
88
+ get media(): MediaControls {
89
+ if (!this.mediaControls) {
90
+ throw new Error("Media controls not available - room not connected");
91
+ }
92
+ return this.mediaControls;
93
+ }
94
+
95
+ get devices(): DeviceManager {
96
+ if (!this.deviceManager) {
97
+ throw new Error("Device manager not available - room not connected");
98
+ }
99
+ return this.deviceManager;
100
+ }
101
+
102
+ destroy(): void {
103
+ this.eventBridge?.destroy();
104
+ this.eventBridge = undefined;
105
+ this.deviceManager?.destroy();
106
+ this.deviceManager = undefined;
107
+ this.mediaControls = undefined;
108
+ this.roomManager.destroy();
109
+ }
110
+ }