@stream-io/video-react-sdk 0.3.46 → 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 (64) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +3 -2
  3. package/dist/index.cjs.js +328 -885
  4. package/dist/index.cjs.js.map +1 -1
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.es.js +329 -870
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/components/CallControls/ScreenShareButton.d.ts +1 -1
  9. package/dist/src/components/Notification/SpeakingWhileMutedNotification.d.ts +3 -0
  10. package/dist/src/components/{Video → VideoPreview}/VideoPreview.d.ts +1 -9
  11. package/dist/src/components/index.d.ts +1 -1
  12. package/dist/src/core/components/ParticipantView/ParticipantView.d.ts +3 -9
  13. package/dist/src/core/components/ParticipantView/ParticipantViewContext.d.ts +9 -0
  14. package/dist/src/core/components/ParticipantView/index.d.ts +1 -0
  15. package/dist/src/core/components/StreamCall/StreamCall.d.ts +2 -11
  16. package/dist/src/core/hooks/index.d.ts +0 -2
  17. package/dist/src/core/hooks/useDevices.d.ts +0 -99
  18. package/dist/src/core/index.d.ts +0 -1
  19. package/dist/src/hooks/index.d.ts +1 -3
  20. package/dist/src/hooks/usePersistedDevicePreferences.d.ts +13 -0
  21. package/dist/src/translations/index.d.ts +2 -0
  22. package/index.ts +2 -2
  23. package/package.json +3 -3
  24. package/src/components/CallControls/CallControls.tsx +6 -8
  25. package/src/components/CallControls/ScreenShareButton.tsx +17 -13
  26. package/src/components/CallControls/ToggleAudioButton.tsx +21 -24
  27. package/src/components/CallControls/ToggleAudioOutputButton.tsx +1 -1
  28. package/src/components/CallControls/ToggleVideoButton.tsx +21 -22
  29. package/src/components/CallParticipantsList/CallParticipantsList.tsx +1 -1
  30. package/src/components/DeviceSettings/DeviceSelectorAudio.tsx +20 -26
  31. package/src/components/DeviceSettings/DeviceSelectorVideo.tsx +9 -8
  32. package/src/components/Icon/Icon.tsx +1 -1
  33. package/src/components/Notification/SpeakingWhileMutedNotification.tsx +5 -49
  34. package/src/components/VideoPreview/VideoPreview.tsx +67 -0
  35. package/src/components/index.ts +1 -1
  36. package/src/core/components/CallLayout/PaginatedGridLayout.tsx +2 -5
  37. package/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx +7 -6
  38. package/src/core/components/ParticipantView/ParticipantView.tsx +2 -19
  39. package/src/core/components/ParticipantView/ParticipantViewContext.tsx +17 -0
  40. package/src/core/components/ParticipantView/index.ts +1 -0
  41. package/src/core/components/StreamCall/StreamCall.tsx +2 -28
  42. package/src/core/hooks/index.ts +0 -2
  43. package/src/core/hooks/useDevices.ts +0 -195
  44. package/src/core/index.ts +0 -1
  45. package/src/hooks/index.ts +1 -3
  46. package/src/hooks/usePersistedDevicePreferences.ts +118 -0
  47. package/src/translations/en.json +3 -0
  48. package/dist/src/core/contexts/MediaDevicesContext.d.ts +0 -180
  49. package/dist/src/core/contexts/index.d.ts +0 -1
  50. package/dist/src/core/hooks/useAudioPublisher.d.ts +0 -12
  51. package/dist/src/core/hooks/useVideoPublisher.d.ts +0 -12
  52. package/dist/src/hooks/useToggleAudioMuteState.d.ts +0 -4
  53. package/dist/src/hooks/useToggleScreenShare.d.ts +0 -5
  54. package/dist/src/hooks/useToggleVideoMuteState.d.ts +0 -4
  55. package/src/components/Video/VideoPreview.tsx +0 -152
  56. package/src/core/contexts/MediaDevicesContext.tsx +0 -416
  57. package/src/core/contexts/index.ts +0 -1
  58. package/src/core/hooks/useAudioPublisher.ts +0 -146
  59. package/src/core/hooks/useVideoPublisher.ts +0 -177
  60. package/src/hooks/useToggleAudioMuteState.ts +0 -34
  61. package/src/hooks/useToggleScreenShare.ts +0 -43
  62. package/src/hooks/useToggleVideoMuteState.ts +0 -34
  63. /package/dist/src/components/{Video → VideoPreview}/index.d.ts +0 -0
  64. /package/src/components/{Video → VideoPreview}/index.ts +0 -0
@@ -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",
@@ -1,180 +0,0 @@
1
- import { PropsWithChildren } from 'react';
2
- import { getAudioStream, getVideoStream } from '@stream-io/video-client';
3
- type EnabledStateType = 'starting' | 'playing';
4
- type DisabledStateType = 'uninitialized' | 'stopped';
5
- type EnabledDeviceState<T extends EnabledStateType> = {
6
- type: T;
7
- enabled: true;
8
- };
9
- type DisabledDeviceState<T extends DisabledStateType> = {
10
- type: T;
11
- enabled: false;
12
- };
13
- type ErrorDeviceState = {
14
- type: 'error';
15
- message: string;
16
- enabled: false;
17
- };
18
- type DeviceState = EnabledDeviceState<EnabledStateType> | DisabledDeviceState<DisabledStateType> | ErrorDeviceState;
19
- /**
20
- * Exclude types from documentation site, but we should still add doc comments
21
- * @internal
22
- */
23
- export declare const DEVICE_STATE: {
24
- starting: EnabledDeviceState<'starting'>;
25
- playing: EnabledDeviceState<'playing'>;
26
- stopped: DisabledDeviceState<'stopped'>;
27
- uninitialized: DisabledDeviceState<'uninitialized'>;
28
- error: ErrorDeviceState;
29
- };
30
- /**
31
- * API to control device enablement, device selection and media stream access for a call.
32
- * @category Device Management
33
- */
34
- export type MediaDevicesContextAPI = {
35
- /**
36
- * Deactivates MediaStream (stops and removes tracks) to be later garbage collected
37
- *
38
- * @param stream MediaStream
39
- * @returns void
40
- */
41
- disposeOfMediaStream: (stream: MediaStream) => void;
42
- /**
43
- * Returns an 'audioinput' media stream with the given `deviceId`, if no `deviceId` is provided, it uses the first available device.
44
- *
45
- * @param deviceId
46
- * @returns
47
- */
48
- getAudioStream: typeof getAudioStream;
49
- /**
50
- * Returns a 'videoinput' media stream with the given `deviceId`, if no `deviceId` is provided, it uses the first available device.
51
- *
52
- * @param deviceId
53
- * @returns
54
- */
55
- getVideoStream: typeof getVideoStream;
56
- /**
57
- * [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
58
- */
59
- isAudioOutputChangeSupported: boolean;
60
- /**
61
- * Signals whether audio stream will be published when the call is joined.
62
- */
63
- initialAudioEnabled: boolean;
64
- /**
65
- * Signals whether audio stream will be published when the call is joined.
66
- */
67
- initialVideoState: DeviceState;
68
- /**
69
- * Publishes audio stream for currently selected audio input (microphone) device to other call participants.
70
- */
71
- publishAudioStream: () => Promise<void>;
72
- /**
73
- * Publishes video stream for currently selected video input (camera) device to other call participants.
74
- */
75
- publishVideoStream: () => Promise<void>;
76
- /**
77
- * Stops publishing audio stream for currently selected audio input (microphone) device to other call participants.
78
- */
79
- stopPublishingAudio: () => Promise<void>;
80
- /**
81
- * Stops publishing video stream for currently selected video input (camera) device to other call participants.
82
- */
83
- stopPublishingVideo: () => Promise<void>;
84
- /**
85
- * Sets the initialAudioEnabled flag to a given boolean value.
86
- * The latest value set will be used to decide, whether audio stream will be published when joining a call.
87
- * @param enabled
88
- */
89
- setInitialAudioEnabled: (enabled: boolean) => void;
90
- /**
91
- * Sets the initialVideoState to a given DeviceState value.
92
- * The latest value set will be used to decide, whether video stream will be published when joining a call.
93
- * @param enabled
94
- */
95
- setInitialVideoState: (state: DeviceState) => void;
96
- /**
97
- * Stores audio input device (microphone) id which is used to publish user's sound to other call participants.
98
- */
99
- selectedAudioInputDeviceId?: string;
100
- /**
101
- * Stores audio output device (speaker) id used to reproduce incoming audio from other call participants.
102
- */
103
- selectedAudioOutputDeviceId?: string;
104
- /**
105
- * Stores video input device (camera) id which is used to publish user's video to other call participants.
106
- */
107
- selectedVideoDeviceId?: string;
108
- /**
109
- * Function should be used to change selected device id.
110
- * The change is later reflected in selectedAudioInputDeviceId, selectedAudioOutputDeviceId or selectedVideoDeviceId depending on kind parameter.
111
- * @param kind
112
- * @param deviceId
113
- */
114
- switchDevice: (kind: MediaDeviceKind, deviceId?: string) => void;
115
- /**
116
- * Sets the initialAudioEnabled flag by negating the current state value.
117
- * The latest value set will be used to decide, whether audio stream will be published when joining a call.
118
- * @param enabled
119
- */
120
- toggleInitialAudioMuteState: () => void;
121
- /**
122
- * Sets the initialVideoState by toggling the current state DeviceState value.
123
- * The latest value set will be used to decide, whether video stream will be published when joining a call.
124
- * @param enabled
125
- */
126
- toggleInitialVideoMuteState: () => void;
127
- };
128
- /**
129
- * Configuration parameters for MediaDevicesProvider.
130
- * @category Device Management
131
- */
132
- export type MediaDevicesProviderProps = {
133
- /**
134
- * Provides external control over the initial audio input (microphone) enablement. Overrides the default false.
135
- */
136
- initialAudioEnabled?: boolean;
137
- /**
138
- * Provides external control over the initial video input (camera) enablement. Overrides the default false.
139
- */
140
- initialVideoEnabled?: boolean;
141
- /**
142
- * Allows to override the default audio input (microphone) stream to be published. Overrides the default string 'default'.
143
- */
144
- initialAudioInputDeviceId?: string;
145
- /**
146
- * Allows to override the default audio output (speaker) device to reproduce incoming audio from the SFU. Overrides the default string 'default'.
147
- */
148
- initialAudioOutputDeviceId?: string;
149
- /**
150
- * Allows to override the default video input (camera) stream to be published. Overrides the default string 'default'.
151
- */
152
- initialVideoInputDeviceId?: string;
153
- };
154
- /**
155
- * Context provider that internally puts in place mechanisms that:
156
- * 1. fall back to selecting a default device when trying to switch to a non-existent device
157
- * 2. fall back to a default device when an active device is disconnected
158
- * 3. stop publishing a media stream when a non-default device is disconnected
159
- * 4. republish a media stream from the newly connected default device
160
- * 5. republish a media stream when a new device is selected
161
- *
162
- * Provides `MediaDevicesContextAPI` that allow the integrators to handle:
163
- * 1. the initial device state enablement (for example apt for lobby scenario)
164
- * 2. media stream retrieval and disposal
165
- * 3. media stream publishing
166
- * 4. specific device selection
167
- * @param params
168
- * @returns
169
- *
170
- * @category Device Management
171
- */
172
- export declare const MediaDevicesProvider: ({ children, initialAudioEnabled, initialVideoEnabled, initialVideoInputDeviceId, initialAudioOutputDeviceId, initialAudioInputDeviceId, }: PropsWithChildren<MediaDevicesProviderProps>) => import("react/jsx-runtime").JSX.Element;
173
- /**
174
- * Context consumer retrieving MediaDevicesContextAPI.
175
- * @returns
176
- *
177
- * @category Device Management
178
- */
179
- export declare const useMediaDevices: () => MediaDevicesContextAPI;
180
- export {};
@@ -1 +0,0 @@
1
- export * from './MediaDevicesContext';
@@ -1,12 +0,0 @@
1
- /**
2
- * @internal
3
- */
4
- export type AudioPublisherInit = {
5
- initialAudioMuted?: boolean;
6
- audioDeviceId?: string;
7
- };
8
- /**
9
- * @internal
10
- * @category Device Management
11
- */
12
- export declare const useAudioPublisher: ({ initialAudioMuted, audioDeviceId, }: AudioPublisherInit) => () => Promise<void>;
@@ -1,12 +0,0 @@
1
- /**
2
- * @internal
3
- */
4
- export type VideoPublisherInit = {
5
- initialVideoMuted?: boolean;
6
- videoDeviceId?: string;
7
- };
8
- /**
9
- * @internal
10
- * @category Device Management
11
- */
12
- export declare const useVideoPublisher: ({ initialVideoMuted, videoDeviceId, }: VideoPublisherInit) => () => Promise<void>;
@@ -1,4 +0,0 @@
1
- export declare const useToggleAudioMuteState: () => {
2
- toggleAudioMuteState: () => Promise<void>;
3
- isAwaitingPermission: boolean;
4
- };
@@ -1,5 +0,0 @@
1
- export declare const useToggleScreenShare: () => {
2
- toggleScreenShare: () => Promise<void>;
3
- isAwaitingPermission: boolean;
4
- isScreenSharing: boolean;
5
- };
@@ -1,4 +0,0 @@
1
- export declare const useToggleVideoMuteState: () => {
2
- toggleVideoMuteState: () => Promise<void>;
3
- isAwaitingPermission: boolean;
4
- };
@@ -1,152 +0,0 @@
1
- import {
2
- ComponentType,
3
- ReactEventHandler,
4
- useCallback,
5
- useEffect,
6
- useState,
7
- } from 'react';
8
- import clsx from 'clsx';
9
- import { disposeOfMediaStream } from '@stream-io/video-client';
10
- import { BaseVideo } from '../../core/components/Video';
11
- import {
12
- DEVICE_STATE,
13
- useMediaDevices,
14
- useOnUnavailableVideoDevices,
15
- useVideoDevices,
16
- } from '../../core';
17
- import { LoadingIndicator } from '../LoadingIndicator';
18
-
19
- const DefaultDisabledVideoPreview = () => {
20
- return <div>Video is disabled</div>;
21
- };
22
-
23
- const DefaultNoCameraPreview = () => {
24
- return <div>No camera found</div>;
25
- };
26
-
27
- type VideoErrorPreviewProps = {
28
- message?: string;
29
- };
30
- const DefaultVideoErrorPreview = ({ message }: VideoErrorPreviewProps) => {
31
- return (
32
- <>
33
- <div>Error:</div>
34
- <p>{message || 'Unexpected error happened'}</p>
35
- </>
36
- );
37
- };
38
-
39
- export type VideoPreviewProps = {
40
- /**
41
- * Component rendered when user turns off the video.
42
- */
43
- DisabledVideoPreview?: ComponentType;
44
- /**
45
- * Enforces mirroring of the video on the X axis. Defaults to true.
46
- */
47
- mirror?: boolean;
48
- /**
49
- * Component rendered when no camera devices are available.
50
- */
51
- NoCameraPreview?: ComponentType;
52
- /**
53
- * Component rendered above the BaseVideo until the video is ready (meaning until the play event is emitted).
54
- */
55
- StartingCameraPreview?: ComponentType;
56
- /**
57
- * Component rendered when the video stream could not be retrieved.
58
- */
59
- VideoErrorPreview?: ComponentType<VideoErrorPreviewProps>;
60
- };
61
-
62
- export const VideoPreview = ({
63
- mirror = true,
64
- DisabledVideoPreview = DefaultDisabledVideoPreview,
65
- NoCameraPreview = DefaultNoCameraPreview,
66
- StartingCameraPreview = LoadingIndicator,
67
- VideoErrorPreview = DefaultVideoErrorPreview,
68
- }: VideoPreviewProps) => {
69
- const [stream, setStream] = useState<MediaStream>();
70
- const {
71
- selectedVideoDeviceId,
72
- getVideoStream,
73
- initialVideoState,
74
- setInitialVideoState,
75
- } = useMediaDevices();
76
- // When there are 0 video devices (e.g. when laptop lid closed),
77
- // we do not restart the video automatically when the device is again available,
78
- // but rather leave turning the video on manually to the user.
79
- useOnUnavailableVideoDevices(() =>
80
- setInitialVideoState(DEVICE_STATE.stopped),
81
- );
82
- const videoDevices = useVideoDevices();
83
-
84
- useEffect(() => {
85
- if (!initialVideoState.enabled) return;
86
-
87
- getVideoStream({ deviceId: selectedVideoDeviceId })
88
- .then((s) => {
89
- setStream((previousStream) => {
90
- if (previousStream) {
91
- disposeOfMediaStream(previousStream);
92
- }
93
- return s;
94
- });
95
- })
96
- .catch((e) =>
97
- setInitialVideoState({
98
- ...DEVICE_STATE.error,
99
- message: (e as Error).message,
100
- }),
101
- );
102
- return () => {
103
- setStream(undefined);
104
- };
105
- }, [
106
- initialVideoState,
107
- getVideoStream,
108
- selectedVideoDeviceId,
109
- setInitialVideoState,
110
- videoDevices.length,
111
- ]);
112
-
113
- useEffect(() => {
114
- if (initialVideoState.type === 'stopped') {
115
- setStream(undefined);
116
- }
117
- }, [initialVideoState]);
118
-
119
- const handleOnPlay: ReactEventHandler<HTMLVideoElement> = useCallback(() => {
120
- setInitialVideoState(DEVICE_STATE.playing);
121
- }, [setInitialVideoState]);
122
-
123
- let contents;
124
- if (initialVideoState.type === 'error') {
125
- contents = <VideoErrorPreview />;
126
- } else if (initialVideoState.type === 'stopped' && !videoDevices.length) {
127
- contents = <NoCameraPreview />;
128
- } else if (initialVideoState.enabled) {
129
- const loading = initialVideoState.type === 'starting';
130
- contents = (
131
- <>
132
- {stream && (
133
- <BaseVideo
134
- stream={stream}
135
- className={clsx('str-video__video-preview', {
136
- 'str-video__video-preview--mirror': mirror,
137
- 'str-video__video-preview--loading': loading,
138
- })}
139
- onPlay={handleOnPlay}
140
- />
141
- )}
142
- {loading && <StartingCameraPreview />}
143
- </>
144
- );
145
- } else {
146
- contents = <DisabledVideoPreview />;
147
- }
148
-
149
- return (
150
- <div className={clsx('str-video__video-preview-container')}>{contents}</div>
151
- );
152
- };