@stream-io/video-react-sdk 1.32.4 → 1.33.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 +13 -0
- package/dist/css/embedded.css +3630 -0
- package/dist/css/embedded.css.map +1 -0
- package/dist/css/styles.css +13 -2
- package/dist/css/styles.css.map +1 -1
- package/dist/embedded-BackgroundFilters-RdXfNf6_.es.js +353 -0
- package/dist/embedded-BackgroundFilters-RdXfNf6_.es.js.map +1 -0
- package/dist/embedded-BackgroundFilters-Zu84SkRR.cjs.js +355 -0
- package/dist/embedded-BackgroundFilters-Zu84SkRR.cjs.js.map +1 -0
- package/dist/embedded-CallStatsLatencyChart-Bj5OSYzg.es.js +57 -0
- package/dist/embedded-CallStatsLatencyChart-Bj5OSYzg.es.js.map +1 -0
- package/dist/embedded-CallStatsLatencyChart-CpL1M_s0.cjs.js +59 -0
- package/dist/embedded-CallStatsLatencyChart-CpL1M_s0.cjs.js.map +1 -0
- package/dist/embedded.cjs.js +3410 -0
- package/dist/embedded.cjs.js.map +1 -0
- package/dist/embedded.d.ts +1 -0
- package/dist/embedded.es.js +3407 -0
- package/dist/embedded.es.js.map +1 -0
- package/dist/index.cjs.js +67 -202
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +69 -204
- package/dist/index.es.js.map +1 -1
- package/dist/src/embedded/EmbeddedClientProvider.d.ts +21 -0
- package/dist/src/embedded/call/CallControls.d.ts +9 -0
- package/dist/src/embedded/call/CallHeader.d.ts +4 -0
- package/dist/src/embedded/call/CallLayout.d.ts +4 -0
- package/dist/src/embedded/call/CallStateRouter.d.ts +4 -0
- package/dist/src/embedded/call/EmbeddedCall.d.ts +6 -0
- package/dist/src/embedded/call/index.d.ts +1 -0
- package/dist/src/embedded/context/ConfigurationContext.d.ts +11 -0
- package/dist/src/embedded/context/index.d.ts +1 -0
- package/dist/src/embedded/hooks/index.d.ts +8 -0
- package/dist/src/embedded/hooks/useCallDuration.d.ts +7 -0
- package/dist/src/embedded/hooks/useEmbeddedClient.d.ts +22 -0
- package/dist/src/embedded/hooks/useInitializeCall.d.ts +11 -0
- package/dist/src/embedded/hooks/useInitializeVideoClient.d.ts +16 -0
- package/dist/src/embedded/hooks/useIsLivestreamPaused.d.ts +8 -0
- package/dist/src/embedded/hooks/useLayout.d.ts +9 -0
- package/dist/src/embedded/hooks/useNoiseCancellationLoader.d.ts +12 -0
- package/dist/src/embedded/hooks/useWakeLock.d.ts +5 -0
- package/dist/src/embedded/index.d.ts +3 -0
- package/dist/src/embedded/livestream/EmbeddedLivestream.d.ts +5 -0
- package/dist/src/embedded/livestream/LivestreamUI.d.ts +1 -0
- package/dist/src/embedded/livestream/host/HostLayout.d.ts +7 -0
- package/dist/src/embedded/livestream/host/HostStateRouter.d.ts +1 -0
- package/dist/src/embedded/livestream/index.d.ts +1 -0
- package/dist/src/embedded/livestream/viewer/ViewerLayout.d.ts +1 -0
- package/dist/src/embedded/livestream/viewer/ViewerLobby.d.ts +4 -0
- package/dist/src/embedded/livestream/viewer/ViewerStateRouter.d.ts +1 -0
- package/dist/src/embedded/shared/BlurToggleButton/BlurToggleButton.d.ts +2 -0
- package/dist/src/embedded/shared/CallFeedback/CallEndedScreen.d.ts +6 -0
- package/dist/src/embedded/shared/CallFeedback/CallFeedback.d.ts +4 -0
- package/dist/src/embedded/shared/CallFeedback/RatingScreen.d.ts +5 -0
- package/dist/src/embedded/shared/CallFeedback/StarRating.d.ts +6 -0
- package/dist/src/embedded/shared/CallFeedback/ThankYouScreen.d.ts +1 -0
- package/dist/src/embedded/shared/ConnectionNotification/ConnectionNotification.d.ts +1 -0
- package/dist/src/embedded/shared/EmbeddedParticipantViewUI/EmbeddedParticipantViewUI.d.ts +1 -0
- package/dist/src/embedded/shared/JoinError/JoinError.d.ts +5 -0
- package/dist/src/embedded/shared/Lobby/DeviceControls.d.ts +5 -0
- package/dist/src/embedded/shared/Lobby/DisabledDeviceButton.d.ts +6 -0
- package/dist/src/embedded/shared/Lobby/Lobby.d.ts +10 -0
- package/dist/src/embedded/shared/Lobby/ToggleCameraButton.d.ts +1 -0
- package/dist/src/embedded/shared/Lobby/ToggleMicButton.d.ts +1 -0
- package/dist/src/embedded/shared/Lobby/VideoPreviewFallbacks.d.ts +2 -0
- package/dist/src/embedded/shared/ViewersCount/ViewersCount.d.ts +5 -0
- package/dist/src/embedded/shared/index.d.ts +7 -0
- package/dist/src/embedded/types.d.ts +65 -0
- package/dist/src/hooks/usePersistedDevicePreferences.d.ts +3 -12
- package/dist/src/translations/index.d.ts +42 -1
- package/embedded.ts +1 -0
- package/package.json +18 -4
- package/src/core/components/CallLayout/LivestreamLayout.tsx +53 -41
- package/src/embedded/EmbeddedClientProvider.tsx +125 -0
- package/src/embedded/call/CallControls.tsx +124 -0
- package/src/embedded/call/CallHeader.tsx +30 -0
- package/src/embedded/call/CallLayout.tsx +66 -0
- package/src/embedded/call/CallStateRouter.tsx +56 -0
- package/src/embedded/call/EmbeddedCall.tsx +14 -0
- package/src/embedded/call/index.ts +1 -0
- package/src/embedded/context/ConfigurationContext.tsx +36 -0
- package/src/embedded/context/index.ts +1 -0
- package/src/embedded/hooks/index.ts +8 -0
- package/src/embedded/hooks/useCallDuration.ts +40 -0
- package/src/embedded/hooks/useEmbeddedClient.ts +64 -0
- package/src/embedded/hooks/useInitializeCall.ts +51 -0
- package/src/embedded/hooks/useInitializeVideoClient.ts +118 -0
- package/src/embedded/hooks/useIsLivestreamPaused.ts +44 -0
- package/src/embedded/hooks/useLayout.ts +100 -0
- package/src/embedded/hooks/useNoiseCancellationLoader.ts +62 -0
- package/src/embedded/hooks/useWakeLock.ts +33 -0
- package/src/embedded/index.ts +12 -0
- package/src/embedded/livestream/EmbeddedLivestream.tsx +16 -0
- package/src/embedded/livestream/LivestreamUI.tsx +17 -0
- package/src/embedded/livestream/host/HostLayout.tsx +210 -0
- package/src/embedded/livestream/host/HostStateRouter.tsx +100 -0
- package/src/embedded/livestream/index.ts +1 -0
- package/src/embedded/livestream/viewer/ViewerLayout.tsx +160 -0
- package/src/embedded/livestream/viewer/ViewerLobby.tsx +135 -0
- package/src/embedded/livestream/viewer/ViewerStateRouter.tsx +82 -0
- package/src/embedded/shared/BlurToggleButton/BlurToggleButton.tsx +75 -0
- package/src/embedded/shared/CallFeedback/CallEndedScreen.tsx +55 -0
- package/src/embedded/shared/CallFeedback/CallFeedback.tsx +51 -0
- package/src/embedded/shared/CallFeedback/RatingScreen.tsx +47 -0
- package/src/embedded/shared/CallFeedback/StarRating.tsx +46 -0
- package/src/embedded/shared/CallFeedback/ThankYouScreen.tsx +19 -0
- package/src/embedded/shared/ConnectionNotification/ConnectionNotification.tsx +59 -0
- package/src/embedded/shared/EmbeddedParticipantViewUI/EmbeddedParticipantViewUI.tsx +32 -0
- package/src/embedded/shared/JoinError/JoinError.tsx +27 -0
- package/src/embedded/shared/Lobby/DeviceControls.tsx +54 -0
- package/src/embedded/shared/Lobby/DisabledDeviceButton.tsx +21 -0
- package/src/embedded/shared/Lobby/Lobby.tsx +59 -0
- package/src/embedded/shared/Lobby/ToggleCameraButton.tsx +44 -0
- package/src/embedded/shared/Lobby/ToggleMicButton.tsx +48 -0
- package/src/embedded/shared/Lobby/VideoPreviewFallbacks.tsx +55 -0
- package/src/embedded/shared/ViewersCount/ViewersCount.tsx +18 -0
- package/src/embedded/shared/index.ts +7 -0
- package/src/embedded/types.ts +80 -0
- package/src/hooks/usePersistedDevicePreferences.ts +8 -307
- package/src/translations/en.json +44 -2
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useI18n } from '@stream-io/video-react-bindings';
|
|
3
|
+
import { StarRating } from './StarRating';
|
|
4
|
+
|
|
5
|
+
interface RatingScreenProps {
|
|
6
|
+
onSubmit: (rating: number, message: string) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const RatingScreen = ({ onSubmit }: RatingScreenProps) => {
|
|
10
|
+
const { t } = useI18n();
|
|
11
|
+
const [rating, setRating] = useState(0);
|
|
12
|
+
const [message, setMessage] = useState('');
|
|
13
|
+
|
|
14
|
+
const handleSubmit = () => {
|
|
15
|
+
if (rating > 0) onSubmit(rating, message);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="str-video__embedded-call-feedback__container">
|
|
20
|
+
<h2 className="str-video__embedded-call-feedback__title">
|
|
21
|
+
{t('Share your feedback')}
|
|
22
|
+
</h2>
|
|
23
|
+
|
|
24
|
+
<StarRating value={rating} onChange={setRating} />
|
|
25
|
+
|
|
26
|
+
<textarea
|
|
27
|
+
aria-label={t('Feedback message')}
|
|
28
|
+
className="str-video__embedded-call-feedback__textarea"
|
|
29
|
+
placeholder={t('Tell us about your experience...')}
|
|
30
|
+
value={message}
|
|
31
|
+
onChange={(e) => setMessage(e.target.value)}
|
|
32
|
+
rows={3}
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
<div className="str-video__embedded-call-feedback__actions">
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
className="str-video__button"
|
|
39
|
+
onClick={handleSubmit}
|
|
40
|
+
disabled={rating === 0}
|
|
41
|
+
>
|
|
42
|
+
{t('Submit feedback')}
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { useI18n } from '@stream-io/video-react-bindings';
|
|
4
|
+
import { Icon } from '../../../components';
|
|
5
|
+
|
|
6
|
+
interface StarRatingProps {
|
|
7
|
+
value: number;
|
|
8
|
+
onChange: (rating: number) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const StarRating = ({ value, onChange }: StarRatingProps) => {
|
|
12
|
+
const { t } = useI18n();
|
|
13
|
+
const [hovered, setHovered] = useState(0);
|
|
14
|
+
const displayValue = hovered || value;
|
|
15
|
+
|
|
16
|
+
const getStarClasses = (star: number) =>
|
|
17
|
+
clsx(
|
|
18
|
+
'str-video__embedded-call-feedback__star',
|
|
19
|
+
star <= displayValue && 'str-video__embedded-call-feedback__star--active',
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="str-video__embedded-call-feedback__rating-section">
|
|
24
|
+
<p className="str-video__embedded-call-feedback__rating-label">
|
|
25
|
+
{t('How was your call quality?')}
|
|
26
|
+
</p>
|
|
27
|
+
<div
|
|
28
|
+
className="str-video__embedded-call-feedback__stars"
|
|
29
|
+
onMouseLeave={() => setHovered(0)}
|
|
30
|
+
>
|
|
31
|
+
{[1, 2, 3, 4, 5].map((star) => (
|
|
32
|
+
<button
|
|
33
|
+
key={star}
|
|
34
|
+
type="button"
|
|
35
|
+
className={getStarClasses(star)}
|
|
36
|
+
onClick={() => onChange(star)}
|
|
37
|
+
onMouseEnter={() => setHovered(star)}
|
|
38
|
+
aria-label={t('Rate {{ count }} star', { count: star })}
|
|
39
|
+
>
|
|
40
|
+
<Icon icon="star-filled" />
|
|
41
|
+
</button>
|
|
42
|
+
))}
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useI18n } from '@stream-io/video-react-bindings';
|
|
2
|
+
import { Icon } from '../../../components';
|
|
3
|
+
|
|
4
|
+
export const ThankYouScreen = () => {
|
|
5
|
+
const { t } = useI18n();
|
|
6
|
+
return (
|
|
7
|
+
<div className="str-video__embedded-call-feedback__container">
|
|
8
|
+
<div className="str-video__embedded-call-feedback__checkmark">
|
|
9
|
+
<Icon icon="checkmark" />
|
|
10
|
+
</div>
|
|
11
|
+
<h2 className="str-video__embedded-call-feedback__title">
|
|
12
|
+
{t('Thank you!')}
|
|
13
|
+
</h2>
|
|
14
|
+
<p className="str-video__embedded-call-feedback__subtitle">
|
|
15
|
+
{t('Your feedback helps improve call quality.')}
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { CallingState } from '@stream-io/video-client';
|
|
2
|
+
import { useCallStateHooks, useI18n } from '@stream-io/video-react-bindings';
|
|
3
|
+
import { LoadingIndicator, Notification } from '../../../components';
|
|
4
|
+
|
|
5
|
+
export const ConnectionNotification = () => {
|
|
6
|
+
const { t } = useI18n();
|
|
7
|
+
const { useCallCallingState } = useCallStateHooks();
|
|
8
|
+
const callingState = useCallCallingState();
|
|
9
|
+
|
|
10
|
+
const isOffline = callingState === CallingState.OFFLINE;
|
|
11
|
+
const isReconnecting = callingState === CallingState.RECONNECTING;
|
|
12
|
+
const isMigrating = callingState === CallingState.MIGRATING;
|
|
13
|
+
const isJoining = callingState === CallingState.JOINING;
|
|
14
|
+
const hasFailedToRecover = callingState === CallingState.RECONNECTING_FAILED;
|
|
15
|
+
|
|
16
|
+
const showError = isOffline || hasFailedToRecover;
|
|
17
|
+
const showLoading = isJoining || isReconnecting || isMigrating;
|
|
18
|
+
|
|
19
|
+
if (showError) {
|
|
20
|
+
return (
|
|
21
|
+
<div className="str-video__embedded-connection-notification">
|
|
22
|
+
<Notification
|
|
23
|
+
isVisible
|
|
24
|
+
placement="bottom"
|
|
25
|
+
message={
|
|
26
|
+
isOffline
|
|
27
|
+
? t('You are offline. Check your internet connection.')
|
|
28
|
+
: t('Failed to restore connection. Please try again.')
|
|
29
|
+
}
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (showLoading) {
|
|
36
|
+
return (
|
|
37
|
+
<div className="str-video__embedded-connection-notification">
|
|
38
|
+
<Notification
|
|
39
|
+
isVisible
|
|
40
|
+
placement="bottom"
|
|
41
|
+
iconClassName={null}
|
|
42
|
+
message={
|
|
43
|
+
<LoadingIndicator
|
|
44
|
+
text={
|
|
45
|
+
isMigrating
|
|
46
|
+
? t('Migrating...')
|
|
47
|
+
: isJoining
|
|
48
|
+
? t('Joining')
|
|
49
|
+
: t('Reconnecting...')
|
|
50
|
+
}
|
|
51
|
+
/>
|
|
52
|
+
}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return null;
|
|
59
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { DefaultParticipantViewUI } from '../../../core';
|
|
4
|
+
import { useParticipantViewContext } from '../../../core';
|
|
5
|
+
|
|
6
|
+
export const EmbeddedParticipantViewUI = () => {
|
|
7
|
+
const { participantViewElement, trackType } = useParticipantViewContext();
|
|
8
|
+
|
|
9
|
+
const handleDoubleClick = useCallback(() => {
|
|
10
|
+
if (!participantViewElement) return;
|
|
11
|
+
if (typeof participantViewElement.requestFullscreen === 'undefined') return;
|
|
12
|
+
|
|
13
|
+
if (!document.fullscreenElement) {
|
|
14
|
+
participantViewElement.requestFullscreen().catch(console.error);
|
|
15
|
+
} else {
|
|
16
|
+
document.exitFullscreen().catch(console.error);
|
|
17
|
+
}
|
|
18
|
+
}, [participantViewElement]);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
className={clsx(
|
|
23
|
+
'str-video__embedded-participant-view-ui',
|
|
24
|
+
trackType === 'screenShareTrack' &&
|
|
25
|
+
'str-video__embedded-participant-view-ui--screen-share',
|
|
26
|
+
)}
|
|
27
|
+
onDoubleClick={handleDoubleClick}
|
|
28
|
+
>
|
|
29
|
+
<DefaultParticipantViewUI />
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useI18n } from '@stream-io/video-react-bindings';
|
|
2
|
+
import { Icon } from '../../../components';
|
|
3
|
+
|
|
4
|
+
interface JoinErrorProps {
|
|
5
|
+
onJoin: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const JoinError = ({ onJoin }: JoinErrorProps) => {
|
|
9
|
+
const { t } = useI18n();
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="str-video__embedded-join-error">
|
|
13
|
+
<h2 className="str-video__embedded-join-error__title">
|
|
14
|
+
{t('Failed to join the call')}
|
|
15
|
+
</h2>
|
|
16
|
+
<p className="str-video__embedded-join-error__message">
|
|
17
|
+
{t(
|
|
18
|
+
"We couldn't connect to the server. Please check your connection and try again.",
|
|
19
|
+
)}
|
|
20
|
+
</p>
|
|
21
|
+
<button type="button" className="str-video__button" onClick={onJoin}>
|
|
22
|
+
<Icon icon="login" />
|
|
23
|
+
{t('Try again')}
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useCallStateHooks, useI18n } from '@stream-io/video-react-bindings';
|
|
2
|
+
import {
|
|
3
|
+
ToggleAudioPreviewButton,
|
|
4
|
+
ToggleVideoPreviewButton,
|
|
5
|
+
VideoPreview,
|
|
6
|
+
} from '../../../components';
|
|
7
|
+
import { ToggleMicButton } from './ToggleMicButton';
|
|
8
|
+
import { ToggleCameraButton } from './ToggleCameraButton';
|
|
9
|
+
import { DisabledDeviceButton } from './DisabledDeviceButton';
|
|
10
|
+
import { DisabledVideoPreview, NoCameraPreview } from './VideoPreviewFallbacks';
|
|
11
|
+
|
|
12
|
+
interface DeviceControlsProps {
|
|
13
|
+
isVideoEnabled: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const DeviceControls = ({ isVideoEnabled }: DeviceControlsProps) => {
|
|
17
|
+
const { t } = useI18n();
|
|
18
|
+
const { useCameraState, useMicrophoneState } = useCallStateHooks();
|
|
19
|
+
|
|
20
|
+
const { hasBrowserPermission: hasCameraPermission } = useCameraState();
|
|
21
|
+
const { hasBrowserPermission: hasMicPermission } = useMicrophoneState();
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<>
|
|
25
|
+
<div className="str-video__embedded-lobby__video-preview">
|
|
26
|
+
<VideoPreview
|
|
27
|
+
DisabledVideoPreview={DisabledVideoPreview}
|
|
28
|
+
NoCameraPreview={NoCameraPreview}
|
|
29
|
+
/>
|
|
30
|
+
<div className="str-video__embedded-lobby__media-toggle">
|
|
31
|
+
<ToggleAudioPreviewButton Menu={null} />
|
|
32
|
+
{isVideoEnabled && <ToggleVideoPreviewButton Menu={null} />}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div className="str-video__embedded-lobby__media">
|
|
37
|
+
{hasMicPermission ? (
|
|
38
|
+
<ToggleMicButton />
|
|
39
|
+
) : (
|
|
40
|
+
<DisabledDeviceButton icon="mic" label={t('Permission needed')} />
|
|
41
|
+
)}
|
|
42
|
+
{isVideoEnabled &&
|
|
43
|
+
(hasCameraPermission ? (
|
|
44
|
+
<ToggleCameraButton />
|
|
45
|
+
) : (
|
|
46
|
+
<DisabledDeviceButton
|
|
47
|
+
icon="camera"
|
|
48
|
+
label={t('Permission needed')}
|
|
49
|
+
/>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
</>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Icon } from '../../../components';
|
|
2
|
+
|
|
3
|
+
interface DisabledDeviceButtonProps {
|
|
4
|
+
icon: string;
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const DisabledDeviceButton = ({
|
|
9
|
+
icon,
|
|
10
|
+
label,
|
|
11
|
+
}: DisabledDeviceButtonProps) => (
|
|
12
|
+
<div className="str-video__embedded-lobby__device-button str-video__embedded-lobby__device-button--disabled">
|
|
13
|
+
<Icon
|
|
14
|
+
className="str-video__embedded-lobby__device-button-icon"
|
|
15
|
+
icon={icon}
|
|
16
|
+
/>
|
|
17
|
+
<span className="str-video__embedded-lobby__device-button-label">
|
|
18
|
+
{label}
|
|
19
|
+
</span>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import {
|
|
3
|
+
useCallStateHooks,
|
|
4
|
+
useConnectedUser,
|
|
5
|
+
useI18n,
|
|
6
|
+
} from '@stream-io/video-react-bindings';
|
|
7
|
+
import { Icon } from '../../../components';
|
|
8
|
+
import { DeviceControls } from './DeviceControls';
|
|
9
|
+
|
|
10
|
+
interface LobbyProps {
|
|
11
|
+
onJoin: () => void;
|
|
12
|
+
title?: string;
|
|
13
|
+
joinLabel?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Lobby component - Device setup screen before joining a call.
|
|
18
|
+
*/
|
|
19
|
+
export const Lobby = ({ onJoin, title, joinLabel }: LobbyProps) => {
|
|
20
|
+
const { t } = useI18n();
|
|
21
|
+
const user = useConnectedUser();
|
|
22
|
+
const { useCameraState, useCallSettings } = useCallStateHooks();
|
|
23
|
+
|
|
24
|
+
const { isMute } = useCameraState();
|
|
25
|
+
const settings = useCallSettings();
|
|
26
|
+
|
|
27
|
+
const isVideoEnabled = settings?.video.enabled ?? true;
|
|
28
|
+
const resolvedJoinLabel = joinLabel ?? t('Join');
|
|
29
|
+
const resolvedTitle = title ?? t('Set up your call before joining');
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="str-video__embedded-lobby">
|
|
33
|
+
<div className="str-video__embedded-lobby__content">
|
|
34
|
+
<h1 className="str-video__embedded-lobby__heading">{resolvedTitle}</h1>
|
|
35
|
+
<div
|
|
36
|
+
className={clsx(
|
|
37
|
+
'str-video__embedded-lobby__camera',
|
|
38
|
+
isMute && 'str-video__embedded-lobby__camera--off',
|
|
39
|
+
)}
|
|
40
|
+
>
|
|
41
|
+
<DeviceControls isVideoEnabled={isVideoEnabled} />
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div className="str-video__embedded-lobby__display-name">
|
|
45
|
+
<div className="str-video__embedded-lobby__display-name-label">
|
|
46
|
+
{t('Display name')}
|
|
47
|
+
</div>
|
|
48
|
+
<span className="str-video__embedded-lobby__display-name-value">
|
|
49
|
+
{user?.name}
|
|
50
|
+
</span>
|
|
51
|
+
<button className="str-video__button" onClick={onJoin}>
|
|
52
|
+
<Icon className="str-video__button__icon" icon="login" />
|
|
53
|
+
{resolvedJoinLabel}
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
4
|
+
import {
|
|
5
|
+
Icon,
|
|
6
|
+
MenuToggle,
|
|
7
|
+
MenuVisualType,
|
|
8
|
+
ToggleMenuButtonProps,
|
|
9
|
+
} from '../../../components';
|
|
10
|
+
import { CameraMenuWithBlur } from '../BlurToggleButton/BlurToggleButton';
|
|
11
|
+
|
|
12
|
+
const ToggleMenuButton = forwardRef<HTMLButtonElement, ToggleMenuButtonProps>(
|
|
13
|
+
function ToggleMenuButton(props, ref) {
|
|
14
|
+
const { useCameraState } = useCallStateHooks();
|
|
15
|
+
const { selectedDevice: selectedCamera, devices: cameras } =
|
|
16
|
+
useCameraState();
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<button ref={ref} className="str-video__embedded-lobby__device-button">
|
|
20
|
+
<Icon
|
|
21
|
+
className="str-video__embedded-lobby__device-button-icon"
|
|
22
|
+
icon="camera"
|
|
23
|
+
/>
|
|
24
|
+
<span className="str-video__embedded-lobby__device-button-label">
|
|
25
|
+
{cameras?.find((c: MediaDeviceInfo) => c.deviceId === selectedCamera)
|
|
26
|
+
?.label || 'Default'}
|
|
27
|
+
</span>
|
|
28
|
+
<Icon icon={props.menuShown ? 'chevron-down' : 'chevron-up'} />
|
|
29
|
+
</button>
|
|
30
|
+
);
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export const ToggleCameraButton = () => {
|
|
35
|
+
return (
|
|
36
|
+
<MenuToggle
|
|
37
|
+
placement="top-start"
|
|
38
|
+
ToggleButton={ToggleMenuButton}
|
|
39
|
+
visualType={MenuVisualType.MENU}
|
|
40
|
+
>
|
|
41
|
+
<CameraMenuWithBlur />
|
|
42
|
+
</MenuToggle>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useCallStateHooks, useI18n } from '@stream-io/video-react-bindings';
|
|
4
|
+
import {
|
|
5
|
+
DeviceSelectorAudioInput,
|
|
6
|
+
DeviceSelectorAudioOutput,
|
|
7
|
+
Icon,
|
|
8
|
+
MenuToggle,
|
|
9
|
+
MenuVisualType,
|
|
10
|
+
ToggleMenuButtonProps,
|
|
11
|
+
} from '../../../components';
|
|
12
|
+
|
|
13
|
+
const ToggleMenuButton = forwardRef<HTMLButtonElement, ToggleMenuButtonProps>(
|
|
14
|
+
function ToggleMenuButton(props, ref) {
|
|
15
|
+
const { useMicrophoneState } = useCallStateHooks();
|
|
16
|
+
const { selectedDevice: selectedMic, devices: microphones } =
|
|
17
|
+
useMicrophoneState();
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<button ref={ref} className="str-video__embedded-lobby__device-button">
|
|
21
|
+
<Icon
|
|
22
|
+
className="str-video__embedded-lobby__device-button-icon"
|
|
23
|
+
icon="mic"
|
|
24
|
+
/>
|
|
25
|
+
<span className="str-video__embedded-lobby__device-button-label">
|
|
26
|
+
{microphones?.find((m: MediaDeviceInfo) => m.deviceId === selectedMic)
|
|
27
|
+
?.label || 'Default'}
|
|
28
|
+
</span>
|
|
29
|
+
<Icon icon={props.menuShown ? 'chevron-down' : 'chevron-up'} />
|
|
30
|
+
</button>
|
|
31
|
+
);
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export const ToggleMicButton = () => {
|
|
36
|
+
const { t } = useI18n();
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<MenuToggle
|
|
40
|
+
placement="top-start"
|
|
41
|
+
ToggleButton={ToggleMenuButton}
|
|
42
|
+
visualType={MenuVisualType.MENU}
|
|
43
|
+
>
|
|
44
|
+
<DeviceSelectorAudioInput visualType="list" title={t('Microphone')} />
|
|
45
|
+
<DeviceSelectorAudioOutput visualType="list" title={t('Speaker')} />
|
|
46
|
+
</MenuToggle>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallStateHooks,
|
|
3
|
+
useConnectedUser,
|
|
4
|
+
useI18n,
|
|
5
|
+
} from '@stream-io/video-react-bindings';
|
|
6
|
+
import { Avatar } from '../../../components';
|
|
7
|
+
|
|
8
|
+
export const DisabledVideoPreview = () => {
|
|
9
|
+
const { t } = useI18n();
|
|
10
|
+
|
|
11
|
+
const user = useConnectedUser();
|
|
12
|
+
const { useCameraState, useMicrophoneState } = useCallStateHooks();
|
|
13
|
+
|
|
14
|
+
const { hasBrowserPermission: hasCameraPermission } = useCameraState();
|
|
15
|
+
const { hasBrowserPermission: hasMicPermission } = useMicrophoneState();
|
|
16
|
+
|
|
17
|
+
const hasBrowserMediaPermission = hasCameraPermission && hasMicPermission;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="str-video__embedded-lobby__no-permission">
|
|
21
|
+
{hasBrowserMediaPermission ? (
|
|
22
|
+
<Avatar imageSrc={user?.image} name={user?.name || user?.id} />
|
|
23
|
+
) : (
|
|
24
|
+
t(
|
|
25
|
+
'Please grant your browser permission to access your camera and microphone.',
|
|
26
|
+
)
|
|
27
|
+
)}
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const NoCameraPreview = () => {
|
|
33
|
+
const { t } = useI18n();
|
|
34
|
+
const { useCameraState, useMicrophoneState } = useCallStateHooks();
|
|
35
|
+
const { hasBrowserPermission: hasCameraPermission } = useCameraState();
|
|
36
|
+
const { hasBrowserPermission: hasMicPermission } = useMicrophoneState();
|
|
37
|
+
|
|
38
|
+
const hasBrowserMediaPermission = hasCameraPermission && hasMicPermission;
|
|
39
|
+
|
|
40
|
+
if (!hasBrowserMediaPermission) {
|
|
41
|
+
return (
|
|
42
|
+
<div className="str-video__embedded-lobby__no-permission">
|
|
43
|
+
{t(
|
|
44
|
+
'Please grant your browser permission to access your camera and microphone.',
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="str-video__video-preview__no-camera-preview">
|
|
52
|
+
{t('No camera found')}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { humanize } from '@stream-io/video-client';
|
|
2
|
+
import { Icon } from '../../../components';
|
|
3
|
+
|
|
4
|
+
interface ViewersCountProps {
|
|
5
|
+
count: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const ViewersCount = ({ count }: ViewersCountProps) => (
|
|
9
|
+
<div className="str-video__embedded-livestream-duration__viewers">
|
|
10
|
+
<Icon
|
|
11
|
+
icon="eye"
|
|
12
|
+
className="str-video__embedded-livestream-duration__eye-icon"
|
|
13
|
+
/>
|
|
14
|
+
<span className="str-video__embedded-livestream-duration__count">
|
|
15
|
+
{humanize(count)}
|
|
16
|
+
</span>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './BlurToggleButton/BlurToggleButton';
|
|
2
|
+
export * from './CallFeedback/CallFeedback';
|
|
3
|
+
export * from './ConnectionNotification/ConnectionNotification';
|
|
4
|
+
export * from './EmbeddedParticipantViewUI/EmbeddedParticipantViewUI';
|
|
5
|
+
export * from './JoinError/JoinError';
|
|
6
|
+
export * from './Lobby/Lobby';
|
|
7
|
+
export * from './ViewersCount/ViewersCount';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { LogLevel, TokenProvider } from '@stream-io/video-client';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Available layout options.
|
|
6
|
+
*/
|
|
7
|
+
export type LayoutOption =
|
|
8
|
+
| 'Livestream'
|
|
9
|
+
| 'PaginatedGrid'
|
|
10
|
+
| 'SpeakerLeft'
|
|
11
|
+
| 'SpeakerRight'
|
|
12
|
+
| 'SpeakerTop'
|
|
13
|
+
| 'SpeakerBottom';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* An authenticated user with a known identity.
|
|
17
|
+
* Requires a `token` or `tokenProvider` on the component props.
|
|
18
|
+
*/
|
|
19
|
+
export interface EmbeddedAuthenticatedUser {
|
|
20
|
+
type?: 'authenticated';
|
|
21
|
+
id: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
image?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A guest user — the server generates credentials automatically.
|
|
28
|
+
*/
|
|
29
|
+
export interface EmbeddedGuestUser {
|
|
30
|
+
type: 'guest';
|
|
31
|
+
id: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
image?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* An anonymous user with no identity.
|
|
38
|
+
* May optionally receive a call-scoped token via the `token` prop.
|
|
39
|
+
*/
|
|
40
|
+
export interface EmbeddedAnonymousUser {
|
|
41
|
+
type: 'anonymous';
|
|
42
|
+
id?: '!anon';
|
|
43
|
+
name?: string;
|
|
44
|
+
image?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Discriminated union for embedded user configuration.
|
|
49
|
+
*/
|
|
50
|
+
export type EmbeddedUser =
|
|
51
|
+
| EmbeddedAuthenticatedUser
|
|
52
|
+
| EmbeddedGuestUser
|
|
53
|
+
| EmbeddedAnonymousUser;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Base props shared by EmbeddedCall and EmbeddedLivestream.
|
|
57
|
+
*/
|
|
58
|
+
export interface EmbeddedClientBaseProps {
|
|
59
|
+
apiKey: string;
|
|
60
|
+
callType: string;
|
|
61
|
+
callId: string;
|
|
62
|
+
user: EmbeddedUser;
|
|
63
|
+
token?: string;
|
|
64
|
+
tokenProvider?: TokenProvider;
|
|
65
|
+
logLevel?: LogLevel;
|
|
66
|
+
layout?: LayoutOption;
|
|
67
|
+
theme?: Record<string, string>;
|
|
68
|
+
onError?: (error: any) => void;
|
|
69
|
+
children?: ReactNode;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Props for the EmbeddedCall component.
|
|
74
|
+
*/
|
|
75
|
+
export interface EmbeddedMeetingProps extends EmbeddedClientBaseProps {}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Props for the EmbeddedLivestream component.
|
|
79
|
+
*/
|
|
80
|
+
export interface EmbeddedLivestreamProps extends EmbeddedClientBaseProps {}
|