livekit-client 0.16.0 → 0.16.4

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 (60) hide show
  1. package/dist/api/RequestQueue.d.ts +12 -0
  2. package/dist/api/RequestQueue.js +61 -0
  3. package/dist/api/RequestQueue.js.map +1 -0
  4. package/dist/api/SignalClient.d.ts +4 -1
  5. package/dist/api/SignalClient.js +14 -1
  6. package/dist/api/SignalClient.js.map +1 -1
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/options.d.ts +0 -10
  11. package/dist/room/RTCEngine.d.ts +19 -4
  12. package/dist/room/RTCEngine.js +27 -5
  13. package/dist/room/RTCEngine.js.map +1 -1
  14. package/dist/room/Room.d.ts +43 -6
  15. package/dist/room/Room.js +62 -51
  16. package/dist/room/Room.js.map +1 -1
  17. package/dist/room/events.d.ts +8 -1
  18. package/dist/room/events.js +10 -3
  19. package/dist/room/events.js.map +1 -1
  20. package/dist/room/participant/LocalParticipant.js +2 -3
  21. package/dist/room/participant/LocalParticipant.js.map +1 -1
  22. package/dist/room/participant/Participant.d.ts +30 -4
  23. package/dist/room/participant/Participant.js +2 -2
  24. package/dist/room/participant/Participant.js.map +1 -1
  25. package/dist/room/participant/RemoteParticipant.d.ts +3 -4
  26. package/dist/room/participant/RemoteParticipant.js.map +1 -1
  27. package/dist/room/track/LocalAudioTrack.js +8 -1
  28. package/dist/room/track/LocalAudioTrack.js.map +1 -1
  29. package/dist/room/track/LocalVideoTrack.d.ts +1 -5
  30. package/dist/room/track/LocalVideoTrack.js +12 -117
  31. package/dist/room/track/LocalVideoTrack.js.map +1 -1
  32. package/dist/room/track/RemoteVideoTrack.js +12 -7
  33. package/dist/room/track/RemoteVideoTrack.js.map +1 -1
  34. package/dist/room/track/Track.d.ts +16 -3
  35. package/dist/room/track/Track.js +24 -18
  36. package/dist/room/track/Track.js.map +1 -1
  37. package/dist/room/track/types.d.ts +4 -4
  38. package/dist/room/utils.d.ts +1 -0
  39. package/dist/room/utils.js +5 -21
  40. package/dist/room/utils.js.map +1 -1
  41. package/dist/version.d.ts +1 -1
  42. package/dist/version.js +1 -1
  43. package/package.json +2 -3
  44. package/src/api/RequestQueue.ts +53 -0
  45. package/src/api/SignalClient.ts +19 -1
  46. package/src/index.ts +1 -1
  47. package/src/options.ts +0 -12
  48. package/src/room/RTCEngine.ts +54 -7
  49. package/src/room/Room.ts +135 -59
  50. package/src/room/events.ts +10 -1
  51. package/src/room/participant/LocalParticipant.ts +1 -2
  52. package/src/room/participant/Participant.ts +39 -4
  53. package/src/room/participant/RemoteParticipant.ts +6 -4
  54. package/src/room/track/LocalAudioTrack.ts +8 -1
  55. package/src/room/track/LocalVideoTrack.ts +11 -142
  56. package/src/room/track/RemoteVideoTrack.ts +14 -7
  57. package/src/room/track/Track.ts +39 -23
  58. package/src/room/track/types.ts +4 -4
  59. package/src/room/utils.ts +4 -16
  60. package/src/version.ts +1 -1
@@ -21,6 +21,7 @@ import {
21
21
  } from '../proto/livekit_rtc';
22
22
  import { ConnectionError } from '../room/errors';
23
23
  import { getClientInfo, sleep } from '../room/utils';
24
+ import Queue from './RequestQueue';
24
25
 
25
26
  // internal options
26
27
  interface ConnectOpts {
@@ -38,6 +39,10 @@ export interface SignalOptions {
38
39
  export class SignalClient {
39
40
  isConnected: boolean;
40
41
 
42
+ isReconnecting: boolean;
43
+
44
+ requestQueue: Queue;
45
+
41
46
  useJSON: boolean;
42
47
 
43
48
  /** simulate signaling latency by delaying messages */
@@ -80,7 +85,9 @@ export class SignalClient {
80
85
 
81
86
  constructor(useJSON: boolean = false) {
82
87
  this.isConnected = false;
88
+ this.isReconnecting = false;
83
89
  this.useJSON = useJSON;
90
+ this.requestQueue = new Queue();
84
91
  }
85
92
 
86
93
  async join(
@@ -98,9 +105,12 @@ export class SignalClient {
98
105
  }
99
106
 
100
107
  async reconnect(url: string, token: string): Promise<void> {
108
+ this.isReconnecting = true;
101
109
  await this.connect(url, token, {
102
110
  reconnect: true,
103
111
  });
112
+ this.isReconnecting = false;
113
+ this.requestQueue.run();
104
114
  }
105
115
 
106
116
  connect(
@@ -286,7 +296,15 @@ export class SignalClient {
286
296
  this.sendRequest(SignalRequest.fromPartial({ leave: {} }));
287
297
  }
288
298
 
289
- async sendRequest(req: SignalRequest) {
299
+ async sendRequest(req: SignalRequest, fromQueue: boolean = false) {
300
+ // capture all requests while reconnecting and put them in a queue.
301
+ // keep order by queueing up new events as long as the queue is not empty
302
+ // unless the request originates from the queue, then don't enqueue again
303
+
304
+ if ((this.isReconnecting && !req.simulate) || (!this.requestQueue.isEmpty() && !fromQueue)) {
305
+ this.requestQueue.enqueue(() => this.sendRequest(req, true));
306
+ return;
307
+ }
290
308
  if (this.signalLatency) {
291
309
  await sleep(this.signalLatency);
292
310
  }
package/src/index.ts CHANGED
@@ -29,7 +29,6 @@ export {
29
29
  Room,
30
30
  RoomState,
31
31
  DataPacket_Kind,
32
- ConnectionQuality,
33
32
  Participant,
34
33
  RemoteParticipant,
35
34
  LocalParticipant,
@@ -44,4 +43,5 @@ export {
44
43
  ParticipantTrackPermission,
45
44
  TrackPublication,
46
45
  VideoQuality,
46
+ ConnectionQuality,
47
47
  };
package/src/options.ts CHANGED
@@ -46,12 +46,6 @@ export interface RoomOptions {
46
46
  */
47
47
  stopLocalTrackOnUnpublish?: boolean;
48
48
 
49
- /**
50
- * @internal
51
- * experimental flag, disable client managed layer pause when publishing capability is limited
52
- */
53
- expDisableLayerPause?: boolean;
54
-
55
49
  /**
56
50
  * @internal
57
51
  * experimental flag, introduce a delay before sending signaling messages
@@ -143,10 +137,4 @@ export interface ConnectOptions extends CreateLocalTracksOptions {
143
137
  * set this to false if you would prefer to clean up unpublished local tracks manually.
144
138
  */
145
139
  stopLocalTrackOnUnpublish?: boolean;
146
-
147
- /**
148
- * @internal
149
- * experimental flag, disable client managed layer pause when publishing capability is limited
150
- */
151
- expDisableLayerPause?: boolean;
152
140
  }
@@ -1,7 +1,10 @@
1
1
  import { EventEmitter } from 'events';
2
+ import type TypedEventEmitter from 'typed-emitter';
2
3
  import { SignalClient, SignalOptions } from '../api/SignalClient';
3
4
  import log from '../logger';
4
- import { DataPacket, DataPacket_Kind, TrackInfo } from '../proto/livekit_models';
5
+ import {
6
+ DataPacket, DataPacket_Kind, SpeakerInfo, TrackInfo, UserPacket,
7
+ } from '../proto/livekit_models';
5
8
  import {
6
9
  AddTrackRequest, JoinResponse,
7
10
  LeaveRequest,
@@ -11,7 +14,7 @@ import {
11
14
  import { ConnectionError, TrackInvalidError, UnexpectedConnectionState } from './errors';
12
15
  import { EngineEvent } from './events';
13
16
  import PCTransport from './PCTransport';
14
- import { sleep } from './utils';
17
+ import { isFireFox, sleep } from './utils';
15
18
 
16
19
  const lossyDataChannel = '_lossy';
17
20
  const reliableDataChannel = '_reliable';
@@ -21,7 +24,9 @@ const maxReconnectDuration = 60 * 1000;
21
24
  export const maxICEConnectTimeout = 15 * 1000;
22
25
 
23
26
  /** @internal */
24
- export default class RTCEngine extends EventEmitter {
27
+ export default class RTCEngine extends (
28
+ EventEmitter as new () => TypedEventEmitter<EngineEventCallbacks>
29
+ ) {
25
30
  publisher?: PCTransport;
26
31
 
27
32
  subscriber?: PCTransport;
@@ -164,6 +169,8 @@ export default class RTCEngine extends EventEmitter {
164
169
  this.publisher = new PCTransport(this.rtcConfig);
165
170
  this.subscriber = new PCTransport(this.rtcConfig);
166
171
 
172
+ this.emit(EngineEvent.TransportsCreated, this.publisher, this.subscriber);
173
+
167
174
  this.publisher.pc.onicecandidate = (ev) => {
168
175
  if (!ev.candidate) return;
169
176
  log.trace('adding ICE candidate for peer', ev.candidate);
@@ -186,16 +193,18 @@ export default class RTCEngine extends EventEmitter {
186
193
  this.subscriber.pc.ondatachannel = this.handleDataChannel;
187
194
  }
188
195
  this.primaryPC = primaryPC;
189
- primaryPC.onconnectionstatechange = () => {
196
+ primaryPC.onconnectionstatechange = async () => {
190
197
  if (primaryPC.connectionState === 'connected') {
191
198
  log.trace('pc connected');
199
+ try {
200
+ this.connectedServerAddr = await getConnectedAddress(primaryPC);
201
+ } catch (e) {
202
+ log.warn('could not get connected server address', e);
203
+ }
192
204
  if (!this.pcConnected) {
193
205
  this.pcConnected = true;
194
206
  this.emit(EngineEvent.Connected);
195
207
  }
196
- getConnectedAddress(primaryPC).then((v) => {
197
- this.connectedServerAddr = v;
198
- });
199
208
  } else if (primaryPC.connectionState === 'failed') {
200
209
  // on Safari, PeerConnection will switch to 'disconnected' during renegotiation
201
210
  log.trace('pc disconnected');
@@ -225,6 +234,10 @@ export default class RTCEngine extends EventEmitter {
225
234
  this.lossyDC.onmessage = this.handleDataMessage;
226
235
  this.reliableDC.onmessage = this.handleDataMessage;
227
236
 
237
+ // handle datachannel errors
238
+ this.lossyDC.onerror = this.handleDataError;
239
+ this.reliableDC.onerror = this.handleDataError;
240
+
228
241
  // configure signaling client
229
242
  this.client.onAnswer = async (sd) => {
230
243
  if (!this.publisher) {
@@ -333,6 +346,18 @@ export default class RTCEngine extends EventEmitter {
333
346
  }
334
347
  };
335
348
 
349
+ private handleDataError = (event: Event) => {
350
+ const channel = event.currentTarget as RTCDataChannel;
351
+ const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable';
352
+
353
+ if (event instanceof ErrorEvent) {
354
+ const { error } = event.error;
355
+ log.error(`DataChannel error on ${channelKind}: ${event.message}`, error);
356
+ } else {
357
+ log.error(`Unknown DataChannel Error on ${channelKind}`, event);
358
+ }
359
+ };
360
+
336
361
  // websocket reconnect behavior. if websocket is interrupted, and the PeerConnection
337
362
  // continues to work, we can reconnect to websocket to continue the session
338
363
  // after a number of retries, we'll close and give up permanently
@@ -351,6 +376,10 @@ export default class RTCEngine extends EventEmitter {
351
376
  if (this.isClosed) {
352
377
  return;
353
378
  }
379
+ if (isFireFox()) {
380
+ // FF does not support DTLS restart.
381
+ this.fullReconnect = true;
382
+ }
354
383
 
355
384
  try {
356
385
  if (this.fullReconnect) {
@@ -580,3 +609,21 @@ async function getConnectedAddress(pc: RTCPeerConnection): Promise<string | unde
580
609
 
581
610
  class SignalReconnectError extends Error {
582
611
  }
612
+
613
+ export type EngineEventCallbacks = {
614
+ connected: () => void,
615
+ disconnected: () => void,
616
+ resuming: () => void,
617
+ resumed: () => void,
618
+ restarting: () => void,
619
+ restarted: (joinResp: JoinResponse) => void,
620
+ signalResumed: () => void,
621
+ mediaTrackAdded: (
622
+ track: MediaStreamTrack,
623
+ streams: MediaStream,
624
+ receiver: RTCRtpReceiver
625
+ ) => void,
626
+ activeSpeakersUpdate: (speakers: Array<SpeakerInfo>) => void,
627
+ dataPacketReceived: (userPacket: UserPacket, kind: DataPacket_Kind) => void,
628
+ transportsCreated: (publisher: PCTransport, subscriber: PCTransport) => void,
629
+ };
package/src/room/Room.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { EventEmitter } from 'events';
2
+ import type TypedEmitter from 'typed-emitter';
2
3
  import { toProtoSessionDescription } from '../api/SignalClient';
3
4
  import log from '../logger';
4
5
  import { RoomConnectOptions, RoomOptions } from '../options';
@@ -44,7 +45,7 @@ export enum RoomState {
44
45
  *
45
46
  * @noInheritDoc
46
47
  */
47
- class Room extends EventEmitter {
48
+ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) {
48
49
  state: RoomState = RoomState.Disconnected;
49
50
 
50
51
  /** map of sid: [[RemoteParticipant]] */
@@ -145,10 +146,12 @@ class Room extends EventEmitter {
145
146
  .on(EngineEvent.Resuming, () => {
146
147
  this.state = RoomState.Reconnecting;
147
148
  this.emit(RoomEvent.Reconnecting);
149
+ this.emit(RoomEvent.StateChanged, this.state);
148
150
  })
149
151
  .on(EngineEvent.Resumed, () => {
150
152
  this.state = RoomState.Connected;
151
153
  this.emit(RoomEvent.Reconnected);
154
+ this.emit(RoomEvent.StateChanged, this.state);
152
155
  this.updateSubscriptions();
153
156
  })
154
157
  .on(EngineEvent.SignalResumed, () => {
@@ -205,6 +208,7 @@ class Room extends EventEmitter {
205
208
  }
206
209
 
207
210
  this.state = RoomState.Connected;
211
+ this.emit(RoomEvent.StateChanged, this.state);
208
212
  const pi = joinResponse.participant!;
209
213
  this.localParticipant = new LocalParticipant(
210
214
  pi.sid,
@@ -216,10 +220,10 @@ class Room extends EventEmitter {
216
220
  this.localParticipant.updateInfo(pi);
217
221
  // forward metadata changed for the local participant
218
222
  this.localParticipant
219
- .on(ParticipantEvent.MetadataChanged, (metadata: object) => {
223
+ .on(ParticipantEvent.MetadataChanged, (metadata: string | undefined) => {
220
224
  this.emit(RoomEvent.MetadataChanged, metadata, this.localParticipant);
221
225
  })
222
- .on(ParticipantEvent.ParticipantMetadataChanged, (metadata: object) => {
226
+ .on(ParticipantEvent.ParticipantMetadataChanged, (metadata: string | undefined) => {
223
227
  this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
224
228
  })
225
229
  .on(ParticipantEvent.TrackMuted, (pub: TrackPublication) => {
@@ -445,6 +449,7 @@ class Room extends EventEmitter {
445
449
  private handleRestarting = () => {
446
450
  this.state = RoomState.Reconnecting;
447
451
  this.emit(RoomEvent.Reconnecting);
452
+ this.emit(RoomEvent.StateChanged, this.state);
448
453
 
449
454
  // also unwind existing participants & existing subscriptions
450
455
  for (const p of this.participants.values()) {
@@ -455,6 +460,7 @@ class Room extends EventEmitter {
455
460
  private handleRestarted = async (joinResponse: JoinResponse) => {
456
461
  this.state = RoomState.Connected;
457
462
  this.emit(RoomEvent.Reconnected);
463
+ this.emit(RoomEvent.StateChanged, this.state);
458
464
 
459
465
  // rehydrate participants
460
466
  if (joinResponse.participant) {
@@ -509,6 +515,7 @@ class Room extends EventEmitter {
509
515
  navigator.mediaDevices.removeEventListener('devicechange', this.handleDeviceChange);
510
516
  this.state = RoomState.Disconnected;
511
517
  this.emit(RoomEvent.Disconnected);
518
+ this.emit(RoomEvent.StateChanged, this.state);
512
519
  }
513
520
 
514
521
  private handleParticipantUpdates = (participantInfos: ParticipantInfo[]) => {
@@ -717,66 +724,72 @@ class Room extends EventEmitter {
717
724
  }
718
725
  }
719
726
 
727
+ private createParticipant(id: string, info?: ParticipantInfo): RemoteParticipant {
728
+ let participant: RemoteParticipant;
729
+ if (info) {
730
+ participant = RemoteParticipant.fromParticipantInfo(
731
+ this.engine.client,
732
+ info,
733
+ );
734
+ } else {
735
+ participant = new RemoteParticipant(this.engine.client, id, '');
736
+ }
737
+ return participant;
738
+ }
739
+
720
740
  private getOrCreateParticipant(
721
741
  id: string,
722
742
  info?: ParticipantInfo,
723
743
  ): RemoteParticipant {
724
- let participant = this.participants.get(id);
725
- if (!participant) {
726
- // it's possible for the RTC track to arrive before signaling data
727
- // when this happens, we'll create the participant and make the track work
728
- if (info) {
729
- participant = RemoteParticipant.fromParticipantInfo(
730
- this.engine.client,
731
- info,
732
- );
733
- } else {
734
- participant = new RemoteParticipant(this.engine.client, id, '');
735
- }
736
- this.participants.set(id, participant);
737
- // also forward events
738
-
739
- // trackPublished is only fired for tracks added after both local participant
740
- // and remote participant joined the room
741
- participant
742
- .on(ParticipantEvent.TrackPublished, (trackPublication: RemoteTrackPublication) => {
743
- this.emit(RoomEvent.TrackPublished, trackPublication, participant);
744
- })
745
- .on(ParticipantEvent.TrackSubscribed,
746
- (track: RemoteTrack, publication: RemoteTrackPublication) => {
747
- // monitor playback status
748
- if (track.kind === Track.Kind.Audio) {
749
- track.on(TrackEvent.AudioPlaybackStarted, this.handleAudioPlaybackStarted);
750
- track.on(TrackEvent.AudioPlaybackFailed, this.handleAudioPlaybackFailed);
751
- }
752
- this.emit(RoomEvent.TrackSubscribed, track, publication, participant);
753
- })
754
- .on(ParticipantEvent.TrackUnpublished, (publication: RemoteTrackPublication) => {
755
- this.emit(RoomEvent.TrackUnpublished, publication, participant);
756
- })
757
- .on(ParticipantEvent.TrackUnsubscribed,
758
- (track: RemoteTrack, publication: RemoteTrackPublication) => {
759
- this.emit(RoomEvent.TrackUnsubscribed, track, publication, participant);
760
- })
761
- .on(ParticipantEvent.TrackSubscriptionFailed, (sid: string) => {
762
- this.emit(RoomEvent.TrackSubscriptionFailed, sid, participant);
763
- })
764
- .on(ParticipantEvent.TrackMuted, (pub: TrackPublication) => {
765
- this.emit(RoomEvent.TrackMuted, pub, participant);
766
- })
767
- .on(ParticipantEvent.TrackUnmuted, (pub: TrackPublication) => {
768
- this.emit(RoomEvent.TrackUnmuted, pub, participant);
769
- })
770
- .on(ParticipantEvent.MetadataChanged, (metadata: any) => {
771
- this.emit(RoomEvent.MetadataChanged, metadata, participant);
744
+ if (this.participants.has(id)) {
745
+ return (this.participants.get(id) as RemoteParticipant);
746
+ }
747
+ // it's possible for the RTC track to arrive before signaling data
748
+ // when this happens, we'll create the participant and make the track work
749
+ const participant = this.createParticipant(id, info);
750
+ this.participants.set(id, participant);
751
+
752
+ // also forward events
753
+ // trackPublished is only fired for tracks added after both local participant
754
+ // and remote participant joined the room
755
+ participant
756
+ .on(ParticipantEvent.TrackPublished, (trackPublication: RemoteTrackPublication) => {
757
+ this.emit(RoomEvent.TrackPublished, trackPublication, participant);
758
+ })
759
+ .on(ParticipantEvent.TrackSubscribed,
760
+ (track: RemoteTrack, publication: RemoteTrackPublication) => {
761
+ // monitor playback status
762
+ if (track.kind === Track.Kind.Audio) {
763
+ track.on(TrackEvent.AudioPlaybackStarted, this.handleAudioPlaybackStarted);
764
+ track.on(TrackEvent.AudioPlaybackFailed, this.handleAudioPlaybackFailed);
765
+ }
766
+ this.emit(RoomEvent.TrackSubscribed, track, publication, participant);
772
767
  })
773
- .on(ParticipantEvent.ParticipantMetadataChanged, (metadata: any) => {
774
- this.emit(RoomEvent.ParticipantMetadataChanged, metadata, participant);
768
+ .on(ParticipantEvent.TrackUnpublished, (publication: RemoteTrackPublication) => {
769
+ this.emit(RoomEvent.TrackUnpublished, publication, participant);
770
+ })
771
+ .on(ParticipantEvent.TrackUnsubscribed,
772
+ (track: RemoteTrack, publication: RemoteTrackPublication) => {
773
+ this.emit(RoomEvent.TrackUnsubscribed, track, publication, participant);
775
774
  })
776
- .on(ParticipantEvent.ConnectionQualityChanged, (quality: ConnectionQuality) => {
777
- this.emit(RoomEvent.ConnectionQualityChanged, quality, participant);
778
- });
779
- }
775
+ .on(ParticipantEvent.TrackSubscriptionFailed, (sid: string) => {
776
+ this.emit(RoomEvent.TrackSubscriptionFailed, sid, participant);
777
+ })
778
+ .on(ParticipantEvent.TrackMuted, (pub: TrackPublication) => {
779
+ this.emit(RoomEvent.TrackMuted, pub, participant);
780
+ })
781
+ .on(ParticipantEvent.TrackUnmuted, (pub: TrackPublication) => {
782
+ this.emit(RoomEvent.TrackUnmuted, pub, participant);
783
+ })
784
+ .on(ParticipantEvent.MetadataChanged, (metadata: string | undefined) => {
785
+ this.emit(RoomEvent.MetadataChanged, metadata, participant);
786
+ })
787
+ .on(ParticipantEvent.ParticipantMetadataChanged, (metadata: string | undefined) => {
788
+ this.emit(RoomEvent.ParticipantMetadataChanged, metadata, participant);
789
+ })
790
+ .on(ParticipantEvent.ConnectionQualityChanged, (quality: ConnectionQuality) => {
791
+ this.emit(RoomEvent.ConnectionQualityChanged, quality, participant);
792
+ });
780
793
  return participant;
781
794
  }
782
795
 
@@ -830,11 +843,74 @@ class Room extends EventEmitter {
830
843
  }
831
844
  }
832
845
 
833
- /** @internal */
834
- emit(event: string | symbol, ...args: any[]): boolean {
846
+ // /** @internal */
847
+ emit<E extends keyof RoomEventCallbacks>(
848
+ event: E, ...args: Parameters<RoomEventCallbacks[E]>
849
+ ): boolean {
835
850
  log.debug('room event', event, ...args);
836
851
  return super.emit(event, ...args);
837
852
  }
838
853
  }
839
854
 
840
855
  export default Room;
856
+
857
+ export type RoomEventCallbacks = {
858
+ reconnecting: () => void,
859
+ reconnected: () => void,
860
+ disconnected: () => void,
861
+ stateChanged: (state: RoomState) => void,
862
+ mediaDevicesChanged: () => void,
863
+ participantConnected: (participant: RemoteParticipant) => void,
864
+ participantDisconnected: (participant: RemoteParticipant) => void,
865
+ trackPublished: (publication: RemoteTrackPublication, participant: RemoteParticipant) => void,
866
+ trackSubscribed: (
867
+ track: RemoteTrack,
868
+ publication: RemoteTrackPublication,
869
+ participant: RemoteParticipant
870
+ ) => void,
871
+ trackSubscriptionFailed: (trackSid: string, participant: RemoteParticipant) => void,
872
+ trackUnpublished: (publication: RemoteTrackPublication, participant: RemoteParticipant) => void,
873
+ trackUnsubscribed: (
874
+ track: RemoteTrack,
875
+ publication: RemoteTrackPublication,
876
+ participant: RemoteParticipant,
877
+ ) => void,
878
+ trackMuted: (publication: TrackPublication, participant: Participant) => void,
879
+ trackUnmuted: (publication: TrackPublication, participant: Participant) => void,
880
+ localTrackPublished: (publication: LocalTrackPublication, participant: LocalParticipant) => void,
881
+ localTrackUnpublished: (
882
+ publication: LocalTrackPublication,
883
+ participant: LocalParticipant
884
+ ) => void,
885
+ /**
886
+ * @deprecated use [[participantMetadataChanged]] instead
887
+ */
888
+ metadataChanged: (
889
+ metadata: string | undefined,
890
+ participant?: RemoteParticipant | LocalParticipant
891
+ ) => void,
892
+ participantMetadataChanged: (
893
+ metadata: string | undefined,
894
+ participant: RemoteParticipant | LocalParticipant
895
+ ) => void,
896
+ activeSpeakersChanged: (speakers: Array<Participant>) => void,
897
+ roomMetadataChanged: (metadata: string) => void,
898
+ dataReceived: (
899
+ payload: Uint8Array,
900
+ participant?: RemoteParticipant,
901
+ kind?: DataPacket_Kind
902
+ ) => void,
903
+ connectionQualityChanged: (quality: ConnectionQuality, participant: Participant) => void,
904
+ mediaDevicesError: (error: Error) => void,
905
+ trackStreamStateChanged: (
906
+ publication: RemoteTrackPublication,
907
+ streamState: Track.StreamState,
908
+ participant: RemoteParticipant,
909
+ ) => void,
910
+ trackSubscriptionPermissionChanged: (
911
+ publication: RemoteTrackPublication,
912
+ status: TrackPublication.SubscriptionStatus,
913
+ participant: RemoteParticipant,
914
+ ) => void,
915
+ audioPlaybackChanged: (playing: boolean) => void,
916
+ };
@@ -7,6 +7,7 @@
7
7
  * room.on(RoomEvent.TrackPublished, (track, publication, participant) => {})
8
8
  * ```
9
9
  */
10
+
10
11
  export enum RoomEvent {
11
12
  /**
12
13
  * When the connection to the server has been interrupted and it's attempting
@@ -25,6 +26,13 @@ export enum RoomEvent {
25
26
  */
26
27
  Disconnected = 'disconnected',
27
28
 
29
+ /**
30
+ * Whenever the connection state of the room changes
31
+ *
32
+ * args: ([[RoomState]])
33
+ */
34
+ StateChanged = 'stateChanged',
35
+
28
36
  /**
29
37
  * When input or output devices on the machine have changed.
30
38
  */
@@ -146,7 +154,7 @@ export enum RoomEvent {
146
154
  * args: (prevMetadata: string, [[Participant]])
147
155
  *
148
156
  */
149
- ParticipantMetadataChanged = 'participantMetaDataChanged',
157
+ ParticipantMetadataChanged = 'participantMetadataChanged',
150
158
 
151
159
  /**
152
160
  * Room metadata is a simple way for app-specific state to be pushed to
@@ -366,6 +374,7 @@ export enum ParticipantEvent {
366
374
 
367
375
  /** @internal */
368
376
  export enum EngineEvent {
377
+ TransportsCreated = 'transportsCreated',
369
378
  Connected = 'connected',
370
379
  Disconnected = 'disconnected',
371
380
  Resuming = 'resuming',
@@ -416,8 +416,7 @@ export default class LocalParticipant extends Participant {
416
416
  // store RTPSender
417
417
  track.sender = transceiver.sender;
418
418
  if (track instanceof LocalVideoTrack) {
419
- const disableLayerPause = this.roomOptions?.expDisableLayerPause ?? false;
420
- track.startMonitor(this.engine.client, disableLayerPause);
419
+ track.startMonitor(this.engine.client);
421
420
  } else if (track instanceof LocalAudioTrack) {
422
421
  track.startMonitor();
423
422
  }
@@ -1,8 +1,12 @@
1
1
  import { EventEmitter } from 'events';
2
- import { ConnectionQuality as ProtoQuality, ParticipantInfo } from '../../proto/livekit_models';
2
+ import type TypedEmitter from 'typed-emitter';
3
+ import { ConnectionQuality as ProtoQuality, DataPacket_Kind, ParticipantInfo } from '../../proto/livekit_models';
3
4
  import { ParticipantEvent, TrackEvent } from '../events';
5
+ import LocalTrackPublication from '../track/LocalTrackPublication';
6
+ import RemoteTrackPublication from '../track/RemoteTrackPublication';
4
7
  import { Track } from '../track/Track';
5
8
  import { TrackPublication } from '../track/TrackPublication';
9
+ import { RemoteTrack } from '../track/types';
6
10
 
7
11
  export enum ConnectionQuality {
8
12
  Excellent = 'excellent',
@@ -24,7 +28,9 @@ function qualityFromProto(q: ProtoQuality): ConnectionQuality {
24
28
  }
25
29
  }
26
30
 
27
- export default class Participant extends EventEmitter {
31
+ export default class Participant extends (
32
+ EventEmitter as new () => TypedEmitter<ParticipantEventCallbacks>
33
+ ) {
28
34
  protected participantInfo?: ParticipantInfo;
29
35
 
30
36
  audioTracks: Map<string, TrackPublication>;
@@ -158,8 +164,8 @@ export default class Participant extends EventEmitter {
158
164
  this.metadata = md;
159
165
 
160
166
  if (changed) {
161
- this.emit(ParticipantEvent.MetadataChanged, prevMetadata, this);
162
- this.emit(ParticipantEvent.ParticipantMetadataChanged, prevMetadata, this);
167
+ this.emit(ParticipantEvent.MetadataChanged, prevMetadata);
168
+ this.emit(ParticipantEvent.ParticipantMetadataChanged, prevMetadata);
163
169
  }
164
170
  }
165
171
 
@@ -212,3 +218,32 @@ export default class Participant extends EventEmitter {
212
218
  }
213
219
  }
214
220
  }
221
+
222
+ export type ParticipantEventCallbacks = {
223
+ trackPublished: (publication: RemoteTrackPublication) => void,
224
+ trackSubscribed: (track: RemoteTrack, publication: RemoteTrackPublication) => void,
225
+ trackSubscriptionFailed: (trackSid: string) => void,
226
+ trackUnpublished: (publication: RemoteTrackPublication) => void,
227
+ trackUnsubscribed: (track: RemoteTrack, publication: RemoteTrackPublication) => void,
228
+ trackMuted: (publication: TrackPublication) => void,
229
+ trackUnmuted: (publication: TrackPublication) => void,
230
+ localTrackPublished: (publication: LocalTrackPublication) => void,
231
+ localTrackUnpublished: (publication: LocalTrackPublication) => void,
232
+ /**
233
+ * @deprecated use [[participantMetadataChanged]] instead
234
+ */
235
+ metadataChanged: (prevMetadata: string | undefined, participant?: any) => void,
236
+ participantMetadataChanged: (prevMetadata: string | undefined, participant?: any) => void,
237
+ dataReceived: (payload: Uint8Array, kind: DataPacket_Kind) => void,
238
+ isSpeakingChanged: (speaking: boolean) => void,
239
+ connectionQualityChanged: (connectionQuality: ConnectionQuality) => void,
240
+ trackStreamStateChanged: (
241
+ publication: RemoteTrackPublication,
242
+ streamState: Track.StreamState
243
+ ) => void,
244
+ trackSubscriptionPermissionChanged: (
245
+ publication: RemoteTrackPublication,
246
+ status: TrackPublication.SubscriptionStatus
247
+ ) => void,
248
+ mediaDevicesError: (error: Error) => void,
249
+ };
@@ -10,9 +10,8 @@ import RemoteAudioTrack from '../track/RemoteAudioTrack';
10
10
  import RemoteTrackPublication from '../track/RemoteTrackPublication';
11
11
  import RemoteVideoTrack from '../track/RemoteVideoTrack';
12
12
  import { Track } from '../track/Track';
13
- import { TrackPublication } from '../track/TrackPublication';
14
13
  import { RemoteTrack } from '../track/types';
15
- import Participant from './Participant';
14
+ import Participant, { ParticipantEventCallbacks } from './Participant';
16
15
 
17
16
  export default class RemoteParticipant extends Participant {
18
17
  audioTracks: Map<string, RemoteTrackPublication>;
@@ -42,7 +41,7 @@ export default class RemoteParticipant extends Participant {
42
41
  this.videoTracks = new Map();
43
42
  }
44
43
 
45
- protected addTrackPublication(publication: TrackPublication) {
44
+ protected addTrackPublication(publication: RemoteTrackPublication) {
46
45
  super.addTrackPublication(publication);
47
46
 
48
47
  // register action events
@@ -234,7 +233,10 @@ export default class RemoteParticipant extends Participant {
234
233
  }
235
234
 
236
235
  /** @internal */
237
- emit(event: string | symbol, ...args: any[]): boolean {
236
+ emit<E extends keyof ParticipantEventCallbacks>(
237
+ event: E,
238
+ ...args: Parameters<ParticipantEventCallbacks[E]>
239
+ ): boolean {
238
240
  log.trace('participant event', this.sid, event, ...args);
239
241
  return super.emit(event, ...args);
240
242
  }
@@ -73,7 +73,14 @@ export default class LocalAudioTrack extends LocalTrack {
73
73
  this._currentBitrate = 0;
74
74
  return;
75
75
  }
76
- const stats = await this.getSenderStats();
76
+
77
+ let stats: AudioSenderStats | undefined;
78
+ try {
79
+ stats = await this.getSenderStats();
80
+ } catch (e) {
81
+ log.error('could not get audio sender stats', e);
82
+ return;
83
+ }
77
84
 
78
85
  if (stats && this.prevStats) {
79
86
  this._currentBitrate = computeBitrate(stats, this.prevStats);