@stream-io/video-react-sdk 1.0.6 → 1.0.8
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 +21 -0
- package/dist/css/styles.css +3 -3
- package/dist/index.cjs.js +173 -123
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +174 -124
- package/dist/index.es.js.map +1 -1
- package/dist/src/components/Button/CompositeButton.d.ts +1 -0
- package/dist/src/components/CallControls/CancelCallButton.d.ts +2 -1
- package/dist/src/components/CallControls/ScreenShareButton.d.ts +3 -2
- package/dist/src/components/CallControls/ToggleAudioButton.d.ts +3 -2
- package/dist/src/components/CallControls/ToggleVideoButton.d.ts +3 -2
- package/dist/src/components/Menu/MenuToggle.d.ts +2 -1
- package/dist/src/components/Tooltip/WithTooltip.d.ts +4 -2
- package/dist/src/utilities/callControlHandler.d.ts +16 -0
- package/package.json +4 -4
- package/src/components/Button/CompositeButton.tsx +3 -0
- package/src/components/CallControls/CallControls.tsx +3 -3
- package/src/components/CallControls/CancelCallButton.tsx +12 -8
- package/src/components/CallControls/ReactionsButton.tsx +14 -9
- package/src/components/CallControls/RecordCallButton.tsx +21 -15
- package/src/components/CallControls/ScreenShareButton.tsx +34 -26
- package/src/components/CallControls/ToggleAudioButton.tsx +84 -56
- package/src/components/CallControls/ToggleVideoButton.tsx +87 -59
- package/src/components/CallParticipantsList/CallParticipantListingItem.tsx +10 -9
- package/src/components/DeviceSettings/DeviceSelector.tsx +4 -0
- package/src/components/Menu/MenuToggle.tsx +9 -0
- package/src/components/Tooltip/WithTooltip.tsx +7 -2
- package/src/core/components/Audio/ParticipantsAudio.tsx +10 -13
- package/src/core/components/CallLayout/LivestreamLayout.tsx +5 -7
- package/src/core/components/CallLayout/SpeakerLayout.tsx +3 -5
- package/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx +12 -12
- package/src/core/components/ParticipantView/ParticipantActionsContextMenu.tsx +16 -14
- package/src/core/components/ParticipantView/ParticipantView.tsx +12 -17
- package/src/core/components/Video/Video.tsx +4 -4
- package/src/utilities/callControlHandler.ts +43 -0
package/dist/index.cjs.js
CHANGED
|
@@ -30,11 +30,11 @@ const ParticipantsAudio = (props) => {
|
|
|
30
30
|
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: participants.map((participant) => {
|
|
31
31
|
if (participant.isLocalParticipant)
|
|
32
32
|
return null;
|
|
33
|
-
const {
|
|
34
|
-
const
|
|
35
|
-
const audioTrackElement =
|
|
36
|
-
const
|
|
37
|
-
const screenShareAudioTrackElement =
|
|
33
|
+
const { audioStream, screenShareAudioStream, sessionId } = participant;
|
|
34
|
+
const hasAudioTrack = videoClient.hasAudio(participant);
|
|
35
|
+
const audioTrackElement = hasAudioTrack && audioStream && (jsxRuntime.jsx(Audio, { ...audioProps, trackType: "audioTrack", participant: participant }));
|
|
36
|
+
const hasScreenShareAudioTrack = videoClient.hasScreenShareAudio(participant);
|
|
37
|
+
const screenShareAudioTrackElement = hasScreenShareAudioTrack &&
|
|
38
38
|
screenShareAudioStream && (jsxRuntime.jsx(Audio, { ...audioProps, trackType: "screenShareAudioTrack", participant: participant }));
|
|
39
39
|
return (jsxRuntime.jsxs(react.Fragment, { children: [audioTrackElement, screenShareAudioTrackElement] }, sessionId));
|
|
40
40
|
}) }));
|
|
@@ -516,8 +516,10 @@ const MenuPortal = ({ children, refs, }) => {
|
|
|
516
516
|
const portalId = react.useMemo(() => `str-video-portal-${Math.random().toString(36).substring(2, 9)}`, []);
|
|
517
517
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { id: portalId, className: "str-video__portal" }), jsxRuntime.jsx(react$1.FloatingOverlay, { children: jsxRuntime.jsx(react$1.FloatingPortal, { id: portalId, children: jsxRuntime.jsx("div", { className: "str-video__portal-content", ref: refs.setFloating, children: children }) }) })] }));
|
|
518
518
|
};
|
|
519
|
-
const MenuToggle = ({ ToggleButton, placement = 'top-start', strategy = 'absolute', offset, visualType = exports.MenuVisualType.MENU, children, }) => {
|
|
519
|
+
const MenuToggle = ({ ToggleButton, placement = 'top-start', strategy = 'absolute', offset, visualType = exports.MenuVisualType.MENU, children, onToggle, }) => {
|
|
520
520
|
const [menuShown, setMenuShown] = react.useState(false);
|
|
521
|
+
const toggleHandler = react.useRef(onToggle);
|
|
522
|
+
toggleHandler.current = onToggle;
|
|
521
523
|
const { floating, domReference, refs, x, y } = useFloatingUIPreset({
|
|
522
524
|
placement,
|
|
523
525
|
strategy,
|
|
@@ -527,9 +529,11 @@ const MenuToggle = ({ ToggleButton, placement = 'top-start', strategy = 'absolut
|
|
|
527
529
|
const handleClick = (event) => {
|
|
528
530
|
if (!floating && domReference?.contains(event.target)) {
|
|
529
531
|
setMenuShown(true);
|
|
532
|
+
toggleHandler.current?.(true);
|
|
530
533
|
}
|
|
531
534
|
else if (floating && !floating?.contains(event.target)) {
|
|
532
535
|
setMenuShown(false);
|
|
536
|
+
toggleHandler.current?.(false);
|
|
533
537
|
}
|
|
534
538
|
};
|
|
535
539
|
const handleKeyDown = (event) => {
|
|
@@ -537,6 +541,7 @@ const MenuToggle = ({ ToggleButton, placement = 'top-start', strategy = 'absolut
|
|
|
537
541
|
!event.altKey &&
|
|
538
542
|
!event.ctrlKey) {
|
|
539
543
|
setMenuShown(false);
|
|
544
|
+
toggleHandler.current?.(false);
|
|
540
545
|
}
|
|
541
546
|
};
|
|
542
547
|
document?.addEventListener('click', handleClick, { capture: true });
|
|
@@ -551,7 +556,7 @@ const MenuToggle = ({ ToggleButton, placement = 'top-start', strategy = 'absolut
|
|
|
551
556
|
top: y ?? 0,
|
|
552
557
|
left: x ?? 0,
|
|
553
558
|
overflowY: 'auto',
|
|
554
|
-
}, children: children })) : null })), jsxRuntime.jsx(ToggleButton, { menuShown: menuShown, ref: refs.setReference })] }));
|
|
559
|
+
}, role: "menu", children: children })) : null })), jsxRuntime.jsx(ToggleButton, { menuShown: menuShown, ref: refs.setReference })] }));
|
|
555
560
|
};
|
|
556
561
|
|
|
557
562
|
const GenericMenu = ({ children, onItemClick, }) => {
|
|
@@ -600,7 +605,7 @@ const applyElementToRef = (ref, element) => {
|
|
|
600
605
|
ref.current = element;
|
|
601
606
|
};
|
|
602
607
|
|
|
603
|
-
const CompositeButton = react.forwardRef(function CompositeButton({ caption, children, className, active, Menu, menuPlacement, menuOffset, title, ToggleMenuButton = DefaultToggleMenuButton, variant, onClick, ...restButtonProps }, ref) {
|
|
608
|
+
const CompositeButton = react.forwardRef(function CompositeButton({ caption, children, className, active, Menu, menuPlacement, menuOffset, title, ToggleMenuButton = DefaultToggleMenuButton, variant, onClick, onMenuToggle, ...restButtonProps }, ref) {
|
|
604
609
|
return (jsxRuntime.jsxs("div", { className: clsx('str-video__composite-button', className, {
|
|
605
610
|
'str-video__composite-button--caption': caption,
|
|
606
611
|
'str-video__composite-button--menu': Menu,
|
|
@@ -611,7 +616,7 @@ const CompositeButton = react.forwardRef(function CompositeButton({ caption, chi
|
|
|
611
616
|
}), children: [jsxRuntime.jsx("button", { type: "button", className: "str-video__composite-button__button", onClick: (e) => {
|
|
612
617
|
e.preventDefault();
|
|
613
618
|
onClick?.(e);
|
|
614
|
-
}, ...restButtonProps, children: children }), Menu && (jsxRuntime.jsx(MenuToggle, { offset: menuOffset, placement: menuPlacement, ToggleButton: ToggleMenuButton, children: isComponentType(Menu) ? jsxRuntime.jsx(Menu, {}) : Menu }))] }), caption && (jsxRuntime.jsx("div", { className: "str-video__composite-button__caption", children: caption }))] }));
|
|
619
|
+
}, ...restButtonProps, children: children }), Menu && (jsxRuntime.jsx(MenuToggle, { offset: menuOffset, placement: menuPlacement, ToggleButton: ToggleMenuButton, onToggle: onMenuToggle, children: isComponentType(Menu) ? jsxRuntime.jsx(Menu, {}) : Menu }))] }), caption && (jsxRuntime.jsx("div", { className: "str-video__composite-button__caption", children: caption }))] }));
|
|
615
620
|
});
|
|
616
621
|
const DefaultToggleMenuButton = react.forwardRef(function DefaultToggleMenuButton({ menuShown }, ref) {
|
|
617
622
|
return (jsxRuntime.jsx(IconButton, { className: clsx('str-video__menu-toggle-button', {
|
|
@@ -710,6 +715,45 @@ const LoadingIndicator = ({ className, type = 'spinner', text, tooltip, }) => {
|
|
|
710
715
|
return (jsxRuntime.jsxs("div", { className: clsx('str-video__loading-indicator', className), title: tooltip, children: [jsxRuntime.jsx("div", { className: clsx('str-video__loading-indicator__icon', type) }), text && jsxRuntime.jsx("p", { className: "str-video__loading-indicator-text", children: text })] }));
|
|
711
716
|
};
|
|
712
717
|
|
|
718
|
+
const Tooltip = ({ children, referenceElement, tooltipClassName, tooltipPlacement = 'top', visible = false, }) => {
|
|
719
|
+
const { refs, x, y, strategy } = useFloatingUIPreset({
|
|
720
|
+
placement: tooltipPlacement,
|
|
721
|
+
strategy: 'absolute',
|
|
722
|
+
});
|
|
723
|
+
react.useEffect(() => {
|
|
724
|
+
refs.setReference(referenceElement);
|
|
725
|
+
}, [referenceElement, refs]);
|
|
726
|
+
if (!visible)
|
|
727
|
+
return null;
|
|
728
|
+
return (jsxRuntime.jsx("div", { className: clsx('str-video__tooltip', tooltipClassName), ref: refs.setFloating, style: {
|
|
729
|
+
position: strategy,
|
|
730
|
+
top: y ?? 0,
|
|
731
|
+
left: x ?? 0,
|
|
732
|
+
overflowY: 'auto',
|
|
733
|
+
}, children: children }));
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
const useEnterLeaveHandlers = ({ onMouseEnter, onMouseLeave, } = {}) => {
|
|
737
|
+
const [tooltipVisible, setTooltipVisible] = react.useState(false);
|
|
738
|
+
const handleMouseEnter = react.useCallback((e) => {
|
|
739
|
+
setTooltipVisible(true);
|
|
740
|
+
onMouseEnter?.(e);
|
|
741
|
+
}, [onMouseEnter]);
|
|
742
|
+
const handleMouseLeave = react.useCallback((e) => {
|
|
743
|
+
setTooltipVisible(false);
|
|
744
|
+
onMouseLeave?.(e);
|
|
745
|
+
}, [onMouseLeave]);
|
|
746
|
+
return { handleMouseEnter, handleMouseLeave, tooltipVisible };
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
// todo: duplicate of CallParticipantList.tsx#MediaIndicator - refactor to a single component
|
|
750
|
+
const WithTooltip = ({ title, tooltipClassName, tooltipPlacement, tooltipDisabled, ...props }) => {
|
|
751
|
+
const { handleMouseEnter, handleMouseLeave, tooltipVisible } = useEnterLeaveHandlers();
|
|
752
|
+
const [tooltipAnchor, setTooltipAnchor] = react.useState(null);
|
|
753
|
+
const tooltipActuallyVisible = !tooltipDisabled && Boolean(title) && tooltipVisible;
|
|
754
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Tooltip, { referenceElement: tooltipAnchor, visible: tooltipActuallyVisible, tooltipClassName: tooltipClassName, tooltipPlacement: tooltipPlacement, children: title || '' }), jsxRuntime.jsx("div", { ref: setTooltipAnchor, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, ...props })] }));
|
|
755
|
+
};
|
|
756
|
+
|
|
713
757
|
const RecordEndConfirmation = () => {
|
|
714
758
|
const { t } = videoReactBindings.useI18n();
|
|
715
759
|
const { toggleCallRecording, isAwaitingResponse } = useToggleCallRecording();
|
|
@@ -728,15 +772,18 @@ const RecordCallConfirmationButton = ({ caption, }) => {
|
|
|
728
772
|
videoClient.OwnCapability.STOP_RECORD_CALL,
|
|
729
773
|
], children: jsxRuntime.jsx(MenuToggle, { ToggleButton: ToggleEndRecordingMenuButton, visualType: exports.MenuVisualType.PORTAL, children: jsxRuntime.jsx(RecordEndConfirmation, {}) }) }));
|
|
730
774
|
}
|
|
775
|
+
const title = isAwaitingResponse
|
|
776
|
+
? t('Waiting for recording to start...')
|
|
777
|
+
: caption ?? t('Record call');
|
|
731
778
|
return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [
|
|
732
779
|
videoClient.OwnCapability.START_RECORD_CALL,
|
|
733
780
|
videoClient.OwnCapability.STOP_RECORD_CALL,
|
|
734
|
-
], children: jsxRuntime.jsx(
|
|
781
|
+
], children: jsxRuntime.jsx(WithTooltip, { title: title, children: jsxRuntime.jsx(CompositeButton, { active: isCallRecordingInProgress, caption: caption, variant: "secondary", "data-testid": "recording-start-button", onClick: isAwaitingResponse ? undefined : toggleCallRecording, children: isAwaitingResponse ? (jsxRuntime.jsx(LoadingIndicator, {})) : (jsxRuntime.jsx(Icon, { icon: "recording-off" })) }) }) }));
|
|
735
782
|
};
|
|
736
783
|
const RecordCallButton = ({ caption }) => {
|
|
737
784
|
const { t } = videoReactBindings.useI18n();
|
|
738
785
|
const { toggleCallRecording, isAwaitingResponse, isCallRecordingInProgress } = useToggleCallRecording();
|
|
739
|
-
let title = caption
|
|
786
|
+
let title = caption ?? t('Record call');
|
|
740
787
|
if (isAwaitingResponse) {
|
|
741
788
|
title = isCallRecordingInProgress
|
|
742
789
|
? t('Waiting for recording to stop...')
|
|
@@ -808,18 +855,48 @@ const ReactionsButton = ({ reactions = defaultReactions, }) => {
|
|
|
808
855
|
};
|
|
809
856
|
const ToggleReactionsMenuButton = react.forwardRef(function ToggleReactionsMenuButton({ menuShown }, ref) {
|
|
810
857
|
const { t } = videoReactBindings.useI18n();
|
|
811
|
-
return (jsxRuntime.jsx(CompositeButton, { ref: ref, active: menuShown, variant: "primary",
|
|
858
|
+
return (jsxRuntime.jsx(WithTooltip, { title: t('Reactions'), tooltipDisabled: menuShown, children: jsxRuntime.jsx(CompositeButton, { ref: ref, active: menuShown, variant: "primary", children: jsxRuntime.jsx(Icon, { icon: "reactions" }) }) }));
|
|
812
859
|
});
|
|
813
860
|
const DefaultReactionsMenu = ({ reactions, layout = 'horizontal', }) => {
|
|
814
861
|
const call = videoReactBindings.useCall();
|
|
862
|
+
const { close } = useMenuContext();
|
|
815
863
|
return (jsxRuntime.jsx("div", { className: clsx('str-video__reactions-menu', {
|
|
816
864
|
'str-video__reactions-menu--horizontal': layout === 'horizontal',
|
|
817
865
|
'str-video__reactions-menu--vertical': layout === 'vertical',
|
|
818
866
|
}), children: reactions.map((reaction) => (jsxRuntime.jsx("button", { type: "button", className: "str-video__reactions-menu__button", onClick: () => {
|
|
819
867
|
call?.sendReaction(reaction);
|
|
868
|
+
close?.();
|
|
820
869
|
}, children: reaction.emoji_code && defaultEmojiReactionMap[reaction.emoji_code] }, reaction.emoji_code))) }));
|
|
821
870
|
};
|
|
822
871
|
|
|
872
|
+
/**
|
|
873
|
+
* Wraps an event handler, silencing and logging exceptions (excluding the NotAllowedError
|
|
874
|
+
* DOMException, which is a normal situation handled by the SDK)
|
|
875
|
+
*
|
|
876
|
+
* @param props component props, including the onError callback
|
|
877
|
+
* @param handler event handler to wrap
|
|
878
|
+
*/
|
|
879
|
+
const createCallControlHandler = (props, handler) => {
|
|
880
|
+
const logger = videoClient.getLogger(['react-sdk']);
|
|
881
|
+
return async () => {
|
|
882
|
+
try {
|
|
883
|
+
await handler();
|
|
884
|
+
}
|
|
885
|
+
catch (error) {
|
|
886
|
+
if (props.onError) {
|
|
887
|
+
props.onError(error);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
if (!isNotAllowedError(error)) {
|
|
891
|
+
logger('error', 'Call control handler failed', error);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
};
|
|
896
|
+
function isNotAllowedError(error) {
|
|
897
|
+
return error instanceof DOMException && error.name === 'NotAllowedError';
|
|
898
|
+
}
|
|
899
|
+
|
|
823
900
|
const ScreenShareButton = (props) => {
|
|
824
901
|
const { t } = videoReactBindings.useI18n();
|
|
825
902
|
const { caption } = props;
|
|
@@ -832,16 +909,17 @@ const ScreenShareButton = (props) => {
|
|
|
832
909
|
const amIScreenSharing = !optimisticIsMute;
|
|
833
910
|
const disableScreenShareButton = !amIScreenSharing &&
|
|
834
911
|
(isSomeoneScreenSharing || isScreenSharingAllowed === false);
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
912
|
+
const handleClick = createCallControlHandler(props, async () => {
|
|
913
|
+
if (!hasPermission) {
|
|
914
|
+
await requestPermission();
|
|
915
|
+
}
|
|
916
|
+
else {
|
|
917
|
+
await screenShare.toggle();
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SCREENSHARE], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SCREENSHARE, isAwaitingApproval: isAwaitingPermission, messageApproved: t('You can now share your screen.'), messageAwaitingApproval: t('Awaiting for an approval to share screen.'), messageRevoked: t('You can no longer share your screen.'), children: jsxRuntime.jsx(WithTooltip, { title: caption ?? t('Share screen'), children: jsxRuntime.jsx(CompositeButton, { active: isSomeoneScreenSharing || amIScreenSharing, caption: caption, variant: "primary", "data-testid": isSomeoneScreenSharing
|
|
921
|
+
? 'screen-share-stop-button'
|
|
922
|
+
: 'screen-share-start-button', disabled: disableScreenShareButton, onClick: handleClick, children: jsxRuntime.jsx(Icon, { icon: isSomeoneScreenSharing ? 'screen-share-on' : 'screen-share-off' }) }) }) }) }));
|
|
845
923
|
};
|
|
846
924
|
|
|
847
925
|
const SelectContext = react.createContext({});
|
|
@@ -923,6 +1001,7 @@ const DeviceSelectorOption = ({ disabled, id, label, onChange, name, selected, d
|
|
|
923
1001
|
};
|
|
924
1002
|
const DeviceSelectorList = (props) => {
|
|
925
1003
|
const { devices = [], selectedDeviceId: selectedDeviceFromProps, title, type, onChange, } = props;
|
|
1004
|
+
const { close } = useMenuContext();
|
|
926
1005
|
// sometimes the browser (Chrome) will report the system-default device
|
|
927
1006
|
// with an id of 'default'. In case when it doesn't, we'll select the first
|
|
928
1007
|
// available device.
|
|
@@ -934,6 +1013,7 @@ const DeviceSelectorList = (props) => {
|
|
|
934
1013
|
return (jsxRuntime.jsxs("div", { className: "str-video__device-settings__device-kind", children: [title && (jsxRuntime.jsx("div", { className: "str-video__device-settings__device-selector-title", children: title })), !devices.length ? (jsxRuntime.jsx(DeviceSelectorOption, { id: `${type}--default`, label: "Default", name: type, defaultChecked: true, value: "default" })) : (devices.map((device) => {
|
|
935
1014
|
return (jsxRuntime.jsx(DeviceSelectorOption, { id: `${type}--${device.deviceId}`, value: device.deviceId, label: device.label, onChange: (e) => {
|
|
936
1015
|
onChange?.(e.target.value);
|
|
1016
|
+
close?.();
|
|
937
1017
|
}, name: type, selected: device.deviceId === selectedDeviceId || devices.length === 1 }, device.deviceId));
|
|
938
1018
|
}))] }));
|
|
939
1019
|
};
|
|
@@ -1007,11 +1087,13 @@ const ToggleAudioPreviewButton = (props) => {
|
|
|
1007
1087
|
const { t } = videoReactBindings.useI18n();
|
|
1008
1088
|
const { useMicrophoneState } = videoReactBindings.useCallStateHooks();
|
|
1009
1089
|
const { microphone, optimisticIsMute, hasBrowserPermission } = useMicrophoneState();
|
|
1010
|
-
|
|
1090
|
+
const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
|
|
1091
|
+
const handleClick = createCallControlHandler(props, () => microphone.toggle());
|
|
1092
|
+
return (jsxRuntime.jsx(WithTooltip, { title: !hasBrowserPermission
|
|
1011
1093
|
? t('Check your browser audio permissions')
|
|
1012
|
-
: caption
|
|
1013
|
-
|
|
1014
|
-
|
|
1094
|
+
: caption ?? t('Mic'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", disabled: !hasBrowserPermission, "data-testid": optimisticIsMute
|
|
1095
|
+
? 'preview-audio-unmute-button'
|
|
1096
|
+
: 'preview-audio-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, onMenuToggle: (shown) => setTooltipDisabled(shown), ...restCompositeButtonProps, children: [jsxRuntime.jsx(Icon, { icon: !optimisticIsMute ? 'mic' : 'mic-off' }), !hasBrowserPermission && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", title: t('Check your browser audio permissions'), children: "!" }))] }) }));
|
|
1015
1097
|
};
|
|
1016
1098
|
const ToggleAudioPublishingButton = (props) => {
|
|
1017
1099
|
const { t } = videoReactBindings.useI18n();
|
|
@@ -1019,18 +1101,20 @@ const ToggleAudioPublishingButton = (props) => {
|
|
|
1019
1101
|
const { hasPermission, requestPermission, isAwaitingPermission } = useRequestPermission(videoClient.OwnCapability.SEND_AUDIO);
|
|
1020
1102
|
const { useMicrophoneState } = videoReactBindings.useCallStateHooks();
|
|
1021
1103
|
const { microphone, optimisticIsMute, hasBrowserPermission } = useMicrophoneState();
|
|
1022
|
-
|
|
1104
|
+
const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
|
|
1105
|
+
const handleClick = createCallControlHandler(props, async () => {
|
|
1106
|
+
if (!hasPermission) {
|
|
1107
|
+
await requestPermission();
|
|
1108
|
+
}
|
|
1109
|
+
else {
|
|
1110
|
+
await microphone.toggle();
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SEND_AUDIO], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SEND_AUDIO, isAwaitingApproval: isAwaitingPermission, messageApproved: t('You can now speak.'), messageAwaitingApproval: t('Awaiting for an approval to speak.'), messageRevoked: t('You can no longer speak.'), children: jsxRuntime.jsx(WithTooltip, { title: !hasPermission
|
|
1023
1114
|
? t('You have no permission to share your audio')
|
|
1024
1115
|
: !hasBrowserPermission
|
|
1025
1116
|
? t('Check your browser mic permissions')
|
|
1026
|
-
: caption
|
|
1027
|
-
if (!hasPermission) {
|
|
1028
|
-
await requestPermission();
|
|
1029
|
-
}
|
|
1030
|
-
else {
|
|
1031
|
-
await microphone.toggle();
|
|
1032
|
-
}
|
|
1033
|
-
}, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, ...restCompositeButtonProps, children: [jsxRuntime.jsx(Icon, { icon: optimisticIsMute ? 'mic-off' : 'mic' }), (!hasBrowserPermission || !hasPermission) && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", children: "!" }))] }) }) }));
|
|
1117
|
+
: caption ?? t('Mic'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission || !hasPermission, "data-testid": optimisticIsMute ? 'audio-unmute-button' : 'audio-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, onMenuToggle: (shown) => setTooltipDisabled(shown), ...restCompositeButtonProps, children: [jsxRuntime.jsx(Icon, { icon: optimisticIsMute ? 'mic-off' : 'mic' }), (!hasBrowserPermission || !hasPermission) && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", children: "!" }))] }) }) }) }));
|
|
1034
1118
|
};
|
|
1035
1119
|
|
|
1036
1120
|
const ToggleVideoPreviewButton = (props) => {
|
|
@@ -1038,11 +1122,13 @@ const ToggleVideoPreviewButton = (props) => {
|
|
|
1038
1122
|
const { t } = videoReactBindings.useI18n();
|
|
1039
1123
|
const { useCameraState } = videoReactBindings.useCallStateHooks();
|
|
1040
1124
|
const { camera, optimisticIsMute, hasBrowserPermission } = useCameraState();
|
|
1041
|
-
|
|
1125
|
+
const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
|
|
1126
|
+
const handleClick = createCallControlHandler(props, () => camera.toggle());
|
|
1127
|
+
return (jsxRuntime.jsx(WithTooltip, { title: !hasBrowserPermission
|
|
1042
1128
|
? t('Check your browser video permissions')
|
|
1043
|
-
: caption
|
|
1044
|
-
|
|
1045
|
-
|
|
1129
|
+
: caption ?? t('Video'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", "data-testid": optimisticIsMute
|
|
1130
|
+
? 'preview-video-unmute-button'
|
|
1131
|
+
: 'preview-video-mute-button', onClick: handleClick, disabled: !hasBrowserPermission, Menu: Menu, menuPlacement: menuPlacement, onMenuToggle: (shown) => setTooltipDisabled(shown), ...restCompositeButtonProps, children: [jsxRuntime.jsx(Icon, { icon: !optimisticIsMute ? 'camera' : 'camera-off' }), !hasBrowserPermission && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", title: t('Check your browser video permissions'), children: "!" }))] }) }));
|
|
1046
1132
|
};
|
|
1047
1133
|
const ToggleVideoPublishingButton = (props) => {
|
|
1048
1134
|
const { t } = videoReactBindings.useI18n();
|
|
@@ -1052,22 +1138,26 @@ const ToggleVideoPublishingButton = (props) => {
|
|
|
1052
1138
|
const { camera, optimisticIsMute, hasBrowserPermission } = useCameraState();
|
|
1053
1139
|
const callSettings = useCallSettings();
|
|
1054
1140
|
const isPublishingVideoAllowed = callSettings?.video.enabled;
|
|
1055
|
-
|
|
1141
|
+
const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
|
|
1142
|
+
const handleClick = createCallControlHandler(props, async () => {
|
|
1143
|
+
if (!hasPermission) {
|
|
1144
|
+
await requestPermission();
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
await camera.toggle();
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SEND_VIDEO], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SEND_VIDEO, isAwaitingApproval: isAwaitingPermission, messageApproved: t('You can now share your video.'), messageAwaitingApproval: t('Awaiting for an approval to share your video.'), messageRevoked: t('You can no longer share your video.'), children: jsxRuntime.jsx(WithTooltip, { title: !hasPermission
|
|
1056
1151
|
? t('You have no permission to share your video')
|
|
1057
1152
|
: !hasBrowserPermission
|
|
1058
1153
|
? t('Check your browser video permissions')
|
|
1059
1154
|
: !isPublishingVideoAllowed
|
|
1060
1155
|
? t('Video publishing is disabled by the system')
|
|
1061
|
-
: caption || t('Video'),
|
|
1062
|
-
if (!hasPermission) {
|
|
1063
|
-
await requestPermission();
|
|
1064
|
-
}
|
|
1065
|
-
else {
|
|
1066
|
-
await camera.toggle();
|
|
1067
|
-
}
|
|
1068
|
-
}, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, ...restCompositeButtonProps, children: [jsxRuntime.jsx(Icon, { icon: optimisticIsMute ? 'camera-off' : 'camera' }), (!hasBrowserPermission ||
|
|
1156
|
+
: caption || t('Video'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission ||
|
|
1069
1157
|
!hasPermission ||
|
|
1070
|
-
!isPublishingVideoAllowed)
|
|
1158
|
+
!isPublishingVideoAllowed, "data-testid": optimisticIsMute ? 'video-unmute-button' : 'video-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, onMenuToggle: (shown) => setTooltipDisabled(shown), ...restCompositeButtonProps, children: [jsxRuntime.jsx(Icon, { icon: optimisticIsMute ? 'camera-off' : 'camera' }), (!hasBrowserPermission ||
|
|
1159
|
+
!hasPermission ||
|
|
1160
|
+
!isPublishingVideoAllowed) && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", children: "!" }))] }) }) }) }));
|
|
1071
1161
|
};
|
|
1072
1162
|
|
|
1073
1163
|
const EndCallMenu = (props) => {
|
|
@@ -1077,7 +1167,7 @@ const EndCallMenu = (props) => {
|
|
|
1077
1167
|
};
|
|
1078
1168
|
const CancelCallToggleMenuButton = react.forwardRef(function CancelCallToggleMenuButton(props, ref) {
|
|
1079
1169
|
const { t } = videoReactBindings.useI18n();
|
|
1080
|
-
return (jsxRuntime.jsx(IconButton, { icon: "call-end", variant: "danger",
|
|
1170
|
+
return (jsxRuntime.jsx(WithTooltip, { title: t('Leave call'), children: jsxRuntime.jsx(IconButton, { icon: "call-end", variant: "danger", "data-testid": "leave-call-button", ref: ref }) }));
|
|
1081
1171
|
});
|
|
1082
1172
|
const CancelCallConfirmButton = ({ onClick, onLeave, }) => {
|
|
1083
1173
|
const call = videoReactBindings.useCall();
|
|
@@ -1101,7 +1191,7 @@ const CancelCallConfirmButton = ({ onClick, onLeave, }) => {
|
|
|
1101
1191
|
}, [onClick, onLeave, call]);
|
|
1102
1192
|
return (jsxRuntime.jsx(MenuToggle, { placement: "top-start", ToggleButton: CancelCallToggleMenuButton, children: jsxRuntime.jsx(EndCallMenu, { onEnd: handleEndCall, onLeave: handleLeave }) }));
|
|
1103
1193
|
};
|
|
1104
|
-
const CancelCallButton = ({ disabled, onClick, onLeave, }) => {
|
|
1194
|
+
const CancelCallButton = ({ disabled, caption, onClick, onLeave, }) => {
|
|
1105
1195
|
const call = videoReactBindings.useCall();
|
|
1106
1196
|
const { t } = videoReactBindings.useI18n();
|
|
1107
1197
|
const handleClick = react.useCallback(async (e) => {
|
|
@@ -1113,10 +1203,10 @@ const CancelCallButton = ({ disabled, onClick, onLeave, }) => {
|
|
|
1113
1203
|
onLeave?.();
|
|
1114
1204
|
}
|
|
1115
1205
|
}, [onClick, onLeave, call]);
|
|
1116
|
-
return (jsxRuntime.jsx(IconButton, { disabled: disabled, icon: "call-end", variant: "danger", title: t('Leave call'), "data-testid": "cancel-call-button", onClick: handleClick }));
|
|
1206
|
+
return (jsxRuntime.jsx(IconButton, { disabled: disabled, icon: "call-end", variant: "danger", title: caption ?? t('Leave call'), "data-testid": "cancel-call-button", onClick: handleClick }));
|
|
1117
1207
|
};
|
|
1118
1208
|
|
|
1119
|
-
const CallControls = ({ onLeave }) => (jsxRuntime.jsxs("div", { className: "str-video__call-controls", children: [jsxRuntime.jsx(
|
|
1209
|
+
const CallControls = ({ onLeave }) => (jsxRuntime.jsxs("div", { className: "str-video__call-controls", children: [jsxRuntime.jsx(SpeakingWhileMutedNotification, { children: jsxRuntime.jsx(ToggleAudioPublishingButton, {}) }), jsxRuntime.jsx(ToggleVideoPublishingButton, {}), jsxRuntime.jsx(ReactionsButton, {}), jsxRuntime.jsx(ScreenShareButton, {}), jsxRuntime.jsx(RecordCallButton, {}), jsxRuntime.jsx(CancelCallButton, { onLeave: onLeave })] }));
|
|
1120
1210
|
|
|
1121
1211
|
chart_js.Chart.register(chart_js.CategoryScale, chart_js.LinearScale, chart_js.LineElement, chart_js.PointElement);
|
|
1122
1212
|
const CallStatsLatencyChart = (props) => {
|
|
@@ -1333,50 +1423,12 @@ const CallParticipantListHeader = ({ onClose, }) => {
|
|
|
1333
1423
|
return (jsxRuntime.jsxs("div", { className: "str-video__participant-list-header", children: [jsxRuntime.jsxs("div", { className: "str-video__participant-list-header__title", children: [t('Participants'), ' ', jsxRuntime.jsxs("span", { className: "str-video__participant-list-header__title-count", children: ["[", participants.length, "]"] }), anonymousParticipantCount > 0 && (jsxRuntime.jsx("span", { className: "str-video__participant-list-header__title-anonymous", children: t('Anonymous', { count: anonymousParticipantCount }) }))] }), jsxRuntime.jsx(IconButton, { onClick: onClose, className: "str-video__participant-list-header__close-button", icon: "close" })] }));
|
|
1334
1424
|
};
|
|
1335
1425
|
|
|
1336
|
-
const Tooltip = ({ children, referenceElement, tooltipClassName, tooltipPlacement = 'top', visible = false, }) => {
|
|
1337
|
-
const { refs, x, y, strategy } = useFloatingUIPreset({
|
|
1338
|
-
placement: tooltipPlacement,
|
|
1339
|
-
strategy: 'absolute',
|
|
1340
|
-
});
|
|
1341
|
-
react.useEffect(() => {
|
|
1342
|
-
refs.setReference(referenceElement);
|
|
1343
|
-
}, [referenceElement, refs]);
|
|
1344
|
-
if (!visible)
|
|
1345
|
-
return null;
|
|
1346
|
-
return (jsxRuntime.jsx("div", { className: clsx('str-video__tooltip', tooltipClassName), ref: refs.setFloating, style: {
|
|
1347
|
-
position: strategy,
|
|
1348
|
-
top: y ?? 0,
|
|
1349
|
-
left: x ?? 0,
|
|
1350
|
-
overflowY: 'auto',
|
|
1351
|
-
}, children: children }));
|
|
1352
|
-
};
|
|
1353
|
-
|
|
1354
|
-
const useEnterLeaveHandlers = ({ onMouseEnter, onMouseLeave, } = {}) => {
|
|
1355
|
-
const [tooltipVisible, setTooltipVisible] = react.useState(false);
|
|
1356
|
-
const handleMouseEnter = react.useCallback((e) => {
|
|
1357
|
-
setTooltipVisible(true);
|
|
1358
|
-
onMouseEnter?.(e);
|
|
1359
|
-
}, [onMouseEnter]);
|
|
1360
|
-
const handleMouseLeave = react.useCallback((e) => {
|
|
1361
|
-
setTooltipVisible(false);
|
|
1362
|
-
onMouseLeave?.(e);
|
|
1363
|
-
}, [onMouseLeave]);
|
|
1364
|
-
return { handleMouseEnter, handleMouseLeave, tooltipVisible };
|
|
1365
|
-
};
|
|
1366
|
-
|
|
1367
|
-
// todo: duplicate of CallParticipantList.tsx#MediaIndicator - refactor to a single component
|
|
1368
|
-
const WithTooltip = ({ title, tooltipClassName, tooltipPlacement, ...props }) => {
|
|
1369
|
-
const { handleMouseEnter, handleMouseLeave, tooltipVisible } = useEnterLeaveHandlers();
|
|
1370
|
-
const [tooltipAnchor, setTooltipAnchor] = react.useState(null);
|
|
1371
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Tooltip, { referenceElement: tooltipAnchor, visible: tooltipVisible, tooltipClassName: tooltipClassName, tooltipPlacement: tooltipPlacement, children: title || '' }), jsxRuntime.jsx("div", { ref: setTooltipAnchor, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, ...props })] }));
|
|
1372
|
-
};
|
|
1373
|
-
|
|
1374
1426
|
const CallParticipantListingItem = ({ participant, DisplayName = DefaultDisplayName, }) => {
|
|
1375
|
-
const isAudioOn =
|
|
1376
|
-
const isVideoOn =
|
|
1377
|
-
const
|
|
1427
|
+
const isAudioOn = videoClient.hasAudio(participant);
|
|
1428
|
+
const isVideoOn = videoClient.hasVideo(participant);
|
|
1429
|
+
const isPinnedOn = videoClient.isPinned(participant);
|
|
1378
1430
|
const { t } = videoReactBindings.useI18n();
|
|
1379
|
-
return (jsxRuntime.jsxs("div", { className: "str-video__participant-listing-item", children: [jsxRuntime.jsx(Avatar, { name: participant.name, imageSrc: participant.image }), jsxRuntime.jsx(DisplayName, { participant: participant }), jsxRuntime.jsxs("div", { className: "str-video__participant-listing-item__media-indicator-group", children: [jsxRuntime.jsx(MediaIndicator, { title: isAudioOn ? t('Microphone on') : t('Microphone off'), className: clsx('str-video__participant-listing-item__icon', `str-video__participant-listing-item__icon-${isAudioOn ? 'mic' : 'mic-off'}`) }), jsxRuntime.jsx(MediaIndicator, { title: isVideoOn ? t('Camera on') : t('Camera off'), className: clsx('str-video__participant-listing-item__icon', `str-video__participant-listing-item__icon-${isVideoOn ? 'camera' : 'camera-off'}`) }),
|
|
1431
|
+
return (jsxRuntime.jsxs("div", { className: "str-video__participant-listing-item", children: [jsxRuntime.jsx(Avatar, { name: participant.name, imageSrc: participant.image }), jsxRuntime.jsx(DisplayName, { participant: participant }), jsxRuntime.jsxs("div", { className: "str-video__participant-listing-item__media-indicator-group", children: [jsxRuntime.jsx(MediaIndicator, { title: isAudioOn ? t('Microphone on') : t('Microphone off'), className: clsx('str-video__participant-listing-item__icon', `str-video__participant-listing-item__icon-${isAudioOn ? 'mic' : 'mic-off'}`) }), jsxRuntime.jsx(MediaIndicator, { title: isVideoOn ? t('Camera on') : t('Camera off'), className: clsx('str-video__participant-listing-item__icon', `str-video__participant-listing-item__icon-${isVideoOn ? 'camera' : 'camera-off'}`) }), isPinnedOn && (jsxRuntime.jsx(MediaIndicator, { title: t('Pinned'), className: clsx('str-video__participant-listing-item__icon', 'str-video__participant-listing-item__icon-pinned') })), jsxRuntime.jsx(MenuToggle, { placement: "bottom-end", ToggleButton: ToggleButton$2, children: jsxRuntime.jsx(ParticipantViewContext.Provider, { value: { participant, trackType: 'none' }, children: jsxRuntime.jsx(ParticipantActionsContextMenu, {}) }) })] })] }));
|
|
1380
1432
|
};
|
|
1381
1433
|
const MediaIndicator = (props) => (jsxRuntime.jsx(WithTooltip, { ...props }));
|
|
1382
1434
|
const DefaultDisplayName = ({ participant }) => {
|
|
@@ -1787,7 +1839,7 @@ const InitialsFallback = (props) => {
|
|
|
1787
1839
|
};
|
|
1788
1840
|
|
|
1789
1841
|
const Video$1 = ({ trackType, participant, className, VideoPlaceholder = DefaultVideoPlaceholder, refs, ...rest }) => {
|
|
1790
|
-
const { sessionId, videoStream, screenShareStream,
|
|
1842
|
+
const { sessionId, videoStream, screenShareStream, viewportVisibilityState, isLocalParticipant, userId, } = participant;
|
|
1791
1843
|
const call = videoReactBindings.useCall();
|
|
1792
1844
|
const [videoElement, setVideoElement] = react.useState(null);
|
|
1793
1845
|
// start with true, will flip once the video starts playing
|
|
@@ -1835,9 +1887,9 @@ const Video$1 = ({ trackType, participant, className, VideoPlaceholder = Default
|
|
|
1835
1887
|
if (!call)
|
|
1836
1888
|
return null;
|
|
1837
1889
|
const isPublishingTrack = trackType === 'videoTrack'
|
|
1838
|
-
?
|
|
1890
|
+
? videoClient.hasVideo(participant)
|
|
1839
1891
|
: trackType === 'screenShareTrack'
|
|
1840
|
-
?
|
|
1892
|
+
? videoClient.hasScreenShare(participant)
|
|
1841
1893
|
: false;
|
|
1842
1894
|
const isInvisible = trackType === 'none' ||
|
|
1843
1895
|
viewportVisibilityState?.[trackType] === videoClient.VisibilityState.INVISIBLE;
|
|
@@ -1921,11 +1973,11 @@ const ParticipantActionsContextMenu = () => {
|
|
|
1921
1973
|
const [pictureInPictureElement, setPictureInPictureElement] = react.useState(document.pictureInPictureElement);
|
|
1922
1974
|
const call = videoReactBindings.useCall();
|
|
1923
1975
|
const { t } = videoReactBindings.useI18n();
|
|
1924
|
-
const { pin,
|
|
1925
|
-
const
|
|
1926
|
-
const
|
|
1927
|
-
const
|
|
1928
|
-
const
|
|
1976
|
+
const { pin, sessionId, userId } = participant;
|
|
1977
|
+
const hasAudioTrack = videoClient.hasAudio(participant);
|
|
1978
|
+
const hasVideoTrack = videoClient.hasVideo(participant);
|
|
1979
|
+
const hasScreenShareTrack = videoClient.hasScreenShare(participant);
|
|
1980
|
+
const hasScreenShareAudioTrack = videoClient.hasScreenShareAudio(participant);
|
|
1929
1981
|
const blockUser = () => call?.blockUser(userId);
|
|
1930
1982
|
const muteAudio = () => call?.muteUser(userId, 'audio');
|
|
1931
1983
|
const muteVideo = () => call?.muteUser(userId, 'video');
|
|
@@ -2010,7 +2062,7 @@ const ParticipantActionsContextMenu = () => {
|
|
|
2010
2062
|
return document.exitPictureInPicture().catch(console.error);
|
|
2011
2063
|
};
|
|
2012
2064
|
const { close } = useMenuContext() || {};
|
|
2013
|
-
return (jsxRuntime.jsxs(GenericMenu, { onItemClick: close, children: [jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: toggleParticipantPin, disabled: pin && !pin.isLocalPin, children: [jsxRuntime.jsx(Icon, { icon: "pin" }), pin ? t('Unpin') : t('Pin')] }), jsxRuntime.jsxs(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.PIN_FOR_EVERYONE], children: [jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: pinForEveryone, disabled: pin && !pin.isLocalPin, children: [jsxRuntime.jsx(Icon, { icon: "pin" }), t('Pin for everyone')] }), jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: unpinForEveryone, disabled: !pin || pin.isLocalPin, children: [jsxRuntime.jsx(Icon, { icon: "pin" }), t('Unpin for everyone')] })] }), jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.BLOCK_USERS], children: jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: blockUser, children: [jsxRuntime.jsx(Icon, { icon: "not-allowed" }), t('Block')] }) }), jsxRuntime.jsxs(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.MUTE_USERS], children: [
|
|
2065
|
+
return (jsxRuntime.jsxs(GenericMenu, { onItemClick: close, children: [jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: toggleParticipantPin, disabled: pin && !pin.isLocalPin, children: [jsxRuntime.jsx(Icon, { icon: "pin" }), pin ? t('Unpin') : t('Pin')] }), jsxRuntime.jsxs(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.PIN_FOR_EVERYONE], children: [jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: pinForEveryone, disabled: pin && !pin.isLocalPin, children: [jsxRuntime.jsx(Icon, { icon: "pin" }), t('Pin for everyone')] }), jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: unpinForEveryone, disabled: !pin || pin.isLocalPin, children: [jsxRuntime.jsx(Icon, { icon: "pin" }), t('Unpin for everyone')] })] }), jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.BLOCK_USERS], children: jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: blockUser, children: [jsxRuntime.jsx(Icon, { icon: "not-allowed" }), t('Block')] }) }), jsxRuntime.jsxs(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.MUTE_USERS], children: [hasVideoTrack && (jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: muteVideo, children: [jsxRuntime.jsx(Icon, { icon: "camera-off-outline" }), t('Turn off video')] })), hasScreenShareTrack && (jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: muteScreenShare, children: [jsxRuntime.jsx(Icon, { icon: "screen-share-off" }), t('Turn off screen share')] })), hasAudioTrack && (jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: muteAudio, children: [jsxRuntime.jsx(Icon, { icon: "no-audio" }), t('Mute audio')] })), hasScreenShareAudioTrack && (jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: muteScreenShareAudio, children: [jsxRuntime.jsx(Icon, { icon: "no-audio" }), t('Mute screen share audio')] }))] }), participantViewElement && (jsxRuntime.jsx(GenericMenuButtonItem, { onClick: toggleFullscreenMode, children: t('{{ direction }} fullscreen', {
|
|
2014
2066
|
direction: fullscreenModeOn ? t('Leave') : t('Enter'),
|
|
2015
2067
|
}) })), videoElement && document.pictureInPictureEnabled && (jsxRuntime.jsx(GenericMenuButtonItem, { onClick: togglePictureInPicture, children: t('{{ direction }} picture-in-picture', {
|
|
2016
2068
|
direction: pictureInPictureElement === videoElement
|
|
@@ -2045,10 +2097,9 @@ const DefaultScreenShareOverlay = () => {
|
|
|
2045
2097
|
};
|
|
2046
2098
|
const DefaultParticipantViewUI = ({ indicatorsVisible = true, menuPlacement = 'bottom-start', showMenuButton = true, ParticipantActionsContextMenu: ParticipantActionsContextMenu$1 = ParticipantActionsContextMenu, }) => {
|
|
2047
2099
|
const { participant, trackType } = useParticipantViewContext();
|
|
2048
|
-
const
|
|
2049
|
-
const hasScreenShare = publishedTracks.includes(videoClient.SfuModels.TrackType.SCREEN_SHARE);
|
|
2100
|
+
const isScreenSharing = videoClient.hasScreenShare(participant);
|
|
2050
2101
|
if (participant.isLocalParticipant &&
|
|
2051
|
-
|
|
2102
|
+
isScreenSharing &&
|
|
2052
2103
|
trackType === 'screenShareTrack') {
|
|
2053
2104
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(DefaultScreenShareOverlay, {}), jsxRuntime.jsx(ParticipantDetails, { indicatorsVisible: indicatorsVisible })] }));
|
|
2054
2105
|
}
|
|
@@ -2056,15 +2107,15 @@ const DefaultParticipantViewUI = ({ indicatorsVisible = true, menuPlacement = 'b
|
|
|
2056
2107
|
};
|
|
2057
2108
|
const ParticipantDetails = ({ indicatorsVisible = true, }) => {
|
|
2058
2109
|
const { participant } = useParticipantViewContext();
|
|
2059
|
-
const { isLocalParticipant, connectionQuality,
|
|
2110
|
+
const { isLocalParticipant, connectionQuality, pin, sessionId, name, userId, } = participant;
|
|
2060
2111
|
const call = videoReactBindings.useCall();
|
|
2061
2112
|
const { t } = videoReactBindings.useI18n();
|
|
2062
2113
|
const connectionQualityAsString = !!connectionQuality &&
|
|
2063
2114
|
videoClient.SfuModels.ConnectionQuality[connectionQuality].toLowerCase();
|
|
2064
|
-
const
|
|
2065
|
-
const
|
|
2115
|
+
const hasAudioTrack = videoClient.hasAudio(participant);
|
|
2116
|
+
const hasVideoTrack = videoClient.hasVideo(participant);
|
|
2066
2117
|
const canUnpin = !!pin && pin.isLocalPin;
|
|
2067
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "str-video__participant-details", children: jsxRuntime.jsxs("span", { className: "str-video__participant-details__name", children: [name || userId, indicatorsVisible && !
|
|
2118
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "str-video__participant-details", children: jsxRuntime.jsxs("span", { className: "str-video__participant-details__name", children: [name || userId, indicatorsVisible && !hasAudioTrack && (jsxRuntime.jsx("span", { className: "str-video__participant-details__name--audio-muted" })), indicatorsVisible && !hasVideoTrack && (jsxRuntime.jsx("span", { className: "str-video__participant-details__name--video-muted" })), indicatorsVisible && canUnpin && (
|
|
2068
2119
|
// TODO: remove this monstrosity once we have a proper design
|
|
2069
2120
|
jsxRuntime.jsx("span", { title: t('Unpin'), onClick: () => call?.unpin(sessionId), className: "str-video__participant-details__name--pinned" })), indicatorsVisible && jsxRuntime.jsx(SpeechIndicator, {})] }) }), indicatorsVisible && (jsxRuntime.jsx(Notification, { isVisible: isLocalParticipant &&
|
|
2070
2121
|
connectionQuality === videoClient.SfuModels.ConnectionQuality.POOR, message: t('Poor connection quality'), children: connectionQualityAsString && (jsxRuntime.jsx("span", { className: clsx('str-video__participant-details__connection-quality', `str-video__participant-details__connection-quality--${connectionQualityAsString}`), title: connectionQualityAsString })) }))] }));
|
|
@@ -2076,10 +2127,10 @@ const SpeechIndicator = () => {
|
|
|
2076
2127
|
};
|
|
2077
2128
|
|
|
2078
2129
|
const ParticipantView = react.forwardRef(function ParticipantView({ participant, trackType = 'videoTrack', muteAudio, refs: { setVideoElement, setVideoPlaceholderElement } = {}, className, VideoPlaceholder, ParticipantViewUI = DefaultParticipantViewUI, }, ref) {
|
|
2079
|
-
const { isLocalParticipant, isSpeaking, isDominantSpeaker,
|
|
2080
|
-
const
|
|
2081
|
-
const
|
|
2082
|
-
const
|
|
2130
|
+
const { isLocalParticipant, isSpeaking, isDominantSpeaker, sessionId } = participant;
|
|
2131
|
+
const hasAudioTrack = videoClient.hasAudio(participant);
|
|
2132
|
+
const hasVideoTrack = videoClient.hasVideo(participant);
|
|
2133
|
+
const hasScreenShareAudioTrack = videoClient.hasScreenShareAudio(participant);
|
|
2083
2134
|
const [trackedElement, setTrackedElement] = react.useState(null);
|
|
2084
2135
|
const [contextVideoElement, setContextVideoElement] = react.useState(null);
|
|
2085
2136
|
const [contextVideoPlaceholderElement, setContextVideoPlaceholderElement] = react.useState(null);
|
|
@@ -2115,7 +2166,7 @@ const ParticipantView = react.forwardRef(function ParticipantView({ participant,
|
|
|
2115
2166
|
return (jsxRuntime.jsx("div", { "data-testid": "participant-view", ref: (element) => {
|
|
2116
2167
|
applyElementToRef(ref, element);
|
|
2117
2168
|
setTrackedElement(element);
|
|
2118
|
-
}, className: clsx('str-video__participant-view', isDominantSpeaker && 'str-video__participant-view--dominant-speaker', isSpeaking && 'str-video__participant-view--speaking', !
|
|
2169
|
+
}, className: clsx('str-video__participant-view', isDominantSpeaker && 'str-video__participant-view--dominant-speaker', isSpeaking && 'str-video__participant-view--speaking', !hasVideoTrack && 'str-video__participant-view--no-video', !hasAudioTrack && 'str-video__participant-view--no-audio', className), children: jsxRuntime.jsxs(ParticipantViewContext.Provider, { value: participantViewContextValue, children: [!isLocalParticipant && !muteAudio && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasAudioTrack && (jsxRuntime.jsx(Audio, { participant: participant, trackType: "audioTrack" })), hasScreenShareAudioTrack && (jsxRuntime.jsx(Audio, { participant: participant, trackType: "screenShareAudioTrack" }))] })), jsxRuntime.jsx(Video$1, { VideoPlaceholder: VideoPlaceholder, participant: participant, trackType: trackType, refs: videoRefs, autoPlay: true }), isComponentType(ParticipantViewUI) ? (jsxRuntime.jsx(ParticipantViewUI, {})) : (ParticipantViewUI)] }) }));
|
|
2119
2170
|
});
|
|
2120
2171
|
|
|
2121
2172
|
// re-exporting the StreamCallProvider as StreamCall
|
|
@@ -2284,12 +2335,13 @@ const loggedIn = (a, b) => {
|
|
|
2284
2335
|
const LivestreamLayout = (props) => {
|
|
2285
2336
|
const { useParticipants, useRemoteParticipants, useHasOngoingScreenShare } = videoReactBindings.useCallStateHooks();
|
|
2286
2337
|
const call = videoReactBindings.useCall();
|
|
2287
|
-
const
|
|
2338
|
+
const participants = useParticipants();
|
|
2339
|
+
const [currentSpeaker] = participants;
|
|
2288
2340
|
const remoteParticipants = useRemoteParticipants();
|
|
2289
2341
|
const hasOngoingScreenShare = useHasOngoingScreenShare();
|
|
2290
2342
|
const presenter = hasOngoingScreenShare
|
|
2291
|
-
? hasScreenShare
|
|
2292
|
-
:
|
|
2343
|
+
? participants.find(videoClient.hasScreenShare)
|
|
2344
|
+
: undefined;
|
|
2293
2345
|
usePaginatedLayoutSortPreset(call);
|
|
2294
2346
|
const Overlay = (jsxRuntime.jsx(ParticipantOverlay, { showParticipantCount: props.showParticipantCount, showDuration: props.showDuration, showLiveBadge: props.showLiveBadge, showSpeakerName: props.showSpeakerName }));
|
|
2295
2347
|
const { floatingParticipantProps } = props;
|
|
@@ -2303,7 +2355,6 @@ const LivestreamLayout = (props) => {
|
|
|
2303
2355
|
clsx('str-video__livestream-layout__floating-participant', `str-video__livestream-layout__floating-participant--${floatingParticipantProps?.position ?? 'top-right'}`)), participant: currentSpeaker, ParticipantViewUI: FloatingParticipantOverlay || Overlay, muteAudio // audio is rendered by ParticipantsAudio
|
|
2304
2356
|
: true }))] }));
|
|
2305
2357
|
};
|
|
2306
|
-
const hasScreenShare$1 = (p) => !!p?.publishedTracks.includes(videoClient.SfuModels.TrackType.SCREEN_SHARE);
|
|
2307
2358
|
const ParticipantOverlay = (props) => {
|
|
2308
2359
|
const { enableFullScreen = true, showParticipantCount = true, showDuration = true, showLiveBadge = true, showSpeakerName = false, } = props;
|
|
2309
2360
|
const { participant } = useParticipantViewContext();
|
|
@@ -2467,7 +2518,7 @@ const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, Par
|
|
|
2467
2518
|
const [participantsBarWrapperElement, setParticipantsBarWrapperElement] = react.useState(null);
|
|
2468
2519
|
const [participantsBarElement, setParticipantsBarElement] = react.useState(null);
|
|
2469
2520
|
const [buttonsWrapperElement, setButtonsWrapperElement] = react.useState(null);
|
|
2470
|
-
const isSpeakerScreenSharing = hasScreenShare(participantInSpotlight);
|
|
2521
|
+
const isSpeakerScreenSharing = participantInSpotlight && videoClient.hasScreenShare(participantInSpotlight);
|
|
2471
2522
|
const hardLimit = useCalculateHardLimit(buttonsWrapperElement, participantsBarElement, participantsBarLimit);
|
|
2472
2523
|
const isVertical = participantsBarPosition === 'left' || participantsBarPosition === 'right';
|
|
2473
2524
|
const isHorizontal = participantsBarPosition === 'top' || participantsBarPosition === 'bottom';
|
|
@@ -2515,9 +2566,8 @@ const VerticalScrollButtons = ({ scrollWrapper, }) => {
|
|
|
2515
2566
|
};
|
|
2516
2567
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [scrollPosition && scrollPosition !== 'top' && (jsxRuntime.jsx(IconButton, { onClick: scrollTopClickHandler, icon: "caret-up", className: "str-video__speaker-layout__participants-bar--button-top" })), scrollPosition && scrollPosition !== 'bottom' && (jsxRuntime.jsx(IconButton, { onClick: scrollBottomClickHandler, icon: "caret-down", className: "str-video__speaker-layout__participants-bar--button-bottom" }))] }));
|
|
2517
2568
|
};
|
|
2518
|
-
const hasScreenShare = (p) => !!p?.publishedTracks.includes(videoClient.SfuModels.TrackType.SCREEN_SHARE);
|
|
2519
2569
|
|
|
2520
|
-
const [major, minor, patch] = ("1.0.
|
|
2570
|
+
const [major, minor, patch] = ("1.0.8" ).split('.');
|
|
2521
2571
|
videoClient.setSdkInfo({
|
|
2522
2572
|
type: videoClient.SfuModels.SdkType.REACT,
|
|
2523
2573
|
major,
|