Reuse html element for local preview visuals

This commit is contained in:
Fedor Indutny 2024-10-01 15:17:43 -07:00 committed by GitHub
parent bb69f81b9f
commit 17c908bbf4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 86 additions and 109 deletions

View file

@ -4231,6 +4231,10 @@ button.module-image__border-overlay:focus {
}
}
&__local-preview-video-container {
display: contents;
}
&__local-preview-fullsize {
position: absolute;
top: 0;
@ -4283,14 +4287,19 @@ button.module-image__border-overlay:focus {
}
&__video {
// The background-color is seen while the video loads.
background-color: $color-gray-75;
height: 100%;
transform: rotateY(180deg);
width: 100%;
&--presenting {
transform: inherit;
video {
// The background-color is seen while the video loads.
background-color: $color-gray-75;
height: 100%;
width: 100%;
transform: rotateY(180deg);
&--presenting {
transform: inherit;
}
}
}
}
@ -4373,8 +4382,13 @@ button.module-image__border-overlay:focus {
height: 32px;
position: absolute;
inset-inline-end: 4px;
transform: rotateY(180deg);
width: 32px;
video {
transform: rotateY(180deg);
width: 100%;
height: 100%;
}
}
}

View file

@ -14,6 +14,14 @@
height: 100%;
max-height: calc(100% - 140px);
opacity: 0.6;
// Border radius should clip children
overflow: hidden;
}
&--camera-is-on video {
width: auto;
height: 100%;
}
&--camera-is-off {

View file

@ -130,7 +130,7 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
setGroupCallVideoRequest: action('set-group-call-video-request'),
setIsCallActive: action('set-is-call-active'),
setLocalAudio: action('set-local-audio'),
setLocalPreview: action('set-local-preview'),
setLocalPreviewContainer: action('set-local-preview-container'),
setLocalVideo: action('set-local-video'),
setRendererCanvas: action('set-renderer-canvas'),
setOutgoingRing: action('set-outgoing-ring'),

View file

@ -37,7 +37,6 @@ import type {
SendGroupCallReactionType,
SetGroupCallVideoRequestType,
SetLocalAudioType,
SetLocalPreviewType,
SetLocalVideoType,
SetRendererCanvasType,
StartCallType,
@ -127,7 +126,7 @@ export type PropsType = {
setIsCallActive: (_: boolean) => void;
setLocalAudio: (_: SetLocalAudioType) => void;
setLocalVideo: (_: SetLocalVideoType) => void;
setLocalPreview: (_: SetLocalPreviewType) => void;
setLocalPreviewContainer: (container: HTMLDivElement | null) => void;
setOutgoingRing: (_: boolean) => void;
setRendererCanvas: (_: SetRendererCanvasType) => void;
showShareCallLinkViaSignal: (
@ -192,7 +191,7 @@ function ActiveCallManager({
sendGroupCallReaction,
setGroupCallVideoRequest,
setLocalAudio,
setLocalPreview,
setLocalPreviewContainer,
setLocalVideo,
setRendererCanvas,
setOutgoingRing,
@ -351,7 +350,7 @@ function ActiveCallManager({
hasLocalVideo={hasLocalVideo}
i18n={i18n}
setGroupCallVideoRequest={setGroupCallVideoRequestForConversation}
setLocalPreview={setLocalPreview}
setLocalPreviewContainer={setLocalPreviewContainer}
setRendererCanvas={setRendererCanvas}
switchToPresentationView={switchToPresentationView}
switchFromPresentationView={switchFromPresentationView}
@ -383,7 +382,7 @@ function ActiveCallManager({
onJoinCall={joinActiveCall}
outgoingRing={outgoingRing}
peekedParticipants={peekedParticipants}
setLocalPreview={setLocalPreview}
setLocalPreviewContainer={setLocalPreviewContainer}
setLocalAudio={setLocalAudio}
setLocalVideo={setLocalVideo}
setOutgoingRing={setOutgoingRing}
@ -471,7 +470,7 @@ function ActiveCallManager({
sendGroupCallRaiseHand={sendGroupCallRaiseHand}
sendGroupCallReaction={sendGroupCallReaction}
setGroupCallVideoRequest={setGroupCallVideoRequestForConversation}
setLocalPreview={setLocalPreview}
setLocalPreviewContainer={setLocalPreviewContainer}
setRendererCanvas={setRendererCanvas}
setLocalAudio={setLocalAudio}
setLocalVideo={setLocalVideo}
@ -567,7 +566,7 @@ export function CallManager({
setGroupCallVideoRequest,
setIsCallActive,
setLocalAudio,
setLocalPreview,
setLocalPreviewContainer,
setLocalVideo,
setOutgoingRing,
setRendererCanvas,
@ -662,7 +661,7 @@ export function CallManager({
sendGroupCallReaction={sendGroupCallReaction}
setGroupCallVideoRequest={setGroupCallVideoRequest}
setLocalAudio={setLocalAudio}
setLocalPreview={setLocalPreview}
setLocalPreviewContainer={setLocalPreviewContainer}
setLocalVideo={setLocalVideo}
setOutgoingRing={setOutgoingRing}
setRendererCanvas={setRendererCanvas}

View file

@ -219,7 +219,7 @@ const createProps = (
sendGroupCallReaction: action('send-group-call-reaction'),
setGroupCallVideoRequest: action('set-group-call-video-request'),
setLocalAudio: action('set-local-audio'),
setLocalPreview: action('set-local-preview'),
setLocalPreviewContainer: action('set-local-preview-container'),
setLocalVideo: action('set-local-video'),
setRendererCanvas: action('set-renderer-canvas'),
stickyControls: false,

View file

@ -13,7 +13,6 @@ import type {
SendGroupCallRaiseHandType,
SendGroupCallReactionType,
SetLocalAudioType,
SetLocalPreviewType,
SetLocalVideoType,
SetRendererCanvasType,
} from '../state/ducks/calling';
@ -119,7 +118,7 @@ export type PropsType = {
) => void;
setLocalAudio: (_: SetLocalAudioType) => void;
setLocalVideo: (_: SetLocalVideoType) => void;
setLocalPreview: (_: SetLocalPreviewType) => void;
setLocalPreviewContainer: (container: HTMLDivElement | null) => void;
setRendererCanvas: (_: SetRendererCanvasType) => void;
stickyControls: boolean;
switchToPresentationView: () => void;
@ -209,7 +208,7 @@ export function CallScreen({
sendGroupCallReaction,
setLocalAudio,
setLocalVideo,
setLocalPreview,
setLocalPreviewContainer,
setRendererCanvas,
stickyControls,
switchToPresentationView,
@ -294,15 +293,6 @@ export function CallScreen({
const [showControls, setShowControls] = useState(true);
const localVideoRef = useRef<HTMLVideoElement | null>(null);
useEffect(() => {
setLocalPreview({ element: localVideoRef });
return () => {
setLocalPreview({ element: undefined });
};
}, [setLocalPreview, setRendererCanvas]);
useEffect(() => {
if (
!showControls ||
@ -431,7 +421,10 @@ export function CallScreen({
)}
>
{isSendingVideo ? (
<video ref={localVideoRef} autoPlay />
<div
className="module-ongoing-call__local-preview-container"
ref={setLocalPreviewContainer}
/>
) : (
<CallBackgroundBlur avatarUrl={me.avatarUrl}>
<div className="module-calling__spacer module-calling__camera-is-off-spacer" />
@ -444,14 +437,13 @@ export function CallScreen({
);
} else {
localPreviewNode = isSendingVideo ? (
<video
<div
className={classNames(
'module-ongoing-call__footer__local-preview__video',
presentingSource &&
'module-ongoing-call__footer__local-preview__video--presenting'
)}
ref={localVideoRef}
autoPlay
ref={setLocalPreviewContainer}
/>
) : (
<CallBackgroundBlur avatarUrl={me.avatarUrl}>

View file

@ -84,7 +84,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
outgoingRing: overrideProps.outgoingRing ?? false,
peekedParticipants: overrideProps.peekedParticipants || [],
setLocalAudio: action('set-local-audio'),
setLocalPreview: action('set-local-preview'),
setLocalPreviewContainer: action('set-local-preview-container'),
setLocalVideo: action('set-local-video'),
setOutgoingRing: action('set-outgoing-ring'),
showParticipantsList: overrideProps.showParticipantsList ?? false,

View file

@ -6,7 +6,6 @@ import FocusTrap from 'focus-trap-react';
import classNames from 'classnames';
import type {
SetLocalAudioType,
SetLocalPreviewType,
SetLocalVideoType,
} from '../state/ducks/calling';
import { CallingButton, CallingButtonType } from './CallingButton';
@ -74,7 +73,7 @@ export type PropsType = {
peekedParticipants: Array<ConversationType>;
setLocalAudio: (_: SetLocalAudioType) => void;
setLocalVideo: (_: SetLocalVideoType) => void;
setLocalPreview: (_: SetLocalPreviewType) => void;
setLocalPreviewContainer: (container: HTMLDivElement | null) => void;
setOutgoingRing: (_: boolean) => void;
showParticipantsList: boolean;
toggleParticipants: () => void;
@ -100,7 +99,7 @@ export function CallingLobby({
onJoinCall,
peekedParticipants,
setLocalAudio,
setLocalPreview,
setLocalPreviewContainer,
setLocalVideo,
setOutgoingRing,
toggleParticipants,
@ -108,8 +107,6 @@ export function CallingLobby({
toggleSettings,
outgoingRing,
}: PropsType): JSX.Element {
const localVideoRef = React.useRef<null | HTMLVideoElement>(null);
const shouldShowLocalVideo = hasLocalVideo && availableCameras.length > 0;
const isGroupOrAdhocCall = isGroupOrAdhocCallMode(callMode);
@ -130,14 +127,6 @@ export function CallingLobby({
? togglePip
: undefined;
React.useEffect(() => {
setLocalPreview({ element: localVideoRef });
return () => {
setLocalPreview({ element: undefined });
};
}, [setLocalPreview]);
React.useEffect(() => {
function handleKeyDown(event: KeyboardEvent): void {
let eventHandled = false;
@ -275,10 +264,9 @@ export function CallingLobby({
>
<div className="module-calling__container dark-theme">
{shouldShowLocalVideo ? (
<video
<div
className="module-CallingLobby__local-preview module-CallingLobby__local-preview--camera-is-on"
ref={localVideoRef}
autoPlay
ref={setLocalPreviewContainer}
/>
) : (
<CallBackgroundBlur

View file

@ -78,7 +78,7 @@ export default {
hasLocalVideo: false,
i18n,
setGroupCallVideoRequest: action('set-group-call-video-request'),
setLocalPreview: action('set-local-preview'),
setLocalPreviewContainer: action('set-local-preview-container'),
setRendererCanvas: action('set-renderer-canvas'),
switchFromPresentationView: action('switch-to-presentation-view'),
switchToPresentationView: action('switch-to-presentation-view'),

View file

@ -7,10 +7,7 @@ import type { VideoFrameSource } from '@signalapp/ringrtc';
import { CallingPipRemoteVideo } from './CallingPipRemoteVideo';
import type { LocalizerType } from '../types/Util';
import type { ActiveCallType, GroupCallVideoRequest } from '../types/Calling';
import type {
SetLocalPreviewType,
SetRendererCanvasType,
} from '../state/ducks/calling';
import type { SetRendererCanvasType } from '../state/ducks/calling';
import { missingCaseError } from '../util/missingCaseError';
import { useActivateSpeakerViewOnPresenting } from '../hooks/useActivateSpeakerViewOnPresenting';
import type { CallingImageDataCache } from './CallManager';
@ -60,7 +57,7 @@ export type PropsType = {
_: Array<GroupCallVideoRequest>,
speakerHeight: number
) => void;
setLocalPreview: (_: SetLocalPreviewType) => void;
setLocalPreviewContainer: (container: HTMLDivElement | null) => void;
setRendererCanvas: (_: SetRendererCanvasType) => void;
switchToPresentationView: () => void;
switchFromPresentationView: () => void;
@ -80,7 +77,7 @@ export function CallingPip({
imageDataCache,
i18n,
setGroupCallVideoRequest,
setLocalPreview,
setLocalPreviewContainer,
setRendererCanvas,
switchToPresentationView,
switchFromPresentationView,
@ -89,7 +86,6 @@ export function CallingPip({
const isRTL = i18n.getLocaleDirection() === 'rtl';
const videoContainerRef = React.useRef<null | HTMLDivElement>(null);
const localVideoRef = React.useRef(null);
const [windowWidth, setWindowWidth] = React.useState(window.innerWidth);
const [windowHeight, setWindowHeight] = React.useState(window.innerHeight);
@ -104,10 +100,6 @@ export function CallingPip({
switchFromPresentationView,
});
React.useEffect(() => {
setLocalPreview({ element: localVideoRef });
}, [setLocalPreview]);
const hangUp = React.useCallback(() => {
hangUpActiveCall('pip button click');
}, [hangUpActiveCall]);
@ -313,10 +305,9 @@ export function CallingPip({
setGroupCallVideoRequest={setGroupCallVideoRequest}
/>
{hasLocalVideo ? (
<video
<div
className="module-calling-pip__video--local"
ref={localVideoRef}
autoPlay
ref={setLocalPreviewContainer}
/>
) : null}
<div className="module-calling-pip__actions">

View file

@ -326,10 +326,14 @@ export type NotifyScreenShareStatusOptionsType = Readonly<
>;
export class CallingClass {
readonly videoCapturer: GumVideoCapturer;
private readonly videoCapturer: GumVideoCapturer;
readonly videoRenderer: CanvasVideoRenderer;
private localPreviewContainer: HTMLDivElement | null = null;
private localPreview: HTMLVideoElement | undefined;
private reduxInterface?: CallingReduxInterface;
public _sfuUrl?: string;
@ -953,6 +957,21 @@ export class CallingClass {
);
}
public setLocalPreviewContainer(container: HTMLDivElement | null): void {
// Reuse HTMLVideoElement between different containers so that the preview
// of the last frame stays valid even if there are no new frames on the
// underlying MediaStream.
if (this.localPreview == null) {
this.localPreview = document.createElement('video');
this.localPreview.autoplay = true;
this.videoCapturer.setLocalPreview({ current: this.localPreview });
}
this.localPreviewContainer?.removeChild(this.localPreview);
this.localPreviewContainer = container;
this.localPreviewContainer?.appendChild(this.localPreview);
}
public async cleanupStaleRingingCalls(): Promise<void> {
const calls = await DataWriter.getRecentStaleRingsAndMarkOlderMissed();

View file

@ -420,11 +420,6 @@ type StartCallLinkLobbyPayloadType = {
callLinkRootKey: string;
};
// eslint-disable-next-line local-rules/type-alias-readonlydeep
export type SetLocalPreviewType = {
element: React.RefObject<HTMLVideoElement> | undefined;
};
// eslint-disable-next-line local-rules/type-alias-readonlydeep
export type SetRendererCanvasType = {
element: React.RefObject<HTMLCanvasElement> | undefined;
@ -1835,14 +1830,6 @@ function setIsCallActive(
};
}
function setLocalPreview(
payload: SetLocalPreviewType
): ThunkAction<void, RootStateType, unknown, never> {
return () => {
calling.videoCapturer.setLocalPreview(payload.element);
};
}
function setRendererCanvas(
payload: SetRendererCanvasType
): ThunkAction<void, RootStateType, unknown, never> {
@ -2661,7 +2648,6 @@ export const actions = {
setGroupCallVideoRequest,
setIsCallActive,
setLocalAudio,
setLocalPreview,
setLocalVideo,
setOutgoingRing,
setRendererCanvas,
@ -3802,8 +3788,7 @@ export function reducer(
}
return {
...state,
capturerBaton: undefined,
...(action.payload == null ? abortCapturer(state) : state),
activeCallState: {
...activeCallState,
presentingSource: action.payload,

View file

@ -116,6 +116,10 @@ async function notifyForCall(
});
}
function setLocalPreviewContainer(container: HTMLDivElement | null): void {
callingService.setLocalPreviewContainer(container);
}
const playRingtone = callingTones.playRingtone.bind(callingTones);
const stopRingtone = callingTones.stopRingtone.bind(callingTones);
@ -449,7 +453,6 @@ export const SmartCallManager = memo(function SmartCallManager() {
setIsCallActive,
setLocalAudio,
setLocalVideo,
setLocalPreview,
setOutgoingRing,
setRendererCanvas,
switchToPresentationView,
@ -509,7 +512,7 @@ export const SmartCallManager = memo(function SmartCallManager() {
setGroupCallVideoRequest={setGroupCallVideoRequest}
setIsCallActive={setIsCallActive}
setLocalAudio={setLocalAudio}
setLocalPreview={setLocalPreview}
setLocalPreviewContainer={setLocalPreviewContainer}
setLocalVideo={setLocalVideo}
setOutgoingRing={setOutgoingRing}
setRendererCanvas={setRendererCanvas}

View file

@ -2032,13 +2032,6 @@
"updated": "2024-01-06T00:59:20.678Z",
"reasonDetail": "Recent bursts shown for burst behavior like throttling."
},
{
"rule": "React-useRef",
"path": "ts/components/CallScreen.tsx",
"line": " const localVideoRef = useRef<HTMLVideoElement | null>(null);",
"reasonCategory": "usageTrusted",
"updated": "2021-12-01T01:31:12.757Z"
},
{
"rule": "React-useRef",
"path": "ts/components/CallScreen.tsx",
@ -2085,13 +2078,6 @@
"reasonCategory": "usageTrusted",
"updated": "2024-01-16T22:59:06.336Z"
},
{
"rule": "React-useRef",
"path": "ts/components/CallingLobby.tsx",
"line": " const localVideoRef = React.useRef<null | HTMLVideoElement>(null);",
"reasonCategory": "usageTrusted",
"updated": "2021-07-30T16:57:33.618Z"
},
{
"rule": "React-useRef",
"path": "ts/components/CallingPendingParticipants.tsx",
@ -2102,11 +2088,11 @@
},
{
"rule": "React-useRef",
"path": "ts/components/CallingPip.tsx",
"line": " const localVideoRef = React.useRef(null);",
"path": "ts/components/CallingPendingParticipants.tsx",
"line": " const lastParticipantRef = React.useRef<ConversationType | undefined>();",
"reasonCategory": "usageTrusted",
"updated": "2020-10-26T19:12:24.410Z",
"reasonDetail": "Used to get the local video element for rendering."
"updated": "2024-09-20T02:11:27.851Z",
"reasonDetail": "For fading out, to keep showing the last known participant"
},
{
"rule": "React-useRef",
@ -3126,13 +3112,5 @@
"line": " message.innerHTML = window.i18n('icu:optimizingApplication');",
"reasonCategory": "usageTrusted",
"updated": "2021-09-17T21:02:59.414Z"
},
{
"rule": "React-useRef",
"path": "ts/components/CallingPendingParticipants.tsx",
"line": " const lastParticipantRef = React.useRef<ConversationType | undefined>();",
"reasonCategory": "usageTrusted",
"updated": "2024-09-20T02:11:27.851Z",
"reasonDetail": "For fading out, to keep showing the last known participant"
}
]