@stream-io/video-react-sdk 1.34.1 → 1.35.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 +24 -0
- package/dist/css/embedded.css +54 -0
- package/dist/css/embedded.css.map +1 -1
- package/dist/css/styles.css +54 -0
- package/dist/css/styles.css.map +1 -1
- package/dist/embedded.cjs.js +91 -6
- package/dist/embedded.cjs.js.map +1 -1
- package/dist/embedded.es.js +91 -6
- package/dist/embedded.es.js.map +1 -1
- package/dist/index.cjs.js +92 -7
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +92 -7
- package/dist/index.es.js.map +1 -1
- package/dist/src/components/DeviceSettings/DeviceAudioPreviewItem.d.ts +6 -0
- package/dist/src/components/DeviceSettings/DeviceSelector.d.ts +11 -2
- package/dist/src/components/DeviceSettings/DeviceSelectorAudio.d.ts +1 -1
- package/dist/src/components/DeviceSettings/DeviceSelectorVideo.d.ts +1 -1
- package/dist/src/components/DeviceSettings/DeviceVideoPreviewItem.d.ts +6 -0
- package/package.json +5 -5
- package/src/components/DeviceSettings/DeviceAudioPreviewItem.tsx +83 -0
- package/src/components/DeviceSettings/DeviceSelector.tsx +70 -5
- package/src/components/DeviceSettings/DeviceSelectorAudio.tsx +3 -1
- package/src/components/DeviceSettings/DeviceSelectorVideo.tsx +5 -2
- package/src/components/DeviceSettings/DeviceVideoPreviewItem.tsx +73 -0
- package/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx +3 -4
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DeviceListItem } from '../../hooks';
|
|
2
|
+
export type DeviceAudioPreviewItemProps = {
|
|
3
|
+
device: DeviceListItem;
|
|
4
|
+
onSelect: (deviceId: string) => void;
|
|
5
|
+
};
|
|
6
|
+
export declare const DeviceAudioPreviewItem: ({ device, onSelect, }: DeviceAudioPreviewItemProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { PropsWithChildren } from 'react';
|
|
1
|
+
import { ComponentType, PropsWithChildren } from 'react';
|
|
2
|
+
import { DeviceListItem } from '../../hooks';
|
|
2
3
|
export type DeviceSelectorType = 'audioinput' | 'audiooutput' | 'videoinput';
|
|
4
|
+
export type PreviewItemProps = {
|
|
5
|
+
device: DeviceListItem;
|
|
6
|
+
onSelect: (deviceId: string) => void;
|
|
7
|
+
};
|
|
3
8
|
export declare const DeviceSelector: (props: PropsWithChildren<{
|
|
4
9
|
devices: MediaDeviceInfo[];
|
|
5
10
|
icon: string;
|
|
@@ -7,5 +12,9 @@ export declare const DeviceSelector: (props: PropsWithChildren<{
|
|
|
7
12
|
selectedDeviceId?: string;
|
|
8
13
|
title?: string;
|
|
9
14
|
onChange?: (deviceId: string) => void;
|
|
15
|
+
}> & ({
|
|
10
16
|
visualType?: "list" | "dropdown";
|
|
11
|
-
}
|
|
17
|
+
} | {
|
|
18
|
+
visualType: "preview";
|
|
19
|
+
PreviewItem: ComponentType<PreviewItemProps>;
|
|
20
|
+
})) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type DeviceSelectorAudioInputProps = {
|
|
2
2
|
title?: string;
|
|
3
|
-
visualType?: 'list' | 'dropdown';
|
|
3
|
+
visualType?: 'list' | 'dropdown' | 'preview';
|
|
4
4
|
volumeIndicatorVisible?: boolean;
|
|
5
5
|
};
|
|
6
6
|
export declare const DeviceSelectorAudioInput: ({ title, visualType, volumeIndicatorVisible, }: DeviceSelectorAudioInputProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type DeviceSelectorVideoProps = {
|
|
2
2
|
title?: string;
|
|
3
|
-
visualType?: 'list' | 'dropdown';
|
|
3
|
+
visualType?: 'list' | 'dropdown' | 'preview';
|
|
4
4
|
};
|
|
5
5
|
export declare const DeviceSelectorVideo: ({ title, visualType, }: DeviceSelectorVideoProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DeviceListItem } from '../../hooks';
|
|
2
|
+
export type DeviceVideoPreviewItemProps = {
|
|
3
|
+
device: DeviceListItem;
|
|
4
|
+
onSelect: (deviceId: string) => void;
|
|
5
|
+
};
|
|
6
|
+
export declare const DeviceVideoPreviewItem: ({ device, onSelect, }: DeviceVideoPreviewItemProps) => import("react/jsx-runtime").JSX.Element | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-react-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.0",
|
|
4
4
|
"main": "./dist/index.cjs.js",
|
|
5
5
|
"module": "./dist/index.es.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@floating-ui/react": "^0.27.6",
|
|
48
|
-
"@stream-io/video-client": "1.
|
|
49
|
-
"@stream-io/video-filters-web": "0.7.
|
|
50
|
-
"@stream-io/video-react-bindings": "1.
|
|
48
|
+
"@stream-io/video-client": "1.46.0",
|
|
49
|
+
"@stream-io/video-filters-web": "0.7.4",
|
|
50
|
+
"@stream-io/video-react-bindings": "1.14.0",
|
|
51
51
|
"chart.js": "^4.4.4",
|
|
52
52
|
"clsx": "^2.0.0",
|
|
53
53
|
"react-chartjs-2": "^5.3.0"
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@rollup/plugin-replace": "^6.0.2",
|
|
62
62
|
"@rollup/plugin-typescript": "^12.1.4",
|
|
63
63
|
"@stream-io/audio-filters-web": "^0.7.3",
|
|
64
|
-
"@stream-io/video-styling": "^1.
|
|
64
|
+
"@stream-io/video-styling": "^1.13.0",
|
|
65
65
|
"@types/react": "~19.1.17",
|
|
66
66
|
"@types/react-dom": "~19.1.11",
|
|
67
67
|
"react": "19.1.0",
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { createSoundDetector } from '@stream-io/video-client';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { DeviceListItem } from '../../hooks';
|
|
5
|
+
|
|
6
|
+
const LEVEL_BARS = 5;
|
|
7
|
+
|
|
8
|
+
const DeviceLevelIndicator = ({ deviceId }: { deviceId: string }) => {
|
|
9
|
+
const [audioLevel, setAudioLevel] = useState(0);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
let cancelled = false;
|
|
13
|
+
let dispose: (() => Promise<void>) | undefined;
|
|
14
|
+
|
|
15
|
+
navigator.mediaDevices
|
|
16
|
+
.getUserMedia({
|
|
17
|
+
audio: { deviceId: { exact: deviceId } },
|
|
18
|
+
video: false,
|
|
19
|
+
})
|
|
20
|
+
.then((mediaStream) => {
|
|
21
|
+
if (cancelled) {
|
|
22
|
+
mediaStream.getTracks().forEach((t) => t.stop());
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
dispose = createSoundDetector(
|
|
26
|
+
mediaStream,
|
|
27
|
+
({ audioLevel: al }) => setAudioLevel(al),
|
|
28
|
+
{ detectionFrequencyInMs: 80 },
|
|
29
|
+
);
|
|
30
|
+
})
|
|
31
|
+
.catch(console.error);
|
|
32
|
+
|
|
33
|
+
return () => {
|
|
34
|
+
cancelled = true;
|
|
35
|
+
dispose?.().catch(console.error);
|
|
36
|
+
};
|
|
37
|
+
}, [deviceId]);
|
|
38
|
+
|
|
39
|
+
const activeBars = Math.round((audioLevel / 100) * LEVEL_BARS);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="str-video__device-level-indicator" aria-label="Audio level">
|
|
43
|
+
{Array.from({ length: LEVEL_BARS }, (_, i) => (
|
|
44
|
+
<div
|
|
45
|
+
key={i}
|
|
46
|
+
className={clsx('str-video__device-level-indicator__bar', {
|
|
47
|
+
'str-video__device-level-indicator__bar--active': i < activeBars,
|
|
48
|
+
})}
|
|
49
|
+
/>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type DeviceAudioPreviewItemProps = {
|
|
56
|
+
device: DeviceListItem;
|
|
57
|
+
onSelect: (deviceId: string) => void;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const DeviceAudioPreviewItem = ({
|
|
61
|
+
device,
|
|
62
|
+
onSelect,
|
|
63
|
+
}: DeviceAudioPreviewItemProps) => {
|
|
64
|
+
if (device.deviceId === 'default') return null;
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<label
|
|
68
|
+
className={`str-video__device-settings__option${device.isSelected ? ' str-video__device-settings__option--selected' : ''}`}
|
|
69
|
+
htmlFor={`audioinput--${device.deviceId}`}
|
|
70
|
+
>
|
|
71
|
+
<input
|
|
72
|
+
type="radio"
|
|
73
|
+
name="audioinput"
|
|
74
|
+
value={device.deviceId}
|
|
75
|
+
id={`audioinput--${device.deviceId}`}
|
|
76
|
+
checked={device.isSelected}
|
|
77
|
+
onChange={(e) => onSelect(e.target.value)}
|
|
78
|
+
/>
|
|
79
|
+
{device.label}
|
|
80
|
+
<DeviceLevelIndicator deviceId={device.deviceId} />
|
|
81
|
+
</label>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ChangeEventHandler,
|
|
4
|
+
ComponentType,
|
|
5
|
+
PropsWithChildren,
|
|
6
|
+
useCallback,
|
|
7
|
+
} from 'react';
|
|
3
8
|
|
|
4
|
-
import { useDeviceList } from '../../hooks';
|
|
9
|
+
import { DeviceListItem, useDeviceList } from '../../hooks';
|
|
5
10
|
import { DropDownSelect, DropDownSelectOption } from '../DropdownSelect';
|
|
6
11
|
import { useMenuContext } from '../Menu';
|
|
7
12
|
|
|
@@ -102,6 +107,59 @@ const DeviceSelectorList = (
|
|
|
102
107
|
);
|
|
103
108
|
};
|
|
104
109
|
|
|
110
|
+
export type PreviewItemProps = {
|
|
111
|
+
device: DeviceListItem;
|
|
112
|
+
onSelect: (deviceId: string) => void;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const DeviceSelectorPreview = (
|
|
116
|
+
props: PropsWithChildren<{
|
|
117
|
+
devices: MediaDeviceInfo[];
|
|
118
|
+
selectedDeviceId?: string;
|
|
119
|
+
title?: string;
|
|
120
|
+
onChange?: (deviceId: string) => void;
|
|
121
|
+
PreviewItem: ComponentType<PreviewItemProps>;
|
|
122
|
+
}>,
|
|
123
|
+
) => {
|
|
124
|
+
const {
|
|
125
|
+
devices = [],
|
|
126
|
+
selectedDeviceId,
|
|
127
|
+
title,
|
|
128
|
+
onChange,
|
|
129
|
+
children,
|
|
130
|
+
PreviewItem,
|
|
131
|
+
} = props;
|
|
132
|
+
const { close } = useMenuContext();
|
|
133
|
+
const { deviceList } = useDeviceList(devices, selectedDeviceId);
|
|
134
|
+
|
|
135
|
+
const onSelect = useCallback(
|
|
136
|
+
(deviceId: string) => {
|
|
137
|
+
if (deviceId === 'default') return;
|
|
138
|
+
onChange?.(deviceId);
|
|
139
|
+
close?.();
|
|
140
|
+
},
|
|
141
|
+
[onChange, close],
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<div className="str-video__device-settings__device-kind">
|
|
146
|
+
{title && (
|
|
147
|
+
<div className="str-video__device-settings__device-selector-title">
|
|
148
|
+
{title}
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
{deviceList.map((device) => (
|
|
152
|
+
<PreviewItem
|
|
153
|
+
key={device.deviceId}
|
|
154
|
+
device={device}
|
|
155
|
+
onSelect={onSelect}
|
|
156
|
+
/>
|
|
157
|
+
))}
|
|
158
|
+
{children}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
105
163
|
const DeviceSelectorDropdown = (props: {
|
|
106
164
|
devices: MediaDeviceInfo[];
|
|
107
165
|
selectedDeviceId?: string;
|
|
@@ -157,11 +215,18 @@ export const DeviceSelector = (
|
|
|
157
215
|
selectedDeviceId?: string;
|
|
158
216
|
title?: string;
|
|
159
217
|
onChange?: (deviceId: string) => void;
|
|
160
|
-
|
|
161
|
-
|
|
218
|
+
}> &
|
|
219
|
+
(
|
|
220
|
+
| { visualType?: 'list' | 'dropdown' }
|
|
221
|
+
| { visualType: 'preview'; PreviewItem: ComponentType<PreviewItemProps> }
|
|
222
|
+
),
|
|
162
223
|
) => {
|
|
163
|
-
|
|
224
|
+
if (props.visualType === 'preview') {
|
|
225
|
+
const { PreviewItem, ...rest } = props;
|
|
226
|
+
return <DeviceSelectorPreview {...rest} PreviewItem={PreviewItem} />;
|
|
227
|
+
}
|
|
164
228
|
|
|
229
|
+
const { visualType = 'list', icon, ...rest } = props;
|
|
165
230
|
if (visualType === 'list') {
|
|
166
231
|
return <DeviceSelectorList {...rest} />;
|
|
167
232
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
2
|
+
import { DeviceAudioPreviewItem } from './DeviceAudioPreviewItem';
|
|
2
3
|
import { DeviceSelector } from './DeviceSelector';
|
|
3
4
|
import { AudioVolumeIndicator } from './AudioVolumeIndicator';
|
|
4
5
|
import { SpeakerTest } from './SpeakerTest';
|
|
5
6
|
|
|
6
7
|
export type DeviceSelectorAudioInputProps = {
|
|
7
8
|
title?: string;
|
|
8
|
-
visualType?: 'list' | 'dropdown';
|
|
9
|
+
visualType?: 'list' | 'dropdown' | 'preview';
|
|
9
10
|
volumeIndicatorVisible?: boolean;
|
|
10
11
|
};
|
|
11
12
|
|
|
@@ -28,6 +29,7 @@ export const DeviceSelectorAudioInput = ({
|
|
|
28
29
|
title={title}
|
|
29
30
|
visualType={visualType}
|
|
30
31
|
icon="mic"
|
|
32
|
+
PreviewItem={DeviceAudioPreviewItem}
|
|
31
33
|
>
|
|
32
34
|
{volumeIndicatorVisible && (
|
|
33
35
|
<>
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { DeviceSelector } from './DeviceSelector';
|
|
2
1
|
import { useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
3
2
|
|
|
3
|
+
import { DeviceSelector } from './DeviceSelector';
|
|
4
|
+
import { DeviceVideoPreviewItem } from './DeviceVideoPreviewItem';
|
|
5
|
+
|
|
4
6
|
export type DeviceSelectorVideoProps = {
|
|
5
7
|
title?: string;
|
|
6
|
-
visualType?: 'list' | 'dropdown';
|
|
8
|
+
visualType?: 'list' | 'dropdown' | 'preview';
|
|
7
9
|
};
|
|
8
10
|
|
|
9
11
|
export const DeviceSelectorVideo = ({
|
|
@@ -24,6 +26,7 @@ export const DeviceSelectorVideo = ({
|
|
|
24
26
|
title={title}
|
|
25
27
|
visualType={visualType}
|
|
26
28
|
icon="camera"
|
|
29
|
+
PreviewItem={DeviceVideoPreviewItem}
|
|
27
30
|
/>
|
|
28
31
|
);
|
|
29
32
|
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { DeviceListItem } from '../../hooks';
|
|
4
|
+
|
|
5
|
+
const DeviceVideoPreview = ({ deviceId }: { deviceId: string }) => {
|
|
6
|
+
const videoRef = useRef<HTMLVideoElement>(null);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
let cancelled = false;
|
|
10
|
+
let stream: MediaStream | undefined;
|
|
11
|
+
|
|
12
|
+
navigator.mediaDevices
|
|
13
|
+
.getUserMedia({
|
|
14
|
+
video: { deviceId: { exact: deviceId } },
|
|
15
|
+
audio: false,
|
|
16
|
+
})
|
|
17
|
+
.then((mediaStream) => {
|
|
18
|
+
if (cancelled) {
|
|
19
|
+
mediaStream.getTracks().forEach((t) => t.stop());
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
stream = mediaStream;
|
|
24
|
+
if (videoRef.current) {
|
|
25
|
+
videoRef.current.srcObject = mediaStream;
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
.catch(console.error);
|
|
29
|
+
|
|
30
|
+
return () => {
|
|
31
|
+
cancelled = true;
|
|
32
|
+
stream?.getTracks().forEach((t) => t.stop());
|
|
33
|
+
};
|
|
34
|
+
}, [deviceId]);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="str-video__device-video-preview">
|
|
38
|
+
<video
|
|
39
|
+
ref={videoRef}
|
|
40
|
+
autoPlay
|
|
41
|
+
playsInline
|
|
42
|
+
muted
|
|
43
|
+
className="str-video__device-video-preview__video"
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type DeviceVideoPreviewItemProps = {
|
|
50
|
+
device: DeviceListItem;
|
|
51
|
+
onSelect: (deviceId: string) => void;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const DeviceVideoPreviewItem = ({
|
|
55
|
+
device,
|
|
56
|
+
onSelect,
|
|
57
|
+
}: DeviceVideoPreviewItemProps) => {
|
|
58
|
+
if (device.deviceId === 'default') return null;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<button
|
|
62
|
+
type="button"
|
|
63
|
+
className={clsx('str-video__device-preview', {
|
|
64
|
+
'str-video__device-preview--selected': device.isSelected,
|
|
65
|
+
})}
|
|
66
|
+
onClick={() => onSelect(device.deviceId)}
|
|
67
|
+
aria-pressed={device.isSelected}
|
|
68
|
+
>
|
|
69
|
+
<DeviceVideoPreview deviceId={device.deviceId} />
|
|
70
|
+
<span className="str-video__device-preview__label">{device.label}</span>
|
|
71
|
+
</button>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
@@ -165,11 +165,10 @@ export const ParticipantDetails = ({
|
|
|
165
165
|
className="str-video__participant-details__name--track-paused"
|
|
166
166
|
/>
|
|
167
167
|
)}
|
|
168
|
-
{indicatorsVisible &&
|
|
169
|
-
// TODO: remove this monstrosity once we have a proper design
|
|
168
|
+
{indicatorsVisible && pin && (
|
|
170
169
|
<span
|
|
171
|
-
title={t('Unpin')}
|
|
172
|
-
onClick={() => call?.unpin(sessionId)}
|
|
170
|
+
title={canUnpin ? t('Unpin') : t('Pinned')}
|
|
171
|
+
onClick={canUnpin ? () => call?.unpin(sessionId) : undefined}
|
|
173
172
|
className="str-video__participant-details__name--pinned"
|
|
174
173
|
/>
|
|
175
174
|
)}
|