livekit-client 1.6.7 → 1.6.8

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 (40) hide show
  1. package/dist/livekit-client.esm.mjs +429 -250
  2. package/dist/livekit-client.esm.mjs.map +1 -1
  3. package/dist/livekit-client.umd.js +1 -1
  4. package/dist/livekit-client.umd.js.map +1 -1
  5. package/dist/src/proto/google/protobuf/timestamp.d.ts +9 -2
  6. package/dist/src/proto/google/protobuf/timestamp.d.ts.map +1 -1
  7. package/dist/src/proto/livekit_models.d.ts +958 -58
  8. package/dist/src/proto/livekit_models.d.ts.map +1 -1
  9. package/dist/src/proto/livekit_rtc.d.ts +8394 -3725
  10. package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
  11. package/dist/src/room/Room.d.ts +1 -1
  12. package/dist/src/room/Room.d.ts.map +1 -1
  13. package/dist/src/room/events.d.ts +1 -1
  14. package/dist/src/room/participant/LocalParticipant.d.ts +18 -0
  15. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  16. package/dist/src/room/track/options.d.ts +8 -0
  17. package/dist/src/room/track/options.d.ts.map +1 -1
  18. package/dist/src/room/types.d.ts +7 -0
  19. package/dist/src/room/types.d.ts.map +1 -1
  20. package/dist/src/version.d.ts +1 -1
  21. package/dist/ts4.2/src/proto/google/protobuf/timestamp.d.ts +11 -2
  22. package/dist/ts4.2/src/proto/livekit_models.d.ts +1199 -177
  23. package/dist/ts4.2/src/proto/livekit_rtc.d.ts +9260 -4023
  24. package/dist/ts4.2/src/room/Room.d.ts +1 -1
  25. package/dist/ts4.2/src/room/events.d.ts +1 -1
  26. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +18 -0
  27. package/dist/ts4.2/src/room/track/options.d.ts +8 -0
  28. package/dist/ts4.2/src/room/types.d.ts +7 -0
  29. package/dist/ts4.2/src/version.d.ts +1 -1
  30. package/package.json +2 -2
  31. package/src/proto/google/protobuf/timestamp.ts +4 -0
  32. package/src/proto/livekit_models.ts +128 -31
  33. package/src/proto/livekit_rtc.ts +262 -161
  34. package/src/room/RTCEngine.ts +9 -9
  35. package/src/room/Room.ts +50 -43
  36. package/src/room/events.ts +1 -1
  37. package/src/room/participant/LocalParticipant.ts +99 -21
  38. package/src/room/track/options.ts +12 -0
  39. package/src/room/types.ts +9 -0
  40. package/src/version.ts +1 -1
@@ -341,8 +341,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
341
341
  this.handleDisconnect(
342
342
  'primary peerconnection',
343
343
  subscriberPrimary
344
- ? ReconnectReason.REASON_SUBSCRIBER_FAILED
345
- : ReconnectReason.REASON_PUBLISHER_FAILED,
344
+ ? ReconnectReason.RR_SUBSCRIBER_FAILED
345
+ : ReconnectReason.RR_PUBLISHER_FAILED,
346
346
  );
347
347
  }
348
348
  }
@@ -354,8 +354,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
354
354
  this.handleDisconnect(
355
355
  'secondary peerconnection',
356
356
  subscriberPrimary
357
- ? ReconnectReason.REASON_PUBLISHER_FAILED
358
- : ReconnectReason.REASON_SUBSCRIBER_FAILED,
357
+ ? ReconnectReason.RR_PUBLISHER_FAILED
358
+ : ReconnectReason.RR_SUBSCRIBER_FAILED,
359
359
  );
360
360
  }
361
361
  };
@@ -423,7 +423,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
423
423
  };
424
424
 
425
425
  this.client.onClose = () => {
426
- this.handleDisconnect('signal', ReconnectReason.REASON_SIGNAL_DISCONNECTED);
426
+ this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED);
427
427
  };
428
428
 
429
429
  this.client.onLeave = (leave?: LeaveRequest) => {
@@ -775,7 +775,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
775
775
  }
776
776
 
777
777
  if (recoverable) {
778
- this.handleDisconnect('reconnect', ReconnectReason.REASON_UNKOWN);
778
+ this.handleDisconnect('reconnect', ReconnectReason.RR_UNKOWN);
779
779
  } else {
780
780
  log.info(
781
781
  `could not recover connection after ${this.reconnectAttempts} attempts, ${
@@ -1011,7 +1011,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1011
1011
 
1012
1012
  const negotiationTimeout = setTimeout(() => {
1013
1013
  reject('negotiation timed out');
1014
- this.handleDisconnect('negotiation', ReconnectReason.REASON_SIGNAL_DISCONNECTED);
1014
+ this.handleDisconnect('negotiation', ReconnectReason.RR_SIGNAL_DISCONNECTED);
1015
1015
  }, this.peerConnectionTimeout);
1016
1016
 
1017
1017
  const cleanup = () => {
@@ -1032,7 +1032,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1032
1032
  if (e instanceof NegotiationError) {
1033
1033
  this.fullReconnectOnNext = true;
1034
1034
  }
1035
- this.handleDisconnect('negotiation', ReconnectReason.REASON_UNKOWN);
1035
+ this.handleDisconnect('negotiation', ReconnectReason.RR_UNKOWN);
1036
1036
  });
1037
1037
  });
1038
1038
  }
@@ -1076,7 +1076,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1076
1076
  // in case the engine is currently reconnecting, attempt a reconnect immediately after the browser state has changed to 'onLine'
1077
1077
  if (this.client.isReconnecting) {
1078
1078
  this.clearReconnectTimeout();
1079
- this.attemptReconnect(ReconnectReason.REASON_SIGNAL_DISCONNECTED);
1079
+ this.attemptReconnect(ReconnectReason.RR_SIGNAL_DISCONNECTED);
1080
1080
  }
1081
1081
  };
1082
1082
 
package/src/room/Room.ts CHANGED
@@ -758,48 +758,53 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
758
758
  log.debug(`reconnected to server`, {
759
759
  region: joinResponse.serverRegion,
760
760
  });
761
- this.setAndEmitConnectionState(ConnectionState.Connected);
762
- this.emit(RoomEvent.Reconnected);
763
-
764
- // rehydrate participants
765
- if (joinResponse.participant) {
766
- // with a restart, the sid will have changed, we'll map our understanding to it
767
- this.localParticipant.sid = joinResponse.participant.sid;
768
- this.handleParticipantUpdates([joinResponse.participant]);
769
- }
770
- this.handleParticipantUpdates(joinResponse.otherParticipants);
771
761
 
772
- // unpublish & republish tracks
773
- const localPubs: LocalTrackPublication[] = [];
774
- this.localParticipant.tracks.forEach((pub) => {
775
- if (pub.track) {
776
- localPubs.push(pub);
762
+ try {
763
+ // rehydrate participants
764
+ if (joinResponse.participant) {
765
+ // with a restart, the sid will have changed, we'll map our understanding to it
766
+ this.localParticipant.sid = joinResponse.participant.sid;
767
+ this.handleParticipantUpdates([joinResponse.participant]);
777
768
  }
778
- });
769
+ this.handleParticipantUpdates(joinResponse.otherParticipants);
779
770
 
780
- await Promise.all(
781
- localPubs.map(async (pub) => {
782
- const track = pub.track!;
783
- this.localParticipant.unpublishTrack(track, false);
784
- if (!track.isMuted) {
785
- if (
786
- (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) &&
787
- !track.isUserProvided
788
- ) {
789
- // we need to restart the track before publishing, often a full reconnect
790
- // is necessary because computer had gone to sleep.
791
- log.debug('restarting existing track', {
771
+ // unpublish & republish tracks
772
+ const localPubs: LocalTrackPublication[] = [];
773
+ this.localParticipant.tracks.forEach((pub) => {
774
+ if (pub.track) {
775
+ localPubs.push(pub);
776
+ }
777
+ });
778
+
779
+ await Promise.all(
780
+ localPubs.map(async (pub) => {
781
+ const track = pub.track!;
782
+ this.localParticipant.unpublishTrack(track, false);
783
+ if (!track.isMuted) {
784
+ if (
785
+ (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) &&
786
+ !track.isUserProvided
787
+ ) {
788
+ // we need to restart the track before publishing, often a full reconnect
789
+ // is necessary because computer had gone to sleep.
790
+ log.debug('restarting existing track', {
791
+ track: pub.trackSid,
792
+ });
793
+ await track.restartTrack();
794
+ }
795
+ log.debug('publishing new track', {
792
796
  track: pub.trackSid,
793
797
  });
794
- await track.restartTrack();
798
+ await this.localParticipant.publishTrack(track, pub.options);
795
799
  }
796
- log.debug('publishing new track', {
797
- track: pub.trackSid,
798
- });
799
- await this.localParticipant.publishTrack(track, pub.options);
800
- }
801
- }),
802
- );
800
+ }),
801
+ );
802
+ } catch (error) {
803
+ log.error('error trying to re-publish tracks after reconnection', { error });
804
+ } finally {
805
+ this.setAndEmitConnectionState(ConnectionState.Connected);
806
+ this.emit(RoomEvent.Reconnected);
807
+ }
803
808
  };
804
809
 
805
810
  private handleDisconnect(shouldStopTracks = true, reason?: DisconnectReason) {
@@ -872,15 +877,16 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
872
877
  let remoteParticipant = this.participants.get(info.sid);
873
878
  const isNewParticipant = !remoteParticipant;
874
879
 
875
- // create participant if doesn't exist
876
- remoteParticipant = this.getOrCreateParticipant(info.sid, info);
877
-
878
880
  // when it's disconnected, send updates
879
881
  if (info.state === ParticipantInfo_State.DISCONNECTED) {
880
882
  this.handleParticipantDisconnected(info.sid, remoteParticipant);
881
- } else if (!isNewParticipant) {
882
- // just update, no events
883
- remoteParticipant.updateInfo(info);
883
+ } else {
884
+ // create participant if doesn't exist
885
+ remoteParticipant = this.getOrCreateParticipant(info.sid, info);
886
+ if (!isNewParticipant) {
887
+ // just update, no events
888
+ remoteParticipant.updateInfo(info);
889
+ }
884
890
  }
885
891
  });
886
892
  };
@@ -1001,7 +1007,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1001
1007
  // find the participant
1002
1008
  const participant = this.participants.get(userPacket.participantSid);
1003
1009
 
1004
- this.emit(RoomEvent.DataReceived, userPacket.payload, participant, kind);
1010
+ this.emit(RoomEvent.DataReceived, userPacket.payload, participant, kind, userPacket.topic);
1005
1011
 
1006
1012
  // also emit on the participant
1007
1013
  participant?.emit(ParticipantEvent.DataReceived, userPacket.payload, kind);
@@ -1462,6 +1468,7 @@ export type RoomEventCallbacks = {
1462
1468
  payload: Uint8Array,
1463
1469
  participant?: RemoteParticipant,
1464
1470
  kind?: DataPacket_Kind,
1471
+ topic?: string,
1465
1472
  ) => void;
1466
1473
  connectionQualityChanged: (quality: ConnectionQuality, participant: Participant) => void;
1467
1474
  mediaDevicesError: (error: Error) => void;
@@ -175,7 +175,7 @@ export enum RoomEvent {
175
175
  * Data packets provides the ability to use LiveKit to send/receive arbitrary payloads.
176
176
  * All participants in the room will receive the messages sent to the room.
177
177
  *
178
- * args: (payload: Uint8Array, participant: [[Participant]], kind: [[DataPacket_Kind]])
178
+ * args: (payload: Uint8Array, participant: [[Participant]], kind: [[DataPacket_Kind]], topic?: string)
179
179
  */
180
180
  DataReceived = 'dataReceived',
181
181
 
@@ -29,7 +29,8 @@ import {
29
29
  } from '../track/options';
30
30
  import { Track } from '../track/Track';
31
31
  import { constraintsForOptions, mergeDefaultOptions } from '../track/utils';
32
- import { isFireFox, isSafari, isWeb, supportsAV1 } from '../utils';
32
+ import type { DataPublishOptions } from '../types';
33
+ import { Future, isFireFox, isSafari, isWeb, supportsAV1 } from '../utils';
33
34
  import Participant from './Participant';
34
35
  import { ParticipantTrackPermission, trackPermissionToProto } from './ParticipantTrackPermission';
35
36
  import {
@@ -52,6 +53,8 @@ export default class LocalParticipant extends Participant {
52
53
 
53
54
  private pendingPublishing = new Set<Track.Source>();
54
55
 
56
+ private pendingPublishPromises = new Map<LocalTrack, Promise<LocalTrackPublication>>();
57
+
55
58
  private cameraError: Error | undefined;
56
59
 
57
60
  private microphoneError: Error | undefined;
@@ -63,6 +66,8 @@ export default class LocalParticipant extends Participant {
63
66
  // keep a pointer to room options
64
67
  private roomOptions: InternalRoomOptions;
65
68
 
69
+ private reconnectFuture?: Future<void>;
70
+
66
71
  /** @internal */
67
72
  constructor(sid: string, identity: string, engine: RTCEngine, options: InternalRoomOptions) {
68
73
  super(sid, identity);
@@ -118,11 +123,25 @@ export default class LocalParticipant extends Participant {
118
123
  this.engine.client.onLocalTrackUnpublished = this.handleLocalTrackUnpublished;
119
124
 
120
125
  this.engine
121
- .on(EngineEvent.Connected, this.updateTrackSubscriptionPermissions)
122
- .on(EngineEvent.Restarted, this.updateTrackSubscriptionPermissions)
123
- .on(EngineEvent.Resumed, this.updateTrackSubscriptionPermissions);
126
+ .on(EngineEvent.Connected, this.handleReconnected)
127
+ .on(EngineEvent.Restarted, this.handleReconnected)
128
+ .on(EngineEvent.Resumed, this.handleReconnected)
129
+ .on(EngineEvent.Restarting, this.handleReconnecting)
130
+ .on(EngineEvent.Resuming, this.handleReconnecting);
124
131
  }
125
132
 
133
+ private handleReconnecting = () => {
134
+ if (!this.reconnectFuture) {
135
+ this.reconnectFuture = new Future<void>();
136
+ }
137
+ };
138
+
139
+ private handleReconnected = () => {
140
+ this.reconnectFuture?.resolve?.();
141
+ this.reconnectFuture = undefined;
142
+ this.updateTrackSubscriptionPermissions();
143
+ };
144
+
126
145
  /**
127
146
  * Enable or disable a participant's camera track.
128
147
  *
@@ -382,6 +401,11 @@ export default class LocalParticipant extends Participant {
382
401
  const stream: MediaStream = await navigator.mediaDevices.getDisplayMedia({
383
402
  audio: options.audio ?? false,
384
403
  video: videoConstraints,
404
+ // @ts-expect-error support for experimental display media features
405
+ controller: options.controller,
406
+ selfBrowserSurface: options.selfBrowserSurface,
407
+ surfaceSwitching: options.surfaceSwitching,
408
+ systemAudio: options.systemAudio,
385
409
  });
386
410
 
387
411
  const tracks = stream.getVideoTracks();
@@ -408,6 +432,10 @@ export default class LocalParticipant extends Participant {
408
432
  track: LocalTrack | MediaStreamTrack,
409
433
  options?: TrackPublishOptions,
410
434
  ): Promise<LocalTrackPublication> {
435
+ await this.reconnectFuture?.promise;
436
+ if (track instanceof LocalTrack && this.pendingPublishPromises.has(track)) {
437
+ await this.pendingPublishPromises.get(track);
438
+ }
411
439
  // convert raw media track into audio or video track
412
440
  if (track instanceof MediaStreamTrack) {
413
441
  switch (track.kind) {
@@ -422,6 +450,22 @@ export default class LocalParticipant extends Participant {
422
450
  }
423
451
  }
424
452
 
453
+ // is it already published? if so skip
454
+ let existingPublication: LocalTrackPublication | undefined;
455
+ this.tracks.forEach((publication) => {
456
+ if (!publication.track) {
457
+ return;
458
+ }
459
+ if (publication.track === track) {
460
+ existingPublication = <LocalTrackPublication>publication;
461
+ }
462
+ });
463
+
464
+ if (existingPublication) {
465
+ log.warn('track has already been published, skipping');
466
+ return existingPublication;
467
+ }
468
+
425
469
  const isStereo =
426
470
  options?.forceStereo ||
427
471
  ('channelCount' in track.mediaStreamTrack.getSettings() &&
@@ -452,22 +496,27 @@ export default class LocalParticipant extends Participant {
452
496
  ...options,
453
497
  };
454
498
 
455
- // is it already published? if so skip
456
- let existingPublication: LocalTrackPublication | undefined;
457
- this.tracks.forEach((publication) => {
458
- if (!publication.track) {
459
- return;
460
- }
461
- if (publication.track === track) {
462
- existingPublication = <LocalTrackPublication>publication;
463
- }
464
- });
465
-
466
- if (existingPublication) return existingPublication;
467
-
468
499
  if (opts.source) {
469
500
  track.source = opts.source;
470
501
  }
502
+ const publishPromise = this.publish(track, opts, options, isStereo);
503
+ this.pendingPublishPromises.set(track, publishPromise);
504
+ try {
505
+ const publication = await publishPromise;
506
+ return publication;
507
+ } catch (e) {
508
+ throw e;
509
+ } finally {
510
+ this.pendingPublishPromises.delete(track);
511
+ }
512
+ }
513
+
514
+ private async publish(
515
+ track: LocalTrack,
516
+ opts: TrackPublishOptions,
517
+ options: TrackPublishOptions | undefined,
518
+ isStereo: boolean,
519
+ ) {
471
520
  const existingTrackOfSource = Array.from(this.tracks.values()).find(
472
521
  (publishedTrack) => track instanceof LocalTrack && publishedTrack.source === track.source,
473
522
  );
@@ -806,6 +855,22 @@ export default class LocalParticipant extends Participant {
806
855
  );
807
856
  }
808
857
 
858
+ /**
859
+ * Publish a new data payload to the room. Data will be forwarded to each
860
+ * participant in the room if the destination field in publishOptions is empty
861
+ *
862
+ * @param data Uint8Array of the payload. To send string data, use TextEncoder.encode
863
+ * @param kind whether to send this as reliable or lossy.
864
+ * For data that you need delivery guarantee (such as chat messages), use Reliable.
865
+ * For data that should arrive as quickly as possible, but you are ok with dropped
866
+ * packets, use Lossy.
867
+ * @param publishOptions optionally specify a `topic` and `destination`
868
+ */
869
+ async publishData(
870
+ data: Uint8Array,
871
+ kind: DataPacket_Kind,
872
+ publishOptions?: DataPublishOptions,
873
+ ): Promise<void>;
809
874
  /**
810
875
  * Publish a new data payload to the room. Data will be forwarded to each
811
876
  * participant in the room if the destination argument is empty
@@ -821,14 +886,26 @@ export default class LocalParticipant extends Participant {
821
886
  data: Uint8Array,
822
887
  kind: DataPacket_Kind,
823
888
  destination?: RemoteParticipant[] | string[],
889
+ ): Promise<void>;
890
+
891
+ async publishData(
892
+ data: Uint8Array,
893
+ kind: DataPacket_Kind,
894
+ publishOptions: DataPublishOptions | RemoteParticipant[] | string[] = {},
824
895
  ) {
825
- const dest: string[] = [];
896
+ const destination = Array.isArray(publishOptions)
897
+ ? publishOptions
898
+ : publishOptions?.destination;
899
+ const destinationSids: string[] = [];
900
+
901
+ const topic = !Array.isArray(publishOptions) ? publishOptions.topic : undefined;
902
+
826
903
  if (destination !== undefined) {
827
904
  destination.forEach((val: any) => {
828
905
  if (val instanceof RemoteParticipant) {
829
- dest.push(val.sid);
906
+ destinationSids.push(val.sid);
830
907
  } else {
831
- dest.push(val);
908
+ destinationSids.push(val);
832
909
  }
833
910
  });
834
911
  }
@@ -840,7 +917,8 @@ export default class LocalParticipant extends Participant {
840
917
  user: {
841
918
  participantSid: this.sid,
842
919
  payload: data,
843
- destinationSids: dest,
920
+ destinationSids: destinationSids,
921
+ topic,
844
922
  },
845
923
  },
846
924
  };
@@ -133,6 +133,18 @@ export interface ScreenShareCaptureOptions {
133
133
 
134
134
  /** capture resolution, defaults to full HD */
135
135
  resolution?: VideoResolution;
136
+
137
+ /** a CaptureController object instance containing methods that can be used to further manipulate the capture session if included. */
138
+ controller?: unknown; // TODO replace type with CaptureController once it lands in TypeScript
139
+
140
+ /** specifies whether the browser should allow the user to select the current tab for capture */
141
+ selfBrowserSurface?: 'include' | 'exclude';
142
+
143
+ /** specifies whether the browser should display a control to allow the user to dynamically switch the shared tab during screen-sharing. */
144
+ surfaceSwitching?: 'include' | 'exclude';
145
+
146
+ /** specifies whether the browser should include the system audio among the possible audio sources offered to the user */
147
+ systemAudio?: 'include' | 'exclude';
136
148
  }
137
149
 
138
150
  export interface AudioCaptureOptions {
package/src/room/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type RemoteParticipant from './participant/RemoteParticipant';
2
+
1
3
  export type SimulationOptions = {
2
4
  publish?: {
3
5
  audio?: boolean;
@@ -11,3 +13,10 @@ export type SimulationOptions = {
11
13
  video?: boolean;
12
14
  };
13
15
  };
16
+
17
+ export type DataPublishOptions = {
18
+ /** the participants who will receive the message, will be sent to every one if empty */
19
+ destination?: RemoteParticipant[] | string[];
20
+ /** the topic under which the message gets published */
21
+ topic?: string;
22
+ };
package/src/version.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { version as v } from '../package.json';
2
2
 
3
3
  export const version = v;
4
- export const protocolVersion = 8;
4
+ export const protocolVersion = 9;