import { Icon , IStyle , mergeStyles , Persona , Stack , Text } from '@fluentui/react' ; import { Ref } from '@fluentui/react-northstar' ; import React , { useLayoutEffect , useMemo , useRef , useState } from 'react' ; import { useIdentifiers } from '../identifiers' ; import { useTheme } from '../theming' ; import { BaseCustomStyles , CustomAvatarOptions , OnRenderAvatarCallback } from '../types' ; import { disabledVideoHint , displayNameStyle , iconContainerStyle , isSpeakingBorderDiv , overlayContainerStyles , rootStyles , videoContainerStyles , videoHint , tileInfoContainerStyle } from './styles/VideoTile.styles' ; import { getVideoTileOverrideColor } from './utils/videoTileStylesUtils' ; /**
 * Fluent styles for  VideoTile .
 *
 *  public export interface VideoTileStylesProps extends BaseCustomStyles { /** Styles for video container. */ videoContainer ? : IStyle ; /** Styles for container overlayed on the video container. */ overlayContainer ? : IStyle ; /** Styles for displayName on the video container. */ displayNameContainer ? : IStyle ; } /**
 * Props for  VideoTile .
 *
 *  public export interface VideoTileProps { /** React Child components. Child Components will show as overlay component in the VideoTile. */ children ? : React . ReactNode ; Example styles ? : VideoTileStylesProps ; /** user id for the VideoTile placeholder. */ userId ? : string ; /** Component with the video stream. */ renderElement ? : JSX . Element | null ; /** Determines if the video is mirrored or not. */ isMirrored ? : boolean ; /** Custom render Component function for no video is available. Render a Persona Icon if undefined. */ onRenderPlaceholder ? : OnRenderAvatarCallback ; defaultValue showLabel ? : boolean ; defaultValue showMuteIndicator ? : boolean ; /**
     * Whether the video is muted or not.
     */ isMuted ? : boolean ; remarks displayName ? : string ; remarks initialsName ? : string ; /** Optional property to set the aria label of the video tile if there is no available stream. */ noVideoAvailableAriaLabel ? : string ; /** Whether the participant in the videoTile is speaking. Shows a speaking indicator (border). */ isSpeaking ? : boolean ; } const PERSONA_MAX_SIZE = 100 ; const PERSONA_MIN_SIZE = 32 ; const DefaultPlaceholder = ( props ) : JSX . Element => { const { text , noVideoAvailableAriaLabel , coinSize , styles , hidePersonaDetails } = props ; return ( < Stack className = { mergeStyles ( { position : 'absolute' , height : '100%' , width : '100%' } ) } > < Persona styles = { styles } coinSize = { coinSize } hidePersonaDetails = { hidePersonaDetails } text = { text ?? '' } initialsTextColor = "white" aria - label = { noVideoAvailableAriaLabel ?? '' } /> < /Stack>); } ; const defaultPersonaStyles = { root : { margin : 'auto' , maxHeight : '100%' } } ; /**
 * A component to render the video stream for a single call participant.
 *
 * Use with  GridLayout in a  VideoGallery .
 *
 *  public export const VideoTile = ( props ) : JSX . Element => { const { children , displayName , initialsName , isMirrored , isMuted , onRenderPlaceholder , renderElement , showLabel = true , showMuteIndicator = true , styles , userId , noVideoAvailableAriaLabel , isSpeaking } = props ; const [ personaSize , setPersonaSize ] = useState ( 100 ) ; const videoTileRef = useRef < HTMLElement > ( null ) ; const theme = useTheme ( ) ; const isVideoRendered = ! ! renderElement ; const observer = useRef ( new ResizeObserver ( ( entries ) : void => { const { width , height } = entries [ 0 ] . contentRect ; const personaSize = Math . min ( width , height ) / 3 ; setPersonaSize ( Math . max ( Math . min ( personaSize , PERSONA_MAX_SIZE ) , PERSONA_MIN_SIZE ) ) ; } ) ) ; useLayoutEffect ( ( ) => { if ( videoTileRef . current ) { observer . current . observe ( videoTileRef . current ) ; } const currentObserver = observer . current ; return ( ) => currentObserver . disconnect ( ) ; } , [ observer , videoTileRef ] ) ; const placeholderOptions = { userId , text : initialsName || displayName , noVideoAvailableAriaLabel , coinSize : personaSize , styles : defaultPersonaStyles , hidePersonaDetails : true } ; const videoHintWithBorderRadius = mergeStyles ( videoHint , { borderRadius : theme . effects . roundedCorner4 } ) ; const tileInfoStyle = useMemo ( ( ) => mergeStyles ( isVideoRendered ? videoHintWithBorderRadius : disabledVideoHint , getVideoTileOverrideColor ( isVideoRendered , theme , 'neutralPrimary' ) , styles ?. displayNameContainer ) , [ isVideoRendered , videoHintWithBorderRadius , theme , styles ?. displayNameContainer ] ) ; const ids = useIdentifiers ( ) ; return ( < Ref innerRef = { videoTileRef } > < Stack data - ui - id = { ids . videoTile } className = { mergeStyles ( rootStyles , { background : theme . palette . neutralLighter , borderRadius : theme . effects . roundedCorner4 } , styles ?. root ) } > < div className = { mergeStyles ( isSpeakingBorderDiv , { borderRadius : theme . effects . roundedCorner4 , border : `0.25rem solid ${ isSpeaking ? theme . palette . themePrimary : 'transparent' }` } ) } /> { isVideoRendered ? ( < Stack className = { mergeStyles ( videoContainerStyles , isMirrored && { transform : 'scaleX(-1)' } , styles ?. videoContainer ) } > { renderElement } < /Stack>) : (<Stack className={mergeStyles(videoContainerStyles)}> { onRenderPlaceholder ? ( onRenderPlaceholder ( userId ?? '' , placeholderOptions , DefaultPlaceholder ) ) : ( < DefaultPlaceholder { ... placeholderOptions } / > ) } < /Stack>)} { showLabel && ( displayName || ( showMuteIndicator && isMuted ) ) && ( < Stack horizontal className = { tileInfoContainerStyle } > < Stack horizontal className = { tileInfoStyle } > { displayName && ( < Text className = { mergeStyles ( displayNameStyle ) } title = { displayName } > { displayName } < /Text>)} { showMuteIndicator && isMuted && ( < Stack className = { mergeStyles ( iconContainerStyle ) } > < Icon iconName = "VideoTileMicOff" / > < /Stack>)} < /Stack> < /Stack>)} { children && ( < Stack className = { mergeStyles ( overlayContainerStyles , styles ?. overlayContainer ) } > { children } < /Stack>)} < /Stack> < /Ref>); } ;