Group calls: Make renderVideoFrame generate less garbage

This commit is contained in:
Jordan Rose 2021-12-10 16:21:28 -08:00 committed by GitHub
parent fed84be0b6
commit 683823a114
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 30 additions and 21 deletions

View file

@ -12,12 +12,12 @@ import { FRAME_BUFFER_SIZE } from './constants';
* of allocating one per participant. Be careful when using this buffer elsewhere, as it * of allocating one per participant. Be careful when using this buffer elsewhere, as it
* is not cleaned up and may hold stale data. * is not cleaned up and may hold stale data.
*/ */
export function useGetCallingFrameBuffer(): () => ArrayBuffer { export function useGetCallingFrameBuffer(): () => Buffer {
const ref = useRef<ArrayBuffer | null>(null); const ref = useRef<Buffer | null>(null);
return useCallback(() => { return useCallback(() => {
if (!ref.current) { if (!ref.current) {
ref.current = new ArrayBuffer(FRAME_BUFFER_SIZE); ref.current = Buffer.alloc(FRAME_BUFFER_SIZE);
} }
return ref.current; return ref.current;
}, []); }, []);

View file

@ -35,7 +35,7 @@ const allRemoteParticipants = times(MAX_PARTICIPANTS).map(index => ({
const story = storiesOf('Components/GroupCallOverflowArea', module); const story = storiesOf('Components/GroupCallOverflowArea', module);
const defaultProps = { const defaultProps = {
getFrameBuffer: memoize(() => new ArrayBuffer(FRAME_BUFFER_SIZE)), getFrameBuffer: memoize(() => Buffer.alloc(FRAME_BUFFER_SIZE)),
getGroupCallVideoFrameSource: fakeGetGroupCallVideoFrameSource, getGroupCallVideoFrameSource: fakeGetGroupCallVideoFrameSource,
i18n, i18n,
onParticipantVisibilityChanged: action('onParticipantVisibilityChanged'), onParticipantVisibilityChanged: action('onParticipantVisibilityChanged'),

View file

@ -16,7 +16,7 @@ const OVERFLOW_SCROLL_BUTTON_RATIO = 0.75;
export const OVERFLOW_PARTICIPANT_WIDTH = 140; export const OVERFLOW_PARTICIPANT_WIDTH = 140;
type PropsType = { type PropsType = {
getFrameBuffer: () => ArrayBuffer; getFrameBuffer: () => Buffer;
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource; getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
i18n: LocalizerType; i18n: LocalizerType;
onParticipantVisibilityChanged: ( onParticipantVisibilityChanged: (

View file

@ -26,7 +26,7 @@ type OverridePropsType =
width: number; width: number;
}; };
const getFrameBuffer = memoize(() => new ArrayBuffer(FRAME_BUFFER_SIZE)); const getFrameBuffer = memoize(() => Buffer.alloc(FRAME_BUFFER_SIZE));
const createProps = ( const createProps = (
overrideProps: OverridePropsType, overrideProps: OverridePropsType,

View file

@ -26,7 +26,7 @@ import { MAX_FRAME_SIZE } from '../calling/constants';
const MAX_TIME_TO_SHOW_STALE_VIDEO_FRAMES = 5000; const MAX_TIME_TO_SHOW_STALE_VIDEO_FRAMES = 5000;
type BasePropsType = { type BasePropsType = {
getFrameBuffer: () => ArrayBuffer; getFrameBuffer: () => Buffer;
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource; getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
i18n: LocalizerType; i18n: LocalizerType;
onVisibilityChanged?: (demuxId: number, isVisible: boolean) => unknown; onVisibilityChanged?: (demuxId: number, isVisible: boolean) => unknown;
@ -93,6 +93,7 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
const lastReceivedVideoAt = useRef(-Infinity); 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);
const imageDataRef = useRef<ImageData | null>(null);
const [intersectionRef, intersectionObserverEntry] = const [intersectionRef, intersectionObserverEntry] =
useIntersectionObserver(); useIntersectionObserver();
@ -134,9 +135,7 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
// for other participants, or pixel data from a previous frame. That's why we // for other participants, or pixel data from a previous frame. That's why we
// return early and use the `frameWidth` and `frameHeight`. // return early and use the `frameWidth` and `frameHeight`.
const frameBuffer = getFrameBuffer(); const frameBuffer = getFrameBuffer();
const frameDimensions = videoFrameSource.receiveVideoFrame( const frameDimensions = videoFrameSource.receiveVideoFrame(frameBuffer);
Buffer.from(frameBuffer)
);
if (!frameDimensions) { if (!frameDimensions) {
return; return;
} }
@ -154,15 +153,16 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
canvasEl.width = frameWidth; canvasEl.width = frameWidth;
canvasEl.height = frameHeight; canvasEl.height = frameHeight;
canvasContext.putImageData( let imageData = imageDataRef.current;
new ImageData( if (
new Uint8ClampedArray(frameBuffer, 0, frameWidth * frameHeight * 4), imageData?.width !== frameWidth ||
frameWidth, imageData?.height !== frameHeight
frameHeight ) {
), imageData = new ImageData(frameWidth, frameHeight);
0, imageDataRef.current = imageData;
0 }
); imageData.data.set(frameBuffer.subarray(0, frameWidth * frameHeight * 4));
canvasContext.putImageData(imageData, 0, 0);
lastReceivedVideoAt.current = Date.now(); lastReceivedVideoAt.current = Date.now();

View file

@ -6965,9 +6965,10 @@
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/calling/useGetCallingFrameBuffer.ts", "path": "ts/calling/useGetCallingFrameBuffer.ts",
"line": " const ref = useRef<ArrayBuffer | null>(null);", "line": " const ref = useRef<Buffer | null>(null);",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-07-30T16:57:33.618Z" "updated": "2021-12-10T23:24:03.829Z",
"reasonDetail": "Doesn't touch the DOM."
}, },
{ {
"rule": "jQuery-load(", "rule": "jQuery-load(",
@ -7331,6 +7332,14 @@
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-07-30T16:57:33.618Z" "updated": "2021-07-30T16:57:33.618Z"
}, },
{
"rule": "React-useRef",
"path": "ts/components/GroupCallRemoteParticipant.tsx",
"line": " const imageDataRef = useRef<ImageData | null>(null);",
"reasonCategory": "usageTrusted",
"updated": "2021-12-10T23:24:31.237Z",
"reasonDetail": "Doesn't touch the DOM."
},
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/Inbox.tsx", "path": "ts/components/Inbox.tsx",