Add background Blur
This commit is contained in:
@@ -9,7 +9,9 @@ import {
|
||||
ConnectionState,
|
||||
E2EEOptions,
|
||||
ExternalE2EEKeyProvider,
|
||||
LocalTrackPublication,
|
||||
Room,
|
||||
RoomEvent,
|
||||
RoomOptions,
|
||||
Track,
|
||||
} from "livekit-client";
|
||||
@@ -17,6 +19,7 @@ import { useEffect, useMemo, useRef } from "react";
|
||||
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
import { BackgroundBlur } from "@livekit/track-processors";
|
||||
|
||||
import { defaultLiveKitOptions } from "./options";
|
||||
import { SFUConfig } from "./openIDSFU";
|
||||
@@ -26,6 +29,7 @@ import {
|
||||
MediaDevices,
|
||||
useMediaDevices,
|
||||
} from "./MediaDevicesContext";
|
||||
import { backgroundBlur as backgroundBlurSettings } from "../settings/settings";
|
||||
import {
|
||||
ECConnectionState,
|
||||
useECConnectionState,
|
||||
@@ -33,6 +37,7 @@ import {
|
||||
import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
import { EncryptionSystem } from "../e2ee/sharedKeyManagement";
|
||||
import { useSetting } from "../settings/settings";
|
||||
|
||||
interface UseLivekitResult {
|
||||
livekitRoom?: Room;
|
||||
@@ -78,13 +83,16 @@ export function useLiveKit(
|
||||
const initialMuteStates = useRef<MuteStates>(muteStates);
|
||||
const devices = useMediaDevices();
|
||||
const initialDevices = useRef<MediaDevices>(devices);
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
const blur = useMemo(() => BackgroundBlur(15), []);
|
||||
const roomOptions = useMemo(
|
||||
(): RoomOptions => ({
|
||||
...defaultLiveKitOptions,
|
||||
videoCaptureDefaults: {
|
||||
...defaultLiveKitOptions.videoCaptureDefaults,
|
||||
deviceId: initialDevices.current.videoInput.selectedId,
|
||||
// eslint-disable-next-line new-cap
|
||||
processor: BackgroundBlur(15),
|
||||
},
|
||||
audioCaptureDefaults: {
|
||||
...defaultLiveKitOptions.audioCaptureDefaults,
|
||||
@@ -129,6 +137,51 @@ export function useLiveKit(
|
||||
sfuConfig,
|
||||
);
|
||||
|
||||
const [showBackgroundBlur] = useSetting(backgroundBlurSettings);
|
||||
const videoTrackPromise = useRef<
|
||||
undefined | Promise<LocalTrackPublication | undefined>
|
||||
>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (!room || videoTrackPromise.current) return;
|
||||
const update = async (): Promise<void> => {
|
||||
let publishCallback: undefined | ((track: LocalTrackPublication) => void);
|
||||
videoTrackPromise.current = new Promise<
|
||||
LocalTrackPublication | undefined
|
||||
>((resolve) => {
|
||||
const videoTrack = Array.from(
|
||||
room.localParticipant.videoTrackPublications.values(),
|
||||
).find((v) => v.source === Track.Source.Camera);
|
||||
if (videoTrack) {
|
||||
resolve(videoTrack);
|
||||
}
|
||||
publishCallback = (videoTrack: LocalTrackPublication): void => {
|
||||
if (videoTrack.source === Track.Source.Camera) {
|
||||
resolve(videoTrack);
|
||||
}
|
||||
};
|
||||
room.on(RoomEvent.LocalTrackPublished, publishCallback);
|
||||
});
|
||||
|
||||
const videoTrack = await videoTrackPromise.current;
|
||||
|
||||
if (publishCallback)
|
||||
room.off(RoomEvent.LocalTrackPublished, publishCallback);
|
||||
|
||||
if (videoTrack !== undefined) {
|
||||
if (showBackgroundBlur) {
|
||||
logger.info("Blur: set blur");
|
||||
|
||||
void videoTrack.track?.setProcessor(blur);
|
||||
} else {
|
||||
void videoTrack.track?.stopProcessor();
|
||||
}
|
||||
}
|
||||
videoTrackPromise.current = undefined;
|
||||
};
|
||||
void update();
|
||||
}, [blur, room, showBackgroundBlur]);
|
||||
|
||||
useEffect(() => {
|
||||
// Sync the requested mute states with LiveKit's mute states. We do it this
|
||||
// way around rather than using LiveKit as the source of truth, so that the
|
||||
|
||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { FC, useCallback, useMemo, useState } from "react";
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
@@ -16,6 +16,7 @@ import { usePreviewTracks } from "@livekit/components-react";
|
||||
import { LocalVideoTrack, Track } from "livekit-client";
|
||||
import { useObservable } from "observable-hooks";
|
||||
import { map } from "rxjs";
|
||||
import { BackgroundBlur } from "@livekit/track-processors";
|
||||
|
||||
import inCallStyles from "./InCallView.module.css";
|
||||
import styles from "./LobbyView.module.css";
|
||||
@@ -32,12 +33,14 @@ import {
|
||||
VideoButton,
|
||||
} from "../button/Button";
|
||||
import { SettingsModal, defaultSettingsTab } from "../settings/SettingsModal";
|
||||
import { backgroundBlur as backgroundBlurSettings } from "../settings/settings";
|
||||
import { useMediaQuery } from "../useMediaQuery";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
import { Link } from "../button/Link";
|
||||
import { useMediaDevices } from "../livekit/MediaDevicesContext";
|
||||
import { useInitial } from "../useInitial";
|
||||
import { useSwitchCamera } from "./useSwitchCamera";
|
||||
import { useSwitchCamera as useShowSwitchCamera } from "./useSwitchCamera";
|
||||
import { useSetting } from "../settings/settings";
|
||||
|
||||
interface Props {
|
||||
client: MatrixClient;
|
||||
@@ -108,6 +111,9 @@ export const LobbyView: FC<Props> = ({
|
||||
muteStates.audio.enabled && { deviceId: devices.audioInput.selectedId },
|
||||
);
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
const blur = useMemo(() => BackgroundBlur(15), []);
|
||||
|
||||
const localTrackOptions = useMemo(
|
||||
() => ({
|
||||
// The only reason we request audio here is to get the audio permission
|
||||
@@ -119,12 +125,15 @@ export const LobbyView: FC<Props> = ({
|
||||
audio: Object.assign({}, initialAudioOptions),
|
||||
video: muteStates.video.enabled && {
|
||||
deviceId: devices.videoInput.selectedId,
|
||||
// It should be possible to set a processor here:
|
||||
// processor: blur,
|
||||
// This causes a crash currently hence we do the effect below...
|
||||
},
|
||||
}),
|
||||
[
|
||||
initialAudioOptions,
|
||||
devices.videoInput.selectedId,
|
||||
muteStates.video.enabled,
|
||||
devices.videoInput.selectedId,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -146,7 +155,21 @@ export const LobbyView: FC<Props> = ({
|
||||
[tracks],
|
||||
);
|
||||
|
||||
const switchCamera = useSwitchCamera(
|
||||
const [showBackgroundBlur] = useSetting(backgroundBlurSettings);
|
||||
|
||||
useEffect(() => {
|
||||
const updateBlur = async (showBlur: boolean): Promise<void> => {
|
||||
if (showBlur && !videoTrack?.getProcessor()) {
|
||||
// eslint-disable-next-line new-cap
|
||||
await videoTrack?.setProcessor(blur);
|
||||
} else {
|
||||
await videoTrack?.stopProcessor();
|
||||
}
|
||||
};
|
||||
if (videoTrack) void updateBlur(showBackgroundBlur);
|
||||
}, [videoTrack, showBackgroundBlur, blur]);
|
||||
|
||||
const showSwitchCamera = useShowSwitchCamera(
|
||||
useObservable(
|
||||
(inputs) => inputs.pipe(map(([video]) => video)),
|
||||
[videoTrack],
|
||||
@@ -208,7 +231,9 @@ export const LobbyView: FC<Props> = ({
|
||||
onClick={onVideoPress}
|
||||
disabled={muteStates.video.setEnabled === null}
|
||||
/>
|
||||
{switchCamera && <SwitchCameraButton onClick={switchCamera} />}
|
||||
{showSwitchCamera && (
|
||||
<SwitchCameraButton onClick={showSwitchCamera} />
|
||||
)}
|
||||
<SettingsButton onClick={openSettings} />
|
||||
{!confineToRoom && <EndCallButton onClick={onLeaveClick} />}
|
||||
</div>
|
||||
|
||||
@@ -5,10 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { ChangeEvent, FC, useCallback, useState } from "react";
|
||||
import { ChangeEvent, FC, ReactNode, useCallback, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { Root as Form, Text } from "@vector-im/compound-web";
|
||||
import {
|
||||
Root as Form,
|
||||
Separator,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@vector-im/compound-web";
|
||||
|
||||
import { Modal } from "../Modal";
|
||||
import styles from "./SettingsModal.module.css";
|
||||
@@ -26,6 +31,7 @@ import {
|
||||
useSetting,
|
||||
developerSettingsTab as developerSettingsTabSetting,
|
||||
duplicateTiles as duplicateTilesSetting,
|
||||
backgroundBlur as backgroundBlurSetting,
|
||||
useOptInAnalytics,
|
||||
soundEffectVolumeSetting,
|
||||
} from "./settings";
|
||||
@@ -70,6 +76,33 @@ export const SettingsModal: FC<Props> = ({
|
||||
);
|
||||
const [duplicateTiles, setDuplicateTiles] = useSetting(duplicateTilesSetting);
|
||||
|
||||
// Generate a `Checkbox` input to turn blur on or off.
|
||||
const BlurCheckbox: React.FC = (): ReactNode => {
|
||||
const [blur, setBlur] = useSetting(backgroundBlurSetting);
|
||||
return (
|
||||
<>
|
||||
<h4>{t("settings.background_blur_header")}</h4>
|
||||
<FieldRow>
|
||||
<Tooltip
|
||||
label={
|
||||
isFirefox() ? t("settings.blur_not_supported_by_browser") : ""
|
||||
}
|
||||
>
|
||||
<InputField
|
||||
id="activateBackgroundBlur"
|
||||
label={t("settings.background_blur_label")}
|
||||
description={t("settings.video_tab_activate_background_blur")}
|
||||
type="checkbox"
|
||||
checked={blur}
|
||||
onChange={(b): void => setBlur(b.target.checked)}
|
||||
disabled={isFirefox()}
|
||||
/>
|
||||
</Tooltip>
|
||||
</FieldRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const optInDescription = (
|
||||
<Text size="sm">
|
||||
<Trans i18nKey="settings.opt_in_description">
|
||||
@@ -124,12 +157,16 @@ export const SettingsModal: FC<Props> = ({
|
||||
key: "video",
|
||||
name: t("common.video"),
|
||||
content: (
|
||||
<Form>
|
||||
<DeviceSelection
|
||||
devices={devices.videoInput}
|
||||
caption={t("common.camera")}
|
||||
/>
|
||||
</Form>
|
||||
<>
|
||||
<Form>
|
||||
<DeviceSelection
|
||||
devices={devices.videoInput}
|
||||
caption={t("common.camera")}
|
||||
/>
|
||||
</Form>
|
||||
<Separator />
|
||||
<BlurCheckbox />
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
@@ -88,6 +88,8 @@ export const videoInput = new Setting<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export const backgroundBlur = new Setting<boolean>("background-blur", true);
|
||||
|
||||
export const showHandRaisedTimer = new Setting<boolean>(
|
||||
"hand-raised-show-timer",
|
||||
false,
|
||||
|
||||
Reference in New Issue
Block a user