livekit-client 2.7.4 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +14 -14
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +151 -88
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
- package/dist/src/room/DeviceManager.d.ts +2 -0
- package/dist/src/room/DeviceManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +1 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/defaults.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts +0 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +2 -0
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +0 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/ts4.2/src/room/DeviceManager.d.ts +2 -0
- package/dist/ts4.2/src/room/Room.d.ts +1 -0
- package/dist/ts4.2/src/room/track/LocalAudioTrack.d.ts +0 -1
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +2 -0
- package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +0 -1
- package/package.json +16 -15
- package/src/e2ee/worker/tsconfig.json +1 -1
- package/src/room/DeviceManager.ts +9 -2
- package/src/room/Room.ts +70 -9
- package/src/room/defaults.ts +2 -0
- package/src/room/participant/LocalParticipant.ts +15 -4
- package/src/room/participant/publishUtils.ts +14 -4
- package/src/room/track/LocalAudioTrack.ts +0 -16
- package/src/room/track/LocalTrack.ts +23 -1
- package/src/room/track/LocalVideoTrack.ts +1 -19
- package/src/room/track/create.ts +2 -2
- package/src/room/track/utils.test.ts +4 -12
- package/src/room/track/utils.ts +6 -2
package/src/room/Room.ts
CHANGED
@@ -86,6 +86,7 @@ import {
|
|
86
86
|
isBrowserSupported,
|
87
87
|
isCloud,
|
88
88
|
isReactNative,
|
89
|
+
isSafari,
|
89
90
|
isWeb,
|
90
91
|
supportsSetSinkId,
|
91
92
|
toHttpUrl,
|
@@ -234,6 +235,21 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
234
235
|
if (this.options.e2ee) {
|
235
236
|
this.setupE2EE();
|
236
237
|
}
|
238
|
+
|
239
|
+
if (isWeb()) {
|
240
|
+
const abortController = new AbortController();
|
241
|
+
|
242
|
+
// in order to catch device changes prior to room connection we need to register the event in the constructor
|
243
|
+
navigator.mediaDevices?.addEventListener('devicechange', this.handleDeviceChange, {
|
244
|
+
signal: abortController.signal,
|
245
|
+
});
|
246
|
+
|
247
|
+
if (Room.cleanupRegistry) {
|
248
|
+
Room.cleanupRegistry.register(this, () => {
|
249
|
+
abortController.abort();
|
250
|
+
});
|
251
|
+
}
|
252
|
+
}
|
237
253
|
}
|
238
254
|
|
239
255
|
/**
|
@@ -434,6 +450,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
434
450
|
return DeviceManager.getInstance().getDevices(kind, requestPermissions);
|
435
451
|
}
|
436
452
|
|
453
|
+
static cleanupRegistry =
|
454
|
+
typeof FinalizationRegistry !== 'undefined' &&
|
455
|
+
new FinalizationRegistry((cleanup: () => void) => {
|
456
|
+
cleanup();
|
457
|
+
});
|
458
|
+
|
437
459
|
/**
|
438
460
|
* prepareConnection should be called as soon as the page is loaded, in order
|
439
461
|
* to speed up the connection attempt. This function will
|
@@ -769,7 +791,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
769
791
|
}
|
770
792
|
if (isWeb()) {
|
771
793
|
document.addEventListener('freeze', this.onPageLeave);
|
772
|
-
navigator.mediaDevices?.addEventListener('devicechange', this.handleDeviceChange);
|
773
794
|
}
|
774
795
|
this.setAndEmitConnectionState(ConnectionState.Connected);
|
775
796
|
this.emit(RoomEvent.Connected);
|
@@ -1097,14 +1118,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1097
1118
|
* @param deviceId
|
1098
1119
|
*/
|
1099
1120
|
async switchActiveDevice(kind: MediaDeviceKind, deviceId: string, exact: boolean = false) {
|
1100
|
-
let deviceHasChanged = false;
|
1101
1121
|
let success = true;
|
1122
|
+
let needsUpdateWithoutTracks = false;
|
1102
1123
|
const deviceConstraint = exact ? { exact: deviceId } : deviceId;
|
1103
1124
|
if (kind === 'audioinput') {
|
1125
|
+
needsUpdateWithoutTracks = this.localParticipant.audioTrackPublications.size === 0;
|
1104
1126
|
const prevDeviceId =
|
1105
1127
|
this.getActiveDevice(kind) ?? this.options.audioCaptureDefaults!.deviceId;
|
1106
1128
|
this.options.audioCaptureDefaults!.deviceId = deviceConstraint;
|
1107
|
-
deviceHasChanged = prevDeviceId !== deviceConstraint;
|
1108
1129
|
const tracks = Array.from(this.localParticipant.audioTrackPublications.values()).filter(
|
1109
1130
|
(track) => track.source === Track.Source.Microphone,
|
1110
1131
|
);
|
@@ -1117,10 +1138,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1117
1138
|
throw e;
|
1118
1139
|
}
|
1119
1140
|
} else if (kind === 'videoinput') {
|
1141
|
+
needsUpdateWithoutTracks = this.localParticipant.videoTrackPublications.size === 0;
|
1120
1142
|
const prevDeviceId =
|
1121
1143
|
this.getActiveDevice(kind) ?? this.options.videoCaptureDefaults!.deviceId;
|
1122
1144
|
this.options.videoCaptureDefaults!.deviceId = deviceConstraint;
|
1123
|
-
deviceHasChanged = prevDeviceId !== deviceConstraint;
|
1124
1145
|
const tracks = Array.from(this.localParticipant.videoTrackPublications.values()).filter(
|
1125
1146
|
(track) => track.source === Track.Source.Camera,
|
1126
1147
|
);
|
@@ -1147,7 +1168,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1147
1168
|
this.options.audioOutput ??= {};
|
1148
1169
|
const prevDeviceId = this.getActiveDevice(kind) ?? this.options.audioOutput.deviceId;
|
1149
1170
|
this.options.audioOutput.deviceId = deviceId;
|
1150
|
-
deviceHasChanged = prevDeviceId !== deviceConstraint;
|
1151
1171
|
|
1152
1172
|
try {
|
1153
1173
|
if (this.options.webAudioMix) {
|
@@ -1164,7 +1184,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1164
1184
|
throw e;
|
1165
1185
|
}
|
1166
1186
|
}
|
1167
|
-
if (
|
1187
|
+
if (needsUpdateWithoutTracks) {
|
1168
1188
|
this.localParticipant.activeDeviceMap.set(kind, deviceId);
|
1169
1189
|
this.emit(RoomEvent.ActiveDeviceChanged, kind, deviceId);
|
1170
1190
|
}
|
@@ -1654,13 +1674,54 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1654
1674
|
};
|
1655
1675
|
|
1656
1676
|
private handleDeviceChange = async () => {
|
1677
|
+
const previousDevices = DeviceManager.getInstance().previousDevices;
|
1657
1678
|
// check for available devices, but don't request permissions in order to avoid prompts for kinds that haven't been used before
|
1658
1679
|
const availableDevices = await DeviceManager.getInstance().getDevices(undefined, false);
|
1680
|
+
const browser = getBrowser();
|
1681
|
+
if (browser?.name === 'Chrome' && browser.os !== 'iOS') {
|
1682
|
+
for (let availableDevice of availableDevices) {
|
1683
|
+
const previousDevice = previousDevices.find(
|
1684
|
+
(info) => info.deviceId === availableDevice.deviceId,
|
1685
|
+
);
|
1686
|
+
if (
|
1687
|
+
previousDevice &&
|
1688
|
+
previousDevice.label !== '' &&
|
1689
|
+
previousDevice.kind === availableDevice.kind &&
|
1690
|
+
previousDevice.label !== availableDevice.label
|
1691
|
+
) {
|
1692
|
+
// label has changed on device the same deviceId, indicating that the default device has changed on the OS level
|
1693
|
+
if (this.getActiveDevice(availableDevice.kind) === 'default') {
|
1694
|
+
// emit an active device change event only if the selected output device is actually on `default`
|
1695
|
+
this.emit(
|
1696
|
+
RoomEvent.ActiveDeviceChanged,
|
1697
|
+
availableDevice.kind,
|
1698
|
+
availableDevice.deviceId,
|
1699
|
+
);
|
1700
|
+
}
|
1701
|
+
}
|
1702
|
+
}
|
1703
|
+
}
|
1704
|
+
|
1659
1705
|
// inputs are automatically handled via TrackEvent.Ended causing a TrackEvent.Restarted. Here we only need to worry about audiooutputs changing
|
1660
|
-
const kinds: MediaDeviceKind[] = ['audiooutput'];
|
1706
|
+
const kinds: MediaDeviceKind[] = ['audiooutput', 'audioinput', 'videoinput'];
|
1661
1707
|
for (let kind of kinds) {
|
1662
|
-
// switch to first available device if previously active device is not available any more
|
1663
1708
|
const devicesOfKind = availableDevices.filter((d) => d.kind === kind);
|
1709
|
+
const activeDevice = this.getActiveDevice(kind);
|
1710
|
+
|
1711
|
+
if (activeDevice === previousDevices.filter((info) => info.kind === kind)[0]?.deviceId) {
|
1712
|
+
// in Safari the first device is always the default, so we assume a user on the default device would like to switch to the default once it changes
|
1713
|
+
// FF doesn't emit an event when the default device changes, so we perform the same best effort and switch to the new device once connected and if it's the first in the array
|
1714
|
+
if (devicesOfKind.length > 0 && devicesOfKind[0]?.deviceId !== activeDevice) {
|
1715
|
+
await this.switchActiveDevice(kind, devicesOfKind[0].deviceId);
|
1716
|
+
continue;
|
1717
|
+
}
|
1718
|
+
}
|
1719
|
+
|
1720
|
+
if ((kind === 'audioinput' && !isSafari()) || kind === 'videoinput') {
|
1721
|
+
// airpods on Safari need special handling for audioinput as the track doesn't end as soon as you take them out
|
1722
|
+
continue;
|
1723
|
+
}
|
1724
|
+
// switch to first available device if previously active device is not available any more
|
1664
1725
|
if (
|
1665
1726
|
devicesOfKind.length > 0 &&
|
1666
1727
|
!devicesOfKind.find((deviceInfo) => deviceInfo.deviceId === this.getActiveDevice(kind))
|
@@ -2013,7 +2074,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
2013
2074
|
this.emit(RoomEvent.LocalAudioSilenceDetected, pub);
|
2014
2075
|
}
|
2015
2076
|
}
|
2016
|
-
const deviceId = await pub.track?.getDeviceId();
|
2077
|
+
const deviceId = await pub.track?.getDeviceId(false);
|
2017
2078
|
const deviceKind = sourceToKind(pub.source);
|
2018
2079
|
if (
|
2019
2080
|
deviceKind &&
|
package/src/room/defaults.ts
CHANGED
@@ -22,6 +22,7 @@ export const publishDefaults: TrackPublishDefaults = {
|
|
22
22
|
} as const;
|
23
23
|
|
24
24
|
export const audioDefaults: AudioCaptureOptions = {
|
25
|
+
deviceId: 'default',
|
25
26
|
autoGainControl: true,
|
26
27
|
echoCancellation: true,
|
27
28
|
noiseSuppression: true,
|
@@ -29,6 +30,7 @@ export const audioDefaults: AudioCaptureOptions = {
|
|
29
30
|
};
|
30
31
|
|
31
32
|
export const videoDefaults: VideoCaptureOptions = {
|
33
|
+
deviceId: 'default',
|
32
34
|
resolution: VideoPresets.h720.resolution,
|
33
35
|
};
|
34
36
|
|
@@ -154,7 +154,11 @@ export default class LocalParticipant extends Participant {
|
|
154
154
|
this.engine = engine;
|
155
155
|
this.roomOptions = options;
|
156
156
|
this.setupEngine(engine);
|
157
|
-
this.activeDeviceMap = new Map(
|
157
|
+
this.activeDeviceMap = new Map([
|
158
|
+
['audioinput', 'default'],
|
159
|
+
['videoinput', 'default'],
|
160
|
+
['audiooutput', 'default'],
|
161
|
+
]);
|
158
162
|
this.pendingSignalRequests = new Map();
|
159
163
|
}
|
160
164
|
|
@@ -486,6 +490,16 @@ export default class LocalParticipant extends Participant {
|
|
486
490
|
default:
|
487
491
|
throw new TrackInvalidError(source);
|
488
492
|
}
|
493
|
+
} catch (e: unknown) {
|
494
|
+
localTracks?.forEach((tr) => {
|
495
|
+
tr.stop();
|
496
|
+
});
|
497
|
+
if (e instanceof Error) {
|
498
|
+
this.emit(ParticipantEvent.MediaDevicesError, e);
|
499
|
+
}
|
500
|
+
throw e;
|
501
|
+
}
|
502
|
+
try {
|
489
503
|
const publishPromises: Array<Promise<LocalTrackPublication>> = [];
|
490
504
|
for (const localTrack of localTracks) {
|
491
505
|
this.log.info('publishing track', {
|
@@ -502,9 +516,6 @@ export default class LocalParticipant extends Participant {
|
|
502
516
|
localTracks?.forEach((tr) => {
|
503
517
|
tr.stop();
|
504
518
|
});
|
505
|
-
if (e instanceof Error && !(e instanceof TrackInvalidError)) {
|
506
|
-
this.emit(ParticipantEvent.MediaDevicesError, e);
|
507
|
-
}
|
508
519
|
throw e;
|
509
520
|
} finally {
|
510
521
|
this.pendingPublishing.delete(source);
|
@@ -125,6 +125,8 @@ export function computeVideoEncodings(
|
|
125
125
|
log.debug('using video encoding', videoEncoding);
|
126
126
|
}
|
127
127
|
|
128
|
+
const sourceFramerate = videoEncoding.maxFramerate;
|
129
|
+
|
128
130
|
const original = new VideoPreset(
|
129
131
|
width,
|
130
132
|
height,
|
@@ -216,10 +218,10 @@ export function computeVideoEncodings(
|
|
216
218
|
// based on other conditions.
|
217
219
|
const size = Math.max(width, height);
|
218
220
|
if (size >= 960 && midPreset) {
|
219
|
-
return encodingsFromPresets(width, height, [lowPreset, midPreset, original]);
|
221
|
+
return encodingsFromPresets(width, height, [lowPreset, midPreset, original], sourceFramerate);
|
220
222
|
}
|
221
223
|
if (size >= 480) {
|
222
|
-
return encodingsFromPresets(width, height, [lowPreset, original]);
|
224
|
+
return encodingsFromPresets(width, height, [lowPreset, original], sourceFramerate);
|
223
225
|
}
|
224
226
|
}
|
225
227
|
return encodingsFromPresets(width, height, [original]);
|
@@ -344,6 +346,7 @@ function encodingsFromPresets(
|
|
344
346
|
width: number,
|
345
347
|
height: number,
|
346
348
|
presets: VideoPreset[],
|
349
|
+
sourceFramerate?: number | undefined,
|
347
350
|
): RTCRtpEncodingParameters[] {
|
348
351
|
const encodings: RTCRtpEncodingParameters[] = [];
|
349
352
|
presets.forEach((preset, idx) => {
|
@@ -352,13 +355,20 @@ function encodingsFromPresets(
|
|
352
355
|
}
|
353
356
|
const size = Math.min(width, height);
|
354
357
|
const rid = videoRids[idx];
|
358
|
+
|
355
359
|
const encoding: RTCRtpEncodingParameters = {
|
356
360
|
rid,
|
357
361
|
scaleResolutionDownBy: Math.max(1, size / Math.min(preset.width, preset.height)),
|
358
362
|
maxBitrate: preset.encoding.maxBitrate,
|
359
363
|
};
|
360
|
-
|
361
|
-
|
364
|
+
// ensure that the sourceFramerate is the highest framerate applied across all layers so that the
|
365
|
+
// original encoding doesn't get bumped unintentionally by any of the other layers
|
366
|
+
const maxFramerate =
|
367
|
+
sourceFramerate && preset.encoding.maxFramerate
|
368
|
+
? Math.min(sourceFramerate, preset.encoding.maxFramerate)
|
369
|
+
: preset.encoding.maxFramerate;
|
370
|
+
if (maxFramerate) {
|
371
|
+
encoding.maxFramerate = maxFramerate;
|
362
372
|
}
|
363
373
|
const canSetPriority = isFireFox() || idx === 0;
|
364
374
|
if (preset.encoding.priority && canSetPriority) {
|
@@ -45,22 +45,6 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
|
|
45
45
|
this.checkForSilence();
|
46
46
|
}
|
47
47
|
|
48
|
-
async setDeviceId(deviceId: ConstrainDOMString): Promise<boolean> {
|
49
|
-
if (
|
50
|
-
this._constraints.deviceId === deviceId &&
|
51
|
-
this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)
|
52
|
-
) {
|
53
|
-
return true;
|
54
|
-
}
|
55
|
-
this._constraints.deviceId = deviceId;
|
56
|
-
if (!this.isMuted) {
|
57
|
-
await this.restartTrack();
|
58
|
-
}
|
59
|
-
return (
|
60
|
-
this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId
|
61
|
-
);
|
62
|
-
}
|
63
|
-
|
64
48
|
async mute(): Promise<typeof this> {
|
65
49
|
const unlock = await this.muteLock.lock();
|
66
50
|
try {
|
@@ -5,7 +5,7 @@ import DeviceManager from '../DeviceManager';
|
|
5
5
|
import { DeviceUnsupportedError, TrackInvalidError } from '../errors';
|
6
6
|
import { TrackEvent } from '../events';
|
7
7
|
import type { LoggerOptions } from '../types';
|
8
|
-
import { compareVersions, isMobile, sleep } from '../utils';
|
8
|
+
import { compareVersions, isMobile, sleep, unwrapConstraint } from '../utils';
|
9
9
|
import { Track, attachToElement, detachTrack } from './Track';
|
10
10
|
import type { VideoCodec } from './options';
|
11
11
|
import type { TrackProcessor } from './processor/types';
|
@@ -221,6 +221,28 @@ export default abstract class LocalTrack<
|
|
221
221
|
throw new TrackInvalidError('unable to get track dimensions after timeout');
|
222
222
|
}
|
223
223
|
|
224
|
+
async setDeviceId(deviceId: ConstrainDOMString): Promise<boolean> {
|
225
|
+
if (
|
226
|
+
this._constraints.deviceId === deviceId &&
|
227
|
+
this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)
|
228
|
+
) {
|
229
|
+
return true;
|
230
|
+
}
|
231
|
+
this._constraints.deviceId = deviceId;
|
232
|
+
|
233
|
+
// when track is muted, underlying media stream track is stopped and
|
234
|
+
// will be restarted later
|
235
|
+
if (this.isMuted) {
|
236
|
+
return true;
|
237
|
+
}
|
238
|
+
|
239
|
+
await this.restartTrack();
|
240
|
+
|
241
|
+
return unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
|
242
|
+
}
|
243
|
+
|
244
|
+
abstract restartTrack(constraints?: unknown): Promise<void>;
|
245
|
+
|
224
246
|
/**
|
225
247
|
* @returns DeviceID of the device that is currently being used for this track
|
226
248
|
*/
|
@@ -11,7 +11,7 @@ import { ScalabilityMode } from '../participant/publishUtils';
|
|
11
11
|
import type { VideoSenderStats } from '../stats';
|
12
12
|
import { computeBitrate, monitorFrequency } from '../stats';
|
13
13
|
import type { LoggerOptions } from '../types';
|
14
|
-
import { isFireFox, isMobile, isWeb
|
14
|
+
import { isFireFox, isMobile, isWeb } from '../utils';
|
15
15
|
import LocalTrack from './LocalTrack';
|
16
16
|
import { Track, VideoQuality } from './Track';
|
17
17
|
import type { VideoCaptureOptions, VideoCodec } from './options';
|
@@ -241,24 +241,6 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
241
241
|
this.setPublishingLayers(qualities);
|
242
242
|
}
|
243
243
|
|
244
|
-
async setDeviceId(deviceId: ConstrainDOMString): Promise<boolean> {
|
245
|
-
if (
|
246
|
-
this._constraints.deviceId === deviceId &&
|
247
|
-
this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)
|
248
|
-
) {
|
249
|
-
return true;
|
250
|
-
}
|
251
|
-
this._constraints.deviceId = deviceId;
|
252
|
-
// when video is muted, underlying media stream track is stopped and
|
253
|
-
// will be restarted later
|
254
|
-
if (!this.isMuted) {
|
255
|
-
await this.restartTrack();
|
256
|
-
}
|
257
|
-
return (
|
258
|
-
this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId
|
259
|
-
);
|
260
|
-
}
|
261
|
-
|
262
244
|
async restartTrack(options?: VideoCaptureOptions) {
|
263
245
|
let constraints: MediaTrackConstraints | undefined;
|
264
246
|
if (options) {
|
package/src/room/track/create.ts
CHANGED
@@ -32,8 +32,8 @@ export async function createLocalTracks(
|
|
32
32
|
): Promise<Array<LocalTrack>> {
|
33
33
|
// set default options to true
|
34
34
|
options ??= {};
|
35
|
-
options.audio ??=
|
36
|
-
options.video ??=
|
35
|
+
options.audio ??= { deviceId: 'default' };
|
36
|
+
options.video ??= { deviceId: 'default' };
|
37
37
|
|
38
38
|
const { audioProcessor, videoProcessor } = extractProcessorsFromOptions(options);
|
39
39
|
const opts = mergeDefaultOptions(options, audioDefaults, videoDefaults);
|
@@ -1,17 +1,9 @@
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
2
|
-
import {
|
2
|
+
import { audioDefaults, videoDefaults } from '../defaults';
|
3
|
+
import { type AudioCaptureOptions, VideoPresets } from './options';
|
3
4
|
import { constraintsForOptions, diffAttributes, mergeDefaultOptions } from './utils';
|
4
5
|
|
5
6
|
describe('mergeDefaultOptions', () => {
|
6
|
-
const audioDefaults: AudioCaptureOptions = {
|
7
|
-
autoGainControl: true,
|
8
|
-
channelCount: 2,
|
9
|
-
};
|
10
|
-
const videoDefaults: VideoCaptureOptions = {
|
11
|
-
deviceId: 'video123',
|
12
|
-
resolution: VideoPresets.h1080.resolution,
|
13
|
-
};
|
14
|
-
|
15
7
|
it('does not enable undefined options', () => {
|
16
8
|
const opts = mergeDefaultOptions(undefined, audioDefaults, videoDefaults);
|
17
9
|
expect(opts.audio).toEqual(undefined);
|
@@ -69,7 +61,7 @@ describe('constraintsForOptions', () => {
|
|
69
61
|
const constraints = constraintsForOptions({
|
70
62
|
audio: true,
|
71
63
|
});
|
72
|
-
expect(constraints.audio).toEqual(
|
64
|
+
expect(constraints.audio).toEqual({ deviceId: audioDefaults.deviceId });
|
73
65
|
expect(constraints.video).toEqual(false);
|
74
66
|
});
|
75
67
|
|
@@ -81,7 +73,7 @@ describe('constraintsForOptions', () => {
|
|
81
73
|
},
|
82
74
|
});
|
83
75
|
const audioOpts = constraints.audio as MediaTrackConstraints;
|
84
|
-
expect(Object.keys(audioOpts)).toEqual(['noiseSuppression', 'echoCancellation']);
|
76
|
+
expect(Object.keys(audioOpts)).toEqual(['noiseSuppression', 'echoCancellation', 'deviceId']);
|
85
77
|
expect(audioOpts.noiseSuppression).toEqual(true);
|
86
78
|
expect(audioOpts.echoCancellation).toEqual(false);
|
87
79
|
});
|
package/src/room/track/utils.ts
CHANGED
@@ -31,6 +31,7 @@ export function mergeDefaultOptions(
|
|
31
31
|
clonedOptions.audio as Record<string, unknown>,
|
32
32
|
audioDefaults as Record<string, unknown>,
|
33
33
|
);
|
34
|
+
clonedOptions.audio.deviceId ??= 'default';
|
34
35
|
if (audioProcessor) {
|
35
36
|
clonedOptions.audio.processor = audioProcessor;
|
36
37
|
}
|
@@ -40,6 +41,7 @@ export function mergeDefaultOptions(
|
|
40
41
|
clonedOptions.video as Record<string, unknown>,
|
41
42
|
videoDefaults as Record<string, unknown>,
|
42
43
|
);
|
44
|
+
clonedOptions.video.deviceId ??= 'default';
|
43
45
|
if (videoProcessor) {
|
44
46
|
clonedOptions.video.processor = videoProcessor;
|
45
47
|
}
|
@@ -77,8 +79,9 @@ export function constraintsForOptions(options: CreateLocalTracksOptions): MediaS
|
|
77
79
|
}
|
78
80
|
});
|
79
81
|
constraints.video = videoOptions;
|
82
|
+
constraints.video.deviceId ??= 'default';
|
80
83
|
} else {
|
81
|
-
constraints.video = options.video;
|
84
|
+
constraints.video = options.video ? { deviceId: 'default' } : false;
|
82
85
|
}
|
83
86
|
} else {
|
84
87
|
constraints.video = false;
|
@@ -87,8 +90,9 @@ export function constraintsForOptions(options: CreateLocalTracksOptions): MediaS
|
|
87
90
|
if (options.audio) {
|
88
91
|
if (typeof options.audio === 'object') {
|
89
92
|
constraints.audio = options.audio;
|
93
|
+
constraints.audio.deviceId ??= 'default';
|
90
94
|
} else {
|
91
|
-
constraints.audio =
|
95
|
+
constraints.audio = { deviceId: 'default' };
|
92
96
|
}
|
93
97
|
} else {
|
94
98
|
constraints.audio = false;
|