livekit-client 1.11.4 → 1.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. package/README.md +13 -1
  2. package/dist/livekit-client.e2ee.worker.js +2 -0
  3. package/dist/livekit-client.e2ee.worker.js.map +1 -0
  4. package/dist/livekit-client.e2ee.worker.mjs +1545 -0
  5. package/dist/livekit-client.e2ee.worker.mjs.map +1 -0
  6. package/dist/livekit-client.esm.mjs +4786 -4065
  7. package/dist/livekit-client.esm.mjs.map +1 -1
  8. package/dist/livekit-client.umd.js +1 -1
  9. package/dist/livekit-client.umd.js.map +1 -1
  10. package/dist/src/api/SignalClient.d.ts +4 -1
  11. package/dist/src/api/SignalClient.d.ts.map +1 -1
  12. package/dist/src/connectionHelper/checks/turn.d.ts.map +1 -1
  13. package/dist/src/connectionHelper/checks/websocket.d.ts.map +1 -1
  14. package/dist/src/e2ee/E2eeManager.d.ts +45 -0
  15. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -0
  16. package/dist/src/e2ee/KeyProvider.d.ts +42 -0
  17. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -0
  18. package/dist/src/e2ee/constants.d.ts +14 -0
  19. package/dist/src/e2ee/constants.d.ts.map +1 -0
  20. package/dist/src/e2ee/errors.d.ts +11 -0
  21. package/dist/src/e2ee/errors.d.ts.map +1 -0
  22. package/dist/src/e2ee/index.d.ts +4 -0
  23. package/dist/src/e2ee/index.d.ts.map +1 -0
  24. package/dist/src/e2ee/types.d.ts +129 -0
  25. package/dist/src/e2ee/types.d.ts.map +1 -0
  26. package/dist/src/e2ee/utils.d.ts +24 -0
  27. package/dist/src/e2ee/utils.d.ts.map +1 -0
  28. package/dist/src/e2ee/worker/FrameCryptor.d.ts +174 -0
  29. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -0
  30. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +54 -0
  31. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -0
  32. package/dist/src/e2ee/worker/e2ee.worker.d.ts +2 -0
  33. package/dist/src/e2ee/worker/e2ee.worker.d.ts.map +1 -0
  34. package/dist/src/index.d.ts +1 -0
  35. package/dist/src/index.d.ts.map +1 -1
  36. package/dist/src/logger.d.ts +4 -1
  37. package/dist/src/logger.d.ts.map +1 -1
  38. package/dist/src/options.d.ts +5 -0
  39. package/dist/src/options.d.ts.map +1 -1
  40. package/dist/src/proto/livekit_models.d.ts +2 -2
  41. package/dist/src/proto/livekit_models.d.ts.map +1 -1
  42. package/dist/src/room/PCTransport.d.ts +3 -1
  43. package/dist/src/room/PCTransport.d.ts.map +1 -1
  44. package/dist/src/room/RTCEngine.d.ts +17 -3
  45. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  46. package/dist/src/room/Room.d.ts +10 -0
  47. package/dist/src/room/Room.d.ts.map +1 -1
  48. package/dist/src/room/events.d.ts +14 -2
  49. package/dist/src/room/events.d.ts.map +1 -1
  50. package/dist/src/room/participant/LocalParticipant.d.ts +7 -2
  51. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  52. package/dist/src/room/participant/Participant.d.ts +1 -0
  53. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  54. package/dist/src/room/participant/RemoteParticipant.d.ts +6 -4
  55. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  56. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  57. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  58. package/dist/src/room/track/TrackPublication.d.ts +3 -0
  59. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  60. package/dist/src/room/track/create.d.ts.map +1 -1
  61. package/dist/src/room/track/options.d.ts +2 -2
  62. package/dist/src/room/track/options.d.ts.map +1 -1
  63. package/dist/src/room/track/utils.d.ts +9 -0
  64. package/dist/src/room/track/utils.d.ts.map +1 -1
  65. package/dist/src/room/utils.d.ts +2 -0
  66. package/dist/src/room/utils.d.ts.map +1 -1
  67. package/dist/src/test/MockMediaStreamTrack.d.ts.map +1 -1
  68. package/dist/src/utils/browserParser.d.ts +2 -0
  69. package/dist/src/utils/browserParser.d.ts.map +1 -1
  70. package/dist/ts4.2/src/api/SignalClient.d.ts +4 -1
  71. package/dist/ts4.2/src/e2ee/E2eeManager.d.ts +45 -0
  72. package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +42 -0
  73. package/dist/ts4.2/src/e2ee/constants.d.ts +14 -0
  74. package/dist/ts4.2/src/e2ee/errors.d.ts +11 -0
  75. package/dist/ts4.2/src/e2ee/index.d.ts +4 -0
  76. package/dist/ts4.2/src/e2ee/types.d.ts +129 -0
  77. package/dist/ts4.2/src/e2ee/utils.d.ts +24 -0
  78. package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +174 -0
  79. package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +54 -0
  80. package/dist/ts4.2/src/e2ee/worker/e2ee.worker.d.ts +2 -0
  81. package/dist/ts4.2/src/index.d.ts +1 -0
  82. package/dist/ts4.2/src/logger.d.ts +4 -1
  83. package/dist/ts4.2/src/options.d.ts +5 -0
  84. package/dist/ts4.2/src/proto/livekit_models.d.ts +2 -2
  85. package/dist/ts4.2/src/room/PCTransport.d.ts +3 -1
  86. package/dist/ts4.2/src/room/RTCEngine.d.ts +17 -3
  87. package/dist/ts4.2/src/room/Room.d.ts +10 -0
  88. package/dist/ts4.2/src/room/events.d.ts +14 -2
  89. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +7 -2
  90. package/dist/ts4.2/src/room/participant/Participant.d.ts +1 -0
  91. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +6 -4
  92. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +3 -0
  93. package/dist/ts4.2/src/room/track/options.d.ts +6 -6
  94. package/dist/ts4.2/src/room/track/utils.d.ts +9 -0
  95. package/dist/ts4.2/src/room/utils.d.ts +2 -0
  96. package/dist/ts4.2/src/utils/browserParser.d.ts +2 -0
  97. package/package.json +17 -7
  98. package/src/api/SignalClient.ts +28 -9
  99. package/src/connectionHelper/checks/turn.ts +1 -0
  100. package/src/connectionHelper/checks/websocket.ts +1 -0
  101. package/src/e2ee/E2eeManager.ts +374 -0
  102. package/src/e2ee/KeyProvider.ts +77 -0
  103. package/src/e2ee/constants.ts +40 -0
  104. package/src/e2ee/errors.ts +16 -0
  105. package/src/e2ee/index.ts +3 -0
  106. package/src/e2ee/types.ts +160 -0
  107. package/src/e2ee/utils.ts +127 -0
  108. package/src/e2ee/worker/FrameCryptor.test.ts +21 -0
  109. package/src/e2ee/worker/FrameCryptor.ts +612 -0
  110. package/src/e2ee/worker/ParticipantKeyHandler.ts +144 -0
  111. package/src/e2ee/worker/e2ee.worker.ts +223 -0
  112. package/src/e2ee/worker/tsconfig.json +6 -0
  113. package/src/index.ts +1 -0
  114. package/src/logger.ts +10 -2
  115. package/src/options.ts +6 -0
  116. package/src/proto/livekit_models.ts +12 -12
  117. package/src/room/PCTransport.ts +39 -9
  118. package/src/room/RTCEngine.ts +127 -34
  119. package/src/room/Room.ts +94 -29
  120. package/src/room/defaults.ts +1 -1
  121. package/src/room/events.ts +14 -0
  122. package/src/room/participant/LocalParticipant.ts +52 -8
  123. package/src/room/participant/Participant.ts +4 -0
  124. package/src/room/participant/RemoteParticipant.ts +19 -15
  125. package/src/room/track/LocalTrack.ts +5 -4
  126. package/src/room/track/RemoteVideoTrack.ts +2 -2
  127. package/src/room/track/TrackPublication.ts +9 -1
  128. package/src/room/track/create.ts +9 -0
  129. package/src/room/track/options.ts +3 -2
  130. package/src/room/track/utils.ts +27 -0
  131. package/src/room/utils.ts +5 -0
  132. package/src/room/worker.d.ts +4 -0
  133. package/src/test/MockMediaStreamTrack.ts +1 -0
  134. package/src/utils/browserParser.ts +5 -0
@@ -1,6 +1,12 @@
1
1
  import log from '../../logger';
2
2
  import type { InternalRoomOptions } from '../../options';
3
- import { DataPacket, DataPacket_Kind, ParticipantInfo } from '../../proto/livekit_models';
3
+ import {
4
+ DataPacket,
5
+ DataPacket_Kind,
6
+ Encryption_Type,
7
+ ParticipantInfo,
8
+ ParticipantPermission,
9
+ } from '../../proto/livekit_models';
4
10
  import {
5
11
  AddTrackRequest,
6
12
  DataChannelInfo,
@@ -68,6 +74,8 @@ export default class LocalParticipant extends Participant {
68
74
  // keep a pointer to room options
69
75
  private roomOptions: InternalRoomOptions;
70
76
 
77
+ private encryptionType: Encryption_Type = Encryption_Type.NONE;
78
+
71
79
  private reconnectFuture?: Future<void>;
72
80
 
73
81
  /** @internal */
@@ -214,6 +222,22 @@ export default class LocalParticipant extends Participant {
214
222
  return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options, publishOptions);
215
223
  }
216
224
 
225
+ /** @internal */
226
+ setPermissions(permissions: ParticipantPermission): boolean {
227
+ const prevPermissions = this.permissions;
228
+ const changed = super.setPermissions(permissions);
229
+ if (changed && prevPermissions) {
230
+ this.emit(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions);
231
+ }
232
+ return changed;
233
+ }
234
+
235
+ /** @internal */
236
+ async setE2EEEnabled(enabled: boolean) {
237
+ this.encryptionType = enabled ? Encryption_Type.GCM : Encryption_Type.NONE;
238
+ await this.republishAllTracks(undefined, false);
239
+ }
240
+
217
241
  /**
218
242
  * Enable or disable publishing for a track by source. This serves as a simple
219
243
  * way to manage the common tracks (camera, mic, or screen share).
@@ -552,6 +576,12 @@ export default class LocalParticipant extends Participant {
552
576
  ...options,
553
577
  };
554
578
 
579
+ // disable simulcast if e2ee is set on safari
580
+ if (isSafari() && this.roomOptions.e2ee) {
581
+ log.info(`End-to-end encryption is set up, simulcast publishing will be disabled on Safari`);
582
+ opts.simulcast = false;
583
+ }
584
+
555
585
  if (opts.source) {
556
586
  track.source = opts.source;
557
587
  }
@@ -624,8 +654,9 @@ export default class LocalParticipant extends Participant {
624
654
  muted: track.isMuted,
625
655
  source: Track.sourceToProto(track.source),
626
656
  disableDtx: !(opts.dtx ?? true),
657
+ encryption: this.encryptionType,
627
658
  stereo: isStereo,
628
- disableRed: !(opts.red ?? true),
659
+ // disableRed: !(opts.red ?? true),
629
660
  });
630
661
 
631
662
  // compute encodings and layers for video
@@ -760,11 +791,11 @@ export default class LocalParticipant extends Participant {
760
791
 
761
792
  if (encodings) {
762
793
  if (isFireFox() && track.kind === Track.Kind.Audio) {
763
- /* Refer to RFC https://datatracker.ietf.org/doc/html/rfc7587#section-6.1,
794
+ /* Refer to RFC https://datatracker.ietf.org/doc/html/rfc7587#section-6.1,
764
795
  livekit-server uses maxaveragebitrate=510000in the answer sdp to permit client to
765
- publish high quality audio track. But firefox always uses this value as the actual
796
+ publish high quality audio track. But firefox always uses this value as the actual
766
797
  bitrates, causing the audio bitrates to rise to 510Kbps in any stereo case unexpectedly.
767
- So the client need to modify maxaverragebitrates in answer sdp to user provided value to
798
+ So the client need to modify maxaverragebitrates in answer sdp to user provided value to
768
799
  fix the issue.
769
800
  */
770
801
  let trackTransceiver: RTCRtpTransceiver | undefined = undefined;
@@ -790,7 +821,7 @@ export default class LocalParticipant extends Participant {
790
821
  }
791
822
  }
792
823
 
793
- this.engine.negotiate();
824
+ await this.engine.negotiate();
794
825
 
795
826
  if (track instanceof LocalVideoTrack) {
796
827
  track.startMonitor(this.engine.client);
@@ -876,7 +907,7 @@ export default class LocalParticipant extends Participant {
876
907
  }
877
908
  await this.engine.createSimulcastSender(track, simulcastTrack, opts, encodings);
878
909
 
879
- this.engine.negotiate();
910
+ await this.engine.negotiate();
880
911
  log.debug(`published ${videoCodec} for track ${track.sid}`, { encodings, trackInfo: ti });
881
912
  }
882
913
 
@@ -980,7 +1011,7 @@ export default class LocalParticipant extends Participant {
980
1011
  ) as LocalTrackPublication[];
981
1012
  }
982
1013
 
983
- async republishAllTracks(options?: TrackPublishOptions) {
1014
+ async republishAllTracks(options?: TrackPublishOptions, restartTracks: boolean = true) {
984
1015
  const localPubs: LocalTrackPublication[] = [];
985
1016
  this.tracks.forEach((pub) => {
986
1017
  if (pub.track) {
@@ -995,6 +1026,19 @@ export default class LocalParticipant extends Participant {
995
1026
  localPubs.map(async (pub) => {
996
1027
  const track = pub.track!;
997
1028
  await this.unpublishTrack(track, false);
1029
+ if (
1030
+ restartTracks &&
1031
+ !track.isMuted &&
1032
+ (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) &&
1033
+ !track.isUserProvided
1034
+ ) {
1035
+ // generally we need to restart the track before publishing, often a full reconnect
1036
+ // is necessary because computer had gone to sleep.
1037
+ log.debug('restarting existing track', {
1038
+ track: pub.trackSid,
1039
+ });
1040
+ await track.restartTrack();
1041
+ }
998
1042
  await this.publishTrack(track, pub.options);
999
1043
  }),
1000
1044
  );
@@ -68,6 +68,10 @@ export default class Participant extends EventEmitter<ParticipantEventCallbacks>
68
68
 
69
69
  private _connectionQuality: ConnectionQuality = ConnectionQuality.Unknown;
70
70
 
71
+ get isEncrypted() {
72
+ return this.tracks.size > 0 && Array.from(this.tracks.values()).every((tr) => tr.isEncrypted);
73
+ }
74
+
71
75
  /** @internal */
72
76
  constructor(sid: string, identity: string, name?: string, metadata?: string) {
73
77
  super();
@@ -24,7 +24,7 @@ export default class RemoteParticipant extends Participant {
24
24
 
25
25
  signalClient: SignalClient;
26
26
 
27
- private volume?: number;
27
+ private volumeMap: Map<Track.Source, number>;
28
28
 
29
29
  private audioContext?: AudioContext;
30
30
 
@@ -48,6 +48,7 @@ export default class RemoteParticipant extends Participant {
48
48
  this.tracks = new Map();
49
49
  this.audioTracks = new Map();
50
50
  this.videoTracks = new Map();
51
+ this.volumeMap = new Map();
51
52
  }
52
53
 
53
54
  protected addTrackPublication(publication: RemoteTrackPublication) {
@@ -102,12 +103,17 @@ export default class RemoteParticipant extends Participant {
102
103
  }
103
104
 
104
105
  /**
105
- * sets the volume on the participant's microphone track
106
+ * sets the volume on the participant's audio track
107
+ * by default, this affects the microphone publication
108
+ * a different source can be passed in as a second argument
106
109
  * if no track exists the volume will be applied when the microphone track is added
107
110
  */
108
- setVolume(volume: number) {
109
- this.volume = volume;
110
- const audioPublication = this.getTrack(Track.Source.Microphone);
111
+ setVolume(
112
+ volume: number,
113
+ source: Track.Source.Microphone | Track.Source.ScreenShareAudio = Track.Source.Microphone,
114
+ ) {
115
+ this.volumeMap.set(source, volume);
116
+ const audioPublication = this.getTrack(source);
111
117
  if (audioPublication && audioPublication.track) {
112
118
  (audioPublication.track as RemoteAudioTrack).setVolume(volume);
113
119
  }
@@ -116,12 +122,14 @@ export default class RemoteParticipant extends Participant {
116
122
  /**
117
123
  * gets the volume on the participant's microphone track
118
124
  */
119
- getVolume() {
120
- const audioPublication = this.getTrack(Track.Source.Microphone);
125
+ getVolume(
126
+ source: Track.Source.Microphone | Track.Source.ScreenShareAudio = Track.Source.Microphone,
127
+ ) {
128
+ const audioPublication = this.getTrack(source);
121
129
  if (audioPublication && audioPublication.track) {
122
130
  return (audioPublication.track as RemoteAudioTrack).getVolume();
123
131
  }
124
- return this.volume;
132
+ return this.volumeMap.get(source);
125
133
  }
126
134
 
127
135
  /** @internal */
@@ -198,13 +206,9 @@ export default class RemoteParticipant extends Participant {
198
206
  track.start();
199
207
 
200
208
  publication.setTrack(track);
201
- // set participant volume on new microphone tracks
202
- if (
203
- this.volume !== undefined &&
204
- track instanceof RemoteAudioTrack &&
205
- track.source === Track.Source.Microphone
206
- ) {
207
- track.setVolume(this.volume);
209
+ // set participant volumes on new audio tracks
210
+ if (this.volumeMap.has(publication.source) && track instanceof RemoteAudioTrack) {
211
+ track.setVolume(this.volumeMap.get(publication.source)!);
208
212
  }
209
213
 
210
214
  return publication;
@@ -56,9 +56,10 @@ export default abstract class LocalTrack extends Track {
56
56
  this.muteLock = new Mutex();
57
57
  this.pauseUpstreamLock = new Mutex();
58
58
  this.processorLock = new Mutex();
59
+ this.setMediaStreamTrack(mediaTrack, true);
60
+
59
61
  // added to satisfy TS compiler, constraints are synced with MediaStreamTrack
60
62
  this._constraints = mediaTrack.getConstraints();
61
- this.setMediaStreamTrack(mediaTrack);
62
63
  if (constraints) {
63
64
  this._constraints = constraints;
64
65
  }
@@ -97,8 +98,8 @@ export default abstract class LocalTrack extends Track {
97
98
  return this.processor?.processedTrack ?? this._mediaStreamTrack;
98
99
  }
99
100
 
100
- private async setMediaStreamTrack(newTrack: MediaStreamTrack) {
101
- if (newTrack === this._mediaStreamTrack) {
101
+ private async setMediaStreamTrack(newTrack: MediaStreamTrack, force?: boolean) {
102
+ if (newTrack === this._mediaStreamTrack && !force) {
102
103
  return;
103
104
  }
104
105
  if (this._mediaStreamTrack) {
@@ -109,7 +110,7 @@ export default abstract class LocalTrack extends Track {
109
110
  this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
110
111
  this._mediaStreamTrack.removeEventListener('mute', this.pauseUpstream);
111
112
  this._mediaStreamTrack.removeEventListener('unmute', this.resumeUpstream);
112
- if (!this.providedByUser) {
113
+ if (!this.providedByUser && this._mediaStreamTrack !== newTrack) {
113
114
  this._mediaStreamTrack.stop();
114
115
  }
115
116
  }
@@ -123,6 +123,7 @@ export default class RemoteVideoTrack extends RemoteTrack {
123
123
  }
124
124
  this.elementInfos = this.elementInfos.filter((info) => info !== elementInfo);
125
125
  this.updateVisibility();
126
+ this.debouncedHandleResize();
126
127
  }
127
128
 
128
129
  detach(): HTMLMediaElement[];
@@ -195,9 +196,8 @@ export default class RemoteVideoTrack extends RemoteTrack {
195
196
  private stopObservingElement(element: HTMLMediaElement) {
196
197
  const stopElementInfos = this.elementInfos.filter((info) => info.element === element);
197
198
  for (const info of stopElementInfos) {
198
- info.stopObserving();
199
+ this.stopObservingElementInfo(info);
199
200
  }
200
- this.elementInfos = this.elementInfos.filter((info) => info.element !== element);
201
201
  }
202
202
 
203
203
  protected async handleAppVisibilityChanged() {
@@ -1,5 +1,6 @@
1
1
  import EventEmitter from 'eventemitter3';
2
2
  import log from '../../logger';
3
+ import { Encryption_Type } from '../../proto/livekit_models';
3
4
  import type { SubscriptionError, TrackInfo } from '../../proto/livekit_models';
4
5
  import type { UpdateSubscription, UpdateTrackSettings } from '../../proto/livekit_rtc';
5
6
  import { TrackEvent } from '../events';
@@ -35,6 +36,8 @@ export class TrackPublication extends EventEmitter<PublicationEventCallbacks> {
35
36
 
36
37
  protected metadataMuted: boolean = false;
37
38
 
39
+ protected encryption: Encryption_Type = Encryption_Type.NONE;
40
+
38
41
  constructor(kind: Track.Kind, id: string, name: string) {
39
42
  super();
40
43
  this.kind = kind;
@@ -71,6 +74,10 @@ export class TrackPublication extends EventEmitter<PublicationEventCallbacks> {
71
74
  return this.track !== undefined;
72
75
  }
73
76
 
77
+ get isEncrypted(): boolean {
78
+ return this.encryption !== Encryption_Type.NONE;
79
+ }
80
+
74
81
  /**
75
82
  * an [AudioTrack] if this publication holds an audio track
76
83
  */
@@ -110,8 +117,9 @@ export class TrackPublication extends EventEmitter<PublicationEventCallbacks> {
110
117
  };
111
118
  this.simulcasted = info.simulcast;
112
119
  }
120
+ this.encryption = info.encryption;
113
121
  this.trackInfo = info;
114
- log.trace('update publication info', { info });
122
+ log.debug('update publication info', { info });
115
123
  }
116
124
  }
117
125
 
@@ -57,6 +57,15 @@ export async function createLocalTracks(
57
57
  if (typeof conOrBool !== 'boolean') {
58
58
  trackConstraints = conOrBool;
59
59
  }
60
+
61
+ // update the constraints with the device id the user gave permissions to in the permission prompt
62
+ // otherwise each track restart (e.g. mute - unmute) will try to initialize the device again -> causing additional permission prompts
63
+ if (trackConstraints) {
64
+ trackConstraints.deviceId = mediaStreamTrack.getSettings().deviceId;
65
+ } else {
66
+ trackConstraints = { deviceId: mediaStreamTrack.getSettings().deviceId };
67
+ }
68
+
60
69
  const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints);
61
70
  if (track.kind === Track.Kind.Video) {
62
71
  track.source = Track.Source.Camera;
@@ -272,10 +272,11 @@ export interface AudioPreset {
272
272
  priority?: RTCPriorityType;
273
273
  }
274
274
 
275
- const codecs = ['vp8', 'h264', 'vp9', 'av1'] as const;
276
275
  const backupCodecs = ['vp8', 'h264'] as const;
277
276
 
278
- export type VideoCodec = (typeof codecs)[number];
277
+ export const videoCodecs = ['vp8', 'h264', 'vp9', 'av1'] as const;
278
+
279
+ export type VideoCodec = (typeof videoCodecs)[number];
279
280
 
280
281
  export type BackupVideoCodec = (typeof backupCodecs)[number];
281
282
 
@@ -1,4 +1,5 @@
1
1
  import { sleep } from '../utils';
2
+ import { Track } from './Track';
2
3
  import type { AudioCaptureOptions, CreateLocalTracksOptions, VideoCaptureOptions } from './options';
3
4
  import type { AudioTrack } from './types';
4
5
 
@@ -112,3 +113,29 @@ export function getNewAudioContext(): AudioContext | void {
112
113
  return new AudioContext({ latencyHint: 'interactive' });
113
114
  }
114
115
  }
116
+
117
+ /**
118
+ * @internal
119
+ */
120
+ export function kindToSource(kind: MediaDeviceKind) {
121
+ if (kind === 'audioinput') {
122
+ return Track.Source.Microphone;
123
+ } else if (kind === 'videoinput') {
124
+ return Track.Source.Camera;
125
+ } else {
126
+ return Track.Source.Unknown;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * @internal
132
+ */
133
+ export function sourceToKind(source: Track.Source): MediaDeviceKind | undefined {
134
+ if (source === Track.Source.Microphone) {
135
+ return 'audioinput';
136
+ } else if (source === Track.Source.Camera) {
137
+ return 'videoinput';
138
+ } else {
139
+ return undefined;
140
+ }
141
+ }
package/src/room/utils.ts CHANGED
@@ -4,6 +4,7 @@ import type { DetectableBrowser } from '../utils/browserParser';
4
4
  import { protocolVersion, version } from '../version';
5
5
  import type LocalAudioTrack from './track/LocalAudioTrack';
6
6
  import type RemoteAudioTrack from './track/RemoteAudioTrack';
7
+ import { VideoCodec, videoCodecs } from './track/options';
7
8
  import { getNewAudioContext } from './track/utils';
8
9
  import type { LiveKitReactNativeInfo } from './types';
9
10
 
@@ -456,6 +457,10 @@ export class Mutex {
456
457
  }
457
458
  }
458
459
 
460
+ export function isVideoCodec(maybeCodec: string): maybeCodec is VideoCodec {
461
+ return videoCodecs.includes(maybeCodec as VideoCodec);
462
+ }
463
+
459
464
  export function unwrapConstraint(constraint: ConstrainDOMString): string {
460
465
  if (typeof constraint === 'string') {
461
466
  return constraint;
@@ -0,0 +1,4 @@
1
+ declare module 'web-worker:*' {
2
+ const WorkerFactory: new () => Worker;
3
+ export default WorkerFactory;
4
+ }
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ // @ts-ignore
2
3
  export default class MockMediaStreamTrack implements MediaStreamTrack {
3
4
  contentHint: string = '';
4
5
 
@@ -4,10 +4,12 @@
4
4
  const commonVersionIdentifier = /version\/(\d+(\.?_?\d+)+)/i;
5
5
 
6
6
  export type DetectableBrowser = 'Chrome' | 'Firefox' | 'Safari';
7
+ export type DetectableOS = 'iOS' | 'macOS';
7
8
 
8
9
  export type BrowserDetails = {
9
10
  name: DetectableBrowser;
10
11
  version: string;
12
+ os?: DetectableOS;
11
13
  };
12
14
 
13
15
  let browserDetails: BrowserDetails | undefined;
@@ -34,6 +36,7 @@ const browsersList = [
34
36
  const browser: BrowserDetails = {
35
37
  name: 'Firefox',
36
38
  version: getMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i, ua),
39
+ os: ua.toLowerCase().includes('fxios') ? 'iOS' : undefined,
37
40
  };
38
41
  return browser;
39
42
  },
@@ -44,6 +47,7 @@ const browsersList = [
44
47
  const browser: BrowserDetails = {
45
48
  name: 'Chrome',
46
49
  version: getMatch(/(?:chrome|chromium|crios|crmo)\/(\d+(\.?_?\d+)+)/i, ua),
50
+ os: ua.toLowerCase().includes('crios') ? 'iOS' : undefined,
47
51
  };
48
52
 
49
53
  return browser;
@@ -56,6 +60,7 @@ const browsersList = [
56
60
  const browser: BrowserDetails = {
57
61
  name: 'Safari',
58
62
  version: getMatch(commonVersionIdentifier, ua),
63
+ os: ua.includes('Mobile/') ? 'iOS' : 'macOS',
59
64
  };
60
65
 
61
66
  return browser;