import React, { memo, useEffect, useState, useRef } from 'react'; import { useVideoTrack } from '@custom/shared/hooks/useVideoTrack'; import { ReactComponent as IconMicMute } from '@custom/shared/icons/mic-off-sm.svg'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import { DEFAULT_ASPECT_RATIO } from '../../constants'; import { Video } from './Video'; import { ReactComponent as Avatar } from './avatar.svg'; const SM_TILE_MAX_WIDTH = 300; export const Tile = memo( ({ participant, mirrored = true, showName = true, showAvatar = true, showActiveSpeaker = true, videoFit = 'contain', aspectRatio = DEFAULT_ASPECT_RATIO, onVideoResize, ...props }) => { const videoTrack = useVideoTrack(participant.id); const videoRef = useRef(null); const tileRef = useRef(null); const [tileWidth, setTileWidth] = useState(0); /** * Effect: Resize * * Add optional event listener for resize event so the parent component * can know the video's native aspect ratio. */ useEffect(() => { const video = videoRef.current; if (!onVideoResize || !video) return false; const handleResize = () => { if (!video) return; const width = video?.videoWidth; const height = video?.videoHeight; if (width && height) { // Return the video's aspect ratio to the parent's handler onVideoResize(width / height); } }; handleResize(); video?.addEventListener('resize', handleResize); return () => video?.removeEventListener('resize', handleResize); }, [onVideoResize, videoRef, participant]); /** * Effect: Resize Observer * * Adjust size of text overlay based on tile size */ useEffect(() => { const tile = tileRef.current; if (!tile || typeof ResizeObserver === 'undefined') return false; let frame; const resizeObserver = new ResizeObserver(() => { if (frame) cancelAnimationFrame(frame); frame = requestAnimationFrame(() => { if (!tile) return; const dimensions = tile?.getBoundingClientRect(); const { width } = dimensions; setTileWidth(width); }); }); resizeObserver.observe(tile); return () => { if (frame) cancelAnimationFrame(frame); resizeObserver.disconnect(); }; }, [tileRef]); const cx = classNames('tile', videoFit, { mirrored, avatar: showAvatar && !videoTrack, screenShare: participant.isScreenShare, active: showActiveSpeaker && participant.isActiveSpeaker, small: tileWidth < SM_TILE_MAX_WIDTH, }); return (
{showName && (
{participant.isMicMuted && !participant.isScreenShare && ( )} {participant.name}
)} {videoTrack ? (
); } ); Tile.propTypes = { participant: PropTypes.object.isRequired, mirrored: PropTypes.bool, showName: PropTypes.bool, showAvatar: PropTypes.bool, aspectRatio: PropTypes.number, onVideoResize: PropTypes.func, showActiveSpeaker: PropTypes.bool, videoFit: PropTypes.string, }; export default Tile;