@stream-io/video-client 1.43.0 → 1.44.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.
- package/CHANGELOG.md +15 -0
- package/dist/index.browser.es.js +206 -59
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +205 -58
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +206 -59
- package/dist/index.es.js.map +1 -1
- package/dist/src/StreamVideoClient.d.ts +2 -8
- package/dist/src/coordinator/connection/types.d.ts +5 -0
- package/dist/src/devices/CameraManager.d.ts +7 -2
- package/dist/src/devices/DeviceManager.d.ts +7 -15
- package/dist/src/devices/MicrophoneManager.d.ts +2 -1
- package/dist/src/devices/SpeakerManager.d.ts +6 -1
- package/dist/src/devices/devicePersistence.d.ts +27 -0
- package/dist/src/helpers/clientUtils.d.ts +1 -1
- package/dist/src/permissions/PermissionsContext.d.ts +1 -1
- package/dist/src/types.d.ts +38 -2
- package/package.json +1 -1
- package/src/Call.ts +5 -3
- package/src/StreamVideoClient.ts +1 -9
- package/src/coordinator/connection/types.ts +6 -0
- package/src/devices/CameraManager.ts +31 -11
- package/src/devices/DeviceManager.ts +113 -31
- package/src/devices/MicrophoneManager.ts +26 -8
- package/src/devices/ScreenShareManager.ts +7 -1
- package/src/devices/SpeakerManager.ts +62 -18
- package/src/devices/__tests__/CameraManager.test.ts +184 -21
- package/src/devices/__tests__/DeviceManager.test.ts +184 -2
- package/src/devices/__tests__/DeviceManagerFilters.test.ts +2 -0
- package/src/devices/__tests__/MicrophoneManager.test.ts +146 -2
- package/src/devices/__tests__/MicrophoneManagerRN.test.ts +2 -0
- package/src/devices/__tests__/ScreenShareManager.test.ts +2 -0
- package/src/devices/__tests__/SpeakerManager.test.ts +90 -0
- package/src/devices/__tests__/devicePersistence.test.ts +142 -0
- package/src/devices/__tests__/devices.test.ts +390 -0
- package/src/devices/__tests__/mediaStreamTestHelpers.ts +58 -0
- package/src/devices/__tests__/mocks.ts +35 -0
- package/src/devices/devicePersistence.ts +106 -0
- package/src/devices/devices.ts +3 -3
- package/src/helpers/__tests__/DynascaleManager.test.ts +3 -1
- package/src/helpers/clientUtils.ts +1 -1
- package/src/permissions/PermissionsContext.ts +1 -0
- package/src/sorting/presets.ts +1 -1
- package/src/store/CallState.ts +1 -1
- package/src/types.ts +49 -2
package/dist/index.cjs.js
CHANGED
|
@@ -5084,7 +5084,7 @@ const paginatedLayoutSortPreset = combineComparators(pinned, ifInvisibleOrUnknow
|
|
|
5084
5084
|
/**
|
|
5085
5085
|
* The sorting preset for livestreams and audio rooms.
|
|
5086
5086
|
*/
|
|
5087
|
-
const livestreamOrAudioRoomSortPreset = combineComparators(
|
|
5087
|
+
const livestreamOrAudioRoomSortPreset = combineComparators(ifInvisibleOrUnknownBy(combineComparators(dominantSpeaker, speaking, reactionType('raised-hand'), withVideoIngressSource, publishingVideo, publishingAudio)), role('admin', 'host', 'speaker'));
|
|
5088
5088
|
|
|
5089
5089
|
const ensureExhausted = (x, message) => {
|
|
5090
5090
|
videoLoggerSystem.getLogger('helpers').warn(message, x);
|
|
@@ -5334,7 +5334,7 @@ class CallState {
|
|
|
5334
5334
|
this.updateParticipant = (sessionId, patch) => {
|
|
5335
5335
|
const participant = this.findParticipantBySessionId(sessionId);
|
|
5336
5336
|
if (!participant) {
|
|
5337
|
-
this.logger.
|
|
5337
|
+
this.logger.debug(`Participant with sessionId ${sessionId} not found`);
|
|
5338
5338
|
return;
|
|
5339
5339
|
}
|
|
5340
5340
|
const thePatch = typeof patch === 'function' ? patch(participant) : patch;
|
|
@@ -6251,7 +6251,7 @@ const getSdkVersion = (sdk) => {
|
|
|
6251
6251
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
6252
6252
|
};
|
|
6253
6253
|
|
|
6254
|
-
const version = "1.
|
|
6254
|
+
const version = "1.44.0";
|
|
6255
6255
|
const [major, minor, patch] = version.split('.');
|
|
6256
6256
|
let sdkInfo = {
|
|
6257
6257
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -10019,6 +10019,7 @@ class PermissionsContext {
|
|
|
10019
10019
|
return false;
|
|
10020
10020
|
default:
|
|
10021
10021
|
ensureExhausted(trackType, 'Unknown track type');
|
|
10022
|
+
return false;
|
|
10022
10023
|
}
|
|
10023
10024
|
};
|
|
10024
10025
|
/**
|
|
@@ -10355,7 +10356,7 @@ const getDeviceChangeObserver = lazy((tracer) => {
|
|
|
10355
10356
|
* the observable errors.
|
|
10356
10357
|
*/
|
|
10357
10358
|
const getAudioDevices = lazy((tracer) => {
|
|
10358
|
-
return rxjs.merge(getDeviceChangeObserver(tracer), getAudioBrowserPermission().asObservable()).pipe(rxjs.startWith(
|
|
10359
|
+
return rxjs.merge(getDeviceChangeObserver(tracer), getAudioBrowserPermission().asObservable()).pipe(rxjs.startWith([]), rxjs.concatMap(() => getDevices(getAudioBrowserPermission(), 'audioinput', tracer)), rxjs.shareReplay(1));
|
|
10359
10360
|
});
|
|
10360
10361
|
/**
|
|
10361
10362
|
* Prompts the user for a permission to use video devices (if not already granted
|
|
@@ -10364,7 +10365,7 @@ const getAudioDevices = lazy((tracer) => {
|
|
|
10364
10365
|
* the observable errors.
|
|
10365
10366
|
*/
|
|
10366
10367
|
const getVideoDevices = lazy((tracer) => {
|
|
10367
|
-
return rxjs.merge(getDeviceChangeObserver(tracer), getVideoBrowserPermission().asObservable()).pipe(rxjs.startWith(
|
|
10368
|
+
return rxjs.merge(getDeviceChangeObserver(tracer), getVideoBrowserPermission().asObservable()).pipe(rxjs.startWith([]), rxjs.concatMap(() => getDevices(getVideoBrowserPermission(), 'videoinput', tracer)), rxjs.shareReplay(1));
|
|
10368
10369
|
});
|
|
10369
10370
|
/**
|
|
10370
10371
|
* Prompts the user for a permission to use video devices (if not already granted
|
|
@@ -10373,7 +10374,7 @@ const getVideoDevices = lazy((tracer) => {
|
|
|
10373
10374
|
* the observable errors.
|
|
10374
10375
|
*/
|
|
10375
10376
|
const getAudioOutputDevices = lazy((tracer) => {
|
|
10376
|
-
return rxjs.merge(getDeviceChangeObserver(tracer), getAudioBrowserPermission().asObservable()).pipe(rxjs.startWith(
|
|
10377
|
+
return rxjs.merge(getDeviceChangeObserver(tracer), getAudioBrowserPermission().asObservable()).pipe(rxjs.startWith([]), rxjs.concatMap(() => getDevices(getAudioBrowserPermission(), 'audiooutput', tracer)), rxjs.shareReplay(1));
|
|
10377
10378
|
});
|
|
10378
10379
|
let getUserMediaExecId = 0;
|
|
10379
10380
|
const getStream = async (constraints, tracer) => {
|
|
@@ -10580,25 +10581,66 @@ function resolveDeviceId(deviceId, kind) {
|
|
|
10580
10581
|
*/
|
|
10581
10582
|
const isMobile = () => /Mobi/i.test(navigator.userAgent);
|
|
10582
10583
|
|
|
10584
|
+
const defaultDeviceId = 'default';
|
|
10585
|
+
const isLocalStorageAvailable = () => typeof window !== 'undefined' && typeof window.localStorage !== 'undefined';
|
|
10586
|
+
const normalize = (options) => {
|
|
10587
|
+
return {
|
|
10588
|
+
storageKey: options?.storageKey ?? `@stream-io/device-preferences`,
|
|
10589
|
+
enabled: isLocalStorageAvailable() && !isReactNative()
|
|
10590
|
+
? (options?.enabled ?? true)
|
|
10591
|
+
: false,
|
|
10592
|
+
};
|
|
10593
|
+
};
|
|
10594
|
+
const createSyntheticDevice = (deviceId, kind) => {
|
|
10595
|
+
return { deviceId, kind, label: '', groupId: '' };
|
|
10596
|
+
};
|
|
10597
|
+
const readPreferences = (storageKey) => {
|
|
10598
|
+
try {
|
|
10599
|
+
const raw = window.localStorage.getItem(storageKey) || '{}';
|
|
10600
|
+
return JSON.parse(raw);
|
|
10601
|
+
}
|
|
10602
|
+
catch {
|
|
10603
|
+
return {};
|
|
10604
|
+
}
|
|
10605
|
+
};
|
|
10606
|
+
const writePreferences = (currentDevice, deviceKey, muted, storageKey) => {
|
|
10607
|
+
if (!isLocalStorageAvailable())
|
|
10608
|
+
return;
|
|
10609
|
+
const selectedDeviceId = currentDevice?.deviceId ?? defaultDeviceId;
|
|
10610
|
+
const selectedDeviceLabel = currentDevice?.label ?? '';
|
|
10611
|
+
const preferences = readPreferences(storageKey);
|
|
10612
|
+
const preferenceHistory = [preferences[deviceKey] ?? []]
|
|
10613
|
+
.flat()
|
|
10614
|
+
.filter((p) => p.selectedDeviceId !== selectedDeviceId &&
|
|
10615
|
+
(p.selectedDeviceLabel === '' ||
|
|
10616
|
+
p.selectedDeviceLabel !== selectedDeviceLabel));
|
|
10617
|
+
const nextPreferences = {
|
|
10618
|
+
...preferences,
|
|
10619
|
+
[deviceKey]: [
|
|
10620
|
+
{
|
|
10621
|
+
selectedDeviceId,
|
|
10622
|
+
selectedDeviceLabel,
|
|
10623
|
+
...(typeof muted === 'boolean' ? { muted } : {}),
|
|
10624
|
+
},
|
|
10625
|
+
...preferenceHistory,
|
|
10626
|
+
].slice(0, 3),
|
|
10627
|
+
};
|
|
10628
|
+
try {
|
|
10629
|
+
window.localStorage.setItem(storageKey, JSON.stringify(nextPreferences));
|
|
10630
|
+
}
|
|
10631
|
+
catch (err) {
|
|
10632
|
+
const logger = videoLoggerSystem.getLogger('DevicePersistence');
|
|
10633
|
+
logger.error('failed to save device preferences', err);
|
|
10634
|
+
}
|
|
10635
|
+
};
|
|
10636
|
+
const toPreferenceList = (preference) => (preference ? [preference].flat() : []);
|
|
10637
|
+
|
|
10583
10638
|
class DeviceManager {
|
|
10584
|
-
constructor(call, state, trackType) {
|
|
10639
|
+
constructor(call, state, trackType, devicePersistence) {
|
|
10585
10640
|
/**
|
|
10586
10641
|
* if true, stops the media stream when call is left
|
|
10587
10642
|
*/
|
|
10588
10643
|
this.stopOnLeave = true;
|
|
10589
|
-
/**
|
|
10590
|
-
* When `true`, the `apply()` method will skip automatically enabling/disabling
|
|
10591
|
-
* the device based on server defaults (`mic_default_on`, `camera_default_on`).
|
|
10592
|
-
*
|
|
10593
|
-
* This is useful when application code wants to handle device preferences
|
|
10594
|
-
* (e.g., persisted user preferences) and prevent server defaults from
|
|
10595
|
-
* overriding them.
|
|
10596
|
-
*
|
|
10597
|
-
* @default false
|
|
10598
|
-
*
|
|
10599
|
-
* @internal
|
|
10600
|
-
*/
|
|
10601
|
-
this.deferServerDefaults = false;
|
|
10602
10644
|
this.subscriptions = [];
|
|
10603
10645
|
this.areSubscriptionsSetUp = false;
|
|
10604
10646
|
this.isTrackStoppedDueToTrackEnd = false;
|
|
@@ -10618,19 +10660,26 @@ class DeviceManager {
|
|
|
10618
10660
|
this.call = call;
|
|
10619
10661
|
this.state = state;
|
|
10620
10662
|
this.trackType = trackType;
|
|
10663
|
+
this.devicePersistence = devicePersistence;
|
|
10621
10664
|
this.logger = videoLoggerSystem.getLogger(`${TrackType[trackType].toLowerCase()} manager`);
|
|
10622
10665
|
this.setup();
|
|
10623
10666
|
}
|
|
10624
10667
|
setup() {
|
|
10625
|
-
if (this.areSubscriptionsSetUp)
|
|
10668
|
+
if (this.areSubscriptionsSetUp)
|
|
10626
10669
|
return;
|
|
10627
|
-
}
|
|
10628
10670
|
this.areSubscriptionsSetUp = true;
|
|
10629
10671
|
if (deviceIds$ &&
|
|
10630
10672
|
!isReactNative() &&
|
|
10631
10673
|
(this.trackType === TrackType.AUDIO || this.trackType === TrackType.VIDEO)) {
|
|
10632
10674
|
this.handleDisconnectedOrReplacedDevices();
|
|
10633
10675
|
}
|
|
10676
|
+
if (this.devicePersistence.enabled) {
|
|
10677
|
+
this.subscriptions.push(createSubscription(rxjs.combineLatest([this.state.selectedDevice$, this.state.status$]), ([selectedDevice, status]) => {
|
|
10678
|
+
if (!status)
|
|
10679
|
+
return;
|
|
10680
|
+
this.persistPreference(selectedDevice, status);
|
|
10681
|
+
}));
|
|
10682
|
+
}
|
|
10634
10683
|
}
|
|
10635
10684
|
/**
|
|
10636
10685
|
* Lists the available audio/video devices
|
|
@@ -10981,13 +11030,11 @@ class DeviceManager {
|
|
|
10981
11030
|
}
|
|
10982
11031
|
}
|
|
10983
11032
|
get mediaDeviceKind() {
|
|
10984
|
-
if (this.trackType === TrackType.AUDIO)
|
|
11033
|
+
if (this.trackType === TrackType.AUDIO)
|
|
10985
11034
|
return 'audioinput';
|
|
10986
|
-
|
|
10987
|
-
if (this.trackType === TrackType.VIDEO) {
|
|
11035
|
+
if (this.trackType === TrackType.VIDEO)
|
|
10988
11036
|
return 'videoinput';
|
|
10989
|
-
|
|
10990
|
-
return '';
|
|
11037
|
+
throw new Error('Invalid track type');
|
|
10991
11038
|
}
|
|
10992
11039
|
handleDisconnectedOrReplacedDevices() {
|
|
10993
11040
|
this.subscriptions.push(createSubscription(rxjs.combineLatest([
|
|
@@ -11035,6 +11082,62 @@ class DeviceManager {
|
|
|
11035
11082
|
const kind = this.mediaDeviceKind;
|
|
11036
11083
|
return devices.find((d) => d.deviceId === deviceId && d.kind === kind);
|
|
11037
11084
|
}
|
|
11085
|
+
persistPreference(selectedDevice, status) {
|
|
11086
|
+
const deviceKind = this.mediaDeviceKind;
|
|
11087
|
+
const deviceKey = deviceKind === 'audioinput' ? 'microphone' : 'camera';
|
|
11088
|
+
const muted = status === 'disabled' ? true : status === 'enabled' ? false : undefined;
|
|
11089
|
+
const { storageKey } = this.devicePersistence;
|
|
11090
|
+
if (!selectedDevice) {
|
|
11091
|
+
writePreferences(undefined, deviceKey, muted, storageKey);
|
|
11092
|
+
return;
|
|
11093
|
+
}
|
|
11094
|
+
const devices = getCurrentValue(this.listDevices()) || [];
|
|
11095
|
+
const currentDevice = this.findDevice(devices, selectedDevice) ??
|
|
11096
|
+
createSyntheticDevice(selectedDevice, deviceKind);
|
|
11097
|
+
writePreferences(currentDevice, deviceKey, muted, storageKey);
|
|
11098
|
+
}
|
|
11099
|
+
async applyPersistedPreferences(enabledInCallType) {
|
|
11100
|
+
const deviceKey = this.trackType === TrackType.AUDIO ? 'microphone' : 'camera';
|
|
11101
|
+
const preferences = readPreferences(this.devicePersistence.storageKey);
|
|
11102
|
+
const preferenceList = toPreferenceList(preferences[deviceKey]);
|
|
11103
|
+
if (preferenceList.length === 0)
|
|
11104
|
+
return false;
|
|
11105
|
+
let muted;
|
|
11106
|
+
let appliedDevice = false;
|
|
11107
|
+
let appliedMute = false;
|
|
11108
|
+
const devices = await rxjs.firstValueFrom(this.listDevices());
|
|
11109
|
+
for (const preference of preferenceList) {
|
|
11110
|
+
muted ?? (muted = preference.muted);
|
|
11111
|
+
if (preference.selectedDeviceId === defaultDeviceId)
|
|
11112
|
+
break;
|
|
11113
|
+
const device = devices.find((d) => d.deviceId === preference.selectedDeviceId) ??
|
|
11114
|
+
devices.find((d) => d.label === preference.selectedDeviceLabel);
|
|
11115
|
+
if (device) {
|
|
11116
|
+
appliedDevice = true;
|
|
11117
|
+
if (!this.state.selectedDevice) {
|
|
11118
|
+
await this.select(device.deviceId);
|
|
11119
|
+
}
|
|
11120
|
+
muted = preference.muted;
|
|
11121
|
+
break;
|
|
11122
|
+
}
|
|
11123
|
+
}
|
|
11124
|
+
const canPublish = this.call.permissionsContext.canPublish(this.trackType);
|
|
11125
|
+
if (typeof muted === 'boolean' && enabledInCallType && canPublish) {
|
|
11126
|
+
await this.applyMutedState(muted);
|
|
11127
|
+
appliedMute = true;
|
|
11128
|
+
}
|
|
11129
|
+
return appliedDevice || appliedMute;
|
|
11130
|
+
}
|
|
11131
|
+
async applyMutedState(muted) {
|
|
11132
|
+
if (this.state.status !== undefined)
|
|
11133
|
+
return;
|
|
11134
|
+
if (muted) {
|
|
11135
|
+
await this.disable();
|
|
11136
|
+
}
|
|
11137
|
+
else {
|
|
11138
|
+
await this.enable();
|
|
11139
|
+
}
|
|
11140
|
+
}
|
|
11038
11141
|
}
|
|
11039
11142
|
|
|
11040
11143
|
class DeviceManagerState {
|
|
@@ -11216,9 +11319,10 @@ class CameraManager extends DeviceManager {
|
|
|
11216
11319
|
* Constructs a new CameraManager.
|
|
11217
11320
|
*
|
|
11218
11321
|
* @param call the call instance.
|
|
11322
|
+
* @param devicePersistence the device persistence preferences to use.
|
|
11219
11323
|
*/
|
|
11220
|
-
constructor(call) {
|
|
11221
|
-
super(call, new CameraManagerState(), TrackType.VIDEO);
|
|
11324
|
+
constructor(call, devicePersistence) {
|
|
11325
|
+
super(call, new CameraManagerState(), TrackType.VIDEO, devicePersistence);
|
|
11222
11326
|
this.targetResolution = {
|
|
11223
11327
|
width: 1280,
|
|
11224
11328
|
height: 720,
|
|
@@ -11231,8 +11335,9 @@ class CameraManager extends DeviceManager {
|
|
|
11231
11335
|
* Select the camera direction.
|
|
11232
11336
|
*
|
|
11233
11337
|
* @param direction the direction of the camera to select.
|
|
11338
|
+
* @param options additional direction selection options.
|
|
11234
11339
|
*/
|
|
11235
|
-
async selectDirection(direction) {
|
|
11340
|
+
async selectDirection(direction, options = {}) {
|
|
11236
11341
|
if (!this.isDirectionSupportedByDevice()) {
|
|
11237
11342
|
this.logger.warn('Setting direction is not supported on this device');
|
|
11238
11343
|
return;
|
|
@@ -11246,9 +11351,9 @@ class CameraManager extends DeviceManager {
|
|
|
11246
11351
|
// providing both device id and direction doesn't work, so we deselect the device
|
|
11247
11352
|
this.state.setDirection(direction);
|
|
11248
11353
|
this.state.setDevice(undefined);
|
|
11249
|
-
|
|
11354
|
+
const { enableCamera = true } = options;
|
|
11355
|
+
if (isReactNative() || !enableCamera)
|
|
11250
11356
|
return;
|
|
11251
|
-
}
|
|
11252
11357
|
this.getTracks().forEach((track) => track.stop());
|
|
11253
11358
|
try {
|
|
11254
11359
|
await this.unmuteStream();
|
|
@@ -11316,15 +11421,23 @@ class CameraManager extends DeviceManager {
|
|
|
11316
11421
|
// Wait for any in progress camera operation
|
|
11317
11422
|
await this.statusChangeSettled();
|
|
11318
11423
|
await this.selectTargetResolution(settings.target_resolution);
|
|
11319
|
-
|
|
11320
|
-
|
|
11424
|
+
const enabledInCallType = settings.enabled ?? true;
|
|
11425
|
+
const shouldApplyDefaults = this.state.status === undefined &&
|
|
11426
|
+
this.state.optimisticStatus === undefined;
|
|
11427
|
+
let persistedPreferencesApplied = false;
|
|
11428
|
+
if (shouldApplyDefaults && this.devicePersistence.enabled) {
|
|
11429
|
+
persistedPreferencesApplied =
|
|
11430
|
+
await this.applyPersistedPreferences(enabledInCallType);
|
|
11431
|
+
}
|
|
11432
|
+
// apply a direction and enable the camera only if in "pristine" state,
|
|
11433
|
+
// and there are no persisted preferences
|
|
11321
11434
|
const canPublish = this.call.permissionsContext.canPublish(this.trackType);
|
|
11322
|
-
if (
|
|
11435
|
+
if (shouldApplyDefaults && !persistedPreferencesApplied) {
|
|
11323
11436
|
if (!this.state.direction && !this.state.selectedDevice) {
|
|
11324
11437
|
const direction = settings.camera_facing === 'front' ? 'front' : 'back';
|
|
11325
|
-
await this.selectDirection(direction);
|
|
11438
|
+
await this.selectDirection(direction, { enableCamera: false });
|
|
11326
11439
|
}
|
|
11327
|
-
if (canPublish && settings.camera_default_on &&
|
|
11440
|
+
if (canPublish && settings.camera_default_on && enabledInCallType) {
|
|
11328
11441
|
await this.enable();
|
|
11329
11442
|
}
|
|
11330
11443
|
}
|
|
@@ -11760,8 +11873,8 @@ class RNSpeechDetector {
|
|
|
11760
11873
|
}
|
|
11761
11874
|
|
|
11762
11875
|
class MicrophoneManager extends AudioDeviceManager {
|
|
11763
|
-
constructor(call, disableMode = 'stop-tracks') {
|
|
11764
|
-
super(call, new MicrophoneManagerState(disableMode), TrackType.AUDIO);
|
|
11876
|
+
constructor(call, devicePersistence, disableMode = 'stop-tracks') {
|
|
11877
|
+
super(call, new MicrophoneManagerState(disableMode), TrackType.AUDIO, devicePersistence);
|
|
11765
11878
|
this.speakingWhileMutedNotificationEnabled = true;
|
|
11766
11879
|
this.soundDetectorConcurrencyTag = Symbol('soundDetectorConcurrencyTag');
|
|
11767
11880
|
this.silenceThresholdMs = 5000;
|
|
@@ -11850,7 +11963,7 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
11850
11963
|
if (this.silenceThresholdMs <= 0)
|
|
11851
11964
|
return;
|
|
11852
11965
|
const deviceId = this.state.selectedDevice;
|
|
11853
|
-
const devices =
|
|
11966
|
+
const devices = await rxjs.firstValueFrom(this.listDevices());
|
|
11854
11967
|
const label = devices.find((d) => d.deviceId === deviceId)?.label;
|
|
11855
11968
|
this.noAudioDetectorCleanup = createNoAudioDetector(mediaStream, {
|
|
11856
11969
|
noAudioThresholdMs: this.silenceThresholdMs,
|
|
@@ -11863,6 +11976,7 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
11863
11976
|
deviceId,
|
|
11864
11977
|
label,
|
|
11865
11978
|
};
|
|
11979
|
+
console.log(event);
|
|
11866
11980
|
this.call.tracer.trace('mic.capture_report', event);
|
|
11867
11981
|
this.call.streamClient.dispatchEvent(event);
|
|
11868
11982
|
},
|
|
@@ -12014,10 +12128,16 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
12014
12128
|
async apply(settings, publish) {
|
|
12015
12129
|
// Wait for any in progress mic operation
|
|
12016
12130
|
await this.statusChangeSettled();
|
|
12017
|
-
const canPublish = this.call.permissionsContext.canPublish(this.trackType);
|
|
12018
12131
|
// apply server-side settings only when the device state is pristine
|
|
12019
|
-
// and
|
|
12020
|
-
|
|
12132
|
+
// and there are no persisted preferences
|
|
12133
|
+
const shouldApplyDefaults = this.state.status === undefined &&
|
|
12134
|
+
this.state.optimisticStatus === undefined;
|
|
12135
|
+
let persistedPreferencesApplied = false;
|
|
12136
|
+
if (shouldApplyDefaults && this.devicePersistence.enabled) {
|
|
12137
|
+
persistedPreferencesApplied = await this.applyPersistedPreferences(true);
|
|
12138
|
+
}
|
|
12139
|
+
const canPublish = this.call.permissionsContext.canPublish(this.trackType);
|
|
12140
|
+
if (shouldApplyDefaults && !persistedPreferencesApplied) {
|
|
12021
12141
|
if (canPublish && settings.mic_default_on) {
|
|
12022
12142
|
await this.enable();
|
|
12023
12143
|
}
|
|
@@ -12168,7 +12288,7 @@ class ScreenShareState extends AudioDeviceManagerState {
|
|
|
12168
12288
|
|
|
12169
12289
|
class ScreenShareManager extends AudioDeviceManager {
|
|
12170
12290
|
constructor(call) {
|
|
12171
|
-
super(call, new ScreenShareState(), TrackType.SCREEN_SHARE);
|
|
12291
|
+
super(call, new ScreenShareState(), TrackType.SCREEN_SHARE, normalize({ enabled: false }));
|
|
12172
12292
|
}
|
|
12173
12293
|
setup() {
|
|
12174
12294
|
if (this.areSubscriptionsSetUp)
|
|
@@ -12317,7 +12437,7 @@ class SpeakerState {
|
|
|
12317
12437
|
}
|
|
12318
12438
|
|
|
12319
12439
|
class SpeakerManager {
|
|
12320
|
-
constructor(call) {
|
|
12440
|
+
constructor(call, devicePreferences) {
|
|
12321
12441
|
this.subscriptions = [];
|
|
12322
12442
|
this.areSubscriptionsSetUp = false;
|
|
12323
12443
|
/**
|
|
@@ -12326,18 +12446,35 @@ class SpeakerManager {
|
|
|
12326
12446
|
* @internal
|
|
12327
12447
|
*/
|
|
12328
12448
|
this.dispose = () => {
|
|
12329
|
-
this.subscriptions.forEach((
|
|
12449
|
+
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
12330
12450
|
this.subscriptions = [];
|
|
12331
12451
|
this.areSubscriptionsSetUp = false;
|
|
12332
12452
|
};
|
|
12333
12453
|
this.call = call;
|
|
12334
12454
|
this.state = new SpeakerState(call.tracer);
|
|
12455
|
+
this.devicePersistence = devicePreferences;
|
|
12335
12456
|
this.setup();
|
|
12336
12457
|
}
|
|
12337
12458
|
apply(settings) {
|
|
12338
|
-
|
|
12459
|
+
return isReactNative() ? this.applyRN(settings) : this.applyWeb();
|
|
12460
|
+
}
|
|
12461
|
+
applyWeb() {
|
|
12462
|
+
const { enabled, storageKey } = this.devicePersistence;
|
|
12463
|
+
if (!enabled)
|
|
12339
12464
|
return;
|
|
12465
|
+
const preferences = readPreferences(storageKey);
|
|
12466
|
+
const preferenceList = toPreferenceList(preferences.speaker);
|
|
12467
|
+
if (preferenceList.length === 0)
|
|
12468
|
+
return;
|
|
12469
|
+
const preference = preferenceList[0];
|
|
12470
|
+
const nextDeviceId = preference.selectedDeviceId === defaultDeviceId
|
|
12471
|
+
? ''
|
|
12472
|
+
: preference.selectedDeviceId;
|
|
12473
|
+
if (this.state.selectedDevice !== nextDeviceId) {
|
|
12474
|
+
this.select(nextDeviceId);
|
|
12340
12475
|
}
|
|
12476
|
+
}
|
|
12477
|
+
applyRN(settings) {
|
|
12341
12478
|
/// Determines if the speaker should be enabled based on a priority hierarchy of
|
|
12342
12479
|
/// settings.
|
|
12343
12480
|
///
|
|
@@ -12366,19 +12503,21 @@ class SpeakerManager {
|
|
|
12366
12503
|
}
|
|
12367
12504
|
}
|
|
12368
12505
|
setup() {
|
|
12369
|
-
if (this.areSubscriptionsSetUp)
|
|
12506
|
+
if (this.areSubscriptionsSetUp)
|
|
12370
12507
|
return;
|
|
12371
|
-
}
|
|
12372
12508
|
this.areSubscriptionsSetUp = true;
|
|
12373
12509
|
if (deviceIds$ && !isReactNative()) {
|
|
12374
|
-
this.subscriptions.push(rxjs.combineLatest([deviceIds$, this.state.selectedDevice$])
|
|
12375
|
-
if (!deviceId)
|
|
12510
|
+
this.subscriptions.push(createSubscription(rxjs.combineLatest([deviceIds$, this.state.selectedDevice$]), ([devices, deviceId]) => {
|
|
12511
|
+
if (!deviceId)
|
|
12376
12512
|
return;
|
|
12377
|
-
}
|
|
12378
12513
|
const device = devices.find((d) => d.deviceId === deviceId && d.kind === 'audiooutput');
|
|
12379
|
-
if (!device)
|
|
12514
|
+
if (!device)
|
|
12380
12515
|
this.select('');
|
|
12381
|
-
|
|
12516
|
+
}));
|
|
12517
|
+
}
|
|
12518
|
+
if (!isReactNative() && this.devicePersistence.enabled) {
|
|
12519
|
+
this.subscriptions.push(createSubscription(this.state.selectedDevice$, (selectedDevice) => {
|
|
12520
|
+
this.persistSpeakerDevicePreference(selectedDevice);
|
|
12382
12521
|
}));
|
|
12383
12522
|
}
|
|
12384
12523
|
}
|
|
@@ -12438,6 +12577,13 @@ class SpeakerManager {
|
|
|
12438
12577
|
return { audioVolume: volume };
|
|
12439
12578
|
});
|
|
12440
12579
|
}
|
|
12580
|
+
persistSpeakerDevicePreference(selectedDevice) {
|
|
12581
|
+
const { storageKey } = this.devicePersistence;
|
|
12582
|
+
const devices = getCurrentValue(this.listDevices()) || [];
|
|
12583
|
+
const currentDevice = devices.find((d) => d.deviceId === selectedDevice) ??
|
|
12584
|
+
createSyntheticDevice(selectedDevice, 'audiooutput');
|
|
12585
|
+
writePreferences(currentDevice, 'speaker', undefined, storageKey);
|
|
12586
|
+
}
|
|
12441
12587
|
}
|
|
12442
12588
|
const assertUnsupportedInReactNative = () => {
|
|
12443
12589
|
if (isReactNative()) {
|
|
@@ -14542,9 +14688,10 @@ class Call {
|
|
|
14542
14688
|
this.state.setMembers(members || []);
|
|
14543
14689
|
this.state.setOwnCapabilities(ownCapabilities || []);
|
|
14544
14690
|
this.state.setCallingState(ringing ? exports.CallingState.RINGING : exports.CallingState.IDLE);
|
|
14545
|
-
|
|
14546
|
-
this.
|
|
14547
|
-
this.
|
|
14691
|
+
const preferences = normalize(streamClient.options.devicePersistence);
|
|
14692
|
+
this.camera = new CameraManager(this, preferences);
|
|
14693
|
+
this.microphone = new MicrophoneManager(this, preferences);
|
|
14694
|
+
this.speaker = new SpeakerManager(this, preferences);
|
|
14548
14695
|
this.screenShare = new ScreenShareManager(this);
|
|
14549
14696
|
this.dynascaleManager = new DynascaleManager(this.state, this.speaker, this.tracer);
|
|
14550
14697
|
}
|
|
@@ -15688,7 +15835,7 @@ class StreamClient {
|
|
|
15688
15835
|
this.getUserAgent = () => {
|
|
15689
15836
|
if (!this.cachedUserAgent) {
|
|
15690
15837
|
const { clientAppIdentifier = {} } = this.options;
|
|
15691
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
15838
|
+
const { sdkName = 'js', sdkVersion = "1.44.0", ...extras } = clientAppIdentifier;
|
|
15692
15839
|
this.cachedUserAgent = [
|
|
15693
15840
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
15694
15841
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|