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