Group calling: show avatar if we haven't received video yet/in awhile
This commit is contained in:
parent
01eabf9ec6
commit
b1c1bd5e41
2 changed files with 59 additions and 4 deletions
|
@ -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 ? (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue