import * as React from 'react'; import type { Participant } from 'livekit-client'; import { Track } from 'livekit-client'; import type { ParticipantClickEvent, TrackReferenceOrPlaceholder } from '@livekit/components-core'; import { isTrackReference, isTrackReferencePinned } from '@livekit/components-core'; import { AudioTrack, VideoTrack, ParticipantContext, TrackRefContext, useEnsureTrackRef, useFeatureContext, useIsEncrypted, useMaybeLayoutContext, useMaybeParticipantContext, useMaybeTrackRefContext, useParticipantTile, ParticipantPlaceholder, LockLockedIcon, TrackMutedIndicator, ParticipantName, ConnectionQualityIndicator, FocusToggle, useIsSpeaking, useIsMuted, useTrackByName, useTracks, } from '@livekit/components-react'; import { getAvatarColor, getInitials } from './client-utils'; export function ParticipantContextIfNeeded( props: React.PropsWithChildren<{ participant?: Participant; }>, ) { const hasContext = !!useMaybeParticipantContext(); return props.participant && !hasContext ? ( {props.children} ) : ( <>{props.children}> ); } export function TrackRefContextIfNeeded( props: React.PropsWithChildren<{ trackRef?: TrackReferenceOrPlaceholder; }>, ) { const hasContext = !!useMaybeTrackRefContext(); return props.trackRef && !hasContext ? ( {props.children} ) : ( <>{props.children}> ); } export interface ParticipantTileProps extends React.HTMLAttributes { trackRef?: TrackReferenceOrPlaceholder; disableSpeakingIndicator?: boolean; onParticipantClick?: (event: ParticipantClickEvent) => void; } export const ParticipantTile: ( props: ParticipantTileProps & React.RefAttributes, ) => React.ReactNode = React.forwardRef( function ParticipantTile( { trackRef, children, onParticipantClick, disableSpeakingIndicator, ...htmlProps }: ParticipantTileProps, ref, ) { const trackReference = useEnsureTrackRef(trackRef); const { name, identity, metadata, isEncrypted, isSpeaking, isMicrophoneEnabled, isScreenShareEnabled, } = trackReference.participant; const { elementProps } = useParticipantTile({ htmlProps, disableSpeakingIndicator, onParticipantClick, trackRef: trackReference, }); const layoutContext = useMaybeLayoutContext(); const autoManageSubscription = useFeatureContext()?.autoSubscription; const handleSubscribe = React.useCallback( (subscribed: boolean) => { if ( trackReference.source && !subscribed && layoutContext && layoutContext.pin.dispatch && isTrackReferencePinned(trackReference, layoutContext.pin.state) ) { layoutContext.pin.dispatch({ msg: 'clear_pin' }); } }, [trackReference, layoutContext], ); const [profilePictureUrl, setProfilePictureUrl] = React.useState(null); React.useEffect(() => { if (metadata) { try { const parsedMetadata = JSON.parse(metadata); if (parsedMetadata.profilePictureUrl) { setProfilePictureUrl(parsedMetadata.profilePictureUrl); } } catch (e) { console.error('Failed to parse participant metadata', e); } } }, [metadata]); const avatarColor = getAvatarColor(identity); const initials = getInitials(name || identity); return ( {children ?? ( <> {isTrackReference(trackReference) && (trackReference.publication?.kind === 'video' || trackReference.source === Track.Source.Camera || trackReference.source === Track.Source.ScreenShare) ? ( ) : ( isTrackReference(trackReference) && ( ) )} {profilePictureUrl ? ( ) : ( {initials} )} {isMicrophoneEnabled ? ( <> {isSpeaking ? ( graphic_eq ) : ( mic )} > ) : ( mic_off )} {name || identity} > )} ); }, );