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