livekit-client 1.6.7 → 1.6.8

Sign up to get free protection for your applications and to get access to all the features.
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;