Files
upstream-jitsi-meet/react/features/multi-screen/components/SecondScreenTile.web.tsx
upstream-mirror 1d2bfcac0c
Some checks failed
Prosody Plugin Tests / test (push) Failing after 10m11s
Close stale issues and PRs / stale (push) Failing after 1s
snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
2026-09-15 15:56:32 +05:00

148 lines
4.5 KiB
TypeScript

import React from 'react';
import { useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../app/types';
import Avatar from '../../base/avatar/components/Avatar';
import {
getParticipantById,
getParticipantDisplayName,
isScreenShareParticipantById
} from '../../base/participants/functions';
import { getVideoTrackByParticipant } from '../../base/tracks/functions.any';
import SecondScreenVideo from './SecondScreenVideo';
/**
* The type of the React {@code Component} props of {@link SecondScreenTile}.
*/
interface IProps {
/**
* The tile height in pixels.
*/
height: number;
/**
* Whether this participant is the dominant speaker (drives the speaking ring).
*/
isActiveSpeaker: boolean;
/**
* The id of the participant (a person or a virtual screenshare) this tile
* previews.
*/
participantId: string;
/**
* The tile width in pixels.
*/
width: number;
/**
* The second-screen window, needed to render a track in its own realm.
*/
win: Window;
}
/**
* The styles, injected into the second window via its own Emotion cache (see
* {@code SecondScreenPortals}). The dominant-speaker ring colour matches the main
* window's thumbnail indicator (Jitsi's action01 token).
*/
const useStyles = makeStyles()(() => {
return {
tile: {
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxSizing: 'border-box',
backgroundColor: '#292929',
border: '3px solid transparent',
borderRadius: 4,
overflow: 'hidden',
transition: 'border-color 0.2s ease'
},
speaking: {
borderColor: '#4687ED'
},
avatar: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
},
bottom: {
position: 'absolute',
bottom: 6,
left: 6,
display: 'flex',
alignItems: 'center',
gap: 4,
maxWidth: 'calc(100% - 12px)',
padding: '3px 6px',
backgroundColor: 'rgba(0, 0, 0, 0.6)',
borderRadius: 4,
color: '#fff'
},
name: {
minWidth: 0,
fontSize: 12,
lineHeight: '16px',
fontWeight: 600,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}
};
});
/**
* Renders one participant's tile: their camera or shared screen (via the
* realm-safe {@link SecondScreenVideo} clone leaf) or their avatar, with their
* name and a dominant-speaker ring. Used both as a gallery cell and inside a
* stage filmstrip button. Memoized so a dominant-speaker change only re-renders
* the tiles whose ring flips.
*
* @param {IProps} props - The component props.
* @returns {ReactElement}
*/
const SecondScreenTile = ({ height, isActiveSpeaker, participantId, width, win }: IProps) => {
const { classes, cx } = useStyles();
const name = useSelector((state: IReduxState) => getParticipantDisplayName(state, participantId));
const isScreenshare = useSelector((state: IReduxState) => isScreenShareParticipantById(state, participantId));
const videoTrack = useSelector(
(state: IReduxState) => getVideoTrackByParticipant(state, getParticipantById(state, participantId)));
const track = videoTrack?.jitsiTrack && !videoTrack.muted
? (videoTrack.jitsiTrack.getTrack() as MediaStreamTrack) ?? null
: null;
const avatarSize = Math.max(24, Math.floor(Math.min(height / 2, width - 30, 150)));
return (
<div
className = { cx(classes.tile, isActiveSpeaker && classes.speaking) }
style = {{ height: `${height}px`, width: `${width}px` }}>
{ track ? (
<SecondScreenVideo
fit = { isScreenshare ? 'contain' : 'cover' }
track = { track }
win = { win } />
) : (
<div className = { classes.avatar }>
<Avatar
participantId = { participantId }
size = { avatarSize } />
</div>
) }
<div className = { classes.bottom }>
<span className = { classes.name }>
{ name }
</span>
</div>
</div>
);
};
export default React.memo(SecondScreenTile);