snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
Some checks failed
Prosody Plugin Tests / test (push) Failing after 10m11s
Close stale issues and PRs / stale (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-15 15:56:32 +05:00
commit 1d2bfcac0c
3159 changed files with 464524 additions and 0 deletions

View File

@@ -0,0 +1,180 @@
import React, { useEffect, useRef } 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 { getLocalParticipant, getParticipantDisplayName } from '../../../base/participants/functions';
import DisplayNameBadge from '../../../display-name/components/web/DisplayNameBadge';
import { getStageParticipantTypography } from '../../../display-name/components/web/styles';
import { getLargeVideoParticipant } from '../../../large-video/functions';
import { isPrejoinPageVisible } from '../../../prejoin/functions.any';
import HangupButton from '../../../toolbox/components/HangupButton';
import AudioMuteButton from '../../../toolbox/components/web/AudioMuteButton';
import VideoMuteButton from '../../../toolbox/components/web/VideoMuteButton';
import { getPiPVideoTrack, shouldShowPiPAvatar } from '../../functions';
import logger from '../../logger';
const useStyles = makeStyles<void, 'controls'>()((theme, _params, classes) => {
return {
container: {
backgroundColor: theme.palette.largeVideoBackground,
color: theme.palette.text01,
height: '100vh',
overflow: 'hidden',
position: 'relative',
userSelect: 'none',
width: '100vw',
'& *, & *::before, & *::after': {
boxSizing: 'border-box'
}
},
videoArea: {
alignItems: 'center',
backgroundColor: theme.palette.largeVideoBackground,
display: 'flex',
height: '100%',
justifyContent: 'center',
overflow: 'hidden',
position: 'relative',
width: '100%',
[`&:hover .${classes.controls}`]: {
opacity: 1,
pointerEvents: 'auto'
}
},
videoElement: {
backgroundColor: theme.palette.largeVideoBackground,
height: '100%',
objectFit: 'contain',
width: '100%'
},
avatarPlaceholder: {
alignItems: 'center',
backgroundColor: theme.palette.largeVideoPlaceholder,
display: 'flex',
height: '100%',
justifyContent: 'center',
width: '100%'
},
floatingControls: {
alignItems: 'center',
bottom: theme.spacing(4),
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(2),
left: 0,
pointerEvents: 'none',
position: 'absolute',
width: '100%',
zIndex: 10
},
participantName: {
...getStageParticipantTypography(theme),
display: 'flex',
justifyContent: 'center',
pointerEvents: 'none',
width: '100%'
},
controls: {
opacity: 0,
pointerEvents: 'none',
transition: 'opacity 0.3s ease',
'& .toolbox-icon.hangup-button': {
backgroundColor: theme.palette.actionDanger,
'@media (hover: hover) and (pointer: fine)': {
'&:hover': {
backgroundColor: theme.palette.actionDangerHover
},
'&:active': {
backgroundColor: theme.palette.actionDangerActive
}
},
'& svg': {
fill: theme.palette.icon01
}
}
}
};
});
/**
* Renders the participant video, avatar fallback, display name and controls inside the Document PiP window.
*
* @returns {React.ReactElement}
*/
export function DocumentPiPView() {
const { classes, cx } = useStyles();
const videoRef = useRef<HTMLVideoElement>(null);
const isOnPrejoin = useSelector(isPrejoinPageVisible);
const localParticipant = useSelector((state: IReduxState) => getLocalParticipant(state));
const largeVideoParticipant = useSelector(getLargeVideoParticipant);
const participant = isOnPrejoin ? localParticipant : largeVideoParticipant;
const videoTrack = useSelector((state: IReduxState) => getPiPVideoTrack(state, participant));
const participantName = useSelector((state: IReduxState) =>
participant?.id ? getParticipantDisplayName(state, participant.id) : '');
const shouldShowAvatar = shouldShowPiPAvatar(videoTrack);
useEffect(() => {
const video = videoRef.current;
if (!video || shouldShowAvatar || !videoTrack?.jitsiTrack) {
return;
}
videoTrack.jitsiTrack.attach(video).catch((error: Error) => {
logger.error('Failed to attach video track in Document PiP:', error);
});
return () => {
try {
videoTrack.jitsiTrack.detach(video);
} catch (error) {
logger.error('Failed to detach video track in Document PiP:', error);
}
};
}, [ videoTrack, shouldShowAvatar ]);
return (
<div className = { classes.container }>
<div className = { classes.videoArea }>
{shouldShowAvatar ? (
<div className = { classes.avatarPlaceholder }>
<Avatar
participantId = { participant?.id }
size = { 120 } />
</div>
) : (
<video
autoPlay = { true }
className = { classes.videoElement }
muted = { true }
playsInline = { true }
ref = { videoRef } />
)}
<div className = { classes.floatingControls }>
{participantName && <div className = { classes.participantName }>
<DisplayNameBadge name = { participantName } />
</div>}
<div className = { cx(classes.controls, 'toolbox-content-items') }>
<AudioMuteButton
disableTooltip = { true }
registerKeyboardShortcut = { false } />
<VideoMuteButton
disableTooltip = { true }
registerKeyboardShortcut = { false } />
<HangupButton
customClass = 'hangup-button'
disableTooltip = { true } />
</div>
</div>
</div>
</div>
);
}