mirror of
https://github.com/vector-im/element-call.git
synced 2026-03-16 06:17:10 +00:00
feat: video auto fit based on video stream size
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
type ReactNode,
|
||||
type Ref,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
@@ -26,7 +27,6 @@ import {
|
||||
VolumeOffIcon,
|
||||
VisibilityOnIcon,
|
||||
UserProfileIcon,
|
||||
ExpandIcon,
|
||||
VolumeOffSolidIcon,
|
||||
SwitchCameraSolidIcon,
|
||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
Menu,
|
||||
} from "@vector-im/compound-web";
|
||||
import { useObservableEagerState } from "observable-hooks";
|
||||
import useMeasure from "react-use-measure";
|
||||
|
||||
import styles from "./GridTile.module.css";
|
||||
import {
|
||||
@@ -105,18 +106,26 @@ const UserMediaTile: FC<UserMediaTileProps> = ({
|
||||
const audioEnabled = useBehavior(vm.audioEnabled$);
|
||||
const videoEnabled = useBehavior(vm.videoEnabled$);
|
||||
const speaking = useBehavior(vm.speaking$);
|
||||
const cropVideo = useBehavior(vm.cropVideo$);
|
||||
const onSelectFitContain = useCallback(
|
||||
(e: Event) => {
|
||||
e.preventDefault();
|
||||
vm.toggleFitContain();
|
||||
},
|
||||
[vm],
|
||||
);
|
||||
const videoFit = useBehavior(vm.videoFit$);
|
||||
|
||||
const rtcBackendIdentity = vm.rtcBackendIdentity;
|
||||
const handRaised = useBehavior(vm.handRaised$);
|
||||
const reaction = useBehavior(vm.reaction$);
|
||||
|
||||
// We need to keep track of the tile size.
|
||||
// We use this to get the tile ratio, and compare it to the video ratio to decide
|
||||
// whether to fit the video to frame or keep the ratio.
|
||||
const [measureRef, bounds] = useMeasure();
|
||||
// There is already a ref being passed in, so we need to merge it with the measureRef.
|
||||
const tileRef = useMergedRefs(ref, measureRef);
|
||||
|
||||
// Whenever bounds change, inform the viewModel
|
||||
useEffect(() => {
|
||||
if (bounds.width > 0 && bounds.height > 0) {
|
||||
vm.setActualDimensions(bounds.width, bounds.height);
|
||||
}
|
||||
}, [bounds.width, bounds.height, vm]);
|
||||
|
||||
const AudioIcon = locallyMuted
|
||||
? VolumeOffSolidIcon
|
||||
: audioEnabled
|
||||
@@ -132,12 +141,10 @@ const UserMediaTile: FC<UserMediaTileProps> = ({
|
||||
const menu = (
|
||||
<>
|
||||
{menuStart}
|
||||
<ToggleMenuItem
|
||||
Icon={ExpandIcon}
|
||||
label={t("video_tile.change_fit_contain")}
|
||||
checked={cropVideo}
|
||||
onSelect={onSelectFitContain}
|
||||
/>
|
||||
{/*
|
||||
No additional menu item (used to be the manual fit to frame.
|
||||
Placeholder for future menu items that should be placed here.
|
||||
*/}
|
||||
{menuEnd}
|
||||
</>
|
||||
);
|
||||
@@ -150,13 +157,13 @@ const UserMediaTile: FC<UserMediaTileProps> = ({
|
||||
|
||||
const tile = (
|
||||
<MediaView
|
||||
ref={ref}
|
||||
ref={tileRef}
|
||||
video={video}
|
||||
userId={vm.userId}
|
||||
unencryptedWarning={unencryptedWarning}
|
||||
encryptionStatus={encryptionStatus}
|
||||
videoEnabled={videoEnabled}
|
||||
videoFit={cropVideo ? "cover" : "contain"}
|
||||
videoFit={videoFit}
|
||||
className={classNames(className, styles.tile, {
|
||||
[styles.speaking]: showSpeaking,
|
||||
[styles.handRaised]: !showSpeaking && handRaised,
|
||||
|
||||
@@ -27,6 +27,7 @@ import { useObservableRef } from "observable-hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import { type TrackReferenceOrPlaceholder } from "@livekit/components-core";
|
||||
import useMeasure from "react-use-measure";
|
||||
|
||||
import FullScreenMaximiseIcon from "../icons/FullScreenMaximise.svg?react";
|
||||
import FullScreenMinimiseIcon from "../icons/FullScreenMinimise.svg?react";
|
||||
@@ -105,11 +106,11 @@ const SpotlightUserMediaItem: FC<SpotlightUserMediaItemProps> = ({
|
||||
vm,
|
||||
...props
|
||||
}) => {
|
||||
const cropVideo = useBehavior(vm.cropVideo$);
|
||||
const videoFit = useBehavior(vm.videoFit$);
|
||||
|
||||
const baseProps: SpotlightUserMediaItemBaseProps &
|
||||
RefAttributes<HTMLDivElement> = {
|
||||
videoFit: cropVideo ? "cover" : "contain",
|
||||
videoFit,
|
||||
...props,
|
||||
};
|
||||
|
||||
@@ -147,7 +148,22 @@ const SpotlightItem: FC<SpotlightItemProps> = ({
|
||||
"aria-hidden": ariaHidden,
|
||||
}) => {
|
||||
const ourRef = useRef<HTMLDivElement | null>(null);
|
||||
const ref = useMergedRefs(ourRef, theirRef);
|
||||
|
||||
// We need to keep track of the tile size.
|
||||
// We use this to get the tile ratio, and compare it to the video ratio to decide
|
||||
// whether to fit the video to frame or keep the ratio.
|
||||
const [measureRef, bounds] = useMeasure();
|
||||
|
||||
// Whenever bounds change, inform the viewModel
|
||||
useEffect(() => {
|
||||
if (bounds.width > 0 && bounds.height > 0) {
|
||||
if (!(vm instanceof ScreenShareViewModel)) {
|
||||
vm.setActualDimensions(bounds.width, bounds.height);
|
||||
}
|
||||
}
|
||||
}, [bounds.width, bounds.height, vm]);
|
||||
|
||||
const ref = useMergedRefs(ourRef, theirRef, measureRef);
|
||||
const focusUrl = useBehavior(vm.focusUrl$);
|
||||
const displayName = useBehavior(vm.displayName$);
|
||||
const mxcAvatarUrl = useBehavior(vm.mxcAvatarUrl$);
|
||||
|
||||
Reference in New Issue
Block a user