mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-22 22:29:30 +00:00
Add button to disable incoming video
This commit is contained in:
@@ -137,6 +137,7 @@
|
||||
"hangup_button_label": "End call",
|
||||
"header_label": "Element Call Home",
|
||||
"header_participants_label": "Participants",
|
||||
"hide_other_videos_button_label": "Hide other participants' video",
|
||||
"invite_modal": {
|
||||
"link_copied_toast": "Link copied to clipboard",
|
||||
"title": "Invite to this call"
|
||||
@@ -248,6 +249,7 @@
|
||||
"resolution_label": "Resolution",
|
||||
"screen_share_header": "Screen sharing"
|
||||
},
|
||||
"show_other_videos_button_label": "Show other participants' video",
|
||||
"star_rating_input_label_one": "{{count}} star",
|
||||
"star_rating_input_label_other": "{{count}} stars",
|
||||
"start_new_call": "Start new call",
|
||||
|
||||
@@ -25,6 +25,8 @@ import {
|
||||
OverflowVerticalIcon,
|
||||
VolumeOnSolidIcon,
|
||||
VolumeOffSolidIcon,
|
||||
VisibilityOnIcon,
|
||||
VisibilityOffIcon,
|
||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import styles from "./Button.module.css";
|
||||
@@ -103,6 +105,37 @@ export const VideoButton: FC<VideoButtonProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface DisableRemoteVideoButtonProps
|
||||
extends ComponentPropsWithoutRef<"button"> {
|
||||
/** Whether other participants' video is currently being shown. */
|
||||
enabled: boolean;
|
||||
size?: "md" | "lg";
|
||||
}
|
||||
|
||||
export const DisableRemoteVideoButton: FC<DisableRemoteVideoButtonProps> = ({
|
||||
enabled,
|
||||
...props
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const Icon = enabled ? VisibilityOnIcon : VisibilityOffIcon;
|
||||
const label = enabled
|
||||
? t("hide_other_videos_button_label")
|
||||
: t("show_other_videos_button_label");
|
||||
|
||||
return (
|
||||
<Tooltip label={label}>
|
||||
<CpdButton
|
||||
iconOnly
|
||||
Icon={Icon}
|
||||
kind={enabled ? "secondary" : "primary"}
|
||||
role="switch"
|
||||
aria-checked={enabled}
|
||||
{...props}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
interface ShareScreenButtonProps extends ComponentPropsWithoutRef<"button"> {
|
||||
enabled: boolean;
|
||||
size: "md" | "lg";
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
ReactionToggleButton,
|
||||
LoudspeakerButton,
|
||||
SettingsIconButton,
|
||||
DisableRemoteVideoButton,
|
||||
type ReactionData,
|
||||
} from "../button";
|
||||
import styles from "./CallFooter.module.css";
|
||||
@@ -58,6 +59,8 @@ export interface FooterActions {
|
||||
toggleVideo: (() => void) | undefined;
|
||||
toggleBlur: (() => void) | undefined;
|
||||
toggleScreenSharing: (() => void) | undefined;
|
||||
/** Also controls if the "hide other participants' video" button is visible */
|
||||
toggleDisableRemoteVideo: (() => void) | undefined;
|
||||
/** Also controls if the settings button is visible */
|
||||
openSettings: (() => void) | undefined;
|
||||
/** Also controls if the hangup button is visible */
|
||||
@@ -70,6 +73,8 @@ export interface FooterState {
|
||||
videoEnabled: boolean;
|
||||
videoBusy: boolean;
|
||||
videoBlurEnabled: boolean;
|
||||
/** Whether other participants' video feeds are hidden to save bandwidth */
|
||||
disableRemoteVideo: boolean;
|
||||
showFooter: boolean;
|
||||
|
||||
/* This is needed for WindowMode = "flat" */
|
||||
@@ -146,6 +151,8 @@ export const CallFooter: FC<FooterProps> = ({
|
||||
const selectVideoButtonOption = useBehavior(vm.selectVideoButtonOption$);
|
||||
const toggleBlur = useBehavior(vm.toggleBlur$);
|
||||
const videoBlurEnabled = useBehavior(vm.videoBlurEnabled$);
|
||||
const disableRemoteVideo = useBehavior(vm.disableRemoteVideo$);
|
||||
const toggleDisableRemoteVideo = useBehavior(vm.toggleDisableRemoteVideo$);
|
||||
const buttonSize = useBehavior(vm.buttonSize$);
|
||||
const showLogo = useBehavior(vm.showLogo$);
|
||||
|
||||
@@ -224,6 +231,18 @@ export const CallFooter: FC<FooterProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (toggleDisableRemoteVideo !== undefined) {
|
||||
buttons.push(
|
||||
<DisableRemoteVideoButton
|
||||
size={buttonSize}
|
||||
key="disable_remote_video"
|
||||
enabled={!disableRemoteVideo}
|
||||
onClick={toggleDisableRemoteVideo}
|
||||
data-testid="incall_disable_remote_video"
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
if (toggleScreenSharing !== undefined) {
|
||||
buttons.push(
|
||||
<ShareScreenButton
|
||||
|
||||
@@ -188,6 +188,9 @@ export function createCallFooterViewModel(
|
||||
sharingScreen$: callModel.sharingScreen$,
|
||||
toggleScreenSharing$: constant(callModel.toggleScreenSharing ?? undefined),
|
||||
|
||||
disableRemoteVideo$: callModel.disableRemoteVideo$,
|
||||
toggleDisableRemoteVideo$: constant(callModel.toggleDisableRemoteVideo),
|
||||
|
||||
audioOutputSwitcher$: scope.behavior(
|
||||
callModel.audioOutputSwitcher$.pipe(
|
||||
map((switcher) => switcher ?? undefined),
|
||||
@@ -249,10 +252,12 @@ export function createLobbyFooterViewModel(
|
||||
toggleAudio: undefined,
|
||||
toggleVideo: undefined,
|
||||
toggleScreenSharing: undefined,
|
||||
toggleDisableRemoteVideo: undefined,
|
||||
audioEnabled: undefined,
|
||||
audioBusy: false,
|
||||
videoEnabled: undefined,
|
||||
videoBusy: false,
|
||||
disableRemoteVideo: false,
|
||||
layoutSwitchVm: null,
|
||||
sharingScreen: false,
|
||||
audioOutputSwitcher: undefined,
|
||||
|
||||
@@ -268,6 +268,16 @@ export interface CallViewModel {
|
||||
*/
|
||||
sharingScreen$: Behavior<boolean>;
|
||||
|
||||
/**
|
||||
* Whether other participants' video feeds are hidden (and not downloaded)
|
||||
* to save bandwidth. Resets to false at the start of every call.
|
||||
*/
|
||||
disableRemoteVideo$: Behavior<boolean>;
|
||||
/**
|
||||
* Toggles disableRemoteVideo$.
|
||||
*/
|
||||
toggleDisableRemoteVideo: () => void;
|
||||
|
||||
// UI interactions
|
||||
/**
|
||||
* Callback for when the user taps the call view.
|
||||
@@ -730,6 +740,18 @@ export function createCallViewModel$(
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether other participants' video feeds are hidden to save bandwidth.
|
||||
* This is intentionally not persisted: it resets to false at the start of
|
||||
* every call.
|
||||
*/
|
||||
const disableRemoteVideoToggle$ = new Subject<void>();
|
||||
const disableRemoteVideo$ = createToggle$(
|
||||
scope,
|
||||
false,
|
||||
disableRemoteVideoToggle$,
|
||||
);
|
||||
|
||||
/**
|
||||
* List of user media (camera feeds) that we want tiles for.
|
||||
*/
|
||||
@@ -771,6 +793,7 @@ export function createCallViewModel$(
|
||||
),
|
||||
mediaDevices,
|
||||
pretendToBeDisconnected$: localMembership.reconnecting$,
|
||||
disableRemoteVideo$,
|
||||
displayName$: scope.behavior(
|
||||
matrixMemberMetadataStore
|
||||
.createDisplayNameBehavior$(scope, userId)
|
||||
@@ -1786,6 +1809,8 @@ export function createCallViewModel$(
|
||||
leave: localMembership.requestDisconnect,
|
||||
toggleScreenSharing: toggleScreenSharing,
|
||||
sharingScreen$: sharingScreen$,
|
||||
disableRemoteVideo$: disableRemoteVideo$,
|
||||
toggleDisableRemoteVideo: (): void => disableRemoteVideoToggle$.next(),
|
||||
|
||||
tapScreen: (): void => screenTap$.next(),
|
||||
tapControls: (): void => controlsTap$.next(),
|
||||
|
||||
@@ -34,11 +34,21 @@ export interface RemoteUserMediaInputs extends Omit<
|
||||
> {
|
||||
participant$: Behavior<RemoteParticipant | null>;
|
||||
pretendToBeDisconnected$: Behavior<boolean>;
|
||||
/**
|
||||
* Whether other participants' video feeds should be hidden (and thus not
|
||||
* downloaded, since LiveKit's adaptiveStream pauses tracks with no
|
||||
* attached video element) to save bandwidth.
|
||||
*/
|
||||
disableRemoteVideo$: Behavior<boolean>;
|
||||
}
|
||||
|
||||
export function createRemoteUserMedia(
|
||||
scope: ObservableScope,
|
||||
{ pretendToBeDisconnected$, ...inputs }: RemoteUserMediaInputs,
|
||||
{
|
||||
pretendToBeDisconnected$,
|
||||
disableRemoteVideo$,
|
||||
...inputs
|
||||
}: RemoteUserMediaInputs,
|
||||
): RemoteUserMediaViewModel {
|
||||
const baseUserMedia = createBaseUserMedia(scope, {
|
||||
...inputs,
|
||||
@@ -68,6 +78,11 @@ export function createRemoteUserMedia(
|
||||
),
|
||||
),
|
||||
),
|
||||
video$: scope.behavior(
|
||||
combineLatest([baseUserMedia.video$, disableRemoteVideo$]).pipe(
|
||||
map(([video, disabled]) => (disabled ? undefined : video)),
|
||||
),
|
||||
),
|
||||
waitingForMedia$: scope.behavior(
|
||||
combineLatest(
|
||||
[inputs.livekitRoom$, inputs.participant$],
|
||||
|
||||
@@ -89,6 +89,7 @@ interface WrappedUserMediaInputs extends Omit<
|
||||
participant: TaggedParticipant;
|
||||
mediaDevices: MediaDevices;
|
||||
pretendToBeDisconnected$: Behavior<boolean>;
|
||||
disableRemoteVideo$: Behavior<boolean>;
|
||||
}
|
||||
|
||||
export function createWrappedUserMedia(
|
||||
@@ -97,6 +98,7 @@ export function createWrappedUserMedia(
|
||||
participant,
|
||||
mediaDevices,
|
||||
pretendToBeDisconnected$,
|
||||
disableRemoteVideo$,
|
||||
...inputs
|
||||
}: WrappedUserMediaInputs,
|
||||
): WrappedUserMediaViewModel {
|
||||
@@ -110,6 +112,7 @@ export function createWrappedUserMedia(
|
||||
: createRemoteUserMedia(scope, {
|
||||
participant$: participant.value$,
|
||||
pretendToBeDisconnected$,
|
||||
disableRemoteVideo$,
|
||||
...inputs,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user