UNPKG

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
// 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