communication-react-19
Version:
React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)
146 lines • 10.5 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { mergeStyles, Stack, Spinner } from '@fluentui/react';
import { concatStyleSets, Layer } from '@fluentui/react';
import { _formatString } from "../../../acs-ui-common/src";
import React, { useMemo } from 'react';
import { useCallback } from 'react';
import { LocalVideoCameraCycleButton } from './LocalVideoCameraButton';
import { StreamMedia } from './StreamMedia';
import { useLocalVideoStreamLifecycleMaintainer } from './VideoGallery/useVideoStreamLifecycleMaintainer';
import { VideoTile } from './VideoTile';
import { useTheme } from '../theming';
import { MeetingReactionOverlay } from './MeetingReactionOverlay';
import { useVideoTileContextualMenuProps } from './VideoGallery/useVideoTileContextualMenuProps';
import { _DrawerMenu } from './Drawer';
import { drawerMenuWrapperStyles } from './VideoGallery/styles/RemoteVideoTile.styles';
import { videoContainerStyles, overlayStyles, overlayStylesTransparent, loadSpinnerStyles } from './styles/VideoTile.styles';
/**
* A memoized version of VideoTile for rendering local participant.
*
* @internal
*/
export const _LocalVideoTile = React.memo((props) => {
const { isAvailable, isMuted, onCreateLocalStreamView, onDisposeLocalStreamView, localVideoViewOptions, renderElement, userId, showLabel, alwaysShowLabelBackground, displayName, initialsName, onRenderAvatar, showMuteIndicator, styles, showCameraSwitcherInLocalPreview, localVideoCameraCycleButtonProps, localVideoCameraSwitcherLabel, localVideoSelectedDescription, raisedHand, reaction, isSpotlighted, spotlightedParticipantUserIds, onStartSpotlight, onStopSpotlight, maxParticipantsToSpotlight, menuKind, strings, reactionResources, isScreenSharingOn, mediaAccess } = props;
const theme = useTheme();
const localVideoStreamProps = useMemo(() => ({
isMirrored: localVideoViewOptions === null || localVideoViewOptions === void 0 ? void 0 : localVideoViewOptions.isMirrored,
isStreamAvailable: isAvailable,
onCreateLocalStreamView,
onDisposeLocalStreamView,
renderElementExists: !!renderElement,
scalingMode: localVideoViewOptions === null || localVideoViewOptions === void 0 ? void 0 : localVideoViewOptions.scalingMode,
isVideoPermitted: mediaAccess ? mediaAccess.isVideoPermitted : true
}), [
isAvailable,
localVideoViewOptions === null || localVideoViewOptions === void 0 ? void 0 : localVideoViewOptions.isMirrored,
localVideoViewOptions === null || localVideoViewOptions === void 0 ? void 0 : localVideoViewOptions.scalingMode,
onCreateLocalStreamView,
onDisposeLocalStreamView,
renderElement,
mediaAccess
]);
// Handle creating, destroying and updating the video stream as necessary
useLocalVideoStreamLifecycleMaintainer(localVideoStreamProps);
const contextualMenuProps = useVideoTileContextualMenuProps({
participant: { userId: userId !== null && userId !== void 0 ? userId : '' },
strings: Object.assign({}, strings),
spotlightedParticipantUserIds,
isSpotlighted,
onStartSpotlight,
onStopSpotlight,
maxParticipantsToSpotlight,
myUserId: userId
});
const videoTileContextualMenuProps = useMemo(() => {
if (menuKind !== 'contextual' || !contextualMenuProps) {
return {};
}
return {
contextualMenu: contextualMenuProps
};
}, [contextualMenuProps, menuKind]);
const videoTileStyles = useMemo(() => {
if (isSpotlighted) {
return concatStyleSets({
root: {
outline: `0.25rem solid ${theme.palette.neutralTertiaryAlt}`,
outlineOffset: '-0.25rem'
}
}, styles);
}
return styles;
}, [isSpotlighted, theme.palette.neutralTertiaryAlt, styles]);
const [drawerMenuItemProps, setDrawerMenuItemProps] = React.useState([]);
const onKeyDown = useCallback((e) => {
if (e.key === 'Enter') {
setDrawerMenuItemProps(convertContextualMenuItemsToDrawerMenuItemProps(contextualMenuProps, () => setDrawerMenuItemProps([])));
}
}, [setDrawerMenuItemProps, contextualMenuProps]);
const renderVideoStreamElement = useMemo(() => {
// Checking if renderElement is well defined or not as calling SDK has a number of video streams limitation which
// implies that, after their threshold, all streams have no child (blank video)
if (!renderElement || !renderElement.childElementCount) {
// Returning `undefined` results in the placeholder with avatar being shown
return undefined;
}
return (React.createElement(React.Fragment, null,
React.createElement(FloatingLocalCameraCycleButton, { showCameraSwitcherInLocalPreview: showCameraSwitcherInLocalPreview !== null && showCameraSwitcherInLocalPreview !== void 0 ? showCameraSwitcherInLocalPreview : false, localVideoCameraCycleButtonProps: localVideoCameraCycleButtonProps, localVideoCameraSwitcherLabel: localVideoCameraSwitcherLabel, localVideoSelectedDescription: localVideoSelectedDescription }),
React.createElement(StreamMedia, { videoStreamElement: renderElement, isMirrored: true }),
props.participantsCount === 1 && !isScreenSharingOn && (React.createElement(Stack, { className: mergeStyles(videoContainerStyles, overlayStyles()) },
React.createElement(Spinner, { label: strings === null || strings === void 0 ? void 0 : strings.waitingScreenText, ariaLive: "assertive", role: "alert", labelPosition: "bottom", styles: loadSpinnerStyles(theme, true) })))));
}, [
localVideoCameraCycleButtonProps,
localVideoCameraSwitcherLabel,
localVideoSelectedDescription,
renderElement,
showCameraSwitcherInLocalPreview,
props.participantsCount,
strings === null || strings === void 0 ? void 0 : strings.waitingScreenText,
theme,
isScreenSharingOn
]);
const videoTileOverlay = useMemo(() => {
const reactionOverlay = reactionResources !== undefined ? (React.createElement(MeetingReactionOverlay, { overlayMode: "grid-tiles", reaction: reaction, reactionResources: reactionResources })) : undefined;
return reactionOverlay;
}, [reaction, reactionResources]);
const onRenderAvatarOneParticipant = useCallback(() => {
return (React.createElement(Stack, { className: mergeStyles(videoContainerStyles, overlayStylesTransparent()) },
React.createElement(Spinner, { label: strings === null || strings === void 0 ? void 0 : strings.waitingScreenText, ariaLive: "assertive", labelPosition: "bottom", role: "alert", styles: loadSpinnerStyles(theme, false) })));
}, [strings === null || strings === void 0 ? void 0 : strings.waitingScreenText, theme]);
return (React.createElement(Stack, { "data-ui-id": "local-video-tile", className: mergeStyles({ width: '100%', height: '100%' }), onKeyDown: menuKind === 'drawer' ? onKeyDown : undefined },
React.createElement(VideoTile, Object.assign({ key: userId !== null && userId !== void 0 ? userId : 'local-video-tile', userId: userId, renderElement: renderVideoStreamElement, showLabel: showLabel, alwaysShowLabelBackground: alwaysShowLabelBackground, displayName: displayName, initialsName: initialsName, styles: videoTileStyles, onRenderPlaceholder: props.participantsCount === 1 && !isScreenSharingOn ? onRenderAvatarOneParticipant : onRenderAvatar, isMuted: isMuted, showMuteIndicator: showMuteIndicator, personaMinSize: props.personaMinSize, raisedHand: raisedHand, isSpotlighted: isSpotlighted }, videoTileContextualMenuProps, { onLongTouch: () => setDrawerMenuItemProps(convertContextualMenuItemsToDrawerMenuItemProps(contextualMenuProps, () => setDrawerMenuItemProps([]))), overlay: videoTileOverlay, mediaAccess: mediaAccess }), drawerMenuItemProps.length > 0 && (React.createElement(Layer, { hostId: props.drawerMenuHostId },
React.createElement(Stack, { styles: drawerMenuWrapperStyles },
React.createElement(_DrawerMenu, { onLightDismiss: () => setDrawerMenuItemProps([]), items: drawerMenuItemProps })))))));
});
const FloatingLocalCameraCycleButton = (props) => {
const { showCameraSwitcherInLocalPreview, localVideoCameraCycleButtonProps, localVideoCameraSwitcherLabel, localVideoSelectedDescription } = props;
const ariaDescription = (localVideoCameraCycleButtonProps === null || localVideoCameraCycleButtonProps === void 0 ? void 0 : localVideoCameraCycleButtonProps.selectedCamera) &&
localVideoSelectedDescription &&
_formatString(localVideoSelectedDescription, {
cameraName: localVideoCameraCycleButtonProps.selectedCamera.name
});
return (React.createElement(Stack, { horizontalAlign: "end" }, showCameraSwitcherInLocalPreview &&
(localVideoCameraCycleButtonProps === null || localVideoCameraCycleButtonProps === void 0 ? void 0 : localVideoCameraCycleButtonProps.cameras) !== undefined &&
(localVideoCameraCycleButtonProps === null || localVideoCameraCycleButtonProps === void 0 ? void 0 : localVideoCameraCycleButtonProps.selectedCamera) !== undefined &&
(localVideoCameraCycleButtonProps === null || localVideoCameraCycleButtonProps === void 0 ? void 0 : localVideoCameraCycleButtonProps.onSelectCamera) !== undefined && (React.createElement(LocalVideoCameraCycleButton, { cameras: localVideoCameraCycleButtonProps.cameras, selectedCamera: localVideoCameraCycleButtonProps.selectedCamera, onSelectCamera: localVideoCameraCycleButtonProps.onSelectCamera, label: localVideoCameraSwitcherLabel, ariaDescription: ariaDescription }))));
};
const convertContextualMenuItemsToDrawerMenuItemProps = (contextualMenuProps, onLightDismiss) => {
if (!contextualMenuProps) {
return [];
}
return contextualMenuProps.items.map((item) => {
return {
itemKey: item.key,
text: item.text,
iconProps: item.iconProps,
disabled: item.disabled,
onItemClick: () => {
var _a;
(_a = item.onClick) === null || _a === void 0 ? void 0 : _a.call(item);
onLightDismiss === null || onLightDismiss === void 0 ? void 0 : onLightDismiss();
}
};
});
};
//# sourceMappingURL=LocalVideoTile.js.map