Skip to content

Commit

Permalink
Group calling: show avatar if we haven't received video yet/in awhile
Browse files Browse the repository at this point in the history
  • Loading branch information
EvanHahn-Signal committed Jun 25, 2021
1 parent 01eabf9 commit b1c1bd5
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 4 deletions.
47 changes: 43 additions & 4 deletions ts/components/GroupCallRemoteParticipant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { ContactName } from './conversation/ContactName';
import { useIntersectionObserver } from '../util/hooks';
import { MAX_FRAME_SIZE } from '../calling/constants';

const MAX_TIME_TO_SHOW_STALE_VIDEO_FRAMES = 5000;

type BasePropsType = {
getFrameBuffer: () => ArrayBuffer;
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
Expand Down Expand Up @@ -68,12 +70,24 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
videoAspectRatio,
} = props.remoteParticipant;

const [hasReceivedVideoRecently, setHasReceivedVideoRecently] = useState(
false
);
const [isWide, setIsWide] = useState<boolean>(
videoAspectRatio ? videoAspectRatio >= 1 : true
);
const [hasHover, setHover] = 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 canvasContextRef = useRef<CanvasRenderingContext2D | null>(null);

Expand All @@ -85,12 +99,22 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
? intersectionObserverEntry.isIntersecting
: true;

const wantsToShowVideo = hasRemoteVideo && !isBlocked && isVisible;
const hasVideoToShow = wantsToShowVideo && hasReceivedVideoRecently;

const videoFrameSource = useMemo(
() => getGroupCallVideoFrameSource(demuxId),
[getGroupCallVideoFrameSource, demuxId]
);

const renderVideoFrame = useCallback(() => {
if (
Date.now() - lastReceivedVideoAt.current >
MAX_TIME_TO_SHOW_STALE_VIDEO_FRAMES
) {
setHasReceivedVideoRecently(false);
}

const canvasEl = remoteVideoRef.current;
if (!canvasEl) {
return;
Expand Down Expand Up @@ -133,9 +157,18 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
0
);

lastReceivedVideoAt.current = Date.now();

setHasReceivedVideoRecently(true);
setIsWide(frameWidth > frameHeight);
}, [getFrameBuffer, videoFrameSource]);

useEffect(() => {
if (!hasRemoteVideo) {
setHasReceivedVideoRecently(false);
}
}, [hasRemoteVideo]);

useEffect(() => {
if (!hasRemoteVideo || !isVisible) {
return noop;
Expand Down Expand Up @@ -198,7 +231,6 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
}

const showHover = hasHover && !props.isInPip;
const canShowVideo = hasRemoteVideo && !isBlocked && isVisible;

return (
<>
Expand Down Expand Up @@ -254,10 +286,16 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
/>
</div>
)}
{canShowVideo ? (
{wantsToShowVideo && (
<canvas
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 => {
remoteVideoRef.current = canvasEl;
if (canvasEl) {
Expand All @@ -271,7 +309,8 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
}
}}
/>
) : (
)}
{!hasVideoToShow && (
<CallBackgroundBlur avatarPath={avatarPath} color={color}>
{isBlocked ? (
<>
Expand Down
16 changes: 16 additions & 0 deletions ts/util/lint/exceptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -13570,6 +13570,22 @@
"updated": "2020-11-17T23:29:38.698Z",
"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",
"path": "ts/components/GroupDescriptionInput.js",
Expand Down

0 comments on commit b1c1bd5

Please sign in to comment.