feat(virtual-background) add slight blur option

This commit is contained in:
Tudor D. Pop 2021-04-09 15:17:06 +03:00 committed by GitHub
parent 927b40ec71
commit af28080058
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 88 additions and 48 deletions

View File

@ -1,6 +1,6 @@
.virtual-background-dialog { .virtual-background-dialog {
display: inline-grid; display: inline-grid;
grid-template-columns: auto auto auto auto auto auto auto; grid-template-columns: auto auto auto auto auto auto auto auto;
max-width: 370px; max-width: 370px;
cursor: pointer; cursor: pointer;
.thumbnail { .thumbnail {

View File

@ -339,7 +339,8 @@
}, },
"virtualBackground": { "virtualBackground": {
"title": "Backgrounds", "title": "Backgrounds",
"enableBlur": "Enable blur", "blur": "Blur",
"slightBlur": "Slight Blur",
"removeBackground": "Remove background", "removeBackground": "Remove background",
"uploadImage": "Upload image", "uploadImage": "Upload image",
"pleaseWait": "Please wait...", "pleaseWait": "Please wait...",

View File

@ -5,7 +5,6 @@ import {
SET_TIMEOUT, SET_TIMEOUT,
timerWorkerScript timerWorkerScript
} from './TimerWorker'; } from './TimerWorker';
const blurValue = '25px';
/** /**
* Represents a modified MediaStream that adds effects to video background. * Represents a modified MediaStream that adds effects to video background.
@ -40,7 +39,7 @@ export default class JitsiStreamBackgroundEffect {
constructor(model: Object, options: Object) { constructor(model: Object, options: Object) {
this._options = options; this._options = options;
if (this._options.virtualBackground.isVirtualBackground) { if (this._options.virtualBackground.backgroundType === 'image') {
this._virtualImage = document.createElement('img'); this._virtualImage = document.createElement('img');
this._virtualImage.crossOrigin = 'anonymous'; this._virtualImage.crossOrigin = 'anonymous';
this._virtualImage.src = this._options.virtualBackground.virtualSource; this._virtualImage.src = this._options.virtualBackground.virtualSource;
@ -65,9 +64,9 @@ export default class JitsiStreamBackgroundEffect {
* @param {EventHandler} response - The onmessage EventHandler parameter. * @param {EventHandler} response - The onmessage EventHandler parameter.
* @returns {void} * @returns {void}
*/ */
async _onMaskFrameTimer(response: Object) { _onMaskFrameTimer(response: Object) {
if (response.data.id === TIMEOUT_TICK) { if (response.data.id === TIMEOUT_TICK) {
await this._renderMask(); this._renderMask();
} }
} }
@ -83,7 +82,7 @@ export default class JitsiStreamBackgroundEffect {
// //
// Smooth out the edges. // Smooth out the edges.
if (this._options.virtualBackground.isVirtualBackground) { if (this._options.virtualBackground.backgroundType === 'image') {
this._outputCanvasCtx.filter = 'blur(4px)'; this._outputCanvasCtx.filter = 'blur(4px)';
} else { } else {
this._outputCanvasCtx.filter = 'blur(8px)'; this._outputCanvasCtx.filter = 'blur(8px)';
@ -112,7 +111,7 @@ export default class JitsiStreamBackgroundEffect {
// //
this._outputCanvasCtx.globalCompositeOperation = 'destination-over'; this._outputCanvasCtx.globalCompositeOperation = 'destination-over';
if (this._options.virtualBackground.isVirtualBackground) { if (this._options.virtualBackground.backgroundType === 'image') {
this._outputCanvasCtx.drawImage( this._outputCanvasCtx.drawImage(
this._virtualImage, this._virtualImage,
0, 0,
@ -121,7 +120,7 @@ export default class JitsiStreamBackgroundEffect {
this._inputVideoElement.height this._inputVideoElement.height
); );
} else { } else {
this._outputCanvasCtx.filter = `blur(${blurValue})`; this._outputCanvasCtx.filter = `blur(${this._options.virtualBackground.blurValue}px)`;
this._outputCanvasCtx.drawImage(this._inputVideoElement, 0, 0); this._outputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
} }
} }

View File

@ -6,7 +6,7 @@
* *
* @returns {{ * @returns {{
* type: BACKGROUND_ENABLED, * type: BACKGROUND_ENABLED,
* backgroundEffectEnabled: boolean, * backgroundEffectEnabled: boolean
* }} * }}
*/ */
export const BACKGROUND_ENABLED = 'BACKGROUND_ENABLED'; export const BACKGROUND_ENABLED = 'BACKGROUND_ENABLED';
@ -16,8 +16,8 @@ export const BACKGROUND_ENABLED = 'BACKGROUND_ENABLED';
* *
* @returns {{ * @returns {{
* type: SET_VIRTUAL_BACKGROUND, * type: SET_VIRTUAL_BACKGROUND,
* isVirtualBackground: boolean,
* virtualSource: string, * virtualSource: string,
* blurValue: number,
* }} * }}
*/ */
export const SET_VIRTUAL_BACKGROUND = 'SET_VIRTUAL_BACKGROUND'; export const SET_VIRTUAL_BACKGROUND = 'SET_VIRTUAL_BACKGROUND';

View File

@ -9,20 +9,20 @@ import logger from './logger';
/** /**
* Signals the local participant activate the virtual background video or not. * Signals the local participant activate the virtual background video or not.
* *
* @param {boolean} enabled - If true enables video background, false otherwise. * @param {Object} options - Represents the virtual background setted options.
* @returns {Promise} * @returns {Promise}
*/ */
export function toggleBackgroundEffect(enabled: boolean) { export function toggleBackgroundEffect(options: Object) {
return async function(dispatch: Object => Object, getState: () => any) { return async function(dispatch: Object => Object, getState: () => any) {
await dispatch(backgroundEnabled(options.enabled));
await dispatch(setVirtualBackground(options));
const state = getState(); const state = getState();
const { jitsiTrack } = getLocalVideoTrack(state['features/base/tracks']); const { jitsiTrack } = getLocalVideoTrack(state['features/base/tracks']);
const virtualBackground = state['features/virtual-background']; const virtualBackground = state['features/virtual-background'];
try { try {
if (enabled) { if (options.enabled) {
await jitsiTrack.setEffect(await createVirtualBackgroundEffect(virtualBackground)); await jitsiTrack.setEffect(await createVirtualBackgroundEffect(virtualBackground));
dispatch(backgroundEnabled(true));
} else { } else {
await jitsiTrack.setEffect(undefined); await jitsiTrack.setEffect(undefined);
dispatch(backgroundEnabled(false)); dispatch(backgroundEnabled(false));
@ -37,19 +37,20 @@ export function toggleBackgroundEffect(enabled: boolean) {
/** /**
* Sets the selected virtual background image object. * Sets the selected virtual background image object.
* *
* @param {Object} virtualSource - Virtual background image source. * @param {Object} options - Represents the virtual background setted options.
* @param {boolean} isVirtualBackground - Indicate if virtual image is activated.
* @returns {{ * @returns {{
* type: SET_VIRTUAL_BACKGROUND, * type: SET_VIRTUAL_BACKGROUND,
* virtualSource: string, * virtualSource: string,
* isVirtualBackground: boolean, * blurValue: number,
* type: string,
* }} * }}
*/ */
export function setVirtualBackground(virtualSource: string, isVirtualBackground: boolean) { export function setVirtualBackground(options: Object) {
return { return {
type: SET_VIRTUAL_BACKGROUND, type: SET_VIRTUAL_BACKGROUND,
virtualSource, virtualSource: options?.url,
isVirtualBackground blurValue: options?.blurValue,
backgroundType: options?.backgroundType
}; };
} }
@ -59,7 +60,7 @@ export function setVirtualBackground(virtualSource: string, isVirtualBackground:
* @param {boolean} backgroundEffectEnabled - Indicate if virtual background effect is activated. * @param {boolean} backgroundEffectEnabled - Indicate if virtual background effect is activated.
* @returns {{ * @returns {{
* type: BACKGROUND_ENABLED, * type: BACKGROUND_ENABLED,
* backgroundEffectEnabled: boolean, * backgroundEffectEnabled: boolean
* }} * }}
*/ */
export function backgroundEnabled(backgroundEffectEnabled: boolean) { export function backgroundEnabled(backgroundEffectEnabled: boolean) {

View File

@ -10,14 +10,14 @@ import { translate } from '../../base/i18n';
import { Icon, IconBlurBackground, IconCancelSelection } from '../../base/icons'; import { Icon, IconBlurBackground, IconCancelSelection } from '../../base/icons';
import { connect } from '../../base/redux'; import { connect } from '../../base/redux';
import { Tooltip } from '../../base/tooltip'; import { Tooltip } from '../../base/tooltip';
import { toggleBackgroundEffect, setVirtualBackground } from '../actions'; import { toggleBackgroundEffect } from '../actions';
import { resizeImage, toDataURL } from '../functions'; import { resizeImage, toDataURL } from '../functions';
import logger from '../logger'; import logger from '../logger';
// The limit of virtual background uploads is 21. When the number // The limit of virtual background uploads is 24. When the number
// of uploads is 22 we trigger the deleteStoredImage function to delete // of uploads is 25 we trigger the deleteStoredImage function to delete
// the first/oldest uploaded background. // the first/oldest uploaded background.
const backgroundsLimit = 22; const backgroundsLimit = 25;
const images = [ const images = [
{ {
id: 1, id: 1,
@ -67,42 +67,67 @@ function VirtualBackground({ dispatch, t }: Props) {
* Updates stored images on local storage. * Updates stored images on local storage.
*/ */
useEffect(() => { useEffect(() => {
jitsiLocalStorage.setItem('virtualBackgrounds', JSON.stringify(storedImages)); try {
jitsiLocalStorage.setItem('virtualBackgrounds', JSON.stringify(storedImages));
} catch (err) {
// Preventing localStorage QUOTA_EXCEEDED_ERR
err && deleteStoredImage(storedImages[0]);
}
if (storedImages.length === backgroundsLimit) { if (storedImages.length === backgroundsLimit) {
deleteStoredImage(storedImages[0]); deleteStoredImage(storedImages[0]);
} }
}, [ storedImages ]); }, [ storedImages ]);
const [ selected, setSelected ] = useState(''); const [ selected, setSelected ] = useState('');
const enableBlur = async () => { const enableBlur = async (blurValue, selection) => {
isloading(true); isloading(true);
setSelected('blur'); setSelected(selection);
await dispatch(setVirtualBackground('', false)); await dispatch(
await dispatch(toggleBackgroundEffect(true)); toggleBackgroundEffect({
backgroundType: 'blur',
enabled: true,
blurValue
})
);
isloading(false); isloading(false);
}; };
const removeBackground = async () => { const removeBackground = async () => {
isloading(true); isloading(true);
setSelected('none'); setSelected('none');
await dispatch(setVirtualBackground('', false)); await dispatch(
await dispatch(toggleBackgroundEffect(false)); toggleBackgroundEffect({
enabled: false
})
);
isloading(false); isloading(false);
}; };
const setUploadedImageBackground = async image => { const setUploadedImageBackground = async image => {
isloading(true); isloading(true);
setSelected(image.id); setSelected(image.id);
await dispatch(setVirtualBackground(image.src, true)); await dispatch(
await dispatch(toggleBackgroundEffect(true)); toggleBackgroundEffect({
backgroundType: 'image',
enabled: true,
url: image.src
})
);
isloading(false); isloading(false);
}; };
const setImageBackground = async image => { const setImageBackground = async image => {
isloading(true); isloading(true);
setSelected(image.id); setSelected(image.id);
await dispatch(setVirtualBackground(await toDataURL(image.src), true)); const url = await toDataURL(image.src);
await dispatch(toggleBackgroundEffect(true));
await dispatch(
toggleBackgroundEffect({
backgroundType: 'image',
enabled: true,
url
})
);
isloading(false); isloading(false);
}; };
@ -111,19 +136,23 @@ function VirtualBackground({ dispatch, t }: Props) {
reader.readAsDataURL(imageFile[0]); reader.readAsDataURL(imageFile[0]);
reader.onload = async () => { reader.onload = async () => {
const resizedImage = await resizeImage(reader.result); const url = await resizeImage(reader.result);
isloading(true); isloading(true);
setStoredImages([ setStoredImages([
...storedImages, ...storedImages,
{ {
id: uuid.v4(), id: uuid.v4(),
src: resizedImage src: url
} }
]); ]);
await dispatch(
await dispatch(setVirtualBackground(resizedImage, true)); toggleBackgroundEffect({
await dispatch(toggleBackgroundEffect(true)); backgroundType: 'image',
enabled: true,
url
})
);
isloading(false); isloading(false);
}; };
reader.onerror = () => { reader.onerror = () => {
@ -137,7 +166,7 @@ function VirtualBackground({ dispatch, t }: Props) {
hideCancelButton = { true } hideCancelButton = { true }
submitDisabled = { false } submitDisabled = { false }
titleKey = { 'virtualBackground.title' } titleKey = { 'virtualBackground.title' }
width = 'small'> width = '450px'>
{loading ? ( {loading ? (
<div className = 'virtual-background-loading'> <div className = 'virtual-background-loading'>
<span className = 'loading-content-text'>{t('virtualBackground.pleaseWait')}</span> <span className = 'loading-content-text'>{t('virtualBackground.pleaseWait')}</span>
@ -158,11 +187,20 @@ function VirtualBackground({ dispatch, t }: Props) {
</div> </div>
</Tooltip> </Tooltip>
<Tooltip <Tooltip
content = { t('virtualBackground.enableBlur') } content = { t('virtualBackground.slightBlur') }
position = { 'top' }>
<Icon
className = { selected === 'slight-blur' ? 'blur-selected' : '' }
onClick = { () => enableBlur(8, 'slight-blur') }
size = { 50 }
src = { IconBlurBackground } />
</Tooltip>
<Tooltip
content = { t('virtualBackground.blur') }
position = { 'top' }> position = { 'top' }>
<Icon <Icon
className = { selected === 'blur' ? 'blur-selected' : '' } className = { selected === 'blur' ? 'blur-selected' : '' }
onClick = { () => enableBlur() } onClick = { () => enableBlur(25, 'blur') }
size = { 50 } size = { 50 }
src = { IconBlurBackground } /> src = { IconBlurBackground } />
</Tooltip> </Tooltip>

View File

@ -23,14 +23,15 @@ PersistenceRegistry.register(STORE_NAME, true);
* specified action. * specified action.
*/ */
ReducerRegistry.register(STORE_NAME, (state = {}, action) => { ReducerRegistry.register(STORE_NAME, (state = {}, action) => {
const { virtualSource, isVirtualBackground, backgroundEffectEnabled } = action; const { virtualSource, backgroundEffectEnabled, blurValue, backgroundType } = action;
switch (action.type) { switch (action.type) {
case SET_VIRTUAL_BACKGROUND: { case SET_VIRTUAL_BACKGROUND: {
return { return {
...state, ...state,
virtualSource, virtualSource,
isVirtualBackground blurValue,
backgroundType
}; };
} }
case BACKGROUND_ENABLED: { case BACKGROUND_ENABLED: {