diff --git a/package.json b/package.json index c9cf1f9705c..f98ee7f1420 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "@emotion/react": "11.11.4", "@lexical/history": "0.13.1", "@lexical/react": "0.13.1", + "@mediapipe/tasks-vision": "^0.10.12", "@wireapp/avs": "9.6.12", "@wireapp/commons": "5.2.7", "@wireapp/core": "45.2.11", diff --git a/src/i18n/en-US.json b/src/i18n/en-US.json index 404bbe0cf8e..e13d9ba05f2 100644 --- a/src/i18n/en-US.json +++ b/src/i18n/en-US.json @@ -1512,6 +1512,9 @@ "videoCallaudioInputMicrophone": "Microphone", "videoCallaudioOutputSpeaker": "Speaker", "videoCallvideoInputCamera": "Camera", + "videoCallbackgroundBlurHeadline": "Background", + "videoCallbackgroundBlur": "Blur my background", + "videoCallbackgroundNotBlurred": "Don't blur my background", "videoSpeakersTabAll": "All ({{count}})", "videoSpeakersTabSpeakers": "Speakers", "warningCallIssues": "This version of {{brandName}} can not participate in the call. Please use", @@ -1538,4 +1541,4 @@ "wireMacos": "{{brandName}} for macOS", "wireWindows": "{{brandName}} for Windows", "wire_for_web": "{{brandName}} for Web" -} +} \ No newline at end of file diff --git a/src/script/calling/Participant.ts b/src/script/calling/Participant.ts index 52b72de7a29..0ead860eff5 100644 --- a/src/script/calling/Participant.ts +++ b/src/script/calling/Participant.ts @@ -22,6 +22,7 @@ import ko from 'knockout'; import {VIDEO_STATE} from '@wireapp/avs'; +import {backgroundBlur} from 'Components/calling/FullscreenVideoCall'; import {matchQualifiedIds} from 'Util/QualifiedId'; import {User} from '../entity/User'; @@ -41,7 +42,7 @@ export class Participant { public isActivelySpeaking: ko.Observable; public isSendingVideo: ko.PureComputed; public isAudioEstablished: ko.Observable; - + public isBlurred: ko.Observable; // Audio public audioStream: ko.Observable; public isMuted: ko.Observable; @@ -63,6 +64,9 @@ export class Participant { this.hasPausedVideo = ko.pureComputed(() => { return this.videoState() === VIDEO_STATE.PAUSED; }); + this.isBlurred = ko.observable( + localStorage.getItem('blurState') === 'true' ? backgroundBlur.isBlurred : backgroundBlur.isNotBlurred, + ); this.videoStream = ko.observable(); this.audioStream = ko.observable(); this.isActivelySpeaking = ko.observable(false); @@ -87,6 +91,11 @@ export class Participant { this.videoStream(videoStream); } + async setBlur(blurState: backgroundBlur): Promise { + this.isBlurred(blurState); + localStorage.setItem('blurState', blurState === backgroundBlur.isBlurred ? 'true' : 'false'); + } + updateMediaStream(newStream: MediaStream, stopTracks: boolean): MediaStream { if (newStream.getVideoTracks().length) { this.setVideoStream(new MediaStream(newStream.getVideoTracks()), stopTracks); diff --git a/src/script/components/calling/FullscreenVideoCall.tsx b/src/script/components/calling/FullscreenVideoCall.tsx index 22d210b706d..7d91c89f153 100644 --- a/src/script/components/calling/FullscreenVideoCall.tsx +++ b/src/script/components/calling/FullscreenVideoCall.tsx @@ -83,6 +83,11 @@ export interface FullscreenVideoCallProps { videoGrid: Grid; } +export enum backgroundBlur { + isNotBlurred, + isBlurred, +} + const FullscreenVideoCall: React.FC = ({ call, canShareScreen, @@ -108,10 +113,11 @@ const FullscreenVideoCall: React.FC = ({ teamState = container.resolve(TeamState), }) => { const selfParticipant = call.getSelfParticipant(); - const {sharesScreen: selfSharesScreen, sharesCamera: selfSharesCamera} = useKoSubscribableChildren(selfParticipant, [ - 'sharesScreen', - 'sharesCamera', - ]); + const { + sharesScreen: selfSharesScreen, + sharesCamera: selfSharesCamera, + isBlurred, + } = useKoSubscribableChildren(selfParticipant, ['sharesScreen', 'sharesCamera', 'isBlurred']); const { activeSpeakers, @@ -150,7 +156,7 @@ const FullscreenVideoCall: React.FC = ({ (call.initialType === CALL_TYPE.VIDEO || conversation.supportsVideoCall(call.isConference)); const showSwitchMicrophone = audioinput.length > 1; - const showSwitchVideo = videoinput.length > 1; + // const showSwitchVideo = videoinput.length > 1; //TODO: should we show video switch list item when there is only one camera? const audioOptions = [ { @@ -227,17 +233,41 @@ const FullscreenVideoCall: React.FC = ({ }; }), }, + { + label: t('videoCallbackgroundBlurHeadline'), + options: [ + { + label: t('videoCallbackgroundBlur'), + value: backgroundBlur.isBlurred, + dataUieName: backgroundBlur.isBlurred, + id: backgroundBlur.isBlurred, + }, + { + label: t('videoCallbackgroundNotBlurred'), + value: backgroundBlur.isNotBlurred, + dataUieName: backgroundBlur.isNotBlurred, + id: backgroundBlur.isNotBlurred, + }, + ], + }, ]; const [selectedVideoOptions, setSelectedVideoOptions] = React.useState(() => - [currentCameraDevice].flatMap( + [currentCameraDevice, isBlurred].flatMap( device => videoOptions.flatMap(options => options.options.filter(item => item.id === device)) ?? [], ), ); - const updateVideoOptions = (selectedOption: string) => { + + const updateVideoOptions = async (selectedOption: string | backgroundBlur) => { const camera = videoOptions[0].options.find(item => item.value === selectedOption) ?? selectedVideoOptions[0]; - setSelectedVideoOptions([camera]); - switchCameraInput(camera.id); + const blur = videoOptions[1].options.find(item => item.value == selectedOption) ?? selectedVideoOptions[1]; + + if (blur.id !== isBlurred) { + await selfParticipant.setBlur(blur.id as backgroundBlur); + } + + setSelectedVideoOptions([camera, blur]); + switchCameraInput(String(camera.id)); }; const unreadMessagesCount = useAppState(state => state.unreadMessagesCount); @@ -510,44 +540,42 @@ const FullscreenVideoCall: React.FC = ({ - {showSwitchVideo && ( - )} diff --git a/src/script/components/calling/GroupVideoGrid.tsx b/src/script/components/calling/GroupVideoGrid.tsx index fd066534883..69914f974ee 100644 --- a/src/script/components/calling/GroupVideoGrid.tsx +++ b/src/script/components/calling/GroupVideoGrid.tsx @@ -161,6 +161,8 @@ const GroupVideoGrid: React.FunctionComponent = ({ {thumbnail.videoStream && !maximizedParticipant && (