@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.es.js
CHANGED
|
@@ -7,7 +7,7 @@ import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transpor
|
|
|
7
7
|
import * as scopedLogger from '@stream-io/logger';
|
|
8
8
|
export { LogLevelEnum } from '@stream-io/logger';
|
|
9
9
|
import { parse, write } from 'sdp-transform';
|
|
10
|
-
import { ReplaySubject, combineLatest, BehaviorSubject, shareReplay, map, distinctUntilChanged, startWith, takeWhile, distinctUntilKeyChanged, fromEventPattern, concatMap, merge, from, fromEvent, tap, debounceTime, pairwise, of } from 'rxjs';
|
|
10
|
+
import { ReplaySubject, combineLatest, BehaviorSubject, shareReplay, map, distinctUntilChanged, startWith, takeWhile, distinctUntilKeyChanged, fromEventPattern, concatMap, merge, from, fromEvent, tap, debounceTime, pairwise, firstValueFrom, of } from 'rxjs';
|
|
11
11
|
import { UAParser } from 'ua-parser-js';
|
|
12
12
|
import { WorkerTimer } from '@stream-io/worker-timer';
|
|
13
13
|
import https from 'https';
|
|
@@ -5065,7 +5065,7 @@ const paginatedLayoutSortPreset = combineComparators(pinned, ifInvisibleOrUnknow
|
|
|
5065
5065
|
/**
|
|
5066
5066
|
* The sorting preset for livestreams and audio rooms.
|
|
5067
5067
|
*/
|
|
5068
|
-
const livestreamOrAudioRoomSortPreset = combineComparators(
|
|
5068
|
+
const livestreamOrAudioRoomSortPreset = combineComparators(ifInvisibleOrUnknownBy(combineComparators(dominantSpeaker, speaking, reactionType('raised-hand'), withVideoIngressSource, publishingVideo, publishingAudio)), role('admin', 'host', 'speaker'));
|
|
5069
5069
|
|
|
5070
5070
|
const ensureExhausted = (x, message) => {
|
|
5071
5071
|
videoLoggerSystem.getLogger('helpers').warn(message, x);
|
|
@@ -5315,7 +5315,7 @@ class CallState {
|
|
|
5315
5315
|
this.updateParticipant = (sessionId, patch) => {
|
|
5316
5316
|
const participant = this.findParticipantBySessionId(sessionId);
|
|
5317
5317
|
if (!participant) {
|
|
5318
|
-
this.logger.
|
|
5318
|
+
this.logger.debug(`Participant with sessionId ${sessionId} not found`);
|
|
5319
5319
|
return;
|
|
5320
5320
|
}
|
|
5321
5321
|
const thePatch = typeof patch === 'function' ? patch(participant) : patch;
|
|
@@ -6232,7 +6232,7 @@ const getSdkVersion = (sdk) => {
|
|
|
6232
6232
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
6233
6233
|
};
|
|
6234
6234
|
|
|
6235
|
-
const version = "1.
|
|
6235
|
+
const version = "1.44.0";
|
|
6236
6236
|
const [major, minor, patch] = version.split('.');
|
|
6237
6237
|
let sdkInfo = {
|
|
6238
6238
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -10000,6 +10000,7 @@ class PermissionsContext {
|
|
|
10000
10000
|
return false;
|
|
10001
10001
|
default:
|
|
10002
10002
|
ensureExhausted(trackType, 'Unknown track type');
|
|
10003
|
+
return false;
|
|
10003
10004
|
}
|
|
10004
10005
|
};
|
|
10005
10006
|
/**
|
|
@@ -10336,7 +10337,7 @@ const getDeviceChangeObserver = lazy((tracer) => {
|
|
|
10336
10337
|
* the observable errors.
|
|
10337
10338
|
*/
|
|
10338
10339
|
const getAudioDevices = lazy((tracer) => {
|
|
10339
|
-
return merge(getDeviceChangeObserver(tracer), getAudioBrowserPermission().asObservable()).pipe(startWith(
|
|
10340
|
+
return merge(getDeviceChangeObserver(tracer), getAudioBrowserPermission().asObservable()).pipe(startWith([]), concatMap(() => getDevices(getAudioBrowserPermission(), 'audioinput', tracer)), shareReplay(1));
|
|
10340
10341
|
});
|
|
10341
10342
|
/**
|
|
10342
10343
|
* Prompts the user for a permission to use video devices (if not already granted
|
|
@@ -10345,7 +10346,7 @@ const getAudioDevices = lazy((tracer) => {
|
|
|
10345
10346
|
* the observable errors.
|
|
10346
10347
|
*/
|
|
10347
10348
|
const getVideoDevices = lazy((tracer) => {
|
|
10348
|
-
return merge(getDeviceChangeObserver(tracer), getVideoBrowserPermission().asObservable()).pipe(startWith(
|
|
10349
|
+
return merge(getDeviceChangeObserver(tracer), getVideoBrowserPermission().asObservable()).pipe(startWith([]), concatMap(() => getDevices(getVideoBrowserPermission(), 'videoinput', tracer)), shareReplay(1));
|
|
10349
10350
|
});
|
|
10350
10351
|
/**
|
|
10351
10352
|
* Prompts the user for a permission to use video devices (if not already granted
|
|
@@ -10354,7 +10355,7 @@ const getVideoDevices = lazy((tracer) => {
|
|
|
10354
10355
|
* the observable errors.
|
|
10355
10356
|
*/
|
|
10356
10357
|
const getAudioOutputDevices = lazy((tracer) => {
|
|
10357
|
-
return merge(getDeviceChangeObserver(tracer), getAudioBrowserPermission().asObservable()).pipe(startWith(
|
|
10358
|
+
return merge(getDeviceChangeObserver(tracer), getAudioBrowserPermission().asObservable()).pipe(startWith([]), concatMap(() => getDevices(getAudioBrowserPermission(), 'audiooutput', tracer)), shareReplay(1));
|
|
10358
10359
|
});
|
|
10359
10360
|
let getUserMediaExecId = 0;
|
|
10360
10361
|
const getStream = async (constraints, tracer) => {
|
|
@@ -10561,25 +10562,66 @@ function resolveDeviceId(deviceId, kind) {
|
|
|
10561
10562
|
*/
|
|
10562
10563
|
const isMobile = () => /Mobi/i.test(navigator.userAgent);
|
|
10563
10564
|
|
|
10565
|
+
const defaultDeviceId = 'default';
|
|
10566
|
+
const isLocalStorageAvailable = () => typeof window !== 'undefined' && typeof window.localStorage !== 'undefined';
|
|
10567
|
+
const normalize = (options) => {
|
|
10568
|
+
return {
|
|
10569
|
+
storageKey: options?.storageKey ?? `@stream-io/device-preferences`,
|
|
10570
|
+
enabled: isLocalStorageAvailable() && !isReactNative()
|
|
10571
|
+
? (options?.enabled ?? true)
|
|
10572
|
+
: false,
|
|
10573
|
+
};
|
|
10574
|
+
};
|
|
10575
|
+
const createSyntheticDevice = (deviceId, kind) => {
|
|
10576
|
+
return { deviceId, kind, label: '', groupId: '' };
|
|
10577
|
+
};
|
|
10578
|
+
const readPreferences = (storageKey) => {
|
|
10579
|
+
try {
|
|
10580
|
+
const raw = window.localStorage.getItem(storageKey) || '{}';
|
|
10581
|
+
return JSON.parse(raw);
|
|
10582
|
+
}
|
|
10583
|
+
catch {
|
|
10584
|
+
return {};
|
|
10585
|
+
}
|
|
10586
|
+
};
|
|
10587
|
+
const writePreferences = (currentDevice, deviceKey, muted, storageKey) => {
|
|
10588
|
+
if (!isLocalStorageAvailable())
|
|
10589
|
+
return;
|
|
10590
|
+
const selectedDeviceId = currentDevice?.deviceId ?? defaultDeviceId;
|
|
10591
|
+
const selectedDeviceLabel = currentDevice?.label ?? '';
|
|
10592
|
+
const preferences = readPreferences(storageKey);
|
|
10593
|
+
const preferenceHistory = [preferences[deviceKey] ?? []]
|
|
10594
|
+
.flat()
|
|
10595
|
+
.filter((p) => p.selectedDeviceId !== selectedDeviceId &&
|
|
10596
|
+
(p.selectedDeviceLabel === '' ||
|
|
10597
|
+
p.selectedDeviceLabel !== selectedDeviceLabel));
|
|
10598
|
+
const nextPreferences = {
|
|
10599
|
+
...preferences,
|
|
10600
|
+
[deviceKey]: [
|
|
10601
|
+
{
|
|
10602
|
+
selectedDeviceId,
|
|
10603
|
+
selectedDeviceLabel,
|
|
10604
|
+
...(typeof muted === 'boolean' ? { muted } : {}),
|
|
10605
|
+
},
|
|
10606
|
+
...preferenceHistory,
|
|
10607
|
+
].slice(0, 3),
|
|
10608
|
+
};
|
|
10609
|
+
try {
|
|
10610
|
+
window.localStorage.setItem(storageKey, JSON.stringify(nextPreferences));
|
|
10611
|
+
}
|
|
10612
|
+
catch (err) {
|
|
10613
|
+
const logger = videoLoggerSystem.getLogger('DevicePersistence');
|
|
10614
|
+
logger.error('failed to save device preferences', err);
|
|
10615
|
+
}
|
|
10616
|
+
};
|
|
10617
|
+
const toPreferenceList = (preference) => (preference ? [preference].flat() : []);
|
|
10618
|
+
|
|
10564
10619
|
class DeviceManager {
|
|
10565
|
-
constructor(call, state, trackType) {
|
|
10620
|
+
constructor(call, state, trackType, devicePersistence) {
|
|
10566
10621
|
/**
|
|
10567
10622
|
* if true, stops the media stream when call is left
|
|
10568
10623
|
*/
|
|
10569
10624
|
this.stopOnLeave = true;
|
|
10570
|
-
/**
|
|
10571
|
-
* When `true`, the `apply()` method will skip automatically enabling/disabling
|
|
10572
|
-
* the device based on server defaults (`mic_default_on`, `camera_default_on`).
|
|
10573
|
-
*
|
|
10574
|
-
* This is useful when application code wants to handle device preferences
|
|
10575
|
-
* (e.g., persisted user preferences) and prevent server defaults from
|
|
10576
|
-
* overriding them.
|
|
10577
|
-
*
|
|
10578
|
-
* @default false
|
|
10579
|
-
*
|
|
10580
|
-
* @internal
|
|
10581
|
-
*/
|
|
10582
|
-
this.deferServerDefaults = false;
|
|
10583
10625
|
this.subscriptions = [];
|
|
10584
10626
|
this.areSubscriptionsSetUp = false;
|
|
10585
10627
|
this.isTrackStoppedDueToTrackEnd = false;
|
|
@@ -10599,19 +10641,26 @@ class DeviceManager {
|
|
|
10599
10641
|
this.call = call;
|
|
10600
10642
|
this.state = state;
|
|
10601
10643
|
this.trackType = trackType;
|
|
10644
|
+
this.devicePersistence = devicePersistence;
|
|
10602
10645
|
this.logger = videoLoggerSystem.getLogger(`${TrackType[trackType].toLowerCase()} manager`);
|
|
10603
10646
|
this.setup();
|
|
10604
10647
|
}
|
|
10605
10648
|
setup() {
|
|
10606
|
-
if (this.areSubscriptionsSetUp)
|
|
10649
|
+
if (this.areSubscriptionsSetUp)
|
|
10607
10650
|
return;
|
|
10608
|
-
}
|
|
10609
10651
|
this.areSubscriptionsSetUp = true;
|
|
10610
10652
|
if (deviceIds$ &&
|
|
10611
10653
|
!isReactNative() &&
|
|
10612
10654
|
(this.trackType === TrackType.AUDIO || this.trackType === TrackType.VIDEO)) {
|
|
10613
10655
|
this.handleDisconnectedOrReplacedDevices();
|
|
10614
10656
|
}
|
|
10657
|
+
if (this.devicePersistence.enabled) {
|
|
10658
|
+
this.subscriptions.push(createSubscription(combineLatest([this.state.selectedDevice$, this.state.status$]), ([selectedDevice, status]) => {
|
|
10659
|
+
if (!status)
|
|
10660
|
+
return;
|
|
10661
|
+
this.persistPreference(selectedDevice, status);
|
|
10662
|
+
}));
|
|
10663
|
+
}
|
|
10615
10664
|
}
|
|
10616
10665
|
/**
|
|
10617
10666
|
* Lists the available audio/video devices
|
|
@@ -10962,13 +11011,11 @@ class DeviceManager {
|
|
|
10962
11011
|
}
|
|
10963
11012
|
}
|
|
10964
11013
|
get mediaDeviceKind() {
|
|
10965
|
-
if (this.trackType === TrackType.AUDIO)
|
|
11014
|
+
if (this.trackType === TrackType.AUDIO)
|
|
10966
11015
|
return 'audioinput';
|
|
10967
|
-
|
|
10968
|
-
if (this.trackType === TrackType.VIDEO) {
|
|
11016
|
+
if (this.trackType === TrackType.VIDEO)
|
|
10969
11017
|
return 'videoinput';
|
|
10970
|
-
|
|
10971
|
-
return '';
|
|
11018
|
+
throw new Error('Invalid track type');
|
|
10972
11019
|
}
|
|
10973
11020
|
handleDisconnectedOrReplacedDevices() {
|
|
10974
11021
|
this.subscriptions.push(createSubscription(combineLatest([
|
|
@@ -11016,6 +11063,62 @@ class DeviceManager {
|
|
|
11016
11063
|
const kind = this.mediaDeviceKind;
|
|
11017
11064
|
return devices.find((d) => d.deviceId === deviceId && d.kind === kind);
|
|
11018
11065
|
}
|
|
11066
|
+
persistPreference(selectedDevice, status) {
|
|
11067
|
+
const deviceKind = this.mediaDeviceKind;
|
|
11068
|
+
const deviceKey = deviceKind === 'audioinput' ? 'microphone' : 'camera';
|
|
11069
|
+
const muted = status === 'disabled' ? true : status === 'enabled' ? false : undefined;
|
|
11070
|
+
const { storageKey } = this.devicePersistence;
|
|
11071
|
+
if (!selectedDevice) {
|
|
11072
|
+
writePreferences(undefined, deviceKey, muted, storageKey);
|
|
11073
|
+
return;
|
|
11074
|
+
}
|
|
11075
|
+
const devices = getCurrentValue(this.listDevices()) || [];
|
|
11076
|
+
const currentDevice = this.findDevice(devices, selectedDevice) ??
|
|
11077
|
+
createSyntheticDevice(selectedDevice, deviceKind);
|
|
11078
|
+
writePreferences(currentDevice, deviceKey, muted, storageKey);
|
|
11079
|
+
}
|
|
11080
|
+
async applyPersistedPreferences(enabledInCallType) {
|
|
11081
|
+
const deviceKey = this.trackType === TrackType.AUDIO ? 'microphone' : 'camera';
|
|
11082
|
+
const preferences = readPreferences(this.devicePersistence.storageKey);
|
|
11083
|
+
const preferenceList = toPreferenceList(preferences[deviceKey]);
|
|
11084
|
+
if (preferenceList.length === 0)
|
|
11085
|
+
return false;
|
|
11086
|
+
let muted;
|
|
11087
|
+
let appliedDevice = false;
|
|
11088
|
+
let appliedMute = false;
|
|
11089
|
+
const devices = await firstValueFrom(this.listDevices());
|
|
11090
|
+
for (const preference of preferenceList) {
|
|
11091
|
+
muted ?? (muted = preference.muted);
|
|
11092
|
+
if (preference.selectedDeviceId === defaultDeviceId)
|
|
11093
|
+
break;
|
|
11094
|
+
const device = devices.find((d) => d.deviceId === preference.selectedDeviceId) ??
|
|
11095
|
+
devices.find((d) => d.label === preference.selectedDeviceLabel);
|
|
11096
|
+
if (device) {
|
|
11097
|
+
appliedDevice = true;
|
|
11098
|
+
if (!this.state.selectedDevice) {
|
|
11099
|
+
await this.select(device.deviceId);
|
|
11100
|
+
}
|
|
11101
|
+
muted = preference.muted;
|
|
11102
|
+
break;
|
|
11103
|
+
}
|
|
11104
|
+
}
|
|
11105
|
+
const canPublish = this.call.permissionsContext.canPublish(this.trackType);
|
|
11106
|
+
if (typeof muted === 'boolean' && enabledInCallType && canPublish) {
|
|
11107
|
+
await this.applyMutedState(muted);
|
|
11108
|
+
appliedMute = true;
|
|
11109
|
+
}
|
|
11110
|
+
return appliedDevice || appliedMute;
|
|
11111
|
+
}
|
|
11112
|
+
async applyMutedState(muted) {
|
|
11113
|
+
if (this.state.status !== undefined)
|
|
11114
|
+
return;
|
|
11115
|
+
if (muted) {
|
|
11116
|
+
await this.disable();
|
|
11117
|
+
}
|
|
11118
|
+
else {
|
|
11119
|
+
await this.enable();
|
|
11120
|
+
}
|
|
11121
|
+
}
|
|
11019
11122
|
}
|
|
11020
11123
|
|
|
11021
11124
|
class DeviceManagerState {
|
|
@@ -11197,9 +11300,10 @@ class CameraManager extends DeviceManager {
|
|
|
11197
11300
|
* Constructs a new CameraManager.
|
|
11198
11301
|
*
|
|
11199
11302
|
* @param call the call instance.
|
|
11303
|
+
* @param devicePersistence the device persistence preferences to use.
|
|
11200
11304
|
*/
|
|
11201
|
-
constructor(call) {
|
|
11202
|
-
super(call, new CameraManagerState(), TrackType.VIDEO);
|
|
11305
|
+
constructor(call, devicePersistence) {
|
|
11306
|
+
super(call, new CameraManagerState(), TrackType.VIDEO, devicePersistence);
|
|
11203
11307
|
this.targetResolution = {
|
|
11204
11308
|
width: 1280,
|
|
11205
11309
|
height: 720,
|
|
@@ -11212,8 +11316,9 @@ class CameraManager extends DeviceManager {
|
|
|
11212
11316
|
* Select the camera direction.
|
|
11213
11317
|
*
|
|
11214
11318
|
* @param direction the direction of the camera to select.
|
|
11319
|
+
* @param options additional direction selection options.
|
|
11215
11320
|
*/
|
|
11216
|
-
async selectDirection(direction) {
|
|
11321
|
+
async selectDirection(direction, options = {}) {
|
|
11217
11322
|
if (!this.isDirectionSupportedByDevice()) {
|
|
11218
11323
|
this.logger.warn('Setting direction is not supported on this device');
|
|
11219
11324
|
return;
|
|
@@ -11227,9 +11332,9 @@ class CameraManager extends DeviceManager {
|
|
|
11227
11332
|
// providing both device id and direction doesn't work, so we deselect the device
|
|
11228
11333
|
this.state.setDirection(direction);
|
|
11229
11334
|
this.state.setDevice(undefined);
|
|
11230
|
-
|
|
11335
|
+
const { enableCamera = true } = options;
|
|
11336
|
+
if (isReactNative() || !enableCamera)
|
|
11231
11337
|
return;
|
|
11232
|
-
}
|
|
11233
11338
|
this.getTracks().forEach((track) => track.stop());
|
|
11234
11339
|
try {
|
|
11235
11340
|
await this.unmuteStream();
|
|
@@ -11297,15 +11402,23 @@ class CameraManager extends DeviceManager {
|
|
|
11297
11402
|
// Wait for any in progress camera operation
|
|
11298
11403
|
await this.statusChangeSettled();
|
|
11299
11404
|
await this.selectTargetResolution(settings.target_resolution);
|
|
11300
|
-
|
|
11301
|
-
|
|
11405
|
+
const enabledInCallType = settings.enabled ?? true;
|
|
11406
|
+
const shouldApplyDefaults = this.state.status === undefined &&
|
|
11407
|
+
this.state.optimisticStatus === undefined;
|
|
11408
|
+
let persistedPreferencesApplied = false;
|
|
11409
|
+
if (shouldApplyDefaults && this.devicePersistence.enabled) {
|
|
11410
|
+
persistedPreferencesApplied =
|
|
11411
|
+
await this.applyPersistedPreferences(enabledInCallType);
|
|
11412
|
+
}
|
|
11413
|
+
// apply a direction and enable the camera only if in "pristine" state,
|
|
11414
|
+
// and there are no persisted preferences
|
|
11302
11415
|
const canPublish = this.call.permissionsContext.canPublish(this.trackType);
|
|
11303
|
-
if (
|
|
11416
|
+
if (shouldApplyDefaults && !persistedPreferencesApplied) {
|
|
11304
11417
|
if (!this.state.direction && !this.state.selectedDevice) {
|
|
11305
11418
|
const direction = settings.camera_facing === 'front' ? 'front' : 'back';
|
|
11306
|
-
await this.selectDirection(direction);
|
|
11419
|
+
await this.selectDirection(direction, { enableCamera: false });
|
|
11307
11420
|
}
|
|
11308
|
-
if (canPublish && settings.camera_default_on &&
|
|
11421
|
+
if (canPublish && settings.camera_default_on && enabledInCallType) {
|
|
11309
11422
|
await this.enable();
|
|
11310
11423
|
}
|
|
11311
11424
|
}
|
|
@@ -11741,8 +11854,8 @@ class RNSpeechDetector {
|
|
|
11741
11854
|
}
|
|
11742
11855
|
|
|
11743
11856
|
class MicrophoneManager extends AudioDeviceManager {
|
|
11744
|
-
constructor(call, disableMode = 'stop-tracks') {
|
|
11745
|
-
super(call, new MicrophoneManagerState(disableMode), TrackType.AUDIO);
|
|
11857
|
+
constructor(call, devicePersistence, disableMode = 'stop-tracks') {
|
|
11858
|
+
super(call, new MicrophoneManagerState(disableMode), TrackType.AUDIO, devicePersistence);
|
|
11746
11859
|
this.speakingWhileMutedNotificationEnabled = true;
|
|
11747
11860
|
this.soundDetectorConcurrencyTag = Symbol('soundDetectorConcurrencyTag');
|
|
11748
11861
|
this.silenceThresholdMs = 5000;
|
|
@@ -11831,7 +11944,7 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
11831
11944
|
if (this.silenceThresholdMs <= 0)
|
|
11832
11945
|
return;
|
|
11833
11946
|
const deviceId = this.state.selectedDevice;
|
|
11834
|
-
const devices =
|
|
11947
|
+
const devices = await firstValueFrom(this.listDevices());
|
|
11835
11948
|
const label = devices.find((d) => d.deviceId === deviceId)?.label;
|
|
11836
11949
|
this.noAudioDetectorCleanup = createNoAudioDetector(mediaStream, {
|
|
11837
11950
|
noAudioThresholdMs: this.silenceThresholdMs,
|
|
@@ -11844,6 +11957,7 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
11844
11957
|
deviceId,
|
|
11845
11958
|
label,
|
|
11846
11959
|
};
|
|
11960
|
+
console.log(event);
|
|
11847
11961
|
this.call.tracer.trace('mic.capture_report', event);
|
|
11848
11962
|
this.call.streamClient.dispatchEvent(event);
|
|
11849
11963
|
},
|
|
@@ -11995,10 +12109,16 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
11995
12109
|
async apply(settings, publish) {
|
|
11996
12110
|
// Wait for any in progress mic operation
|
|
11997
12111
|
await this.statusChangeSettled();
|
|
11998
|
-
const canPublish = this.call.permissionsContext.canPublish(this.trackType);
|
|
11999
12112
|
// apply server-side settings only when the device state is pristine
|
|
12000
|
-
// and
|
|
12001
|
-
|
|
12113
|
+
// and there are no persisted preferences
|
|
12114
|
+
const shouldApplyDefaults = this.state.status === undefined &&
|
|
12115
|
+
this.state.optimisticStatus === undefined;
|
|
12116
|
+
let persistedPreferencesApplied = false;
|
|
12117
|
+
if (shouldApplyDefaults && this.devicePersistence.enabled) {
|
|
12118
|
+
persistedPreferencesApplied = await this.applyPersistedPreferences(true);
|
|
12119
|
+
}
|
|
12120
|
+
const canPublish = this.call.permissionsContext.canPublish(this.trackType);
|
|
12121
|
+
if (shouldApplyDefaults && !persistedPreferencesApplied) {
|
|
12002
12122
|
if (canPublish && settings.mic_default_on) {
|
|
12003
12123
|
await this.enable();
|
|
12004
12124
|
}
|
|
@@ -12149,7 +12269,7 @@ class ScreenShareState extends AudioDeviceManagerState {
|
|
|
12149
12269
|
|
|
12150
12270
|
class ScreenShareManager extends AudioDeviceManager {
|
|
12151
12271
|
constructor(call) {
|
|
12152
|
-
super(call, new ScreenShareState(), TrackType.SCREEN_SHARE);
|
|
12272
|
+
super(call, new ScreenShareState(), TrackType.SCREEN_SHARE, normalize({ enabled: false }));
|
|
12153
12273
|
}
|
|
12154
12274
|
setup() {
|
|
12155
12275
|
if (this.areSubscriptionsSetUp)
|
|
@@ -12298,7 +12418,7 @@ class SpeakerState {
|
|
|
12298
12418
|
}
|
|
12299
12419
|
|
|
12300
12420
|
class SpeakerManager {
|
|
12301
|
-
constructor(call) {
|
|
12421
|
+
constructor(call, devicePreferences) {
|
|
12302
12422
|
this.subscriptions = [];
|
|
12303
12423
|
this.areSubscriptionsSetUp = false;
|
|
12304
12424
|
/**
|
|
@@ -12307,18 +12427,35 @@ class SpeakerManager {
|
|
|
12307
12427
|
* @internal
|
|
12308
12428
|
*/
|
|
12309
12429
|
this.dispose = () => {
|
|
12310
|
-
this.subscriptions.forEach((
|
|
12430
|
+
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
12311
12431
|
this.subscriptions = [];
|
|
12312
12432
|
this.areSubscriptionsSetUp = false;
|
|
12313
12433
|
};
|
|
12314
12434
|
this.call = call;
|
|
12315
12435
|
this.state = new SpeakerState(call.tracer);
|
|
12436
|
+
this.devicePersistence = devicePreferences;
|
|
12316
12437
|
this.setup();
|
|
12317
12438
|
}
|
|
12318
12439
|
apply(settings) {
|
|
12319
|
-
|
|
12440
|
+
return isReactNative() ? this.applyRN(settings) : this.applyWeb();
|
|
12441
|
+
}
|
|
12442
|
+
applyWeb() {
|
|
12443
|
+
const { enabled, storageKey } = this.devicePersistence;
|
|
12444
|
+
if (!enabled)
|
|
12320
12445
|
return;
|
|
12446
|
+
const preferences = readPreferences(storageKey);
|
|
12447
|
+
const preferenceList = toPreferenceList(preferences.speaker);
|
|
12448
|
+
if (preferenceList.length === 0)
|
|
12449
|
+
return;
|
|
12450
|
+
const preference = preferenceList[0];
|
|
12451
|
+
const nextDeviceId = preference.selectedDeviceId === defaultDeviceId
|
|
12452
|
+
? ''
|
|
12453
|
+
: preference.selectedDeviceId;
|
|
12454
|
+
if (this.state.selectedDevice !== nextDeviceId) {
|
|
12455
|
+
this.select(nextDeviceId);
|
|
12321
12456
|
}
|
|
12457
|
+
}
|
|
12458
|
+
applyRN(settings) {
|
|
12322
12459
|
/// Determines if the speaker should be enabled based on a priority hierarchy of
|
|
12323
12460
|
/// settings.
|
|
12324
12461
|
///
|
|
@@ -12347,19 +12484,21 @@ class SpeakerManager {
|
|
|
12347
12484
|
}
|
|
12348
12485
|
}
|
|
12349
12486
|
setup() {
|
|
12350
|
-
if (this.areSubscriptionsSetUp)
|
|
12487
|
+
if (this.areSubscriptionsSetUp)
|
|
12351
12488
|
return;
|
|
12352
|
-
}
|
|
12353
12489
|
this.areSubscriptionsSetUp = true;
|
|
12354
12490
|
if (deviceIds$ && !isReactNative()) {
|
|
12355
|
-
this.subscriptions.push(combineLatest([deviceIds$, this.state.selectedDevice$])
|
|
12356
|
-
if (!deviceId)
|
|
12491
|
+
this.subscriptions.push(createSubscription(combineLatest([deviceIds$, this.state.selectedDevice$]), ([devices, deviceId]) => {
|
|
12492
|
+
if (!deviceId)
|
|
12357
12493
|
return;
|
|
12358
|
-
}
|
|
12359
12494
|
const device = devices.find((d) => d.deviceId === deviceId && d.kind === 'audiooutput');
|
|
12360
|
-
if (!device)
|
|
12495
|
+
if (!device)
|
|
12361
12496
|
this.select('');
|
|
12362
|
-
|
|
12497
|
+
}));
|
|
12498
|
+
}
|
|
12499
|
+
if (!isReactNative() && this.devicePersistence.enabled) {
|
|
12500
|
+
this.subscriptions.push(createSubscription(this.state.selectedDevice$, (selectedDevice) => {
|
|
12501
|
+
this.persistSpeakerDevicePreference(selectedDevice);
|
|
12363
12502
|
}));
|
|
12364
12503
|
}
|
|
12365
12504
|
}
|
|
@@ -12419,6 +12558,13 @@ class SpeakerManager {
|
|
|
12419
12558
|
return { audioVolume: volume };
|
|
12420
12559
|
});
|
|
12421
12560
|
}
|
|
12561
|
+
persistSpeakerDevicePreference(selectedDevice) {
|
|
12562
|
+
const { storageKey } = this.devicePersistence;
|
|
12563
|
+
const devices = getCurrentValue(this.listDevices()) || [];
|
|
12564
|
+
const currentDevice = devices.find((d) => d.deviceId === selectedDevice) ??
|
|
12565
|
+
createSyntheticDevice(selectedDevice, 'audiooutput');
|
|
12566
|
+
writePreferences(currentDevice, 'speaker', undefined, storageKey);
|
|
12567
|
+
}
|
|
12422
12568
|
}
|
|
12423
12569
|
const assertUnsupportedInReactNative = () => {
|
|
12424
12570
|
if (isReactNative()) {
|
|
@@ -14523,9 +14669,10 @@ class Call {
|
|
|
14523
14669
|
this.state.setMembers(members || []);
|
|
14524
14670
|
this.state.setOwnCapabilities(ownCapabilities || []);
|
|
14525
14671
|
this.state.setCallingState(ringing ? CallingState.RINGING : CallingState.IDLE);
|
|
14526
|
-
|
|
14527
|
-
this.
|
|
14528
|
-
this.
|
|
14672
|
+
const preferences = normalize(streamClient.options.devicePersistence);
|
|
14673
|
+
this.camera = new CameraManager(this, preferences);
|
|
14674
|
+
this.microphone = new MicrophoneManager(this, preferences);
|
|
14675
|
+
this.speaker = new SpeakerManager(this, preferences);
|
|
14529
14676
|
this.screenShare = new ScreenShareManager(this);
|
|
14530
14677
|
this.dynascaleManager = new DynascaleManager(this.state, this.speaker, this.tracer);
|
|
14531
14678
|
}
|
|
@@ -15669,7 +15816,7 @@ class StreamClient {
|
|
|
15669
15816
|
this.getUserAgent = () => {
|
|
15670
15817
|
if (!this.cachedUserAgent) {
|
|
15671
15818
|
const { clientAppIdentifier = {} } = this.options;
|
|
15672
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
15819
|
+
const { sdkName = 'js', sdkVersion = "1.44.0", ...extras } = clientAppIdentifier;
|
|
15673
15820
|
this.cachedUserAgent = [
|
|
15674
15821
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
15675
15822
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|