@stream-io/video-react-sdk 0.3.47 → 0.4.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +1 -1
  3. package/dist/index.cjs.js +324 -882
  4. package/dist/index.cjs.js.map +1 -1
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.es.js +325 -867
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/components/Notification/SpeakingWhileMutedNotification.d.ts +3 -0
  9. package/dist/src/components/{Video → VideoPreview}/VideoPreview.d.ts +1 -9
  10. package/dist/src/components/index.d.ts +1 -1
  11. package/dist/src/core/components/ParticipantView/ParticipantView.d.ts +3 -9
  12. package/dist/src/core/components/ParticipantView/ParticipantViewContext.d.ts +9 -0
  13. package/dist/src/core/components/ParticipantView/index.d.ts +1 -0
  14. package/dist/src/core/components/StreamCall/StreamCall.d.ts +2 -11
  15. package/dist/src/core/hooks/index.d.ts +0 -2
  16. package/dist/src/core/hooks/useDevices.d.ts +0 -99
  17. package/dist/src/core/index.d.ts +0 -1
  18. package/dist/src/hooks/index.d.ts +1 -3
  19. package/dist/src/hooks/usePersistedDevicePreferences.d.ts +13 -0
  20. package/dist/src/translations/index.d.ts +2 -0
  21. package/index.ts +2 -2
  22. package/package.json +3 -3
  23. package/src/components/CallControls/CallControls.tsx +6 -8
  24. package/src/components/CallControls/ScreenShareButton.tsx +14 -10
  25. package/src/components/CallControls/ToggleAudioButton.tsx +21 -24
  26. package/src/components/CallControls/ToggleAudioOutputButton.tsx +1 -1
  27. package/src/components/CallControls/ToggleVideoButton.tsx +21 -22
  28. package/src/components/CallParticipantsList/CallParticipantsList.tsx +1 -1
  29. package/src/components/DeviceSettings/DeviceSelectorAudio.tsx +20 -26
  30. package/src/components/DeviceSettings/DeviceSelectorVideo.tsx +9 -8
  31. package/src/components/Icon/Icon.tsx +1 -1
  32. package/src/components/Notification/SpeakingWhileMutedNotification.tsx +5 -49
  33. package/src/components/VideoPreview/VideoPreview.tsx +67 -0
  34. package/src/components/index.ts +1 -1
  35. package/src/core/components/CallLayout/PaginatedGridLayout.tsx +2 -5
  36. package/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx +7 -6
  37. package/src/core/components/ParticipantView/ParticipantView.tsx +2 -19
  38. package/src/core/components/ParticipantView/ParticipantViewContext.tsx +17 -0
  39. package/src/core/components/ParticipantView/index.ts +1 -0
  40. package/src/core/components/StreamCall/StreamCall.tsx +2 -28
  41. package/src/core/hooks/index.ts +0 -2
  42. package/src/core/hooks/useDevices.ts +0 -195
  43. package/src/core/index.ts +0 -1
  44. package/src/hooks/index.ts +1 -3
  45. package/src/hooks/usePersistedDevicePreferences.ts +118 -0
  46. package/src/translations/en.json +3 -0
  47. package/dist/src/core/contexts/MediaDevicesContext.d.ts +0 -180
  48. package/dist/src/core/contexts/index.d.ts +0 -1
  49. package/dist/src/core/hooks/useAudioPublisher.d.ts +0 -12
  50. package/dist/src/core/hooks/useVideoPublisher.d.ts +0 -12
  51. package/dist/src/hooks/useToggleAudioMuteState.d.ts +0 -4
  52. package/dist/src/hooks/useToggleScreenShare.d.ts +0 -5
  53. package/dist/src/hooks/useToggleVideoMuteState.d.ts +0 -4
  54. package/src/components/Video/VideoPreview.tsx +0 -152
  55. package/src/core/contexts/MediaDevicesContext.tsx +0 -416
  56. package/src/core/contexts/index.ts +0 -1
  57. package/src/core/hooks/useAudioPublisher.ts +0 -146
  58. package/src/core/hooks/useVideoPublisher.ts +0 -177
  59. package/src/hooks/useToggleAudioMuteState.ts +0 -34
  60. package/src/hooks/useToggleScreenShare.ts +0 -43
  61. package/src/hooks/useToggleVideoMuteState.ts +0 -34
  62. /package/dist/src/components/{Video → VideoPreview}/index.d.ts +0 -0
  63. /package/src/components/{Video → VideoPreview}/index.ts +0 -0
@@ -1,13 +1,10 @@
1
- import { PropsWithChildren, useEffect, useState } from 'react';
2
- import { createSoundDetector, SfuModels } from '@stream-io/video-client';
1
+ import { PropsWithChildren } from 'react';
3
2
  import { useCallStateHooks, useI18n } from '@stream-io/video-react-bindings';
4
-
5
- import { useMediaDevices } from '../../core';
6
3
  import { Notification } from './Notification';
7
4
 
8
5
  export type SpeakingWhileMutedNotificationProps = {
9
- /*
10
- Text message displayed by the notification.
6
+ /**
7
+ * Text message displayed by the notification.
11
8
  */
12
9
  text?: string;
13
10
  };
@@ -16,52 +13,11 @@ export const SpeakingWhileMutedNotification = ({
16
13
  children,
17
14
  text,
18
15
  }: PropsWithChildren<SpeakingWhileMutedNotificationProps>) => {
19
- const { useLocalParticipant } = useCallStateHooks();
20
- const localParticipant = useLocalParticipant();
21
- const { getAudioStream } = useMediaDevices();
16
+ const { useMicrophoneState } = useCallStateHooks();
17
+ const { isSpeakingWhileMuted } = useMicrophoneState();
22
18
  const { t } = useI18n();
23
19
 
24
20
  const message = text ?? t('You are muted. Unmute to speak.');
25
- const isAudioMute = !localParticipant?.publishedTracks.includes(
26
- SfuModels.TrackType.AUDIO,
27
- );
28
- const audioDeviceId = localParticipant?.audioDeviceId;
29
- const [isSpeakingWhileMuted, setIsSpeakingWhileMuted] = useState(false);
30
- useEffect(() => {
31
- // do nothing when not muted
32
- if (!isAudioMute) return;
33
- const disposeSoundDetector = getAudioStream({
34
- deviceId: audioDeviceId,
35
- }).then((audioStream) =>
36
- createSoundDetector(audioStream, ({ isSoundDetected }) => {
37
- setIsSpeakingWhileMuted((isNotified) =>
38
- isNotified ? isNotified : isSoundDetected,
39
- );
40
- }),
41
- );
42
- disposeSoundDetector.catch((err) => {
43
- console.error('Error while creating sound detector', err);
44
- });
45
- return () => {
46
- disposeSoundDetector
47
- .then((dispose) => dispose())
48
- .catch((err) => {
49
- console.error('Error while disposing sound detector', err);
50
- });
51
- setIsSpeakingWhileMuted(false);
52
- };
53
- }, [audioDeviceId, getAudioStream, isAudioMute]);
54
-
55
- useEffect(() => {
56
- if (!isSpeakingWhileMuted) return;
57
- const timeout = setTimeout(() => {
58
- setIsSpeakingWhileMuted(false);
59
- }, 3500);
60
- return () => {
61
- clearTimeout(timeout);
62
- setIsSpeakingWhileMuted(false);
63
- };
64
- }, [isSpeakingWhileMuted]);
65
21
  return (
66
22
  <Notification message={message} isVisible={isSpeakingWhileMuted}>
67
23
  {children}
@@ -0,0 +1,67 @@
1
+ import { ComponentType } from 'react';
2
+ import clsx from 'clsx';
3
+ import { useCallStateHooks } from '@stream-io/video-react-bindings';
4
+ import { BaseVideo } from '../../core/components/Video';
5
+ import { LoadingIndicator } from '../LoadingIndicator';
6
+
7
+ const DefaultDisabledVideoPreview = () => {
8
+ return <div>Video is disabled</div>;
9
+ };
10
+
11
+ const DefaultNoCameraPreview = () => {
12
+ return <div>No camera found</div>;
13
+ };
14
+
15
+ export type VideoPreviewProps = {
16
+ /**
17
+ * Component rendered when user turns off the video.
18
+ */
19
+ DisabledVideoPreview?: ComponentType;
20
+ /**
21
+ * Enforces mirroring of the video on the X axis. Defaults to true.
22
+ */
23
+ mirror?: boolean;
24
+ /**
25
+ * Component rendered when no camera devices are available.
26
+ */
27
+ NoCameraPreview?: ComponentType;
28
+ /**
29
+ * Component rendered above the BaseVideo until the video is ready (meaning until the play event is emitted).
30
+ */
31
+ StartingCameraPreview?: ComponentType;
32
+ };
33
+
34
+ export const VideoPreview = ({
35
+ mirror = true,
36
+ DisabledVideoPreview = DefaultDisabledVideoPreview,
37
+ NoCameraPreview = DefaultNoCameraPreview,
38
+ StartingCameraPreview = LoadingIndicator,
39
+ }: VideoPreviewProps) => {
40
+ const { useCameraState } = useCallStateHooks();
41
+ const { devices, status, isMute, mediaStream } = useCameraState();
42
+
43
+ let contents;
44
+ if (isMute && devices?.length === 0) {
45
+ contents = <NoCameraPreview />;
46
+ } else if (status === 'enabled') {
47
+ const loading = !mediaStream;
48
+ contents = (
49
+ <>
50
+ {mediaStream && (
51
+ <BaseVideo
52
+ stream={mediaStream}
53
+ className={clsx('str-video__video-preview', {
54
+ 'str-video__video-preview--mirror': mirror,
55
+ 'str-video__video-preview--loading': loading,
56
+ })}
57
+ />
58
+ )}
59
+ {loading && <StartingCameraPreview />}
60
+ </>
61
+ );
62
+ } else {
63
+ contents = <DisabledVideoPreview />;
64
+ }
65
+
66
+ return <div className="str-video__video-preview-container">{contents}</div>;
67
+ };
@@ -14,4 +14,4 @@ export * from './Permissions';
14
14
  export * from './StreamTheme';
15
15
  export * from './Search';
16
16
  export * from './Tooltip';
17
- export * from './Video';
17
+ export * from './VideoPreview';
@@ -1,9 +1,6 @@
1
1
  import { useEffect, useMemo, useState } from 'react';
2
2
  import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
3
- import {
4
- StreamVideoLocalParticipant,
5
- StreamVideoParticipant,
6
- } from '@stream-io/video-client';
3
+ import { StreamVideoParticipant } from '@stream-io/video-client';
7
4
  import clsx from 'clsx';
8
5
 
9
6
  import {
@@ -22,7 +19,7 @@ type PaginatedGridLayoutGroupProps = {
22
19
  /**
23
20
  * The group of participants to render.
24
21
  */
25
- group: Array<StreamVideoParticipant | StreamVideoLocalParticipant>;
22
+ group: Array<StreamVideoParticipant>;
26
23
  } & Pick<ParticipantViewProps, 'VideoPlaceholder'> &
27
24
  Required<Pick<ParticipantViewProps, 'ParticipantViewUI'>>;
28
25
 
@@ -1,8 +1,8 @@
1
1
  import { forwardRef } from 'react';
2
2
  import { Placement } from '@floating-ui/react';
3
3
  import { SfuModels } from '@stream-io/video-client';
4
- import { useCall } from '@stream-io/video-react-bindings';
5
- import { clsx } from 'clsx';
4
+ import { useCall, useI18n } from '@stream-io/video-react-bindings';
5
+ import clsx from 'clsx';
6
6
 
7
7
  import {
8
8
  Icon,
@@ -17,7 +17,7 @@ import { Reaction } from '../../../components/Reaction';
17
17
  import { DebugParticipantPublishQuality } from '../../../components/Debug/DebugParticipantPublishQuality';
18
18
  import { DebugStatsView } from '../../../components/Debug/DebugStatsView';
19
19
  import { useIsDebugMode } from '../../../components/Debug/useIsDebugMode';
20
- import { useParticipantViewContext } from './ParticipantView';
20
+ import { useParticipantViewContext } from './ParticipantViewContext';
21
21
 
22
22
  export type DefaultParticipantViewUIProps = {
23
23
  /**
@@ -42,22 +42,23 @@ const ToggleButton = forwardRef<HTMLButtonElement, ToggleMenuButtonProps>(
42
42
 
43
43
  export const DefaultScreenShareOverlay = () => {
44
44
  const call = useCall();
45
+ const { t } = useI18n();
45
46
 
46
47
  const stopScreenShare = () => {
47
- call?.stopPublish(SfuModels.TrackType.SCREEN_SHARE).catch(console.error);
48
+ call?.screenShare.disable();
48
49
  };
49
50
 
50
51
  return (
51
52
  <div className="str-video__screen-share-overlay">
52
53
  <Icon icon="screen-share-off" />
53
54
  <span className="str-video__screen-share-overlay__title">
54
- You are presenting your screen
55
+ {t('You are presenting your screen')}
55
56
  </span>
56
57
  <button
57
58
  onClick={stopScreenShare}
58
59
  className="str-video__screen-share-overlay__button"
59
60
  >
60
- <Icon icon="close" /> Stop Screen Sharing
61
+ <Icon icon="close" /> {t('Stop Screen Sharing')}
61
62
  </button>
62
63
  </div>
63
64
  );
@@ -1,16 +1,13 @@
1
1
  import {
2
2
  ComponentType,
3
- createContext,
4
3
  forwardRef,
5
4
  ReactElement,
6
- useContext,
7
5
  useMemo,
8
6
  useState,
9
7
  } from 'react';
10
8
  import clsx from 'clsx';
11
9
  import {
12
10
  SfuModels,
13
- StreamVideoLocalParticipant,
14
11
  StreamVideoParticipant,
15
12
  VideoTrackType,
16
13
  } from '@stream-io/video-client';
@@ -20,27 +17,13 @@ import { Video, VideoProps } from '../Video';
20
17
  import { useTrackElementVisibility } from '../../hooks';
21
18
  import { DefaultParticipantViewUI } from './DefaultParticipantViewUI';
22
19
  import { applyElementToRef, isComponentType } from '../../../utilities';
23
-
24
- export type ParticipantViewContextValue = Required<
25
- Pick<ParticipantViewProps, 'participant' | 'trackType'>
26
- > & {
27
- participantViewElement: HTMLDivElement | null;
28
- videoElement: HTMLVideoElement | null;
29
- videoPlaceholderElement: HTMLDivElement | null;
30
- };
31
-
32
- const ParticipantViewContext = createContext<
33
- ParticipantViewContextValue | undefined
34
- >(undefined);
35
-
36
- export const useParticipantViewContext = () =>
37
- useContext(ParticipantViewContext) as ParticipantViewContextValue;
20
+ import { ParticipantViewContext } from './ParticipantViewContext';
38
21
 
39
22
  export type ParticipantViewProps = {
40
23
  /**
41
24
  * The participant whose video/audio stream we want to play.
42
25
  */
43
- participant: StreamVideoParticipant | StreamVideoLocalParticipant;
26
+ participant: StreamVideoParticipant;
44
27
 
45
28
  /**
46
29
  * Override the default UI for rendering participant information/actions.
@@ -0,0 +1,17 @@
1
+ import { createContext, useContext } from 'react';
2
+ import { ParticipantViewProps } from './ParticipantView';
3
+
4
+ export type ParticipantViewContextValue = Required<
5
+ Pick<ParticipantViewProps, 'participant' | 'trackType'>
6
+ > & {
7
+ participantViewElement: HTMLDivElement | null;
8
+ videoElement: HTMLVideoElement | null;
9
+ videoPlaceholderElement: HTMLDivElement | null;
10
+ };
11
+
12
+ export const ParticipantViewContext = createContext<
13
+ ParticipantViewContextValue | undefined
14
+ >(undefined);
15
+
16
+ export const useParticipantViewContext = () =>
17
+ useContext(ParticipantViewContext) as ParticipantViewContextValue;
@@ -1,2 +1,3 @@
1
1
  export * from './ParticipantView';
2
+ export * from './ParticipantViewContext';
2
3
  export * from './DefaultParticipantViewUI';
@@ -1,30 +1,4 @@
1
- import { PropsWithChildren } from 'react';
2
- import { Call } from '@stream-io/video-client';
3
1
  import { StreamCallProvider } from '@stream-io/video-react-bindings';
4
- import {
5
- MediaDevicesProvider,
6
- MediaDevicesProviderProps,
7
- } from '../../contexts';
8
2
 
9
- export type StreamCallProps = {
10
- call: Call;
11
-
12
- /**
13
- * An optional props to pass to the `MediaDevicesProvider`.
14
- */
15
- mediaDevicesProviderProps?: MediaDevicesProviderProps;
16
- };
17
-
18
- export const StreamCall = ({
19
- children,
20
- call,
21
- mediaDevicesProviderProps,
22
- }: PropsWithChildren<StreamCallProps>) => {
23
- return (
24
- <StreamCallProvider call={call}>
25
- <MediaDevicesProvider {...mediaDevicesProviderProps}>
26
- {children}
27
- </MediaDevicesProvider>
28
- </StreamCallProvider>
29
- );
30
- };
3
+ // re-exporting the StreamCallProvider as StreamCall
4
+ export const StreamCall = StreamCallProvider;
@@ -1,4 +1,2 @@
1
- export * from './useAudioPublisher';
2
1
  export * from './useDevices';
3
- export * from './useVideoPublisher';
4
2
  export * from './useTrackElementVisibility';
@@ -1,10 +1,4 @@
1
1
  import { ChangeEvent, useEffect, useState } from 'react';
2
- import { Observable, pairwise } from 'rxjs';
3
- import {
4
- getAudioDevices,
5
- getAudioOutputDevices,
6
- getVideoDevices,
7
- } from '@stream-io/video-client';
8
2
 
9
3
  export const useHasBrowserPermissions = (permissionName: PermissionName) => {
10
4
  const [canSubscribe, enableSubscription] = useState(false);
@@ -37,192 +31,3 @@ export const useHasBrowserPermissions = (permissionName: PermissionName) => {
37
31
 
38
32
  return canSubscribe;
39
33
  };
40
-
41
- /**
42
- * Observes changes in connected devices and maintains an up-to-date array of connected MediaDeviceInfo objects.
43
- * @param observeDevices
44
- * @category Device Management
45
- */
46
- export const useDevices = (
47
- observeDevices: () => Observable<MediaDeviceInfo[]>,
48
- ) => {
49
- const [devices, setDevices] = useState<MediaDeviceInfo[]>([]);
50
-
51
- useEffect(() => {
52
- const subscription = observeDevices().subscribe(setDevices);
53
-
54
- return () => {
55
- subscription.unsubscribe();
56
- };
57
- }, [observeDevices]);
58
-
59
- return devices;
60
- };
61
-
62
- /**
63
- * Observes changes and maintains an array of connected video input devices
64
- * @category Device Management
65
- */
66
- export const useVideoDevices = () => useDevices(getVideoDevices);
67
-
68
- /**
69
- * Observes changes and maintains an array of connected audio input devices
70
- * @category Device Management
71
- */
72
- export const useAudioInputDevices = () => useDevices(getAudioDevices);
73
-
74
- /**
75
- * Observes changes and maintains an array of connected audio output devices
76
- * @category Device Management
77
- */
78
- export const useAudioOutputDevices = () => useDevices(getAudioOutputDevices);
79
-
80
- /**
81
- * Verifies that newly selected device id exists among the registered devices.
82
- * If the selected device id is not found among existing devices, switches to the default device.
83
- * The media devices are observed only if a given permission ('camera' resp. 'microphone') is granted in browser.
84
- * Regardless of current permissions settings, an intent to observe devices will take place in Firefox.
85
- * This is due to the fact that Firefox does not allow to query for 'camera' and 'microphone' permissions.
86
- * @param canObserve
87
- * @param devices$
88
- * @param switchToDefaultDevice
89
- * @param selectedDeviceId
90
- * @category Device Management
91
- */
92
- export const useDeviceFallback = (
93
- canObserve: boolean,
94
- devices$: Observable<MediaDeviceInfo[]>,
95
- switchToDefaultDevice: () => void,
96
- selectedDeviceId?: string,
97
- ) => {
98
- useEffect(() => {
99
- if (!canObserve) return;
100
- const validateDeviceId = devices$.pipe().subscribe((devices) => {
101
- const deviceFound = devices.find(
102
- (device) => device.deviceId === selectedDeviceId,
103
- );
104
- if (!deviceFound) switchToDefaultDevice();
105
- });
106
-
107
- return () => {
108
- validateDeviceId.unsubscribe();
109
- };
110
- }, [canObserve, devices$, selectedDeviceId, switchToDefaultDevice]);
111
- };
112
-
113
- /**
114
- * Verifies that newly selected video device id exists among the registered devices.
115
- * If the selected device id is not found among existing devices, switches to the default video device.
116
- * The media devices are observed only if 'camera' permission is granted in browser.
117
- * It is integrators responsibility to instruct users how to enable required permissions.
118
- * Regardless of current permissions settings, an intent to observe devices will take place in Firefox.
119
- * This is due to the fact that Firefox does not allow to query for 'camera' and 'microphone' permissions.
120
- * @param switchToDefaultDevice
121
- * @param canObserve
122
- * @param selectedDeviceId
123
- * @category Device Management
124
- */
125
- export const useVideoDeviceFallback = (
126
- switchToDefaultDevice: () => void,
127
- canObserve: boolean,
128
- selectedDeviceId?: string,
129
- ) =>
130
- useDeviceFallback(
131
- canObserve,
132
- getVideoDevices(),
133
- switchToDefaultDevice,
134
- selectedDeviceId,
135
- );
136
-
137
- /**
138
- * Verifies that newly selected audio input device id exists among the registered devices.
139
- * If the selected device id is not found among existing devices, switches to the default audio input device.
140
- * The media devices are observed only if 'microphone' permission is granted in browser.
141
- * It is integrators responsibility to instruct users how to enable required permissions.
142
- * Regardless of current permissions settings, an intent to observe devices will take place in Firefox.
143
- * This is due to the fact that Firefox does not allow to query for 'camera' and 'microphone' permissions.
144
- * @param switchToDefaultDevice
145
- * @param canObserve
146
- * @param selectedDeviceId
147
- * @category Device Management
148
- */
149
- export const useAudioInputDeviceFallback = (
150
- switchToDefaultDevice: () => void,
151
- canObserve: boolean,
152
- selectedDeviceId?: string,
153
- ) =>
154
- useDeviceFallback(
155
- canObserve,
156
- getAudioDevices(),
157
- switchToDefaultDevice,
158
- selectedDeviceId,
159
- );
160
-
161
- /**
162
- * Verifies that newly selected audio output device id exists among the registered devices.
163
- * If the selected device id is not found among existing devices, switches to the default audio output device.
164
- * The media devices are observed only if 'microphone' permission is granted in browser.
165
- * It is integrators responsibility to instruct users how to enable required permissions.
166
- * Regardless of current permissions settings, an intent to observe devices will take place in Firefox.
167
- * This is due to the fact that Firefox does not allow to query for 'camera' and 'microphone' permissions.
168
- * @param switchToDefaultDevice
169
- * @param canObserve
170
- * @param selectedDeviceId
171
- * @category Device Management
172
- */
173
- export const useAudioOutputDeviceFallback = (
174
- switchToDefaultDevice: () => void,
175
- canObserve: boolean,
176
- selectedDeviceId?: string,
177
- ) =>
178
- useDeviceFallback(
179
- canObserve,
180
- getAudioOutputDevices(),
181
- switchToDefaultDevice,
182
- selectedDeviceId,
183
- );
184
-
185
- /**
186
- * Observes devices of certain kind are made unavailable and executes onDisconnect callback.
187
- * @param observeDevices
188
- * @param onDisconnect
189
- * @category Device Management
190
- */
191
- export const useOnUnavailableDevices = (
192
- observeDevices: Observable<MediaDeviceInfo[]>,
193
- onDisconnect: () => void,
194
- ) => {
195
- useEffect(() => {
196
- const subscription = observeDevices
197
- .pipe(pairwise())
198
- .subscribe(([prev, current]) => {
199
- if (prev.length > 0 && current.length === 0) onDisconnect();
200
- });
201
-
202
- return () => subscription.unsubscribe();
203
- }, [observeDevices, onDisconnect]);
204
- };
205
-
206
- /**
207
- * Observes disconnect of all video devices and executes onDisconnect callback.
208
- * @param onDisconnect
209
- * @category Device Management
210
- */
211
- export const useOnUnavailableVideoDevices = (onDisconnect: () => void) =>
212
- useOnUnavailableDevices(getVideoDevices(), onDisconnect);
213
-
214
- /**
215
- * Observes disconnect of all audio input devices and executes onDisconnect callback.
216
- * @param onDisconnect
217
- * @category Device Management
218
- */
219
- export const useOnUnavailableAudioInputDevices = (onDisconnect: () => void) =>
220
- useOnUnavailableDevices(getAudioDevices(), onDisconnect);
221
-
222
- /**
223
- * Observes disconnect of all audio output devices and executes onDisconnect callback.
224
- * @param onDisconnect
225
- * @category Device Management
226
- */
227
- export const useOnUnavailableAudioOutputDevices = (onDisconnect: () => void) =>
228
- useOnUnavailableDevices(getAudioOutputDevices(), onDisconnect);
package/src/core/index.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  export * from './components';
2
- export * from './contexts';
3
2
  export * from './hooks';
@@ -1,7 +1,5 @@
1
1
  export * from './useFloatingUIPreset';
2
+ export * from './usePersistedDevicePreferences';
2
3
  export * from './useScrollPosition';
3
- export * from './useToggleAudioMuteState';
4
- export * from './useToggleVideoMuteState';
5
- export * from './useToggleScreenShare';
6
4
  export * from './useToggleCallRecording';
7
5
  export * from './useRequestPermission';
@@ -0,0 +1,118 @@
1
+ import { useEffect } from 'react';
2
+ import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
3
+
4
+ export type LocalDevicePreference = {
5
+ selectedDeviceId: string;
6
+ muted: boolean;
7
+ };
8
+
9
+ export type LocalDevicePreferences = {
10
+ [type in 'mic' | 'camera' | 'speaker']: LocalDevicePreference;
11
+ };
12
+
13
+ /**
14
+ * This hook will persist the device settings to local storage.
15
+ *
16
+ * @param key the key to use for local storage.
17
+ */
18
+ const usePersistDevicePreferences = (key: string) => {
19
+ const { useMicrophoneState, useCameraState, useSpeakerState } =
20
+ useCallStateHooks();
21
+ const mic = useMicrophoneState();
22
+ const camera = useCameraState();
23
+ const speaker = useSpeakerState();
24
+ useEffect(() => {
25
+ try {
26
+ const defaultDevice = 'default';
27
+ const preferences: LocalDevicePreferences = {
28
+ mic: {
29
+ selectedDeviceId: mic.selectedDevice || defaultDevice,
30
+ muted: mic.isMute,
31
+ },
32
+ camera: {
33
+ selectedDeviceId: camera.selectedDevice || defaultDevice,
34
+ muted: camera.isMute,
35
+ },
36
+ speaker: {
37
+ selectedDeviceId: speaker.selectedDevice || defaultDevice,
38
+ muted: false,
39
+ },
40
+ };
41
+ window.localStorage.setItem(key, JSON.stringify(preferences));
42
+ } catch (err) {
43
+ console.warn('Failed to save device preferences', err);
44
+ }
45
+ }, [
46
+ camera.isMute,
47
+ camera.selectedDevice,
48
+ key,
49
+ mic.isMute,
50
+ mic.selectedDevice,
51
+ speaker.selectedDevice,
52
+ ]);
53
+ };
54
+
55
+ /**
56
+ * This hook will apply the device settings from local storage.
57
+ *
58
+ * @param key the key to use for local storage.
59
+ */
60
+ const useApplyDevicePreferences = (key: string) => {
61
+ const call = useCall();
62
+ useEffect(() => {
63
+ if (!call) return;
64
+
65
+ const apply = async () => {
66
+ const initMic = async (setting: LocalDevicePreference) => {
67
+ await call.microphone.select(setting.selectedDeviceId);
68
+ if (setting.muted) {
69
+ await call.microphone.disable();
70
+ } else {
71
+ await call.microphone.enable();
72
+ }
73
+ };
74
+
75
+ const initCamera = async (setting: LocalDevicePreference) => {
76
+ await call.camera.select(setting.selectedDeviceId);
77
+ if (setting.muted) {
78
+ await call.camera.disable();
79
+ } else {
80
+ await call.camera.enable();
81
+ }
82
+ };
83
+
84
+ const initSpeaker = (setting: LocalDevicePreference) => {
85
+ call.speaker.select(setting.selectedDeviceId);
86
+ };
87
+
88
+ try {
89
+ const preferences = JSON.parse(
90
+ window.localStorage.getItem(key)!,
91
+ ) as LocalDevicePreferences;
92
+ if (preferences) {
93
+ await initMic(preferences.mic);
94
+ await initCamera(preferences.camera);
95
+ initSpeaker(preferences.speaker);
96
+ }
97
+ } catch (err) {
98
+ console.warn('Failed to load device preferences', err);
99
+ }
100
+ };
101
+
102
+ apply().catch((err) => {
103
+ console.warn('Failed to apply device preferences', err);
104
+ });
105
+ }, [call, key]);
106
+ };
107
+
108
+ /**
109
+ * This hook will apply and persist the device preferences from local storage.
110
+ *
111
+ * @param key the key to use for local storage.
112
+ */
113
+ export const usePersistedDevicePreferences = (
114
+ key: string = '@stream-io/device-preferences',
115
+ ) => {
116
+ useApplyDevicePreferences(key);
117
+ usePersistDevicePreferences(key);
118
+ };
@@ -38,6 +38,9 @@
38
38
  "Unknown": "Unknown",
39
39
  "Toggle device menu": "Toggle device menu",
40
40
 
41
+ "You are presenting your screen": "You are presenting your screen",
42
+ "Stop Screen Sharing": "Stop Screen Sharing",
43
+
41
44
  "Allow": "Allow",
42
45
  "Revoke": "Revoke",
43
46
  "Dismiss": "Dismiss",