livekit-client 2.9.9 → 2.11.0

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 (48) hide show
  1. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  2. package/dist/livekit-client.e2ee.worker.mjs +3 -3
  3. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  4. package/dist/livekit-client.esm.mjs +341 -135
  5. package/dist/livekit-client.esm.mjs.map +1 -1
  6. package/dist/livekit-client.umd.js +1 -1
  7. package/dist/livekit-client.umd.js.map +1 -1
  8. package/dist/src/api/SignalClient.d.ts.map +1 -1
  9. package/dist/src/api/utils.d.ts +3 -0
  10. package/dist/src/api/utils.d.ts.map +1 -0
  11. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
  12. package/dist/src/room/RTCEngine.d.ts +1 -0
  13. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  14. package/dist/src/room/Room.d.ts.map +1 -1
  15. package/dist/src/room/StreamWriter.d.ts +1 -1
  16. package/dist/src/room/StreamWriter.d.ts.map +1 -1
  17. package/dist/src/room/events.d.ts +2 -1
  18. package/dist/src/room/events.d.ts.map +1 -1
  19. package/dist/src/room/participant/LocalParticipant.d.ts +10 -1
  20. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  21. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  22. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  23. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
  24. package/dist/src/room/track/create.d.ts.map +1 -1
  25. package/dist/src/room/track/options.d.ts +3 -2
  26. package/dist/src/room/track/options.d.ts.map +1 -1
  27. package/dist/ts4.2/src/api/utils.d.ts +3 -0
  28. package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -0
  29. package/dist/ts4.2/src/room/StreamWriter.d.ts +1 -1
  30. package/dist/ts4.2/src/room/events.d.ts +2 -1
  31. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +10 -1
  32. package/dist/ts4.2/src/room/track/options.d.ts +3 -2
  33. package/package.json +13 -13
  34. package/src/api/SignalClient.ts +8 -17
  35. package/src/api/utils.test.ts +112 -0
  36. package/src/api/utils.ts +23 -0
  37. package/src/room/DeviceManager.ts +1 -1
  38. package/src/room/RTCEngine.ts +5 -0
  39. package/src/room/Room.ts +1 -1
  40. package/src/room/StreamWriter.ts +1 -1
  41. package/src/room/defaults.ts +2 -2
  42. package/src/room/events.ts +1 -0
  43. package/src/room/participant/LocalParticipant.ts +148 -49
  44. package/src/room/track/LocalTrack.ts +2 -2
  45. package/src/room/track/LocalVideoTrack.ts +15 -14
  46. package/src/room/track/create.ts +87 -47
  47. package/src/room/track/options.ts +5 -1
  48. package/src/room/track/utils.ts +6 -6
@@ -31,70 +31,110 @@ export async function createLocalTracks(
31
31
  options?: CreateLocalTracksOptions,
32
32
  ): Promise<Array<LocalTrack>> {
33
33
  // set default options to true
34
- options ??= {};
35
- options.audio ??= { deviceId: 'default' };
36
- options.video ??= { deviceId: 'default' };
34
+ const internalOptions = { ...(options ?? {}) };
35
+ let attemptExactMatch = false;
36
+ let retryAudioOptions: AudioCaptureOptions | undefined | boolean = options?.audio;
37
+ let retryVideoOptions: VideoCaptureOptions | undefined | boolean = options?.video;
38
+ // if the user passes a device id as a string, we default to exact match
39
+ if (
40
+ internalOptions.audio &&
41
+ typeof internalOptions.audio === 'object' &&
42
+ typeof internalOptions.audio.deviceId === 'string'
43
+ ) {
44
+ const deviceId: string = internalOptions.audio.deviceId;
45
+ internalOptions.audio.deviceId = { exact: deviceId };
46
+ attemptExactMatch = true;
47
+ retryAudioOptions = {
48
+ ...internalOptions.audio,
49
+ deviceId: { ideal: deviceId },
50
+ };
51
+ }
52
+ if (
53
+ internalOptions.video &&
54
+ typeof internalOptions.video === 'object' &&
55
+ typeof internalOptions.video.deviceId === 'string'
56
+ ) {
57
+ const deviceId: string = internalOptions.video.deviceId;
58
+ internalOptions.video.deviceId = { exact: deviceId };
59
+ attemptExactMatch = true;
60
+ retryVideoOptions = {
61
+ ...internalOptions.video,
62
+ deviceId: { ideal: deviceId },
63
+ };
64
+ }
65
+ internalOptions.audio ??= { deviceId: 'default' };
66
+ internalOptions.video ??= { deviceId: 'default' };
37
67
 
38
- const { audioProcessor, videoProcessor } = extractProcessorsFromOptions(options);
39
- const opts = mergeDefaultOptions(options, audioDefaults, videoDefaults);
68
+ const { audioProcessor, videoProcessor } = extractProcessorsFromOptions(internalOptions);
69
+ const opts = mergeDefaultOptions(internalOptions, audioDefaults, videoDefaults);
40
70
  const constraints = constraintsForOptions(opts);
41
71
 
42
72
  // Keep a reference to the promise on DeviceManager and await it in getLocalDevices()
43
73
  // works around iOS Safari Bug https://bugs.webkit.org/show_bug.cgi?id=179363
44
74
  const mediaPromise = navigator.mediaDevices.getUserMedia(constraints);
45
75
 
46
- if (options.audio) {
76
+ if (internalOptions.audio) {
47
77
  DeviceManager.userMediaPromiseMap.set('audioinput', mediaPromise);
48
78
  mediaPromise.catch(() => DeviceManager.userMediaPromiseMap.delete('audioinput'));
49
79
  }
50
- if (options.video) {
80
+ if (internalOptions.video) {
51
81
  DeviceManager.userMediaPromiseMap.set('videoinput', mediaPromise);
52
82
  mediaPromise.catch(() => DeviceManager.userMediaPromiseMap.delete('videoinput'));
53
83
  }
84
+ try {
85
+ const stream = await mediaPromise;
86
+ return await Promise.all(
87
+ stream.getTracks().map(async (mediaStreamTrack) => {
88
+ const isAudio = mediaStreamTrack.kind === 'audio';
89
+ let trackOptions = isAudio ? opts!.audio : opts!.video;
90
+ if (typeof trackOptions === 'boolean' || !trackOptions) {
91
+ trackOptions = {};
92
+ }
93
+ let trackConstraints: MediaTrackConstraints | undefined;
94
+ const conOrBool = isAudio ? constraints.audio : constraints.video;
95
+ if (typeof conOrBool !== 'boolean') {
96
+ trackConstraints = conOrBool;
97
+ }
54
98
 
55
- const stream = await mediaPromise;
56
- return Promise.all(
57
- stream.getTracks().map(async (mediaStreamTrack) => {
58
- const isAudio = mediaStreamTrack.kind === 'audio';
59
- let trackOptions = isAudio ? opts!.audio : opts!.video;
60
- if (typeof trackOptions === 'boolean' || !trackOptions) {
61
- trackOptions = {};
62
- }
63
- let trackConstraints: MediaTrackConstraints | undefined;
64
- const conOrBool = isAudio ? constraints.audio : constraints.video;
65
- if (typeof conOrBool !== 'boolean') {
66
- trackConstraints = conOrBool;
67
- }
99
+ // update the constraints with the device id the user gave permissions to in the permission prompt
100
+ // otherwise each track restart (e.g. mute - unmute) will try to initialize the device again -> causing additional permission prompts
101
+ const newDeviceId = mediaStreamTrack.getSettings().deviceId;
102
+ if (
103
+ trackConstraints?.deviceId &&
104
+ unwrapConstraint(trackConstraints.deviceId) !== newDeviceId
105
+ ) {
106
+ trackConstraints.deviceId = newDeviceId;
107
+ } else if (!trackConstraints) {
108
+ trackConstraints = { deviceId: newDeviceId };
109
+ }
68
110
 
69
- // update the constraints with the device id the user gave permissions to in the permission prompt
70
- // otherwise each track restart (e.g. mute - unmute) will try to initialize the device again -> causing additional permission prompts
71
- const newDeviceId = mediaStreamTrack.getSettings().deviceId;
72
- if (
73
- trackConstraints?.deviceId &&
74
- unwrapConstraint(trackConstraints.deviceId) !== newDeviceId
75
- ) {
76
- trackConstraints.deviceId = newDeviceId;
77
- } else if (!trackConstraints) {
78
- trackConstraints = { deviceId: newDeviceId };
79
- }
111
+ const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints);
112
+ if (track.kind === Track.Kind.Video) {
113
+ track.source = Track.Source.Camera;
114
+ } else if (track.kind === Track.Kind.Audio) {
115
+ track.source = Track.Source.Microphone;
116
+ }
117
+ track.mediaStream = stream;
80
118
 
81
- const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints);
82
- if (track.kind === Track.Kind.Video) {
83
- track.source = Track.Source.Camera;
84
- } else if (track.kind === Track.Kind.Audio) {
85
- track.source = Track.Source.Microphone;
86
- }
87
- track.mediaStream = stream;
119
+ if (isAudioTrack(track) && audioProcessor) {
120
+ await track.setProcessor(audioProcessor);
121
+ } else if (isVideoTrack(track) && videoProcessor) {
122
+ await track.setProcessor(videoProcessor);
123
+ }
88
124
 
89
- if (isAudioTrack(track) && audioProcessor) {
90
- await track.setProcessor(audioProcessor);
91
- } else if (isVideoTrack(track) && videoProcessor) {
92
- await track.setProcessor(videoProcessor);
93
- }
94
-
95
- return track;
96
- }),
97
- );
125
+ return track;
126
+ }),
127
+ );
128
+ } catch (e) {
129
+ if (!attemptExactMatch) {
130
+ throw e;
131
+ }
132
+ return createLocalTracks({
133
+ ...options,
134
+ audio: retryAudioOptions,
135
+ video: retryVideoOptions,
136
+ });
137
+ }
98
138
  }
99
139
 
100
140
  /**
@@ -389,8 +389,12 @@ export function isBackupCodec(codec: string): codec is BackupVideoCodec {
389
389
  }
390
390
 
391
391
  export enum BackupCodecPolicy {
392
- REGRESSION = 0,
392
+ // codec regression is preferred, the sfu will try to regress codec if possible but not guaranteed
393
+ PREFER_REGRESSION = 0,
394
+ // multi-codec simulcast, publish both primary and backup codec at the same time
393
395
  SIMULCAST = 1,
396
+ // always use backup codec only
397
+ REGRESSION = 2,
394
398
  }
395
399
 
396
400
  /**
@@ -33,7 +33,7 @@ export function mergeDefaultOptions(
33
33
  clonedOptions.audio as Record<string, unknown>,
34
34
  audioDefaults as Record<string, unknown>,
35
35
  );
36
- clonedOptions.audio.deviceId ??= 'default';
36
+ clonedOptions.audio.deviceId ??= { ideal: 'default' };
37
37
  if (audioProcessor || defaultAudioProcessor) {
38
38
  clonedOptions.audio.processor = audioProcessor ?? defaultAudioProcessor;
39
39
  }
@@ -43,7 +43,7 @@ export function mergeDefaultOptions(
43
43
  clonedOptions.video as Record<string, unknown>,
44
44
  videoDefaults as Record<string, unknown>,
45
45
  );
46
- clonedOptions.video.deviceId ??= 'default';
46
+ clonedOptions.video.deviceId ??= { ideal: 'default' };
47
47
  if (videoProcessor || defaultVideoProcessor) {
48
48
  clonedOptions.video.processor = videoProcessor ?? defaultVideoProcessor;
49
49
  }
@@ -81,9 +81,9 @@ export function constraintsForOptions(options: CreateLocalTracksOptions): MediaS
81
81
  }
82
82
  });
83
83
  constraints.video = videoOptions;
84
- constraints.video.deviceId ??= 'default';
84
+ constraints.video.deviceId ??= { ideal: 'default' };
85
85
  } else {
86
- constraints.video = options.video ? { deviceId: 'default' } : false;
86
+ constraints.video = options.video ? { deviceId: { ideal: 'default' } } : false;
87
87
  }
88
88
  } else {
89
89
  constraints.video = false;
@@ -92,9 +92,9 @@ export function constraintsForOptions(options: CreateLocalTracksOptions): MediaS
92
92
  if (options.audio) {
93
93
  if (typeof options.audio === 'object') {
94
94
  constraints.audio = options.audio;
95
- constraints.audio.deviceId ??= 'default';
95
+ constraints.audio.deviceId ??= { ideal: 'default' };
96
96
  } else {
97
- constraints.audio = { deviceId: 'default' };
97
+ constraints.audio = { deviceId: { ideal: 'default' } };
98
98
  }
99
99
  } else {
100
100
  constraints.audio = false;