@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.
- package/CHANGELOG.md +22 -0
- package/README.md +3 -2
- package/dist/index.cjs.js +328 -885
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +329 -870
- package/dist/index.es.js.map +1 -1
- package/dist/src/components/CallControls/ScreenShareButton.d.ts +1 -1
- package/dist/src/components/Notification/SpeakingWhileMutedNotification.d.ts +3 -0
- package/dist/src/components/{Video → VideoPreview}/VideoPreview.d.ts +1 -9
- package/dist/src/components/index.d.ts +1 -1
- package/dist/src/core/components/ParticipantView/ParticipantView.d.ts +3 -9
- package/dist/src/core/components/ParticipantView/ParticipantViewContext.d.ts +9 -0
- package/dist/src/core/components/ParticipantView/index.d.ts +1 -0
- package/dist/src/core/components/StreamCall/StreamCall.d.ts +2 -11
- package/dist/src/core/hooks/index.d.ts +0 -2
- package/dist/src/core/hooks/useDevices.d.ts +0 -99
- package/dist/src/core/index.d.ts +0 -1
- package/dist/src/hooks/index.d.ts +1 -3
- package/dist/src/hooks/usePersistedDevicePreferences.d.ts +13 -0
- package/dist/src/translations/index.d.ts +2 -0
- package/index.ts +2 -2
- package/package.json +3 -3
- package/src/components/CallControls/CallControls.tsx +6 -8
- package/src/components/CallControls/ScreenShareButton.tsx +17 -13
- package/src/components/CallControls/ToggleAudioButton.tsx +21 -24
- package/src/components/CallControls/ToggleAudioOutputButton.tsx +1 -1
- package/src/components/CallControls/ToggleVideoButton.tsx +21 -22
- package/src/components/CallParticipantsList/CallParticipantsList.tsx +1 -1
- package/src/components/DeviceSettings/DeviceSelectorAudio.tsx +20 -26
- package/src/components/DeviceSettings/DeviceSelectorVideo.tsx +9 -8
- package/src/components/Icon/Icon.tsx +1 -1
- package/src/components/Notification/SpeakingWhileMutedNotification.tsx +5 -49
- package/src/components/VideoPreview/VideoPreview.tsx +67 -0
- package/src/components/index.ts +1 -1
- package/src/core/components/CallLayout/PaginatedGridLayout.tsx +2 -5
- package/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx +7 -6
- package/src/core/components/ParticipantView/ParticipantView.tsx +2 -19
- package/src/core/components/ParticipantView/ParticipantViewContext.tsx +17 -0
- package/src/core/components/ParticipantView/index.ts +1 -0
- package/src/core/components/StreamCall/StreamCall.tsx +2 -28
- package/src/core/hooks/index.ts +0 -2
- package/src/core/hooks/useDevices.ts +0 -195
- package/src/core/index.ts +0 -1
- package/src/hooks/index.ts +1 -3
- package/src/hooks/usePersistedDevicePreferences.ts +118 -0
- package/src/translations/en.json +3 -0
- package/dist/src/core/contexts/MediaDevicesContext.d.ts +0 -180
- package/dist/src/core/contexts/index.d.ts +0 -1
- package/dist/src/core/hooks/useAudioPublisher.d.ts +0 -12
- package/dist/src/core/hooks/useVideoPublisher.d.ts +0 -12
- package/dist/src/hooks/useToggleAudioMuteState.d.ts +0 -4
- package/dist/src/hooks/useToggleScreenShare.d.ts +0 -5
- package/dist/src/hooks/useToggleVideoMuteState.d.ts +0 -4
- package/src/components/Video/VideoPreview.tsx +0 -152
- package/src/core/contexts/MediaDevicesContext.tsx +0 -416
- package/src/core/contexts/index.ts +0 -1
- package/src/core/hooks/useAudioPublisher.ts +0 -146
- package/src/core/hooks/useVideoPublisher.ts +0 -177
- package/src/hooks/useToggleAudioMuteState.ts +0 -34
- package/src/hooks/useToggleScreenShare.ts +0 -43
- package/src/hooks/useToggleVideoMuteState.ts +0 -34
- /package/dist/src/components/{Video → VideoPreview}/index.d.ts +0 -0
- /package/src/components/{Video → VideoPreview}/index.ts +0 -0
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createContext,
|
|
3
|
-
PropsWithChildren,
|
|
4
|
-
useCallback,
|
|
5
|
-
useContext,
|
|
6
|
-
useEffect,
|
|
7
|
-
useState,
|
|
8
|
-
} from 'react';
|
|
9
|
-
import { map } from 'rxjs';
|
|
10
|
-
import {
|
|
11
|
-
CallingState,
|
|
12
|
-
checkIfAudioOutputChangeSupported,
|
|
13
|
-
disposeOfMediaStream,
|
|
14
|
-
getAudioStream,
|
|
15
|
-
getVideoStream,
|
|
16
|
-
SfuModels,
|
|
17
|
-
watchForDisconnectedAudioOutputDevice,
|
|
18
|
-
} from '@stream-io/video-client';
|
|
19
|
-
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
useAudioInputDeviceFallback,
|
|
23
|
-
useAudioOutputDeviceFallback,
|
|
24
|
-
useAudioPublisher,
|
|
25
|
-
useHasBrowserPermissions,
|
|
26
|
-
useVideoDeviceFallback,
|
|
27
|
-
useVideoPublisher,
|
|
28
|
-
} from '../hooks';
|
|
29
|
-
|
|
30
|
-
type EnabledStateType = 'starting' | 'playing';
|
|
31
|
-
type DisabledStateType = 'uninitialized' | 'stopped';
|
|
32
|
-
type ErrorStateType = 'error';
|
|
33
|
-
type DeviceStateType = EnabledStateType | DisabledStateType | ErrorStateType;
|
|
34
|
-
|
|
35
|
-
type EnabledDeviceState<T extends EnabledStateType> = {
|
|
36
|
-
type: T;
|
|
37
|
-
enabled: true;
|
|
38
|
-
};
|
|
39
|
-
type DisabledDeviceState<T extends DisabledStateType> = {
|
|
40
|
-
type: T;
|
|
41
|
-
enabled: false;
|
|
42
|
-
};
|
|
43
|
-
type ErrorDeviceState = {
|
|
44
|
-
type: 'error';
|
|
45
|
-
message: string;
|
|
46
|
-
enabled: false;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
type DeviceState =
|
|
50
|
-
| EnabledDeviceState<EnabledStateType>
|
|
51
|
-
| DisabledDeviceState<DisabledStateType>
|
|
52
|
-
| ErrorDeviceState;
|
|
53
|
-
|
|
54
|
-
const DEVICE_STATE_TOGGLE: Record<DeviceStateType, 'starting' | 'stopped'> = {
|
|
55
|
-
starting: 'stopped',
|
|
56
|
-
playing: 'stopped',
|
|
57
|
-
stopped: 'starting',
|
|
58
|
-
uninitialized: 'starting',
|
|
59
|
-
error: 'starting',
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Exclude types from documentation site, but we should still add doc comments
|
|
64
|
-
* @internal
|
|
65
|
-
*/
|
|
66
|
-
export const DEVICE_STATE: {
|
|
67
|
-
starting: EnabledDeviceState<'starting'>;
|
|
68
|
-
playing: EnabledDeviceState<'playing'>;
|
|
69
|
-
stopped: DisabledDeviceState<'stopped'>;
|
|
70
|
-
uninitialized: DisabledDeviceState<'uninitialized'>;
|
|
71
|
-
error: ErrorDeviceState;
|
|
72
|
-
} = {
|
|
73
|
-
starting: { type: 'starting', enabled: true },
|
|
74
|
-
playing: { type: 'playing', enabled: true },
|
|
75
|
-
stopped: { type: 'stopped', enabled: false },
|
|
76
|
-
uninitialized: { type: 'uninitialized', enabled: false },
|
|
77
|
-
error: { type: 'error', message: '', enabled: false },
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const DEFAULT_DEVICE_ID = 'default';
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* API to control device enablement, device selection and media stream access for a call.
|
|
84
|
-
* @category Device Management
|
|
85
|
-
*/
|
|
86
|
-
export type MediaDevicesContextAPI = {
|
|
87
|
-
/**
|
|
88
|
-
* Deactivates MediaStream (stops and removes tracks) to be later garbage collected
|
|
89
|
-
*
|
|
90
|
-
* @param stream MediaStream
|
|
91
|
-
* @returns void
|
|
92
|
-
*/
|
|
93
|
-
disposeOfMediaStream: (stream: MediaStream) => void;
|
|
94
|
-
/**
|
|
95
|
-
* Returns an 'audioinput' media stream with the given `deviceId`, if no `deviceId` is provided, it uses the first available device.
|
|
96
|
-
*
|
|
97
|
-
* @param deviceId
|
|
98
|
-
* @returns
|
|
99
|
-
*/
|
|
100
|
-
getAudioStream: typeof getAudioStream;
|
|
101
|
-
/**
|
|
102
|
-
* Returns a 'videoinput' media stream with the given `deviceId`, if no `deviceId` is provided, it uses the first available device.
|
|
103
|
-
*
|
|
104
|
-
* @param deviceId
|
|
105
|
-
* @returns
|
|
106
|
-
*/
|
|
107
|
-
getVideoStream: typeof getVideoStream;
|
|
108
|
-
/**
|
|
109
|
-
* [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
|
|
110
|
-
*/
|
|
111
|
-
isAudioOutputChangeSupported: boolean;
|
|
112
|
-
/**
|
|
113
|
-
* Signals whether audio stream will be published when the call is joined.
|
|
114
|
-
*/
|
|
115
|
-
initialAudioEnabled: boolean;
|
|
116
|
-
/**
|
|
117
|
-
* Signals whether audio stream will be published when the call is joined.
|
|
118
|
-
*/
|
|
119
|
-
initialVideoState: DeviceState;
|
|
120
|
-
/**
|
|
121
|
-
* Publishes audio stream for currently selected audio input (microphone) device to other call participants.
|
|
122
|
-
*/
|
|
123
|
-
publishAudioStream: () => Promise<void>;
|
|
124
|
-
/**
|
|
125
|
-
* Publishes video stream for currently selected video input (camera) device to other call participants.
|
|
126
|
-
*/
|
|
127
|
-
publishVideoStream: () => Promise<void>;
|
|
128
|
-
/**
|
|
129
|
-
* Stops publishing audio stream for currently selected audio input (microphone) device to other call participants.
|
|
130
|
-
*/
|
|
131
|
-
stopPublishingAudio: () => Promise<void>;
|
|
132
|
-
/**
|
|
133
|
-
* Stops publishing video stream for currently selected video input (camera) device to other call participants.
|
|
134
|
-
*/
|
|
135
|
-
stopPublishingVideo: () => Promise<void>;
|
|
136
|
-
/**
|
|
137
|
-
* Sets the initialAudioEnabled flag to a given boolean value.
|
|
138
|
-
* The latest value set will be used to decide, whether audio stream will be published when joining a call.
|
|
139
|
-
* @param enabled
|
|
140
|
-
*/
|
|
141
|
-
setInitialAudioEnabled: (enabled: boolean) => void;
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Sets the initialVideoState to a given DeviceState value.
|
|
145
|
-
* The latest value set will be used to decide, whether video stream will be published when joining a call.
|
|
146
|
-
* @param enabled
|
|
147
|
-
*/
|
|
148
|
-
setInitialVideoState: (state: DeviceState) => void;
|
|
149
|
-
/**
|
|
150
|
-
* Stores audio input device (microphone) id which is used to publish user's sound to other call participants.
|
|
151
|
-
*/
|
|
152
|
-
selectedAudioInputDeviceId?: string;
|
|
153
|
-
/**
|
|
154
|
-
* Stores audio output device (speaker) id used to reproduce incoming audio from other call participants.
|
|
155
|
-
*/
|
|
156
|
-
selectedAudioOutputDeviceId?: string;
|
|
157
|
-
/**
|
|
158
|
-
* Stores video input device (camera) id which is used to publish user's video to other call participants.
|
|
159
|
-
*/
|
|
160
|
-
selectedVideoDeviceId?: string;
|
|
161
|
-
/**
|
|
162
|
-
* Function should be used to change selected device id.
|
|
163
|
-
* The change is later reflected in selectedAudioInputDeviceId, selectedAudioOutputDeviceId or selectedVideoDeviceId depending on kind parameter.
|
|
164
|
-
* @param kind
|
|
165
|
-
* @param deviceId
|
|
166
|
-
*/
|
|
167
|
-
switchDevice: (kind: MediaDeviceKind, deviceId?: string) => void;
|
|
168
|
-
/**
|
|
169
|
-
* Sets the initialAudioEnabled flag by negating the current state value.
|
|
170
|
-
* The latest value set will be used to decide, whether audio stream will be published when joining a call.
|
|
171
|
-
* @param enabled
|
|
172
|
-
*/
|
|
173
|
-
toggleInitialAudioMuteState: () => void;
|
|
174
|
-
/**
|
|
175
|
-
* Sets the initialVideoState by toggling the current state DeviceState value.
|
|
176
|
-
* The latest value set will be used to decide, whether video stream will be published when joining a call.
|
|
177
|
-
* @param enabled
|
|
178
|
-
*/
|
|
179
|
-
toggleInitialVideoMuteState: () => void;
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const MediaDevicesContext = createContext<MediaDevicesContextAPI | null>(null);
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Configuration parameters for MediaDevicesProvider.
|
|
186
|
-
* @category Device Management
|
|
187
|
-
*/
|
|
188
|
-
export type MediaDevicesProviderProps = {
|
|
189
|
-
/**
|
|
190
|
-
* Provides external control over the initial audio input (microphone) enablement. Overrides the default false.
|
|
191
|
-
*/
|
|
192
|
-
initialAudioEnabled?: boolean;
|
|
193
|
-
/**
|
|
194
|
-
* Provides external control over the initial video input (camera) enablement. Overrides the default false.
|
|
195
|
-
*/
|
|
196
|
-
initialVideoEnabled?: boolean;
|
|
197
|
-
/**
|
|
198
|
-
* Allows to override the default audio input (microphone) stream to be published. Overrides the default string 'default'.
|
|
199
|
-
*/
|
|
200
|
-
initialAudioInputDeviceId?: string;
|
|
201
|
-
/**
|
|
202
|
-
* Allows to override the default audio output (speaker) device to reproduce incoming audio from the SFU. Overrides the default string 'default'.
|
|
203
|
-
*/
|
|
204
|
-
initialAudioOutputDeviceId?: string;
|
|
205
|
-
/**
|
|
206
|
-
* Allows to override the default video input (camera) stream to be published. Overrides the default string 'default'.
|
|
207
|
-
*/
|
|
208
|
-
initialVideoInputDeviceId?: string;
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Context provider that internally puts in place mechanisms that:
|
|
213
|
-
* 1. fall back to selecting a default device when trying to switch to a non-existent device
|
|
214
|
-
* 2. fall back to a default device when an active device is disconnected
|
|
215
|
-
* 3. stop publishing a media stream when a non-default device is disconnected
|
|
216
|
-
* 4. republish a media stream from the newly connected default device
|
|
217
|
-
* 5. republish a media stream when a new device is selected
|
|
218
|
-
*
|
|
219
|
-
* Provides `MediaDevicesContextAPI` that allow the integrators to handle:
|
|
220
|
-
* 1. the initial device state enablement (for example apt for lobby scenario)
|
|
221
|
-
* 2. media stream retrieval and disposal
|
|
222
|
-
* 3. media stream publishing
|
|
223
|
-
* 4. specific device selection
|
|
224
|
-
* @param params
|
|
225
|
-
* @returns
|
|
226
|
-
*
|
|
227
|
-
* @category Device Management
|
|
228
|
-
*/
|
|
229
|
-
export const MediaDevicesProvider = ({
|
|
230
|
-
children,
|
|
231
|
-
initialAudioEnabled,
|
|
232
|
-
initialVideoEnabled,
|
|
233
|
-
initialVideoInputDeviceId = DEFAULT_DEVICE_ID,
|
|
234
|
-
initialAudioOutputDeviceId = DEFAULT_DEVICE_ID,
|
|
235
|
-
initialAudioInputDeviceId = DEFAULT_DEVICE_ID,
|
|
236
|
-
}: PropsWithChildren<MediaDevicesProviderProps>) => {
|
|
237
|
-
const call = useCall();
|
|
238
|
-
const { useCallCallingState, useCallState, useCallSettings } =
|
|
239
|
-
useCallStateHooks();
|
|
240
|
-
const callingState = useCallCallingState();
|
|
241
|
-
const callState = useCallState();
|
|
242
|
-
const { localParticipant$ } = callState;
|
|
243
|
-
const hasBrowserPermissionVideoInput = useHasBrowserPermissions(
|
|
244
|
-
'camera' as PermissionName,
|
|
245
|
-
);
|
|
246
|
-
const hasBrowserPermissionAudioInput = useHasBrowserPermissions(
|
|
247
|
-
'microphone' as PermissionName,
|
|
248
|
-
);
|
|
249
|
-
const [selectedAudioInputDeviceId, selectAudioInputDeviceId] = useState<
|
|
250
|
-
MediaDevicesContextAPI['selectedAudioInputDeviceId']
|
|
251
|
-
>(initialAudioInputDeviceId);
|
|
252
|
-
const [selectedAudioOutputDeviceId, selectAudioOutputDeviceId] = useState<
|
|
253
|
-
MediaDevicesContextAPI['selectedAudioOutputDeviceId']
|
|
254
|
-
>(initialAudioOutputDeviceId);
|
|
255
|
-
const [selectedVideoDeviceId, selectVideoDeviceId] = useState<
|
|
256
|
-
MediaDevicesContextAPI['selectedVideoDeviceId']
|
|
257
|
-
>(initialVideoInputDeviceId);
|
|
258
|
-
|
|
259
|
-
const [isAudioOutputChangeSupported] = useState<boolean>(() =>
|
|
260
|
-
checkIfAudioOutputChangeSupported(),
|
|
261
|
-
);
|
|
262
|
-
const [initAudioEnabled, setInitialAudioEnabled] = useState<boolean>(
|
|
263
|
-
!!initialAudioEnabled,
|
|
264
|
-
);
|
|
265
|
-
const [initialVideoState, setInitialVideoState] = useState<DeviceState>(() =>
|
|
266
|
-
initialVideoEnabled ? DEVICE_STATE.starting : DEVICE_STATE.uninitialized,
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
const settings = useCallSettings();
|
|
270
|
-
useEffect(() => {
|
|
271
|
-
if (!settings) return;
|
|
272
|
-
const { audio, video } = settings;
|
|
273
|
-
if (typeof initialAudioEnabled === 'undefined' && audio.mic_default_on) {
|
|
274
|
-
setInitialAudioEnabled(audio.mic_default_on);
|
|
275
|
-
}
|
|
276
|
-
if (typeof initialVideoEnabled === 'undefined' && video.camera_default_on) {
|
|
277
|
-
setInitialVideoState(DEVICE_STATE.starting);
|
|
278
|
-
}
|
|
279
|
-
}, [initialAudioEnabled, initialVideoEnabled, settings]);
|
|
280
|
-
|
|
281
|
-
const publishVideoStream = useVideoPublisher({
|
|
282
|
-
initialVideoMuted: !initialVideoState.enabled,
|
|
283
|
-
videoDeviceId: selectedVideoDeviceId,
|
|
284
|
-
});
|
|
285
|
-
const publishAudioStream = useAudioPublisher({
|
|
286
|
-
initialAudioMuted: !initAudioEnabled,
|
|
287
|
-
audioDeviceId: selectedAudioInputDeviceId,
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
const stopPublishingAudio = useCallback(async () => {
|
|
291
|
-
if (
|
|
292
|
-
callingState === CallingState.IDLE ||
|
|
293
|
-
callingState === CallingState.RINGING
|
|
294
|
-
) {
|
|
295
|
-
setInitialAudioEnabled(false);
|
|
296
|
-
} else {
|
|
297
|
-
call?.stopPublish(SfuModels.TrackType.AUDIO);
|
|
298
|
-
}
|
|
299
|
-
}, [call, callingState]);
|
|
300
|
-
|
|
301
|
-
const stopPublishingVideo = useCallback(async () => {
|
|
302
|
-
if (
|
|
303
|
-
callingState === CallingState.IDLE ||
|
|
304
|
-
callingState === CallingState.RINGING
|
|
305
|
-
) {
|
|
306
|
-
setInitialVideoState(DEVICE_STATE.stopped);
|
|
307
|
-
} else {
|
|
308
|
-
call?.stopPublish(SfuModels.TrackType.VIDEO);
|
|
309
|
-
}
|
|
310
|
-
}, [call, callingState]);
|
|
311
|
-
|
|
312
|
-
const toggleInitialAudioMuteState = useCallback(
|
|
313
|
-
() => setInitialAudioEnabled((prev) => !prev),
|
|
314
|
-
[],
|
|
315
|
-
);
|
|
316
|
-
const toggleInitialVideoMuteState = useCallback(
|
|
317
|
-
() =>
|
|
318
|
-
setInitialVideoState((prev) => {
|
|
319
|
-
const newType = DEVICE_STATE_TOGGLE[prev.type];
|
|
320
|
-
return DEVICE_STATE[newType];
|
|
321
|
-
}),
|
|
322
|
-
[],
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
const switchDevice = useCallback(
|
|
326
|
-
(kind: MediaDeviceKind, deviceId?: string) => {
|
|
327
|
-
if (kind === 'videoinput') {
|
|
328
|
-
selectVideoDeviceId(deviceId);
|
|
329
|
-
}
|
|
330
|
-
if (kind === 'audioinput') {
|
|
331
|
-
selectAudioInputDeviceId(deviceId);
|
|
332
|
-
}
|
|
333
|
-
if (kind === 'audiooutput') {
|
|
334
|
-
selectAudioOutputDeviceId(deviceId);
|
|
335
|
-
}
|
|
336
|
-
},
|
|
337
|
-
[],
|
|
338
|
-
);
|
|
339
|
-
|
|
340
|
-
useAudioInputDeviceFallback(
|
|
341
|
-
() => switchDevice('audioinput', DEFAULT_DEVICE_ID),
|
|
342
|
-
hasBrowserPermissionAudioInput,
|
|
343
|
-
selectedAudioInputDeviceId,
|
|
344
|
-
);
|
|
345
|
-
useAudioOutputDeviceFallback(
|
|
346
|
-
() => switchDevice('audiooutput', DEFAULT_DEVICE_ID),
|
|
347
|
-
// audiooutput devices can be enumerated only with microphone permissions
|
|
348
|
-
hasBrowserPermissionAudioInput,
|
|
349
|
-
selectedAudioOutputDeviceId,
|
|
350
|
-
);
|
|
351
|
-
useVideoDeviceFallback(
|
|
352
|
-
() => switchDevice('videoinput', DEFAULT_DEVICE_ID),
|
|
353
|
-
hasBrowserPermissionVideoInput,
|
|
354
|
-
selectedVideoDeviceId,
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
useEffect(() => {
|
|
358
|
-
if (!call || callingState !== CallingState.JOINED) return;
|
|
359
|
-
call.setAudioOutputDevice(selectedAudioOutputDeviceId);
|
|
360
|
-
}, [call, callingState, selectedAudioOutputDeviceId]);
|
|
361
|
-
|
|
362
|
-
useEffect(() => {
|
|
363
|
-
// audiooutput devices can be enumerated only with microphone permissions
|
|
364
|
-
if (!localParticipant$ || !hasBrowserPermissionAudioInput) return;
|
|
365
|
-
|
|
366
|
-
const subscription = watchForDisconnectedAudioOutputDevice(
|
|
367
|
-
localParticipant$.pipe(map((p) => p?.audioOutputDeviceId)),
|
|
368
|
-
).subscribe(async () => {
|
|
369
|
-
selectAudioOutputDeviceId(DEFAULT_DEVICE_ID);
|
|
370
|
-
});
|
|
371
|
-
return () => {
|
|
372
|
-
subscription.unsubscribe();
|
|
373
|
-
};
|
|
374
|
-
}, [hasBrowserPermissionAudioInput, localParticipant$]);
|
|
375
|
-
|
|
376
|
-
const contextValue: MediaDevicesContextAPI = {
|
|
377
|
-
disposeOfMediaStream,
|
|
378
|
-
getAudioStream,
|
|
379
|
-
getVideoStream,
|
|
380
|
-
isAudioOutputChangeSupported,
|
|
381
|
-
selectedAudioInputDeviceId,
|
|
382
|
-
selectedAudioOutputDeviceId,
|
|
383
|
-
selectedVideoDeviceId,
|
|
384
|
-
switchDevice,
|
|
385
|
-
initialAudioEnabled: initAudioEnabled,
|
|
386
|
-
initialVideoState,
|
|
387
|
-
setInitialAudioEnabled,
|
|
388
|
-
setInitialVideoState,
|
|
389
|
-
toggleInitialAudioMuteState,
|
|
390
|
-
toggleInitialVideoMuteState,
|
|
391
|
-
publishAudioStream,
|
|
392
|
-
publishVideoStream,
|
|
393
|
-
stopPublishingAudio,
|
|
394
|
-
stopPublishingVideo,
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
return (
|
|
398
|
-
<MediaDevicesContext.Provider value={contextValue}>
|
|
399
|
-
{children}
|
|
400
|
-
</MediaDevicesContext.Provider>
|
|
401
|
-
);
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Context consumer retrieving MediaDevicesContextAPI.
|
|
406
|
-
* @returns
|
|
407
|
-
*
|
|
408
|
-
* @category Device Management
|
|
409
|
-
*/
|
|
410
|
-
export const useMediaDevices = () => {
|
|
411
|
-
const value = useContext(MediaDevicesContext);
|
|
412
|
-
if (!value) {
|
|
413
|
-
console.warn(`Null MediaDevicesContext`);
|
|
414
|
-
}
|
|
415
|
-
return value as MediaDevicesContextAPI;
|
|
416
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './MediaDevicesContext';
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
-
import { map } from 'rxjs';
|
|
3
|
-
import {
|
|
4
|
-
CallingState,
|
|
5
|
-
getAudioStream,
|
|
6
|
-
OwnCapability,
|
|
7
|
-
SfuModels,
|
|
8
|
-
watchForAddedDefaultAudioDevice,
|
|
9
|
-
watchForDisconnectedAudioDevice,
|
|
10
|
-
} from '@stream-io/video-client';
|
|
11
|
-
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
12
|
-
import { useHasBrowserPermissions } from './useDevices';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @internal
|
|
16
|
-
*/
|
|
17
|
-
export type AudioPublisherInit = {
|
|
18
|
-
initialAudioMuted?: boolean;
|
|
19
|
-
audioDeviceId?: string;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @internal
|
|
24
|
-
* @category Device Management
|
|
25
|
-
*/
|
|
26
|
-
export const useAudioPublisher = ({
|
|
27
|
-
initialAudioMuted,
|
|
28
|
-
audioDeviceId,
|
|
29
|
-
}: AudioPublisherInit) => {
|
|
30
|
-
const call = useCall();
|
|
31
|
-
const { useCallState, useCallCallingState, useLocalParticipant } =
|
|
32
|
-
useCallStateHooks();
|
|
33
|
-
const callState = useCallState();
|
|
34
|
-
const callingState = useCallCallingState();
|
|
35
|
-
const participant = useLocalParticipant();
|
|
36
|
-
const hasBrowserPermissionAudioInput = useHasBrowserPermissions(
|
|
37
|
-
'microphone' as PermissionName,
|
|
38
|
-
);
|
|
39
|
-
const { localParticipant$ } = callState;
|
|
40
|
-
|
|
41
|
-
const isPublishingAudio = participant?.publishedTracks.includes(
|
|
42
|
-
SfuModels.TrackType.AUDIO,
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
const publishAudioStream = useCallback(async () => {
|
|
46
|
-
if (!call) return;
|
|
47
|
-
if (!call.permissionsContext.hasPermission(OwnCapability.SEND_AUDIO)) {
|
|
48
|
-
throw new Error(`No permission to publish audio`);
|
|
49
|
-
}
|
|
50
|
-
try {
|
|
51
|
-
const audioStream = await getAudioStream({
|
|
52
|
-
deviceId: audioDeviceId,
|
|
53
|
-
});
|
|
54
|
-
await call.publishAudioStream(audioStream);
|
|
55
|
-
} catch (e) {
|
|
56
|
-
console.log('Failed to publish audio stream', e);
|
|
57
|
-
}
|
|
58
|
-
}, [audioDeviceId, call]);
|
|
59
|
-
|
|
60
|
-
const lastAudioDeviceId = useRef(audioDeviceId);
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
if (
|
|
63
|
-
callingState === CallingState.JOINED &&
|
|
64
|
-
audioDeviceId !== lastAudioDeviceId.current
|
|
65
|
-
) {
|
|
66
|
-
lastAudioDeviceId.current = audioDeviceId;
|
|
67
|
-
publishAudioStream().catch((e) => {
|
|
68
|
-
console.error('Failed to publish audio stream', e);
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}, [audioDeviceId, callingState, publishAudioStream]);
|
|
72
|
-
|
|
73
|
-
const initialPublishRun = useRef(false);
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
if (
|
|
76
|
-
callingState === CallingState.JOINED &&
|
|
77
|
-
!initialPublishRun.current &&
|
|
78
|
-
!initialAudioMuted
|
|
79
|
-
) {
|
|
80
|
-
// automatic publishing should happen only when joining the call
|
|
81
|
-
// from the lobby, and the audio is not muted
|
|
82
|
-
publishAudioStream().catch((e) => {
|
|
83
|
-
console.error('Failed to publish audio stream', e);
|
|
84
|
-
});
|
|
85
|
-
initialPublishRun.current = true;
|
|
86
|
-
}
|
|
87
|
-
}, [callingState, initialAudioMuted, publishAudioStream]);
|
|
88
|
-
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
if (!localParticipant$ || !hasBrowserPermissionAudioInput) return;
|
|
91
|
-
const subscription = watchForDisconnectedAudioDevice(
|
|
92
|
-
localParticipant$.pipe(map((p) => p?.audioDeviceId)),
|
|
93
|
-
).subscribe(async () => {
|
|
94
|
-
if (!call) return;
|
|
95
|
-
call.setAudioDevice(undefined);
|
|
96
|
-
await call.stopPublish(SfuModels.TrackType.AUDIO);
|
|
97
|
-
});
|
|
98
|
-
return () => {
|
|
99
|
-
subscription.unsubscribe();
|
|
100
|
-
};
|
|
101
|
-
}, [hasBrowserPermissionAudioInput, localParticipant$, call]);
|
|
102
|
-
|
|
103
|
-
useEffect(() => {
|
|
104
|
-
if (!participant?.audioStream || !call || !isPublishingAudio) return;
|
|
105
|
-
|
|
106
|
-
const [track] = participant.audioStream.getAudioTracks();
|
|
107
|
-
const selectedAudioDeviceId = track.getSettings().deviceId;
|
|
108
|
-
|
|
109
|
-
const republishDefaultDevice = watchForAddedDefaultAudioDevice().subscribe(
|
|
110
|
-
async () => {
|
|
111
|
-
if (
|
|
112
|
-
!(
|
|
113
|
-
call &&
|
|
114
|
-
participant.audioStream &&
|
|
115
|
-
selectedAudioDeviceId === 'default'
|
|
116
|
-
)
|
|
117
|
-
)
|
|
118
|
-
return;
|
|
119
|
-
// We need to stop the original track first in order
|
|
120
|
-
// we can retrieve the new default device stream
|
|
121
|
-
track.stop();
|
|
122
|
-
const audioStream = await getAudioStream({
|
|
123
|
-
deviceId: 'default',
|
|
124
|
-
});
|
|
125
|
-
await call.publishAudioStream(audioStream);
|
|
126
|
-
},
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
const handleTrackEnded = async () => {
|
|
130
|
-
if (selectedAudioDeviceId === audioDeviceId) {
|
|
131
|
-
const audioStream = await getAudioStream({
|
|
132
|
-
deviceId: audioDeviceId,
|
|
133
|
-
});
|
|
134
|
-
await call.publishAudioStream(audioStream);
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
track.addEventListener('ended', handleTrackEnded);
|
|
139
|
-
return () => {
|
|
140
|
-
track.removeEventListener('ended', handleTrackEnded);
|
|
141
|
-
republishDefaultDevice.unsubscribe();
|
|
142
|
-
};
|
|
143
|
-
}, [audioDeviceId, call, participant?.audioStream, isPublishingAudio]);
|
|
144
|
-
|
|
145
|
-
return publishAudioStream;
|
|
146
|
-
};
|