mirror of
https://github.com/vector-im/element-call.git
synced 2026-03-13 06:07:04 +00:00
Merge branch 'livekit' into valere/auto_fit_based_on_video_ratio
This commit is contained in:
@@ -14,7 +14,7 @@ import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { GridTile } from "./GridTile";
|
||||
import {
|
||||
mockRtcMembership,
|
||||
createRemoteMedia,
|
||||
mockRemoteMedia,
|
||||
mockRemoteParticipant,
|
||||
} from "../utils/test";
|
||||
import { GridTileViewModel } from "../state/TileViewModel";
|
||||
@@ -45,7 +45,7 @@ beforeAll(() => {
|
||||
});
|
||||
|
||||
test("GridTile is accessible", async () => {
|
||||
const vm = createRemoteMedia(
|
||||
const vm = mockRemoteMedia(
|
||||
mockRtcMembership("@alice:example.org", "AAAA"),
|
||||
{
|
||||
rawDisplayName: "Alice",
|
||||
|
||||
@@ -40,11 +40,6 @@ import { useObservableEagerState } from "observable-hooks";
|
||||
import useMeasure from "react-use-measure";
|
||||
|
||||
import styles from "./GridTile.module.css";
|
||||
import {
|
||||
type UserMediaViewModel,
|
||||
LocalUserMediaViewModel,
|
||||
type RemoteUserMediaViewModel,
|
||||
} from "../state/MediaViewModel";
|
||||
import { Slider } from "../Slider";
|
||||
import { MediaView } from "./MediaView";
|
||||
import { useLatest } from "../useLatest";
|
||||
@@ -52,6 +47,9 @@ import { type GridTileViewModel } from "../state/TileViewModel";
|
||||
import { useMergedRefs } from "../useMergedRefs";
|
||||
import { useReactionsSender } from "../reactions/useReactionsSender";
|
||||
import { useBehavior } from "../useBehavior";
|
||||
import { type LocalUserMediaViewModel } from "../state/media/LocalUserMediaViewModel";
|
||||
import { type RemoteUserMediaViewModel } from "../state/media/RemoteUserMediaViewModel";
|
||||
import { type UserMediaViewModel } from "../state/media/UserMediaViewModel";
|
||||
|
||||
interface TileProps {
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
@@ -69,7 +67,7 @@ interface TileProps {
|
||||
interface UserMediaTileProps extends TileProps {
|
||||
vm: UserMediaViewModel;
|
||||
mirror: boolean;
|
||||
locallyMuted: boolean;
|
||||
playbackMuted: boolean;
|
||||
waitingForMedia?: boolean;
|
||||
primaryButton?: ReactNode;
|
||||
menuStart?: ReactNode;
|
||||
@@ -80,7 +78,7 @@ const UserMediaTile: FC<UserMediaTileProps> = ({
|
||||
ref,
|
||||
vm,
|
||||
showSpeakingIndicators,
|
||||
locallyMuted,
|
||||
playbackMuted,
|
||||
waitingForMedia,
|
||||
primaryButton,
|
||||
menuStart,
|
||||
@@ -126,12 +124,12 @@ const UserMediaTile: FC<UserMediaTileProps> = ({
|
||||
}
|
||||
}, [bounds.width, bounds.height, vm]);
|
||||
|
||||
const AudioIcon = locallyMuted
|
||||
const AudioIcon = playbackMuted
|
||||
? VolumeOffSolidIcon
|
||||
: audioEnabled
|
||||
? MicOnSolidIcon
|
||||
: MicOffSolidIcon;
|
||||
const audioIconLabel = locallyMuted
|
||||
const audioIconLabel = playbackMuted
|
||||
? t("video_tile.muted_for_me")
|
||||
: audioEnabled
|
||||
? t("microphone_on")
|
||||
@@ -173,7 +171,7 @@ const UserMediaTile: FC<UserMediaTileProps> = ({
|
||||
width={20}
|
||||
height={20}
|
||||
aria-label={audioIconLabel}
|
||||
data-muted={locallyMuted || !audioEnabled}
|
||||
data-muted={playbackMuted || !audioEnabled}
|
||||
className={styles.muteIcon}
|
||||
/>
|
||||
}
|
||||
@@ -252,7 +250,7 @@ const LocalUserMediaTile: FC<LocalUserMediaTileProps> = ({
|
||||
<UserMediaTile
|
||||
ref={ref}
|
||||
vm={vm}
|
||||
locallyMuted={false}
|
||||
playbackMuted={false}
|
||||
mirror={mirror}
|
||||
primaryButton={
|
||||
switchCamera === null ? undefined : (
|
||||
@@ -302,36 +300,31 @@ const RemoteUserMediaTile: FC<RemoteUserMediaTileProps> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const waitingForMedia = useBehavior(vm.waitingForMedia$);
|
||||
const locallyMuted = useBehavior(vm.locallyMuted$);
|
||||
const localVolume = useBehavior(vm.localVolume$);
|
||||
const playbackMuted = useBehavior(vm.playbackMuted$);
|
||||
const playbackVolume = useBehavior(vm.playbackVolume$);
|
||||
const onSelectMute = useCallback(
|
||||
(e: Event) => {
|
||||
e.preventDefault();
|
||||
vm.toggleLocallyMuted();
|
||||
vm.togglePlaybackMuted();
|
||||
},
|
||||
[vm],
|
||||
);
|
||||
const onChangeLocalVolume = useCallback(
|
||||
(v: number) => vm.setLocalVolume(v),
|
||||
[vm],
|
||||
);
|
||||
const onCommitLocalVolume = useCallback(() => vm.commitLocalVolume(), [vm]);
|
||||
|
||||
const VolumeIcon = locallyMuted ? VolumeOffIcon : VolumeOnIcon;
|
||||
const VolumeIcon = playbackMuted ? VolumeOffIcon : VolumeOnIcon;
|
||||
|
||||
return (
|
||||
<UserMediaTile
|
||||
ref={ref}
|
||||
vm={vm}
|
||||
waitingForMedia={waitingForMedia}
|
||||
locallyMuted={locallyMuted}
|
||||
playbackMuted={playbackMuted}
|
||||
mirror={false}
|
||||
menuStart={
|
||||
<>
|
||||
<ToggleMenuItem
|
||||
Icon={MicOffIcon}
|
||||
label={t("video_tile.mute_for_me")}
|
||||
checked={locallyMuted}
|
||||
checked={playbackMuted}
|
||||
onSelect={onSelectMute}
|
||||
/>
|
||||
{/* TODO: Figure out how to make this slider keyboard accessible */}
|
||||
@@ -339,9 +332,9 @@ const RemoteUserMediaTile: FC<RemoteUserMediaTileProps> = ({
|
||||
<Slider
|
||||
className={styles.volumeSlider}
|
||||
label={t("video_tile.volume")}
|
||||
value={localVolume}
|
||||
onValueChange={onChangeLocalVolume}
|
||||
onValueCommit={onCommitLocalVolume}
|
||||
value={playbackVolume}
|
||||
onValueChange={vm.adjustPlaybackVolume}
|
||||
onValueCommit={vm.commitPlaybackVolume}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
@@ -381,7 +374,7 @@ export const GridTile: FC<GridTileProps> = ({
|
||||
const displayName = useBehavior(media.displayName$);
|
||||
const mxcAvatarUrl = useBehavior(media.mxcAvatarUrl$);
|
||||
|
||||
if (media instanceof LocalUserMediaViewModel) {
|
||||
if (media.local) {
|
||||
return (
|
||||
<LocalUserMediaTile
|
||||
ref={ref}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { TrackInfo } from "@livekit/protocol";
|
||||
import { type ComponentProps } from "react";
|
||||
|
||||
import { MediaView } from "./MediaView";
|
||||
import { EncryptionStatus } from "../state/MediaViewModel";
|
||||
import { EncryptionStatus } from "../state/media/MemberMediaViewModel";
|
||||
import { mockLocalParticipant } from "../utils/test";
|
||||
|
||||
describe("MediaView", () => {
|
||||
|
||||
@@ -16,10 +16,10 @@ import { ErrorSolidIcon } from "@vector-im/compound-design-tokens/assets/web/ico
|
||||
|
||||
import styles from "./MediaView.module.css";
|
||||
import { Avatar } from "../Avatar";
|
||||
import { type EncryptionStatus } from "../state/MediaViewModel";
|
||||
import { type EncryptionStatus } from "../state/media/MemberMediaViewModel";
|
||||
import { RaisedHandIndicator } from "../reactions/RaisedHandIndicator";
|
||||
import {
|
||||
showConnectionStats,
|
||||
showConnectionStats as showConnectionStatsSetting,
|
||||
showHandRaisedTimer,
|
||||
useSetting,
|
||||
} from "../settings/settings";
|
||||
@@ -85,7 +85,7 @@ export const MediaView: FC<Props> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [handRaiseTimerVisible] = useSetting(showHandRaisedTimer);
|
||||
const [showConnectioStats] = useSetting(showConnectionStats);
|
||||
const [showConnectionStats] = useSetting(showConnectionStatsSetting);
|
||||
|
||||
const avatarSize = Math.round(Math.min(targetWidth, targetHeight) / 2);
|
||||
|
||||
@@ -139,10 +139,10 @@ export const MediaView: FC<Props> = ({
|
||||
{waitingForMedia && (
|
||||
<div className={styles.status}>
|
||||
{t("video_tile.waiting_for_media")}
|
||||
{showConnectioStats ? " " + rtcBackendIdentity : ""}
|
||||
{showConnectionStats ? " " + rtcBackendIdentity : ""}
|
||||
</div>
|
||||
)}
|
||||
{(audioStreamStats || videoStreamStats) && (
|
||||
{showConnectionStats && (
|
||||
<>
|
||||
<RTCConnectionStats
|
||||
audio={audioStreamStats}
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
mockLocalParticipant,
|
||||
mockMediaDevices,
|
||||
mockRtcMembership,
|
||||
createLocalMedia,
|
||||
createRemoteMedia,
|
||||
mockLocalMedia,
|
||||
mockRemoteMedia,
|
||||
mockRemoteParticipant,
|
||||
} from "../utils/test";
|
||||
import { SpotlightTileViewModel } from "../state/TileViewModel";
|
||||
@@ -44,7 +44,7 @@ beforeAll(() => {
|
||||
});
|
||||
|
||||
test("SpotlightTile is accessible", async () => {
|
||||
const vm1 = createRemoteMedia(
|
||||
const vm1 = mockRemoteMedia(
|
||||
mockRtcMembership("@alice:example.org", "AAAA"),
|
||||
{
|
||||
rawDisplayName: "Alice",
|
||||
@@ -53,7 +53,7 @@ test("SpotlightTile is accessible", async () => {
|
||||
mockRemoteParticipant({}),
|
||||
);
|
||||
|
||||
const vm2 = createLocalMedia(
|
||||
const vm2 = mockLocalMedia(
|
||||
mockRtcMembership("@bob:example.org", "BBBB"),
|
||||
{
|
||||
rawDisplayName: "Bob",
|
||||
|
||||
@@ -33,20 +33,19 @@ import FullScreenMaximiseIcon from "../icons/FullScreenMaximise.svg?react";
|
||||
import FullScreenMinimiseIcon from "../icons/FullScreenMinimise.svg?react";
|
||||
import { MediaView } from "./MediaView";
|
||||
import styles from "./SpotlightTile.module.css";
|
||||
import {
|
||||
type EncryptionStatus,
|
||||
LocalUserMediaViewModel,
|
||||
type MediaViewModel,
|
||||
ScreenShareViewModel,
|
||||
type UserMediaViewModel,
|
||||
type RemoteUserMediaViewModel,
|
||||
} from "../state/MediaViewModel";
|
||||
import { useInitial } from "../useInitial";
|
||||
import { useMergedRefs } from "../useMergedRefs";
|
||||
import { useReactiveState } from "../useReactiveState";
|
||||
import { useLatest } from "../useLatest";
|
||||
import { type SpotlightTileViewModel } from "../state/TileViewModel";
|
||||
import { useBehavior } from "../useBehavior";
|
||||
import { type EncryptionStatus } from "../state/media/MemberMediaViewModel";
|
||||
import { type LocalUserMediaViewModel } from "../state/media/LocalUserMediaViewModel";
|
||||
import { type RemoteUserMediaViewModel } from "../state/media/RemoteUserMediaViewModel";
|
||||
import { type UserMediaViewModel } from "../state/media/UserMediaViewModel";
|
||||
import { type ScreenShareViewModel } from "../state/media/ScreenShareViewModel";
|
||||
import { type RemoteScreenShareViewModel } from "../state/media/RemoteScreenShareViewModel";
|
||||
import { type MediaViewModel } from "../state/media/MediaViewModel";
|
||||
|
||||
interface SpotlightItemBaseProps {
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
@@ -55,7 +54,6 @@ interface SpotlightItemBaseProps {
|
||||
targetWidth: number;
|
||||
targetHeight: number;
|
||||
video: TrackReferenceOrPlaceholder | undefined;
|
||||
videoEnabled: boolean;
|
||||
userId: string;
|
||||
unencryptedWarning: boolean;
|
||||
encryptionStatus: EncryptionStatus;
|
||||
@@ -68,6 +66,7 @@ interface SpotlightItemBaseProps {
|
||||
|
||||
interface SpotlightUserMediaItemBaseProps extends SpotlightItemBaseProps {
|
||||
videoFit: "contain" | "cover";
|
||||
videoEnabled: boolean;
|
||||
}
|
||||
|
||||
interface SpotlightLocalUserMediaItemProps extends SpotlightUserMediaItemBaseProps {
|
||||
@@ -107,14 +106,16 @@ const SpotlightUserMediaItem: FC<SpotlightUserMediaItemProps> = ({
|
||||
...props
|
||||
}) => {
|
||||
const videoFit = useBehavior(vm.videoFit$);
|
||||
const videoEnabled = useBehavior(vm.videoEnabled$);
|
||||
|
||||
const baseProps: SpotlightUserMediaItemBaseProps &
|
||||
RefAttributes<HTMLDivElement> = {
|
||||
videoFit,
|
||||
videoEnabled,
|
||||
...props,
|
||||
};
|
||||
|
||||
return vm instanceof LocalUserMediaViewModel ? (
|
||||
return vm.local ? (
|
||||
<SpotlightLocalUserMediaItem vm={vm} {...baseProps} />
|
||||
) : (
|
||||
<SpotlightRemoteUserMediaItem vm={vm} {...baseProps} />
|
||||
@@ -123,6 +124,31 @@ const SpotlightUserMediaItem: FC<SpotlightUserMediaItemProps> = ({
|
||||
|
||||
SpotlightUserMediaItem.displayName = "SpotlightUserMediaItem";
|
||||
|
||||
interface SpotlightScreenShareItemProps extends SpotlightItemBaseProps {
|
||||
vm: ScreenShareViewModel;
|
||||
videoEnabled: boolean;
|
||||
}
|
||||
|
||||
const SpotlightScreenShareItem: FC<SpotlightScreenShareItemProps> = ({
|
||||
vm,
|
||||
...props
|
||||
}) => {
|
||||
return <MediaView videoFit="contain" mirror={false} {...props} />;
|
||||
};
|
||||
|
||||
interface SpotlightRemoteScreenShareItemProps extends SpotlightItemBaseProps {
|
||||
vm: RemoteScreenShareViewModel;
|
||||
}
|
||||
|
||||
const SpotlightRemoteScreenShareItem: FC<
|
||||
SpotlightRemoteScreenShareItemProps
|
||||
> = ({ vm, ...props }) => {
|
||||
const videoEnabled = useBehavior(vm.videoEnabled$);
|
||||
return (
|
||||
<SpotlightScreenShareItem vm={vm} videoEnabled={videoEnabled} {...props} />
|
||||
);
|
||||
};
|
||||
|
||||
interface SpotlightItemProps {
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
vm: MediaViewModel;
|
||||
@@ -168,7 +194,6 @@ const SpotlightItem: FC<SpotlightItemProps> = ({
|
||||
const displayName = useBehavior(vm.displayName$);
|
||||
const mxcAvatarUrl = useBehavior(vm.mxcAvatarUrl$);
|
||||
const video = useBehavior(vm.video$);
|
||||
const videoEnabled = useBehavior(vm.videoEnabled$);
|
||||
const unencryptedWarning = useBehavior(vm.unencryptedWarning$);
|
||||
const encryptionStatus = useBehavior(vm.encryptionStatus$);
|
||||
|
||||
@@ -194,7 +219,6 @@ const SpotlightItem: FC<SpotlightItemProps> = ({
|
||||
targetWidth,
|
||||
targetHeight,
|
||||
video: video ?? undefined,
|
||||
videoEnabled,
|
||||
userId: vm.userId,
|
||||
unencryptedWarning,
|
||||
focusUrl,
|
||||
@@ -205,10 +229,12 @@ const SpotlightItem: FC<SpotlightItemProps> = ({
|
||||
"aria-hidden": ariaHidden,
|
||||
};
|
||||
|
||||
return vm instanceof ScreenShareViewModel ? (
|
||||
<MediaView videoFit="contain" mirror={false} {...baseProps} />
|
||||
if (vm.type === "user")
|
||||
return <SpotlightUserMediaItem vm={vm} {...baseProps} />;
|
||||
return vm.local ? (
|
||||
<SpotlightScreenShareItem vm={vm} videoEnabled {...baseProps} />
|
||||
) : (
|
||||
<SpotlightUserMediaItem vm={vm} {...baseProps} />
|
||||
<SpotlightRemoteScreenShareItem vm={vm} {...baseProps} />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user