@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
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { isReactNative } from '../helpers/platforms';
|
|
2
|
+
import { videoLoggerSystem } from '../logger';
|
|
3
|
+
|
|
4
|
+
export type DevicePersistenceOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* Enables device preference persistence on web.
|
|
7
|
+
* @default true
|
|
8
|
+
*/
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Storage key for persisted preferences.
|
|
12
|
+
* @default '@stream-io/device-preferences'
|
|
13
|
+
*/
|
|
14
|
+
storageKey?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type DevicePreferenceKey = 'microphone' | 'camera' | 'speaker';
|
|
18
|
+
|
|
19
|
+
export type LocalDevicePreference = {
|
|
20
|
+
selectedDeviceId: string;
|
|
21
|
+
selectedDeviceLabel: string;
|
|
22
|
+
muted?: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type LocalDevicePreferences = {
|
|
26
|
+
[type in DevicePreferenceKey]?:
|
|
27
|
+
| LocalDevicePreference
|
|
28
|
+
| LocalDevicePreference[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const defaultDeviceId = 'default';
|
|
32
|
+
|
|
33
|
+
const isLocalStorageAvailable = (): boolean =>
|
|
34
|
+
typeof window !== 'undefined' && typeof window.localStorage !== 'undefined';
|
|
35
|
+
|
|
36
|
+
export const normalize = (
|
|
37
|
+
options: DevicePersistenceOptions | undefined,
|
|
38
|
+
): Required<DevicePersistenceOptions> => {
|
|
39
|
+
return {
|
|
40
|
+
storageKey: options?.storageKey ?? `@stream-io/device-preferences`,
|
|
41
|
+
enabled:
|
|
42
|
+
isLocalStorageAvailable() && !isReactNative()
|
|
43
|
+
? (options?.enabled ?? true)
|
|
44
|
+
: false,
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const createSyntheticDevice = (
|
|
49
|
+
deviceId: string,
|
|
50
|
+
kind: MediaDeviceKind,
|
|
51
|
+
): MediaDeviceInfo => {
|
|
52
|
+
return { deviceId, kind, label: '', groupId: '' } as MediaDeviceInfo;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const readPreferences = (storageKey: string): LocalDevicePreferences => {
|
|
56
|
+
try {
|
|
57
|
+
const raw = window.localStorage.getItem(storageKey) || '{}';
|
|
58
|
+
return JSON.parse(raw) as LocalDevicePreferences;
|
|
59
|
+
} catch {
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const writePreferences = (
|
|
65
|
+
currentDevice: MediaDeviceInfo | undefined,
|
|
66
|
+
deviceKey: DevicePreferenceKey,
|
|
67
|
+
muted: boolean | undefined,
|
|
68
|
+
storageKey: string,
|
|
69
|
+
) => {
|
|
70
|
+
if (!isLocalStorageAvailable()) return;
|
|
71
|
+
|
|
72
|
+
const selectedDeviceId = currentDevice?.deviceId ?? defaultDeviceId;
|
|
73
|
+
const selectedDeviceLabel = currentDevice?.label ?? '';
|
|
74
|
+
|
|
75
|
+
const preferences = readPreferences(storageKey);
|
|
76
|
+
const preferenceHistory = [preferences[deviceKey] ?? []]
|
|
77
|
+
.flat()
|
|
78
|
+
.filter(
|
|
79
|
+
(p) =>
|
|
80
|
+
p.selectedDeviceId !== selectedDeviceId &&
|
|
81
|
+
(p.selectedDeviceLabel === '' ||
|
|
82
|
+
p.selectedDeviceLabel !== selectedDeviceLabel),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const nextPreferences: LocalDevicePreferences = {
|
|
86
|
+
...preferences,
|
|
87
|
+
[deviceKey]: [
|
|
88
|
+
{
|
|
89
|
+
selectedDeviceId,
|
|
90
|
+
selectedDeviceLabel,
|
|
91
|
+
...(typeof muted === 'boolean' ? { muted } : {}),
|
|
92
|
+
} satisfies LocalDevicePreference,
|
|
93
|
+
...preferenceHistory,
|
|
94
|
+
].slice(0, 3),
|
|
95
|
+
};
|
|
96
|
+
try {
|
|
97
|
+
window.localStorage.setItem(storageKey, JSON.stringify(nextPreferences));
|
|
98
|
+
} catch (err) {
|
|
99
|
+
const logger = videoLoggerSystem.getLogger('DevicePersistence');
|
|
100
|
+
logger.error('failed to save device preferences', err);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const toPreferenceList = (
|
|
105
|
+
preference?: LocalDevicePreference | LocalDevicePreference[],
|
|
106
|
+
): LocalDevicePreference[] => (preference ? [preference].flat() : []);
|
package/src/devices/devices.ts
CHANGED
|
@@ -134,7 +134,7 @@ export const getAudioDevices = lazy((tracer?: Tracer) => {
|
|
|
134
134
|
getDeviceChangeObserver(tracer),
|
|
135
135
|
getAudioBrowserPermission().asObservable(),
|
|
136
136
|
).pipe(
|
|
137
|
-
startWith(
|
|
137
|
+
startWith([]),
|
|
138
138
|
concatMap(() =>
|
|
139
139
|
getDevices(getAudioBrowserPermission(), 'audioinput', tracer),
|
|
140
140
|
),
|
|
@@ -153,7 +153,7 @@ export const getVideoDevices = lazy((tracer?: Tracer) => {
|
|
|
153
153
|
getDeviceChangeObserver(tracer),
|
|
154
154
|
getVideoBrowserPermission().asObservable(),
|
|
155
155
|
).pipe(
|
|
156
|
-
startWith(
|
|
156
|
+
startWith([]),
|
|
157
157
|
concatMap(() =>
|
|
158
158
|
getDevices(getVideoBrowserPermission(), 'videoinput', tracer),
|
|
159
159
|
),
|
|
@@ -172,7 +172,7 @@ export const getAudioOutputDevices = lazy((tracer?: Tracer) => {
|
|
|
172
172
|
getDeviceChangeObserver(tracer),
|
|
173
173
|
getAudioBrowserPermission().asObservable(),
|
|
174
174
|
).pipe(
|
|
175
|
-
startWith(
|
|
175
|
+
startWith([]),
|
|
176
176
|
concatMap(() =>
|
|
177
177
|
getDevices(getAudioBrowserPermission(), 'audiooutput', tracer),
|
|
178
178
|
),
|
|
@@ -30,7 +30,9 @@ describe('DynascaleManager', () => {
|
|
|
30
30
|
call = new Call({
|
|
31
31
|
id: 'id',
|
|
32
32
|
type: 'default',
|
|
33
|
-
streamClient: new StreamClient('api-key'
|
|
33
|
+
streamClient: new StreamClient('api-key', {
|
|
34
|
+
devicePersistence: { enabled: false },
|
|
35
|
+
}),
|
|
34
36
|
clientStore: new StreamVideoWriteableStateStore(),
|
|
35
37
|
});
|
|
36
38
|
call.setSortParticipantsBy(noopComparator());
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
import { StreamClient } from '../coordinator/connection/client';
|
|
8
8
|
import { getSdkInfo } from './client-details';
|
|
9
9
|
import { SdkType } from '../gen/video/sfu/models/models';
|
|
10
|
-
import type { StreamVideoClientOptions } from '../
|
|
10
|
+
import type { StreamVideoClientOptions } from '../types';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Utility function to get the instance key.
|
package/src/sorting/presets.ts
CHANGED
|
@@ -102,7 +102,7 @@ export const paginatedLayoutSortPreset = combineComparators(
|
|
|
102
102
|
* The sorting preset for livestreams and audio rooms.
|
|
103
103
|
*/
|
|
104
104
|
export const livestreamOrAudioRoomSortPreset = combineComparators(
|
|
105
|
-
|
|
105
|
+
ifInvisibleOrUnknownBy(
|
|
106
106
|
combineComparators(
|
|
107
107
|
dominantSpeaker,
|
|
108
108
|
speaking,
|
package/src/store/CallState.ts
CHANGED
|
@@ -1042,7 +1042,7 @@ export class CallState {
|
|
|
1042
1042
|
) => {
|
|
1043
1043
|
const participant = this.findParticipantBySessionId(sessionId);
|
|
1044
1044
|
if (!participant) {
|
|
1045
|
-
this.logger.
|
|
1045
|
+
this.logger.debug(`Participant with sessionId ${sessionId} not found`);
|
|
1046
1046
|
return;
|
|
1047
1047
|
}
|
|
1048
1048
|
|
package/src/types.ts
CHANGED
|
@@ -4,20 +4,25 @@ import type {
|
|
|
4
4
|
VideoDimension,
|
|
5
5
|
} from './gen/video/sfu/models/models';
|
|
6
6
|
import type {
|
|
7
|
+
AudioSettingsRequestDefaultDeviceEnum,
|
|
7
8
|
CallRecordingStartedEventRecordingTypeEnum,
|
|
8
9
|
JoinCallRequest,
|
|
9
10
|
MemberResponse,
|
|
10
11
|
OwnCapability,
|
|
11
12
|
ReactionResponse,
|
|
12
|
-
AudioSettingsRequestDefaultDeviceEnum,
|
|
13
13
|
StartRecordingRequest,
|
|
14
14
|
StartRecordingResponse,
|
|
15
15
|
} from './gen/coordinator';
|
|
16
16
|
import type { StreamClient } from './coordinator/connection/client';
|
|
17
|
+
import type {
|
|
18
|
+
RejectReason,
|
|
19
|
+
StreamClientOptions,
|
|
20
|
+
TokenProvider,
|
|
21
|
+
User,
|
|
22
|
+
} from './coordinator/connection/types';
|
|
17
23
|
import type { Comparator } from './sorting';
|
|
18
24
|
import type { StreamVideoWriteableStateStore } from './store';
|
|
19
25
|
import { AxiosError } from 'axios';
|
|
20
|
-
import { RejectReason } from './coordinator/connection/types';
|
|
21
26
|
|
|
22
27
|
export type StreamReaction = Pick<
|
|
23
28
|
ReactionResponse,
|
|
@@ -334,6 +339,48 @@ export type CallConstructor = {
|
|
|
334
339
|
clientStore: StreamVideoWriteableStateStore;
|
|
335
340
|
};
|
|
336
341
|
|
|
342
|
+
type StreamVideoClientBaseOptions = {
|
|
343
|
+
apiKey: string;
|
|
344
|
+
options?: StreamClientOptions;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
type StreamVideoClientOptionsWithoutUser = StreamVideoClientBaseOptions & {
|
|
348
|
+
user?: undefined;
|
|
349
|
+
token?: never;
|
|
350
|
+
tokenProvider?: never;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
type GuestUser = Extract<User, { type: 'guest' }>;
|
|
354
|
+
type AnonymousUser = Extract<User, { type: 'anonymous' }>;
|
|
355
|
+
type AuthenticatedUser = Exclude<User, GuestUser | AnonymousUser>;
|
|
356
|
+
|
|
357
|
+
type StreamVideoClientOptionsWithGuestUser = StreamVideoClientBaseOptions & {
|
|
358
|
+
user: GuestUser;
|
|
359
|
+
token?: never;
|
|
360
|
+
tokenProvider?: never;
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
type StreamVideoClientOptionsWithAnonymousUser =
|
|
364
|
+
StreamVideoClientBaseOptions & {
|
|
365
|
+
user: AnonymousUser;
|
|
366
|
+
token?: string;
|
|
367
|
+
tokenProvider?: TokenProvider;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
type StreamVideoClientOptionsWithAuthenticatedUser =
|
|
371
|
+
StreamVideoClientBaseOptions & {
|
|
372
|
+
user: AuthenticatedUser;
|
|
373
|
+
} & (
|
|
374
|
+
| { token: string; tokenProvider?: TokenProvider }
|
|
375
|
+
| { token?: string; tokenProvider: TokenProvider }
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
export type StreamVideoClientOptions =
|
|
379
|
+
| StreamVideoClientOptionsWithoutUser
|
|
380
|
+
| StreamVideoClientOptionsWithGuestUser
|
|
381
|
+
| StreamVideoClientOptionsWithAnonymousUser
|
|
382
|
+
| StreamVideoClientOptionsWithAuthenticatedUser;
|
|
383
|
+
|
|
337
384
|
export type CallRecordingType = CallRecordingStartedEventRecordingTypeEnum;
|
|
338
385
|
export type StartCallRecordingFnType = {
|
|
339
386
|
(): Promise<StartRecordingResponse>;
|