livekit-client 0.17.5 → 0.17.6-rc1

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 (153) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/README.md +26 -20
  3. package/dist/api/SignalClient.d.ts +1 -0
  4. package/dist/connect.d.ts +2 -0
  5. package/dist/index.d.ts +2 -2
  6. package/dist/livekit-client.esm.js +17344 -0
  7. package/dist/livekit-client.esm.js.map +1 -0
  8. package/dist/livekit-client.umd.js +17388 -0
  9. package/dist/livekit-client.umd.js.map +1 -0
  10. package/dist/logger.d.ts +22 -11
  11. package/dist/options.d.ts +4 -2
  12. package/dist/proto/google/protobuf/timestamp.d.ts +1 -1
  13. package/dist/proto/livekit_models.d.ts +4 -1
  14. package/dist/proto/livekit_rtc.d.ts +45 -2
  15. package/dist/room/DeviceManager.d.ts +1 -1
  16. package/dist/room/RTCEngine.d.ts +1 -1
  17. package/dist/room/Room.d.ts +2 -2
  18. package/dist/room/events.d.ts +1 -1
  19. package/dist/room/participant/LocalParticipant.d.ts +9 -5
  20. package/dist/room/participant/RemoteParticipant.d.ts +9 -0
  21. package/dist/room/track/RemoteAudioTrack.d.ts +11 -0
  22. package/dist/test/mocks.d.ts +11 -0
  23. package/package.json +40 -15
  24. package/.eslintrc.js +0 -17
  25. package/.gitmodules +0 -3
  26. package/dist/api/RequestQueue.js +0 -61
  27. package/dist/api/RequestQueue.js.map +0 -1
  28. package/dist/api/SignalClient.js +0 -428
  29. package/dist/api/SignalClient.js.map +0 -1
  30. package/dist/connect.js +0 -130
  31. package/dist/connect.js.map +0 -1
  32. package/dist/index.js +0 -71
  33. package/dist/index.js.map +0 -1
  34. package/dist/logger.js +0 -24
  35. package/dist/logger.js.map +0 -1
  36. package/dist/options.js +0 -3
  37. package/dist/options.js.map +0 -1
  38. package/dist/proto/google/protobuf/timestamp.js +0 -108
  39. package/dist/proto/google/protobuf/timestamp.js.map +0 -1
  40. package/dist/proto/livekit_models.js +0 -2262
  41. package/dist/proto/livekit_models.js.map +0 -1
  42. package/dist/proto/livekit_rtc.js +0 -2544
  43. package/dist/proto/livekit_rtc.js.map +0 -1
  44. package/dist/room/DeviceManager.js +0 -62
  45. package/dist/room/DeviceManager.js.map +0 -1
  46. package/dist/room/PCTransport.js +0 -91
  47. package/dist/room/PCTransport.js.map +0 -1
  48. package/dist/room/RTCEngine.js +0 -562
  49. package/dist/room/RTCEngine.js.map +0 -1
  50. package/dist/room/Room.js +0 -767
  51. package/dist/room/Room.js.map +0 -1
  52. package/dist/room/errors.js +0 -68
  53. package/dist/room/errors.js.map +0 -1
  54. package/dist/room/events.js +0 -385
  55. package/dist/room/events.js.map +0 -1
  56. package/dist/room/participant/LocalParticipant.js +0 -647
  57. package/dist/room/participant/LocalParticipant.js.map +0 -1
  58. package/dist/room/participant/Participant.js +0 -189
  59. package/dist/room/participant/Participant.js.map +0 -1
  60. package/dist/room/participant/ParticipantTrackPermission.js +0 -16
  61. package/dist/room/participant/ParticipantTrackPermission.js.map +0 -1
  62. package/dist/room/participant/RemoteParticipant.js +0 -194
  63. package/dist/room/participant/RemoteParticipant.js.map +0 -1
  64. package/dist/room/participant/publishUtils.js +0 -189
  65. package/dist/room/participant/publishUtils.js.map +0 -1
  66. package/dist/room/participant/publishUtils.test.d.ts +0 -1
  67. package/dist/room/participant/publishUtils.test.js +0 -118
  68. package/dist/room/participant/publishUtils.test.js.map +0 -1
  69. package/dist/room/stats.js +0 -26
  70. package/dist/room/stats.js.map +0 -1
  71. package/dist/room/track/LocalAudioTrack.js +0 -153
  72. package/dist/room/track/LocalAudioTrack.js.map +0 -1
  73. package/dist/room/track/LocalTrack.js +0 -158
  74. package/dist/room/track/LocalTrack.js.map +0 -1
  75. package/dist/room/track/LocalTrackPublication.js +0 -64
  76. package/dist/room/track/LocalTrackPublication.js.map +0 -1
  77. package/dist/room/track/LocalVideoTrack.js +0 -297
  78. package/dist/room/track/LocalVideoTrack.js.map +0 -1
  79. package/dist/room/track/LocalVideoTrack.test.d.ts +0 -1
  80. package/dist/room/track/LocalVideoTrack.test.js +0 -68
  81. package/dist/room/track/LocalVideoTrack.test.js.map +0 -1
  82. package/dist/room/track/RemoteAudioTrack.js +0 -64
  83. package/dist/room/track/RemoteAudioTrack.js.map +0 -1
  84. package/dist/room/track/RemoteTrack.js +0 -49
  85. package/dist/room/track/RemoteTrack.js.map +0 -1
  86. package/dist/room/track/RemoteTrackPublication.js +0 -178
  87. package/dist/room/track/RemoteTrackPublication.js.map +0 -1
  88. package/dist/room/track/RemoteVideoTrack.js +0 -201
  89. package/dist/room/track/RemoteVideoTrack.js.map +0 -1
  90. package/dist/room/track/Track.js +0 -283
  91. package/dist/room/track/Track.js.map +0 -1
  92. package/dist/room/track/TrackPublication.js +0 -92
  93. package/dist/room/track/TrackPublication.js.map +0 -1
  94. package/dist/room/track/create.js +0 -131
  95. package/dist/room/track/create.js.map +0 -1
  96. package/dist/room/track/defaults.js +0 -21
  97. package/dist/room/track/defaults.js.map +0 -1
  98. package/dist/room/track/options.js +0 -100
  99. package/dist/room/track/options.js.map +0 -1
  100. package/dist/room/track/types.js +0 -3
  101. package/dist/room/track/types.js.map +0 -1
  102. package/dist/room/track/utils.js +0 -113
  103. package/dist/room/track/utils.js.map +0 -1
  104. package/dist/room/track/utils.test.d.ts +0 -1
  105. package/dist/room/track/utils.test.js +0 -85
  106. package/dist/room/track/utils.test.js.map +0 -1
  107. package/dist/room/utils.js +0 -79
  108. package/dist/room/utils.js.map +0 -1
  109. package/dist/version.js +0 -6
  110. package/dist/version.js.map +0 -1
  111. package/jest.config.js +0 -6
  112. package/src/api/RequestQueue.ts +0 -53
  113. package/src/api/SignalClient.ts +0 -499
  114. package/src/connect.ts +0 -100
  115. package/src/index.ts +0 -47
  116. package/src/logger.ts +0 -22
  117. package/src/options.ts +0 -149
  118. package/src/proto/google/protobuf/timestamp.ts +0 -232
  119. package/src/proto/livekit_models.ts +0 -2676
  120. package/src/proto/livekit_rtc.ts +0 -3269
  121. package/src/room/DeviceManager.ts +0 -57
  122. package/src/room/PCTransport.ts +0 -86
  123. package/src/room/RTCEngine.ts +0 -652
  124. package/src/room/Room.ts +0 -951
  125. package/src/room/errors.ts +0 -65
  126. package/src/room/events.ts +0 -424
  127. package/src/room/participant/LocalParticipant.ts +0 -734
  128. package/src/room/participant/Participant.ts +0 -269
  129. package/src/room/participant/ParticipantTrackPermission.ts +0 -32
  130. package/src/room/participant/RemoteParticipant.ts +0 -243
  131. package/src/room/participant/publishUtils.test.ts +0 -145
  132. package/src/room/participant/publishUtils.ts +0 -225
  133. package/src/room/stats.ts +0 -130
  134. package/src/room/track/LocalAudioTrack.ts +0 -137
  135. package/src/room/track/LocalTrack.ts +0 -161
  136. package/src/room/track/LocalTrackPublication.ts +0 -66
  137. package/src/room/track/LocalVideoTrack.test.ts +0 -70
  138. package/src/room/track/LocalVideoTrack.ts +0 -293
  139. package/src/room/track/RemoteAudioTrack.ts +0 -58
  140. package/src/room/track/RemoteTrack.ts +0 -62
  141. package/src/room/track/RemoteTrackPublication.ts +0 -198
  142. package/src/room/track/RemoteVideoTrack.ts +0 -235
  143. package/src/room/track/Track.ts +0 -343
  144. package/src/room/track/TrackPublication.ts +0 -120
  145. package/src/room/track/create.ts +0 -121
  146. package/src/room/track/defaults.ts +0 -23
  147. package/src/room/track/options.ts +0 -281
  148. package/src/room/track/types.ts +0 -20
  149. package/src/room/track/utils.test.ts +0 -93
  150. package/src/room/track/utils.ts +0 -115
  151. package/src/room/utils.ts +0 -70
  152. package/src/version.ts +0 -2
  153. package/tsconfig.eslint.json +0 -11
package/src/room/Room.ts DELETED
@@ -1,951 +0,0 @@
1
- import { EventEmitter } from 'events';
2
- import type TypedEmitter from 'typed-emitter';
3
- import { toProtoSessionDescription } from '../api/SignalClient';
4
- import log from '../logger';
5
- import { RoomConnectOptions, RoomOptions } from '../options';
6
- import {
7
- DataPacket_Kind, ParticipantInfo,
8
- ParticipantInfo_State, ParticipantPermission, Room as RoomModel, SpeakerInfo, UserPacket,
9
- } from '../proto/livekit_models';
10
- import {
11
- ConnectionQualityUpdate,
12
- JoinResponse,
13
- SimulateScenario,
14
- StreamStateUpdate,
15
- SubscriptionPermissionUpdate,
16
- } from '../proto/livekit_rtc';
17
- import DeviceManager from './DeviceManager';
18
- import { ConnectionError, UnsupportedServer } from './errors';
19
- import {
20
- EngineEvent, ParticipantEvent, RoomEvent, TrackEvent,
21
- } from './events';
22
- import LocalParticipant from './participant/LocalParticipant';
23
- import Participant, { ConnectionQuality } from './participant/Participant';
24
- import RemoteParticipant from './participant/RemoteParticipant';
25
- import RTCEngine, { maxICEConnectTimeout } from './RTCEngine';
26
- import { audioDefaults, publishDefaults, videoDefaults } from './track/defaults';
27
- import LocalTrackPublication from './track/LocalTrackPublication';
28
- import RemoteTrackPublication from './track/RemoteTrackPublication';
29
- import { Track } from './track/Track';
30
- import { TrackPublication } from './track/TrackPublication';
31
- import { AdaptiveStreamSettings, RemoteTrack } from './track/types';
32
- import { getNewAudioContext } from './track/utils';
33
- import { isWeb, unpackStreamId } from './utils';
34
-
35
- export enum RoomState {
36
- Disconnected = 'disconnected',
37
- Connected = 'connected',
38
- Reconnecting = 'reconnecting',
39
- }
40
-
41
- /**
42
- * In LiveKit, a room is the logical grouping for a list of participants.
43
- * Participants in a room can publish tracks, and subscribe to others' tracks.
44
- *
45
- * a Room fires [[RoomEvent | RoomEvents]].
46
- *
47
- * @noInheritDoc
48
- */
49
- class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) {
50
- state: RoomState = RoomState.Disconnected;
51
-
52
- /** map of sid: [[RemoteParticipant]] */
53
- participants: Map<string, RemoteParticipant>;
54
-
55
- /**
56
- * list of participants that are actively speaking. when this changes
57
- * a [[RoomEvent.ActiveSpeakersChanged]] event is fired
58
- */
59
- activeSpeakers: Participant[] = [];
60
-
61
- /** @internal */
62
- engine!: RTCEngine;
63
-
64
- // available after connected
65
- /** server assigned unique room id */
66
- sid: string = '';
67
-
68
- /** user assigned name, derived from JWT token */
69
- name: string = '';
70
-
71
- /** the current participant */
72
- localParticipant: LocalParticipant;
73
-
74
- /** room metadata */
75
- metadata: string | undefined = undefined;
76
-
77
- /** options of room */
78
- options: RoomOptions;
79
-
80
- /** connect options of room */
81
- private connOptions?: RoomConnectOptions;
82
-
83
- private audioEnabled = true;
84
-
85
- private audioContext?: AudioContext;
86
-
87
- /**
88
- * Creates a new Room, the primary construct for a LiveKit session.
89
- * @param options
90
- */
91
- constructor(options?: RoomOptions) {
92
- super();
93
- this.participants = new Map();
94
- this.options = options || {};
95
-
96
- switch (this.options?.publishDefaults?.videoCodec) {
97
- case 'av1':
98
- case 'vp9':
99
- this.options.publishDefaults.simulcast = undefined;
100
- break;
101
- default:
102
- }
103
-
104
- this.options.audioCaptureDefaults = {
105
- ...audioDefaults,
106
- ...options?.audioCaptureDefaults,
107
- };
108
- this.options.videoCaptureDefaults = {
109
- ...videoDefaults,
110
- ...options?.videoCaptureDefaults,
111
- };
112
- this.options.publishDefaults = {
113
- ...publishDefaults,
114
- ...options?.publishDefaults,
115
- };
116
-
117
- this.createEngine();
118
-
119
- this.localParticipant = new LocalParticipant(
120
- '', '', this.engine, this.options,
121
- );
122
- }
123
-
124
- private createEngine() {
125
- if (this.engine) {
126
- return;
127
- }
128
-
129
- this.engine = new RTCEngine();
130
-
131
- this.engine.client.signalLatency = this.options.expSignalLatency;
132
- this.engine.client.onParticipantUpdate = this.handleParticipantUpdates;
133
- this.engine.client.onRoomUpdate = this.handleRoomUpdate;
134
- this.engine.client.onSpeakersChanged = this.handleSpeakersChanged;
135
- this.engine.client.onStreamStateUpdate = this.handleStreamStateUpdate;
136
- this.engine.client.onSubscriptionPermissionUpdate = this.handleSubscriptionPermissionUpdate;
137
- this.engine.client.onConnectionQuality = this.handleConnectionQualityUpdate;
138
-
139
- this.engine
140
- .on(
141
- EngineEvent.MediaTrackAdded,
142
- (
143
- mediaTrack: MediaStreamTrack,
144
- stream: MediaStream,
145
- receiver?: RTCRtpReceiver,
146
- ) => {
147
- this.onTrackAdded(mediaTrack, stream, receiver);
148
- },
149
- )
150
- .on(EngineEvent.Disconnected, () => {
151
- this.handleDisconnect();
152
- })
153
- .on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate)
154
- .on(EngineEvent.DataPacketReceived, this.handleDataPacket)
155
- .on(EngineEvent.Resuming, () => {
156
- this.state = RoomState.Reconnecting;
157
- this.emit(RoomEvent.Reconnecting);
158
- this.emit(RoomEvent.StateChanged, this.state);
159
- })
160
- .on(EngineEvent.Resumed, () => {
161
- this.state = RoomState.Connected;
162
- this.emit(RoomEvent.Reconnected);
163
- this.emit(RoomEvent.StateChanged, this.state);
164
- this.updateSubscriptions();
165
- })
166
- .on(EngineEvent.SignalResumed, () => {
167
- if (this.state === RoomState.Reconnecting) {
168
- this.sendSyncState();
169
- }
170
- })
171
- .on(EngineEvent.Restarting, this.handleRestarting)
172
- .on(EngineEvent.Restarted, this.handleRestarted);
173
- }
174
-
175
- /**
176
- * getLocalDevices abstracts navigator.mediaDevices.enumerateDevices.
177
- * In particular, it handles Chrome's unique behavior of creating `default`
178
- * devices. When encountered, it'll be removed from the list of devices.
179
- * The actual default device will be placed at top.
180
- * @param kind
181
- * @returns a list of available local devices
182
- */
183
- static getLocalDevices(kind: MediaDeviceKind): Promise<MediaDeviceInfo[]> {
184
- return DeviceManager.getInstance().getDevices(kind);
185
- }
186
-
187
- connect = async (url: string, token: string, opts?: RoomConnectOptions) => {
188
- // guard against calling connect
189
- if (this.state !== RoomState.Disconnected) {
190
- log.warn('already connected to room', this.name);
191
- return;
192
- }
193
-
194
- // recreate engine if previously disconnected
195
- this.createEngine();
196
-
197
- this.acquireAudioContext();
198
-
199
- if (opts?.rtcConfig) {
200
- this.engine.rtcConfig = opts.rtcConfig;
201
- }
202
-
203
- this.connOptions = opts;
204
-
205
- try {
206
- const joinResponse = await this.engine.join(url, token, opts);
207
- log.debug(`connected to Livekit Server version: ${joinResponse.serverVersion}, region: ${joinResponse.serverRegion}`);
208
-
209
- if (!joinResponse.serverVersion) {
210
- throw new UnsupportedServer('unknown server version');
211
- }
212
-
213
- if (joinResponse.serverVersion === '0.15.1' && this.options.dynacast) {
214
- log.debug('disabling dynacast due to server version');
215
- // dynacast has a bug in 0.15.1, so we cannot use it then
216
- this.options.dynacast = false;
217
- }
218
-
219
- this.state = RoomState.Connected;
220
- this.emit(RoomEvent.StateChanged, this.state);
221
- const pi = joinResponse.participant!;
222
-
223
- this.localParticipant.sid = pi.sid;
224
- this.localParticipant.identity = pi.identity;
225
-
226
- this.localParticipant.updateInfo(pi);
227
- // forward metadata changed for the local participant
228
- this.localParticipant
229
- .on(ParticipantEvent.MetadataChanged, (metadata: string | undefined) => {
230
- this.emit(RoomEvent.MetadataChanged, metadata, this.localParticipant);
231
- })
232
- .on(ParticipantEvent.ParticipantMetadataChanged, (metadata: string | undefined) => {
233
- this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
234
- })
235
- .on(ParticipantEvent.TrackMuted, (pub: TrackPublication) => {
236
- this.emit(RoomEvent.TrackMuted, pub, this.localParticipant);
237
- })
238
- .on(ParticipantEvent.TrackUnmuted, (pub: TrackPublication) => {
239
- this.emit(RoomEvent.TrackUnmuted, pub, this.localParticipant);
240
- })
241
- .on(ParticipantEvent.LocalTrackPublished, (pub: LocalTrackPublication) => {
242
- this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant);
243
- })
244
- .on(ParticipantEvent.LocalTrackUnpublished, (pub: LocalTrackPublication) => {
245
- this.emit(RoomEvent.LocalTrackUnpublished, pub, this.localParticipant);
246
- })
247
- .on(ParticipantEvent.ConnectionQualityChanged, (quality: ConnectionQuality) => {
248
- this.emit(RoomEvent.ConnectionQualityChanged, quality, this.localParticipant);
249
- })
250
- .on(ParticipantEvent.MediaDevicesError, (e: Error) => {
251
- this.emit(RoomEvent.MediaDevicesError, e);
252
- })
253
- .on(ParticipantEvent.ParticipantPermissionsChanged,
254
- (prevPermissions: ParticipantPermission) => {
255
- this.emit(
256
- RoomEvent.ParticipantPermissionsChanged,
257
- prevPermissions,
258
- this.localParticipant,
259
- );
260
- });
261
-
262
- // populate remote participants, these should not trigger new events
263
- joinResponse.otherParticipants.forEach((info) => {
264
- this.getOrCreateParticipant(info.sid, info);
265
- });
266
-
267
- this.name = joinResponse.room!.name;
268
- this.sid = joinResponse.room!.sid;
269
- this.metadata = joinResponse.room!.metadata;
270
- } catch (err) {
271
- this.engine.close();
272
- throw err;
273
- }
274
-
275
- // don't return until ICE connected
276
- return new Promise<Room>((resolve, reject) => {
277
- const connectTimeout = setTimeout(() => {
278
- // timeout
279
- this.engine.close();
280
- reject(new ConnectionError('could not connect after timeout'));
281
- }, maxICEConnectTimeout);
282
-
283
- this.engine.once(EngineEvent.Connected, () => {
284
- clearTimeout(connectTimeout);
285
-
286
- // also hook unload event
287
- if (isWeb()) {
288
- window.addEventListener('beforeunload', this.onBeforeUnload);
289
- navigator.mediaDevices.addEventListener('devicechange', this.handleDeviceChange);
290
- }
291
-
292
- resolve(this);
293
- });
294
- });
295
- };
296
-
297
- /**
298
- * disconnects the room, emits [[RoomEvent.Disconnected]]
299
- */
300
- disconnect = (stopTracks = true) => {
301
- // send leave
302
- if (this.engine) {
303
- this.engine.client.sendLeave();
304
- this.engine.close();
305
- }
306
- this.handleDisconnect(stopTracks);
307
- /* @ts-ignore */
308
- this.engine = undefined;
309
- };
310
-
311
- /**
312
- * retrieves a participant by identity
313
- * @param identity
314
- * @returns
315
- */
316
- getParticipantByIdentity(identity: string): Participant | undefined {
317
- for (const [, p] of this.participants) {
318
- if (p.identity === identity) {
319
- return p;
320
- }
321
- }
322
- if (this.localParticipant.identity === identity) {
323
- return this.localParticipant;
324
- }
325
- }
326
-
327
- /**
328
- * @internal for testing
329
- */
330
- simulateScenario(scenario: string) {
331
- let req: SimulateScenario | undefined;
332
- switch (scenario) {
333
- case 'speaker':
334
- req = SimulateScenario.fromPartial({
335
- speakerUpdate: 3,
336
- });
337
- break;
338
- case 'node-failure':
339
- req = SimulateScenario.fromPartial({
340
- nodeFailure: true,
341
- });
342
- break;
343
- case 'server-leave':
344
- req = SimulateScenario.fromPartial({
345
- serverLeave: true,
346
- });
347
- break;
348
- case 'migration':
349
- req = SimulateScenario.fromPartial({
350
- migration: true,
351
- });
352
- break;
353
- default:
354
- }
355
- if (req) {
356
- this.engine.client.sendSimulateScenario(req);
357
- }
358
- }
359
-
360
- private onBeforeUnload = () => {
361
- this.disconnect();
362
- };
363
-
364
- /**
365
- * Browsers have different policies regarding audio playback. Most requiring
366
- * some form of user interaction (click/tap/etc).
367
- * In those cases, audio will be silent until a click/tap triggering one of the following
368
- * - `startAudio`
369
- * - `getUserMedia`
370
- */
371
- async startAudio() {
372
- this.acquireAudioContext();
373
-
374
- const elements: Array<HTMLMediaElement> = [];
375
- this.participants.forEach((p) => {
376
- p.audioTracks.forEach((t) => {
377
- if (t.track) {
378
- t.track.attachedElements.forEach((e) => {
379
- elements.push(e);
380
- });
381
- }
382
- });
383
- });
384
-
385
- try {
386
- await Promise.all(elements.map((e) => e.play()));
387
- this.handleAudioPlaybackStarted();
388
- } catch (err) {
389
- this.handleAudioPlaybackFailed(err);
390
- throw err;
391
- }
392
- }
393
-
394
- /**
395
- * Returns true if audio playback is enabled
396
- */
397
- get canPlaybackAudio(): boolean {
398
- return this.audioEnabled;
399
- }
400
-
401
- /**
402
- * Switches all active device used in this room to the given device.
403
- *
404
- * Note: setting AudioOutput is not supported on some browsers. See [setSinkId](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId#browser_compatibility)
405
- *
406
- * @param kind use `videoinput` for camera track,
407
- * `audioinput` for microphone track,
408
- * `audiooutput` to set speaker for all incoming audio tracks
409
- * @param deviceId
410
- */
411
- async switchActiveDevice(kind: MediaDeviceKind, deviceId: string) {
412
- if (kind === 'audioinput') {
413
- const tracks = Array
414
- .from(this.localParticipant.audioTracks.values())
415
- .filter((track) => track.source === Track.Source.Microphone);
416
- await Promise.all(tracks.map((t) => t.audioTrack?.setDeviceId(deviceId)));
417
- this.options.audioCaptureDefaults!.deviceId = deviceId;
418
- } else if (kind === 'videoinput') {
419
- const tracks = Array
420
- .from(this.localParticipant.videoTracks.values())
421
- .filter((track) => track.source === Track.Source.Camera);
422
- await Promise.all(tracks.map((t) => t.videoTrack?.setDeviceId(deviceId)));
423
- this.options.videoCaptureDefaults!.deviceId = deviceId;
424
- } else if (kind === 'audiooutput') {
425
- const elements: HTMLMediaElement[] = [];
426
- this.participants.forEach((p) => {
427
- p.audioTracks.forEach((t) => {
428
- if (t.isSubscribed && t.track) {
429
- t.track.attachedElements.forEach((e) => {
430
- elements.push(e);
431
- });
432
- }
433
- });
434
- });
435
-
436
- await Promise.all(elements.map(async (e) => {
437
- if ('setSinkId' in e) {
438
- /* @ts-ignore */
439
- await e.setSinkId(deviceId);
440
- }
441
- }));
442
- }
443
- }
444
-
445
- private onTrackAdded(
446
- mediaTrack: MediaStreamTrack,
447
- stream: MediaStream,
448
- receiver?: RTCRtpReceiver,
449
- ) {
450
- const parts = unpackStreamId(stream.id);
451
- const participantId = parts[0];
452
- let trackId = parts[1];
453
- if (!trackId || trackId === '') trackId = mediaTrack.id;
454
-
455
- const participant = this.getOrCreateParticipant(participantId);
456
- let adaptiveStreamSettings: AdaptiveStreamSettings | undefined;
457
- if (this.options.adaptiveStream) {
458
- if (typeof this.options.adaptiveStream === 'object') {
459
- adaptiveStreamSettings = this.options.adaptiveStream;
460
- } else {
461
- adaptiveStreamSettings = {};
462
- }
463
- }
464
- participant.addSubscribedMediaTrack(
465
- mediaTrack,
466
- trackId,
467
- stream,
468
- receiver,
469
- adaptiveStreamSettings,
470
- );
471
- }
472
-
473
- private handleRestarting = () => {
474
- this.state = RoomState.Reconnecting;
475
- this.emit(RoomEvent.Reconnecting);
476
- this.emit(RoomEvent.StateChanged, this.state);
477
-
478
- // also unwind existing participants & existing subscriptions
479
- for (const p of this.participants.values()) {
480
- this.handleParticipantDisconnected(p.sid, p);
481
- }
482
- };
483
-
484
- private handleRestarted = async (joinResponse: JoinResponse) => {
485
- log.debug('reconnected to server region', joinResponse.serverRegion);
486
- this.state = RoomState.Connected;
487
- this.emit(RoomEvent.Reconnected);
488
- this.emit(RoomEvent.StateChanged, this.state);
489
-
490
- // rehydrate participants
491
- if (joinResponse.participant) {
492
- // with a restart, the sid will have changed, we'll map our understanding to it
493
- this.localParticipant.sid = joinResponse.participant.sid;
494
- this.handleParticipantUpdates([joinResponse.participant]);
495
- }
496
- this.handleParticipantUpdates(joinResponse.otherParticipants);
497
-
498
- // unpublish & republish tracks
499
- const localPubs: LocalTrackPublication[] = [];
500
- this.localParticipant.tracks.forEach((pub) => {
501
- if (pub.track) {
502
- localPubs.push(pub);
503
- }
504
- });
505
-
506
- await Promise.all(localPubs.map(async (pub) => {
507
- const track = pub.track!;
508
- this.localParticipant.unpublishTrack(track, false);
509
- this.localParticipant.publishTrack(track, pub.options);
510
- }));
511
- };
512
-
513
- private handleDisconnect(shouldStopTracks = true) {
514
- if (this.state === RoomState.Disconnected) {
515
- return;
516
- }
517
- this.participants.forEach((p) => {
518
- p.tracks.forEach((pub) => {
519
- p.unpublishTrack(pub.trackSid);
520
- });
521
- });
522
-
523
- this.localParticipant.tracks.forEach((pub) => {
524
- if (pub.track) {
525
- this.localParticipant.unpublishTrack(pub.track);
526
- }
527
- if (shouldStopTracks) {
528
- pub.track?.detach();
529
- pub.track?.stop();
530
- }
531
- });
532
-
533
- this.participants.clear();
534
- this.activeSpeakers = [];
535
- if (this.audioContext) {
536
- this.audioContext.close();
537
- this.audioContext = undefined;
538
- }
539
- if (isWeb()) {
540
- window.removeEventListener('beforeunload', this.onBeforeUnload);
541
- navigator.mediaDevices.removeEventListener('devicechange', this.handleDeviceChange);
542
- }
543
- this.state = RoomState.Disconnected;
544
- this.emit(RoomEvent.Disconnected);
545
- this.emit(RoomEvent.StateChanged, this.state);
546
- }
547
-
548
- private handleParticipantUpdates = (participantInfos: ParticipantInfo[]) => {
549
- // handle changes to participant state, and send events
550
- participantInfos.forEach((info) => {
551
- if (info.sid === this.localParticipant.sid
552
- || info.identity === this.localParticipant.identity) {
553
- this.localParticipant.updateInfo(info);
554
- return;
555
- }
556
-
557
- let remoteParticipant = this.participants.get(info.sid);
558
- const isNewParticipant = !remoteParticipant;
559
-
560
- // create participant if doesn't exist
561
- remoteParticipant = this.getOrCreateParticipant(info.sid, info);
562
-
563
- // when it's disconnected, send updates
564
- if (info.state === ParticipantInfo_State.DISCONNECTED) {
565
- this.handleParticipantDisconnected(info.sid, remoteParticipant);
566
- } else if (isNewParticipant) {
567
- // fire connected event
568
- this.emit(RoomEvent.ParticipantConnected, remoteParticipant);
569
- } else {
570
- // just update, no events
571
- remoteParticipant.updateInfo(info);
572
- }
573
- });
574
- };
575
-
576
- private handleParticipantDisconnected(
577
- sid: string,
578
- participant?: RemoteParticipant,
579
- ) {
580
- // remove and send event
581
- this.participants.delete(sid);
582
- if (!participant) {
583
- return;
584
- }
585
-
586
- participant.tracks.forEach((publication) => {
587
- participant.unpublishTrack(publication.trackSid);
588
- });
589
- this.emit(RoomEvent.ParticipantDisconnected, participant);
590
- }
591
-
592
- // updates are sent only when there's a change to speaker ordering
593
- private handleActiveSpeakersUpdate = (speakers: SpeakerInfo[]) => {
594
- const activeSpeakers: Participant[] = [];
595
- const seenSids: any = {};
596
- speakers.forEach((speaker) => {
597
- seenSids[speaker.sid] = true;
598
- if (speaker.sid === this.localParticipant.sid) {
599
- this.localParticipant.audioLevel = speaker.level;
600
- this.localParticipant.setIsSpeaking(true);
601
- activeSpeakers.push(this.localParticipant);
602
- } else {
603
- const p = this.participants.get(speaker.sid);
604
- if (p) {
605
- p.audioLevel = speaker.level;
606
- p.setIsSpeaking(true);
607
- activeSpeakers.push(p);
608
- }
609
- }
610
- });
611
-
612
- if (!seenSids[this.localParticipant.sid]) {
613
- this.localParticipant.audioLevel = 0;
614
- this.localParticipant.setIsSpeaking(false);
615
- }
616
- this.participants.forEach((p) => {
617
- if (!seenSids[p.sid]) {
618
- p.audioLevel = 0;
619
- p.setIsSpeaking(false);
620
- }
621
- });
622
-
623
- this.activeSpeakers = activeSpeakers;
624
- this.emit(RoomEvent.ActiveSpeakersChanged, activeSpeakers);
625
- };
626
-
627
- // process list of changed speakers
628
- private handleSpeakersChanged = (speakerUpdates: SpeakerInfo[]) => {
629
- const lastSpeakers = new Map<string, Participant>();
630
- this.activeSpeakers.forEach((p) => {
631
- lastSpeakers.set(p.sid, p);
632
- });
633
- speakerUpdates.forEach((speaker) => {
634
- let p: Participant | undefined = this.participants.get(speaker.sid);
635
- if (speaker.sid === this.localParticipant.sid) {
636
- p = this.localParticipant;
637
- }
638
- if (!p) {
639
- return;
640
- }
641
- p.audioLevel = speaker.level;
642
- p.setIsSpeaking(speaker.active);
643
-
644
- if (speaker.active) {
645
- lastSpeakers.set(speaker.sid, p);
646
- } else {
647
- lastSpeakers.delete(speaker.sid);
648
- }
649
- });
650
- const activeSpeakers = Array.from(lastSpeakers.values());
651
- activeSpeakers.sort((a, b) => b.audioLevel - a.audioLevel);
652
- this.activeSpeakers = activeSpeakers;
653
- this.emit(RoomEvent.ActiveSpeakersChanged, activeSpeakers);
654
- };
655
-
656
- private handleStreamStateUpdate = (streamStateUpdate: StreamStateUpdate) => {
657
- streamStateUpdate.streamStates.forEach((streamState) => {
658
- const participant = this.participants.get(streamState.participantSid);
659
- if (!participant) {
660
- return;
661
- }
662
- const pub = participant.getTrackPublication(streamState.trackSid);
663
- if (!pub || !pub.track) {
664
- return;
665
- }
666
- pub.track.streamState = Track.streamStateFromProto(streamState.state);
667
- participant.emit(ParticipantEvent.TrackStreamStateChanged, pub, pub.track.streamState);
668
- this.emit(ParticipantEvent.TrackStreamStateChanged, pub, pub.track.streamState, participant);
669
- });
670
- };
671
-
672
- private handleSubscriptionPermissionUpdate = (update: SubscriptionPermissionUpdate) => {
673
- const participant = this.participants.get(update.participantSid);
674
- if (!participant) {
675
- return;
676
- }
677
- const pub = participant.getTrackPublication(update.trackSid);
678
- if (!pub) {
679
- return;
680
- }
681
-
682
- pub._allowed = update.allowed;
683
- participant.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, pub,
684
- pub.subscriptionStatus);
685
- this.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, pub,
686
- pub.subscriptionStatus, participant);
687
- };
688
-
689
- private handleDataPacket = (
690
- userPacket: UserPacket,
691
- kind: DataPacket_Kind,
692
- ) => {
693
- // find the participant
694
- const participant = this.participants.get(userPacket.participantSid);
695
-
696
- this.emit(RoomEvent.DataReceived, userPacket.payload, participant, kind);
697
-
698
- // also emit on the participant
699
- participant?.emit(ParticipantEvent.DataReceived, userPacket.payload, kind);
700
- };
701
-
702
- private handleAudioPlaybackStarted = () => {
703
- if (this.canPlaybackAudio) {
704
- return;
705
- }
706
- this.audioEnabled = true;
707
- this.emit(RoomEvent.AudioPlaybackStatusChanged, true);
708
- };
709
-
710
- private handleAudioPlaybackFailed = (e: any) => {
711
- log.warn('could not playback audio', e);
712
- if (!this.canPlaybackAudio) {
713
- return;
714
- }
715
- this.audioEnabled = false;
716
- this.emit(RoomEvent.AudioPlaybackStatusChanged, false);
717
- };
718
-
719
- private handleDeviceChange = async () => {
720
- this.emit(RoomEvent.MediaDevicesChanged);
721
- };
722
-
723
- private handleRoomUpdate = (r: RoomModel) => {
724
- this.metadata = r.metadata;
725
- this.emit(RoomEvent.RoomMetadataChanged, r.metadata);
726
- };
727
-
728
- private handleConnectionQualityUpdate = (update: ConnectionQualityUpdate) => {
729
- update.updates.forEach((info) => {
730
- if (info.participantSid === this.localParticipant.sid) {
731
- this.localParticipant.setConnectionQuality(info.quality);
732
- return;
733
- }
734
- const participant = this.participants.get(info.participantSid);
735
- if (participant) {
736
- participant.setConnectionQuality(info.quality);
737
- }
738
- });
739
- };
740
-
741
- private acquireAudioContext() {
742
- if (this.audioContext) {
743
- this.audioContext.close();
744
- }
745
- // by using an AudioContext, it reduces lag on audio elements
746
- // https://stackoverflow.com/questions/9811429/html5-audio-tag-on-safari-has-a-delay/54119854#54119854
747
- const ctx = getNewAudioContext();
748
- if (ctx) {
749
- this.audioContext = ctx;
750
- }
751
- }
752
-
753
- private createParticipant(id: string, info?: ParticipantInfo): RemoteParticipant {
754
- let participant: RemoteParticipant;
755
- if (info) {
756
- participant = RemoteParticipant.fromParticipantInfo(
757
- this.engine.client,
758
- info,
759
- );
760
- } else {
761
- participant = new RemoteParticipant(this.engine.client, id, '');
762
- }
763
- return participant;
764
- }
765
-
766
- private getOrCreateParticipant(
767
- id: string,
768
- info?: ParticipantInfo,
769
- ): RemoteParticipant {
770
- if (this.participants.has(id)) {
771
- return (this.participants.get(id) as RemoteParticipant);
772
- }
773
- // it's possible for the RTC track to arrive before signaling data
774
- // when this happens, we'll create the participant and make the track work
775
- const participant = this.createParticipant(id, info);
776
- this.participants.set(id, participant);
777
-
778
- // also forward events
779
- // trackPublished is only fired for tracks added after both local participant
780
- // and remote participant joined the room
781
- participant
782
- .on(ParticipantEvent.TrackPublished, (trackPublication: RemoteTrackPublication) => {
783
- this.emit(RoomEvent.TrackPublished, trackPublication, participant);
784
- })
785
- .on(ParticipantEvent.TrackSubscribed,
786
- (track: RemoteTrack, publication: RemoteTrackPublication) => {
787
- // monitor playback status
788
- if (track.kind === Track.Kind.Audio) {
789
- track.on(TrackEvent.AudioPlaybackStarted, this.handleAudioPlaybackStarted);
790
- track.on(TrackEvent.AudioPlaybackFailed, this.handleAudioPlaybackFailed);
791
- }
792
- this.emit(RoomEvent.TrackSubscribed, track, publication, participant);
793
- })
794
- .on(ParticipantEvent.TrackUnpublished, (publication: RemoteTrackPublication) => {
795
- this.emit(RoomEvent.TrackUnpublished, publication, participant);
796
- })
797
- .on(ParticipantEvent.TrackUnsubscribed,
798
- (track: RemoteTrack, publication: RemoteTrackPublication) => {
799
- this.emit(RoomEvent.TrackUnsubscribed, track, publication, participant);
800
- })
801
- .on(ParticipantEvent.TrackSubscriptionFailed, (sid: string) => {
802
- this.emit(RoomEvent.TrackSubscriptionFailed, sid, participant);
803
- })
804
- .on(ParticipantEvent.TrackMuted, (pub: TrackPublication) => {
805
- this.emit(RoomEvent.TrackMuted, pub, participant);
806
- })
807
- .on(ParticipantEvent.TrackUnmuted, (pub: TrackPublication) => {
808
- this.emit(RoomEvent.TrackUnmuted, pub, participant);
809
- })
810
- .on(ParticipantEvent.MetadataChanged, (metadata: string | undefined) => {
811
- this.emit(RoomEvent.MetadataChanged, metadata, participant);
812
- })
813
- .on(ParticipantEvent.ParticipantMetadataChanged, (metadata: string | undefined) => {
814
- this.emit(RoomEvent.ParticipantMetadataChanged, metadata, participant);
815
- })
816
- .on(ParticipantEvent.ConnectionQualityChanged, (quality: ConnectionQuality) => {
817
- this.emit(RoomEvent.ConnectionQualityChanged, quality, participant);
818
- })
819
- .on(ParticipantEvent.ParticipantPermissionsChanged,
820
- (prevPermissions: ParticipantPermission) => {
821
- this.emit(RoomEvent.ParticipantPermissionsChanged, prevPermissions, participant);
822
- });
823
- return participant;
824
- }
825
-
826
- private sendSyncState() {
827
- if (this.engine.subscriber === undefined
828
- || this.engine.subscriber.pc.localDescription === null) {
829
- return;
830
- }
831
- const previousSdp = this.engine.subscriber.pc.localDescription;
832
-
833
- /* 1. autosubscribe on, so subscribed tracks = all tracks - unsub tracks,
834
- in this case, we send unsub tracks, so server add all tracks to this
835
- subscribe pc and unsub special tracks from it.
836
- 2. autosubscribe off, we send subscribed tracks.
837
- */
838
- const sendUnsub = this.connOptions?.autoSubscribe || false;
839
- const trackSids = new Array<string>();
840
- this.participants.forEach((participant) => {
841
- participant.tracks.forEach((track) => {
842
- if (track.isSubscribed !== sendUnsub) {
843
- trackSids.push(track.trackSid);
844
- }
845
- });
846
- });
847
-
848
- this.engine.client.sendSyncState({
849
- answer: toProtoSessionDescription({
850
- sdp: previousSdp.sdp,
851
- type: previousSdp.type,
852
- }),
853
- subscription: {
854
- trackSids,
855
- subscribe: !sendUnsub,
856
- participantTracks: [],
857
- },
858
- publishTracks: this.localParticipant.publishedTracksInfo(),
859
- dataChannels: this.localParticipant.dataChannelsInfo(),
860
- });
861
- }
862
-
863
- /**
864
- * After resuming, we'll need to notify the server of the current
865
- * subscription settings.
866
- */
867
- private updateSubscriptions() {
868
- for (const p of this.participants.values()) {
869
- for (const pub of p.videoTracks.values()) {
870
- if (pub.isSubscribed && pub instanceof RemoteTrackPublication) {
871
- pub.emitTrackUpdate();
872
- }
873
- }
874
- }
875
- }
876
-
877
- // /** @internal */
878
- emit<E extends keyof RoomEventCallbacks>(
879
- event: E, ...args: Parameters<RoomEventCallbacks[E]>
880
- ): boolean {
881
- log.debug('room event', event, ...args);
882
- return super.emit(event, ...args);
883
- }
884
- }
885
-
886
- export default Room;
887
-
888
- export type RoomEventCallbacks = {
889
- reconnecting: () => void,
890
- reconnected: () => void,
891
- disconnected: () => void,
892
- stateChanged: (state: RoomState) => void,
893
- mediaDevicesChanged: () => void,
894
- participantConnected: (participant: RemoteParticipant) => void,
895
- participantDisconnected: (participant: RemoteParticipant) => void,
896
- trackPublished: (publication: RemoteTrackPublication, participant: RemoteParticipant) => void,
897
- trackSubscribed: (
898
- track: RemoteTrack,
899
- publication: RemoteTrackPublication,
900
- participant: RemoteParticipant
901
- ) => void,
902
- trackSubscriptionFailed: (trackSid: string, participant: RemoteParticipant) => void,
903
- trackUnpublished: (publication: RemoteTrackPublication, participant: RemoteParticipant) => void,
904
- trackUnsubscribed: (
905
- track: RemoteTrack,
906
- publication: RemoteTrackPublication,
907
- participant: RemoteParticipant,
908
- ) => void,
909
- trackMuted: (publication: TrackPublication, participant: Participant) => void,
910
- trackUnmuted: (publication: TrackPublication, participant: Participant) => void,
911
- localTrackPublished: (publication: LocalTrackPublication, participant: LocalParticipant) => void,
912
- localTrackUnpublished: (
913
- publication: LocalTrackPublication,
914
- participant: LocalParticipant
915
- ) => void,
916
- /**
917
- * @deprecated use [[participantMetadataChanged]] instead
918
- */
919
- metadataChanged: (
920
- metadata: string | undefined,
921
- participant?: RemoteParticipant | LocalParticipant
922
- ) => void,
923
- participantMetadataChanged: (
924
- metadata: string | undefined,
925
- participant: RemoteParticipant | LocalParticipant
926
- ) => void,
927
- participantPermissionsChanged: (
928
- prevPermissions: ParticipantPermission,
929
- participant: RemoteParticipant | LocalParticipant
930
- ) => void,
931
- activeSpeakersChanged: (speakers: Array<Participant>) => void,
932
- roomMetadataChanged: (metadata: string) => void,
933
- dataReceived: (
934
- payload: Uint8Array,
935
- participant?: RemoteParticipant,
936
- kind?: DataPacket_Kind
937
- ) => void,
938
- connectionQualityChanged: (quality: ConnectionQuality, participant: Participant) => void,
939
- mediaDevicesError: (error: Error) => void,
940
- trackStreamStateChanged: (
941
- publication: RemoteTrackPublication,
942
- streamState: Track.StreamState,
943
- participant: RemoteParticipant,
944
- ) => void,
945
- trackSubscriptionPermissionChanged: (
946
- publication: RemoteTrackPublication,
947
- status: TrackPublication.SubscriptionStatus,
948
- participant: RemoteParticipant,
949
- ) => void,
950
- audioPlaybackChanged: (playing: boolean) => void,
951
- };