Group calling: show avatar if we haven't received video yet/in awhile

This commit is contained in:
Evan Hahn 2021-06-25 12:23:15 -05:00 committed by GitHub
parent 01eabf9ec6
commit b1c1bd5e41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 59 additions and 4 deletions

View file

@ -24,6 +24,8 @@ import { ContactName } from './conversation/ContactName';
import { useIntersectionObserver } from '../util/hooks'; import { useIntersectionObserver } from '../util/hooks';
import { MAX_FRAME_SIZE } from '../calling/constants'; import { MAX_FRAME_SIZE } from '../calling/constants';
const MAX_TIME_TO_SHOW_STALE_VIDEO_FRAMES = 5000;
type BasePropsType = { type BasePropsType = {
getFrameBuffer: () => ArrayBuffer; getFrameBuffer: () => ArrayBuffer;
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource; getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
@ -68,12 +70,24 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
videoAspectRatio, videoAspectRatio,
} = props.remoteParticipant; } = props.remoteParticipant;
const [hasReceivedVideoRecently, setHasReceivedVideoRecently] = useState(
false
);
const [isWide, setIsWide] = useState<boolean>( const [isWide, setIsWide] = useState<boolean>(
videoAspectRatio ? videoAspectRatio >= 1 : true videoAspectRatio ? videoAspectRatio >= 1 : true
); );
const [hasHover, setHover] = useState(false); const [hasHover, setHover] = useState(false);
const [showBlockInfo, setShowBlockInfo] = useState(false); const [showBlockInfo, setShowBlockInfo] = useState(false);
// We have some state (`hasReceivedVideoRecently`) and this ref. We can't have a
// single state value like `lastReceivedVideoAt` because (1) it won't automatically
// trigger a re-render after the video has become stale (2) it would cause a full
// re-render of the component for every frame, which is way too often.
//
// Alternatively, we could create a timeout that's reset every time we get a video
// frame (perhaps using a debounce function), but that becomes harder to clean up
// when the component unmounts.
const lastReceivedVideoAt = useRef(-Infinity);
const remoteVideoRef = useRef<HTMLCanvasElement | null>(null); const remoteVideoRef = useRef<HTMLCanvasElement | null>(null);
const canvasContextRef = useRef<CanvasRenderingContext2D | null>(null); const canvasContextRef = useRef<CanvasRenderingContext2D | null>(null);
@ -85,12 +99,22 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
? intersectionObserverEntry.isIntersecting ? intersectionObserverEntry.isIntersecting
: true; : true;
const wantsToShowVideo = hasRemoteVideo && !isBlocked && isVisible;
const hasVideoToShow = wantsToShowVideo && hasReceivedVideoRecently;
const videoFrameSource = useMemo( const videoFrameSource = useMemo(
() => getGroupCallVideoFrameSource(demuxId), () => getGroupCallVideoFrameSource(demuxId),
[getGroupCallVideoFrameSource, demuxId] [getGroupCallVideoFrameSource, demuxId]
); );
const renderVideoFrame = useCallback(() => { const renderVideoFrame = useCallback(() => {
if (
Date.now() - lastReceivedVideoAt.current >
MAX_TIME_TO_SHOW_STALE_VIDEO_FRAMES
) {
setHasReceivedVideoRecently(false);
}
const canvasEl = remoteVideoRef.current; const canvasEl = remoteVideoRef.current;
if (!canvasEl) { if (!canvasEl) {
return; return;
@ -133,9 +157,18 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
0 0
); );
lastReceivedVideoAt.current = Date.now();
setHasReceivedVideoRecently(true);
setIsWide(frameWidth > frameHeight); setIsWide(frameWidth > frameHeight);
}, [getFrameBuffer, videoFrameSource]); }, [getFrameBuffer, videoFrameSource]);
useEffect(() => {
if (!hasRemoteVideo) {
setHasReceivedVideoRecently(false);
}
}, [hasRemoteVideo]);
useEffect(() => { useEffect(() => {
if (!hasRemoteVideo || !isVisible) { if (!hasRemoteVideo || !isVisible) {
return noop; return noop;
@ -198,7 +231,6 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
} }
const showHover = hasHover && !props.isInPip; const showHover = hasHover && !props.isInPip;
const canShowVideo = hasRemoteVideo && !isBlocked && isVisible;
return ( return (
<> <>
@ -254,10 +286,16 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
/> />
</div> </div>
)} )}
{canShowVideo ? ( {wantsToShowVideo && (
<canvas <canvas
className="module-ongoing-call__group-call-remote-participant__remote-video" className="module-ongoing-call__group-call-remote-participant__remote-video"
style={canvasStyles} style={{
...canvasStyles,
// If we want to show video but don't have any yet, we still render the
// canvas invisibly. This lets us render frame data immediately without
// having to juggle anything.
...(hasVideoToShow ? {} : { display: 'none' }),
}}
ref={canvasEl => { ref={canvasEl => {
remoteVideoRef.current = canvasEl; remoteVideoRef.current = canvasEl;
if (canvasEl) { if (canvasEl) {
@ -271,7 +309,8 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
} }
}} }}
/> />
) : ( )}
{!hasVideoToShow && (
<CallBackgroundBlur avatarPath={avatarPath} color={color}> <CallBackgroundBlur avatarPath={avatarPath} color={color}>
{isBlocked ? ( {isBlocked ? (
<> <>

View file

@ -13570,6 +13570,22 @@
"updated": "2020-11-17T23:29:38.698Z", "updated": "2020-11-17T23:29:38.698Z",
"reasonDetail": "Doesn't touch the DOM." "reasonDetail": "Doesn't touch the DOM."
}, },
{
"rule": "React-useRef",
"path": "ts/components/GroupCallRemoteParticipant.js",
"line": " const lastReceivedVideoAt = react_1.useRef(-Infinity);",
"reasonCategory": "usageTrusted",
"updated": "2021-06-17T20:46:02.342Z",
"reasonDetail": "Doesn't reference the DOM."
},
{
"rule": "React-useRef",
"path": "ts/components/GroupCallRemoteParticipant.tsx",
"line": " const lastReceivedVideoAt = useRef(-Infinity);",
"reasonCategory": "usageTrusted",
"updated": "2021-06-17T20:46:02.342Z",
"reasonDetail": "Doesn't reference the DOM."
},
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/GroupDescriptionInput.js", "path": "ts/components/GroupDescriptionInput.js",