@stream-io/video-react-sdk 1.26.0 → 1.27.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 +34 -0
- package/assets/piano.mp3 +0 -0
- package/dist/css/styles.css +19 -4
- package/dist/css/styles.css.map +1 -1
- package/dist/index.cjs.js +313 -66
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +313 -68
- package/dist/index.es.js.map +1 -1
- package/dist/src/components/BackgroundFilters/BackgroundFilters.d.ts +66 -4
- package/dist/src/components/DeviceSettings/DeviceSelectorAudio.d.ts +3 -1
- package/dist/src/components/DeviceSettings/SpeakerTest.d.ts +7 -0
- package/dist/src/components/DeviceSettings/index.d.ts +2 -0
- package/dist/src/components/Notification/Notification.d.ts +1 -0
- package/dist/src/core/components/CallLayout/LivestreamLayout.d.ts +5 -0
- package/dist/src/translations/index.d.ts +1 -0
- package/package.json +6 -5
- package/src/components/BackgroundFilters/BackgroundFilters.tsx +413 -68
- package/src/components/DeviceSettings/DeviceSelectorAudio.tsx +13 -1
- package/src/components/DeviceSettings/SpeakerTest.tsx +75 -0
- package/src/components/DeviceSettings/index.ts +2 -0
- package/src/components/Notification/Notification.tsx +4 -2
- package/src/core/components/CallLayout/LivestreamLayout.tsx +68 -45
- package/src/translations/en.json +1 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useCallStateHooks, useI18n } from '@stream-io/video-react-bindings';
|
|
3
|
+
import { CompositeButton } from '../Button';
|
|
4
|
+
import { Icon } from '../Icon';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* SpeakerTest component that plays a test audio through the selected speaker.
|
|
8
|
+
* This allows users to verify their audio output device is working correctly.
|
|
9
|
+
*/
|
|
10
|
+
export const SpeakerTest = (props: { audioUrl?: string }) => {
|
|
11
|
+
const { useSpeakerState } = useCallStateHooks();
|
|
12
|
+
const { selectedDevice } = useSpeakerState();
|
|
13
|
+
const audioElementRef = useRef<HTMLAudioElement | null>(null);
|
|
14
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
15
|
+
const { t } = useI18n();
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
audioUrl = `https://unpkg.com/${process.env.PKG_NAME}@${process.env.PKG_VERSION}/assets/piano.mp3`,
|
|
19
|
+
} = props;
|
|
20
|
+
|
|
21
|
+
// Update audio output device when selection changes
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const audio = audioElementRef.current;
|
|
24
|
+
if (!audio || !selectedDevice) return;
|
|
25
|
+
|
|
26
|
+
// Set the sinkId to route audio to the selected speaker
|
|
27
|
+
if ('setSinkId' in audio) {
|
|
28
|
+
audio.setSinkId(selectedDevice).catch((err) => {
|
|
29
|
+
console.error('Failed to set audio output device:', err);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}, [selectedDevice]);
|
|
33
|
+
|
|
34
|
+
const handleStartTest = useCallback(async () => {
|
|
35
|
+
const audio = audioElementRef.current;
|
|
36
|
+
if (!audio) return;
|
|
37
|
+
|
|
38
|
+
audio.src = audioUrl;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
if (isPlaying) {
|
|
42
|
+
audio.pause();
|
|
43
|
+
audio.currentTime = 0;
|
|
44
|
+
setIsPlaying(false);
|
|
45
|
+
} else {
|
|
46
|
+
await audio.play();
|
|
47
|
+
setIsPlaying(true);
|
|
48
|
+
}
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error('Failed to play test audio:', err);
|
|
51
|
+
setIsPlaying(false);
|
|
52
|
+
}
|
|
53
|
+
}, [isPlaying, audioUrl]);
|
|
54
|
+
|
|
55
|
+
const handleAudioEnded = useCallback(() => setIsPlaying(false), []);
|
|
56
|
+
return (
|
|
57
|
+
<div className="str-video__speaker-test">
|
|
58
|
+
<audio
|
|
59
|
+
ref={audioElementRef}
|
|
60
|
+
onEnded={handleAudioEnded}
|
|
61
|
+
onPause={handleAudioEnded}
|
|
62
|
+
/>
|
|
63
|
+
<CompositeButton
|
|
64
|
+
className="str-video__speaker-test__button"
|
|
65
|
+
onClick={handleStartTest}
|
|
66
|
+
type="button"
|
|
67
|
+
>
|
|
68
|
+
<div className="str-video__speaker-test__button-content">
|
|
69
|
+
<Icon icon="speaker" />
|
|
70
|
+
{isPlaying ? t('Stop test') : t('Test speaker')}
|
|
71
|
+
</div>
|
|
72
|
+
</CompositeButton>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PropsWithChildren, ReactNode, useEffect } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
2
3
|
import { Placement } from '@floating-ui/react';
|
|
3
|
-
|
|
4
4
|
import { useFloatingUIPreset } from '../../hooks';
|
|
5
5
|
|
|
6
6
|
export type NotificationProps = {
|
|
@@ -9,6 +9,7 @@ export type NotificationProps = {
|
|
|
9
9
|
visibilityTimeout?: number;
|
|
10
10
|
resetIsVisible?: () => void;
|
|
11
11
|
placement?: Placement;
|
|
12
|
+
className?: string;
|
|
12
13
|
iconClassName?: string | null;
|
|
13
14
|
close?: () => void;
|
|
14
15
|
};
|
|
@@ -21,6 +22,7 @@ export const Notification = (props: PropsWithChildren<NotificationProps>) => {
|
|
|
21
22
|
visibilityTimeout,
|
|
22
23
|
resetIsVisible,
|
|
23
24
|
placement = 'top',
|
|
25
|
+
className,
|
|
24
26
|
iconClassName = 'str-video__notification__icon',
|
|
25
27
|
close,
|
|
26
28
|
} = props;
|
|
@@ -44,7 +46,7 @@ export const Notification = (props: PropsWithChildren<NotificationProps>) => {
|
|
|
44
46
|
<div ref={refs.setReference}>
|
|
45
47
|
{isVisible && (
|
|
46
48
|
<div
|
|
47
|
-
className=
|
|
49
|
+
className={clsx('str-video__notification', className)}
|
|
48
50
|
ref={refs.setFloating}
|
|
49
51
|
style={{
|
|
50
52
|
position: strategy,
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ComponentType,
|
|
4
|
+
ReactElement,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react';
|
|
3
9
|
import {
|
|
4
10
|
useCall,
|
|
5
11
|
useCallStateHooks,
|
|
@@ -62,6 +68,11 @@ export type LivestreamLayoutProps = {
|
|
|
62
68
|
*/
|
|
63
69
|
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
64
70
|
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Override the default participant view overlay UI.
|
|
74
|
+
*/
|
|
75
|
+
ParticipantViewUI?: ComponentType | ReactElement | null;
|
|
65
76
|
};
|
|
66
77
|
|
|
67
78
|
export const LivestreamLayout = (props: LivestreamLayoutProps) => {
|
|
@@ -77,27 +88,31 @@ export const LivestreamLayout = (props: LivestreamLayoutProps) => {
|
|
|
77
88
|
|
|
78
89
|
usePaginatedLayoutSortPreset(call);
|
|
79
90
|
|
|
80
|
-
const
|
|
91
|
+
const { floatingParticipantProps, muted, ParticipantViewUI } = props;
|
|
92
|
+
const overlay = ParticipantViewUI ?? (
|
|
81
93
|
<ParticipantOverlay
|
|
82
94
|
showParticipantCount={props.showParticipantCount}
|
|
83
95
|
showDuration={props.showDuration}
|
|
84
96
|
showLiveBadge={props.showLiveBadge}
|
|
85
97
|
showSpeakerName={props.showSpeakerName}
|
|
98
|
+
enableFullScreen={props.enableFullScreen}
|
|
86
99
|
/>
|
|
87
100
|
);
|
|
88
101
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
const floatingParticipantOverlay =
|
|
103
|
+
hasOngoingScreenShare &&
|
|
104
|
+
(ParticipantViewUI ?? (
|
|
105
|
+
<ParticipantOverlay
|
|
106
|
+
// these elements aren't needed for the video feed
|
|
107
|
+
showParticipantCount={
|
|
108
|
+
floatingParticipantProps?.showParticipantCount ?? false
|
|
109
|
+
}
|
|
110
|
+
showDuration={floatingParticipantProps?.showDuration ?? false}
|
|
111
|
+
showLiveBadge={floatingParticipantProps?.showLiveBadge ?? false}
|
|
112
|
+
showSpeakerName={floatingParticipantProps?.showSpeakerName ?? true}
|
|
113
|
+
enableFullScreen={floatingParticipantProps?.enableFullScreen ?? true}
|
|
114
|
+
/>
|
|
115
|
+
));
|
|
101
116
|
|
|
102
117
|
return (
|
|
103
118
|
<div className="str-video__livestream-layout__wrapper">
|
|
@@ -192,6 +207,12 @@ const ParticipantOverlay = (props: {
|
|
|
192
207
|
showLiveBadge = true,
|
|
193
208
|
showSpeakerName = false,
|
|
194
209
|
} = props;
|
|
210
|
+
const overlayBarVisible =
|
|
211
|
+
enableFullScreen ||
|
|
212
|
+
showParticipantCount ||
|
|
213
|
+
showDuration ||
|
|
214
|
+
showLiveBadge ||
|
|
215
|
+
showSpeakerName;
|
|
195
216
|
const { participant } = useParticipantViewContext();
|
|
196
217
|
const { useParticipantCount } = useCallStateHooks();
|
|
197
218
|
const participantCount = useParticipantCount();
|
|
@@ -200,37 +221,39 @@ const ParticipantOverlay = (props: {
|
|
|
200
221
|
const { t } = useI18n();
|
|
201
222
|
return (
|
|
202
223
|
<div className="str-video__livestream-layout__overlay">
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
224
|
+
{overlayBarVisible && (
|
|
225
|
+
<div className="str-video__livestream-layout__overlay__bar">
|
|
226
|
+
{showLiveBadge && (
|
|
227
|
+
<span className="str-video__livestream-layout__live-badge">
|
|
228
|
+
{t('Live')}
|
|
229
|
+
</span>
|
|
230
|
+
)}
|
|
231
|
+
{showParticipantCount && (
|
|
232
|
+
<span className="str-video__livestream-layout__viewers-count">
|
|
233
|
+
{participantCount}
|
|
234
|
+
</span>
|
|
235
|
+
)}
|
|
236
|
+
{showSpeakerName && (
|
|
237
|
+
<span
|
|
238
|
+
className="str-video__livestream-layout__speaker-name"
|
|
239
|
+
title={participant.name || participant.userId || ''}
|
|
240
|
+
>
|
|
241
|
+
{participant.name || participant.userId || ''}
|
|
242
|
+
</span>
|
|
243
|
+
)}
|
|
244
|
+
{showDuration && (
|
|
245
|
+
<span className="str-video__livestream-layout__duration">
|
|
246
|
+
{formatDuration(duration)}
|
|
247
|
+
</span>
|
|
248
|
+
)}
|
|
249
|
+
{enableFullScreen && (
|
|
250
|
+
<span
|
|
251
|
+
className="str-video__livestream-layout__go-fullscreen"
|
|
252
|
+
onClick={toggleFullScreen}
|
|
253
|
+
/>
|
|
254
|
+
)}
|
|
255
|
+
</div>
|
|
256
|
+
)}
|
|
234
257
|
</div>
|
|
235
258
|
);
|
|
236
259
|
};
|
package/src/translations/en.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"Speakers": "Speakers",
|
|
12
12
|
"Video": "Video",
|
|
13
13
|
"You are muted. Unmute to speak.": "You are muted. Unmute to speak.",
|
|
14
|
+
"Background filters performance is degraded. Consider disabling filters for better performance.": "Background filters performance is degraded. Consider disabling filters for better performance.",
|
|
14
15
|
|
|
15
16
|
"Live": "Live",
|
|
16
17
|
"Livestream starts soon": "Livestream starts soon",
|