fix(blur): many small issues.
This commit is contained in:
parent
3b750ddd5a
commit
3b0c5d0b6a
|
@ -105,10 +105,8 @@ import {
|
||||||
trackAdded,
|
trackAdded,
|
||||||
trackRemoved
|
trackRemoved
|
||||||
} from './react/features/base/tracks';
|
} from './react/features/base/tracks';
|
||||||
import {
|
import { getJitsiMeetGlobalNS } from './react/features/base/util';
|
||||||
getJitsiMeetGlobalNS,
|
import { getBlurEffect } from './react/features/blur';
|
||||||
loadScript
|
|
||||||
} from './react/features/base/util';
|
|
||||||
import { addMessage } from './react/features/chat';
|
import { addMessage } from './react/features/chat';
|
||||||
import { showDesktopPicker } from './react/features/desktop-picker';
|
import { showDesktopPicker } from './react/features/desktop-picker';
|
||||||
import { appendSuffix } from './react/features/display-name';
|
import { appendSuffix } from './react/features/display-name';
|
||||||
|
@ -562,22 +560,11 @@ export default {
|
||||||
// Resolve with no tracks
|
// Resolve with no tracks
|
||||||
tryCreateLocalTracks = Promise.resolve([]);
|
tryCreateLocalTracks = Promise.resolve([]);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
const loadEffectsPromise = options.startWithBlurEnabled
|
const loadEffectsPromise = options.startWithBlurEnabled
|
||||||
? loadScript('libs/video-blur-effect.min.js')
|
? getBlurEffect()
|
||||||
.then(() =>
|
.then(blurEffect => [ blurEffect ])
|
||||||
getJitsiMeetGlobalNS().effects.createBlurEffect()
|
|
||||||
.then(blurEffectInstance =>
|
|
||||||
Promise.resolve([ blurEffectInstance ])
|
|
||||||
)
|
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.log('Failed to create JitsiStreamBlurEffect!', error);
|
logger.error('Failed to obtain the blur effect instance with error: ', error);
|
||||||
|
|
||||||
return Promise.resolve([]);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.catch(error => {
|
|
||||||
logger.error('loadScript failed with error: ', error);
|
|
||||||
|
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
})
|
})
|
||||||
|
@ -678,7 +665,6 @@ export default {
|
||||||
*/
|
*/
|
||||||
init(options) {
|
init(options) {
|
||||||
this.roomName = options.roomName;
|
this.roomName = options.roomName;
|
||||||
const videoBlurEffectEnabled = APP.store.getState()['features/blur'].blurEnabled;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
|
@ -692,7 +678,7 @@ export default {
|
||||||
'initial device list initialization failed', error))
|
'initial device list initialization failed', error))
|
||||||
.then(() => this.createInitialLocalTracksAndConnect(
|
.then(() => this.createInitialLocalTracksAndConnect(
|
||||||
options.roomName, {
|
options.roomName, {
|
||||||
startWithBlurEnabled: videoBlurEffectEnabled,
|
startWithBlurEnabled: APP.store.getState()['features/blur'].blurEnabled,
|
||||||
startAudioOnly: config.startAudioOnly,
|
startAudioOnly: config.startAudioOnly,
|
||||||
startScreenSharing: config.startScreenSharing,
|
startScreenSharing: config.startScreenSharing,
|
||||||
startWithAudioMuted: config.startWithAudioMuted || config.startSilent,
|
startWithAudioMuted: config.startWithAudioMuted || config.startSilent,
|
||||||
|
|
|
@ -2563,6 +2563,102 @@
|
||||||
"component-url": "^0.2.1"
|
"component-url": "^0.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@tensorflow-models/body-pix": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tensorflow-models/body-pix/-/body-pix-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-l9bd+b3QI7OzJjw/OuhEfeGRb5l2lRivgDHGMvQbT2Snn8nV7odHSRW55NzhU7Khl7vga00TWo5QDuVnkevQmQ=="
|
||||||
|
},
|
||||||
|
"@tensorflow/tfjs": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-HfhSzL2eTWhlT0r/A5wmo+u3bHe+an16p5wsnFH3ujn21fQ8QtGpSfDHQZjWx1kVFaQnV6KBG+17MOrRHoHlLA==",
|
||||||
|
"requires": {
|
||||||
|
"@tensorflow/tfjs-converter": "1.2.2",
|
||||||
|
"@tensorflow/tfjs-core": "1.2.2",
|
||||||
|
"@tensorflow/tfjs-data": "1.2.2",
|
||||||
|
"@tensorflow/tfjs-layers": "1.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tensorflow/tfjs-converter": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-NM2NcPRHpCNeJdBxHcYpmW9ZHTQ2lJFJgmgGpQ8CxSC9CtQB05bFONs3SKcwMNDE/69QBRVom5DYqLCVUg+A+g=="
|
||||||
|
},
|
||||||
|
"@tensorflow/tfjs-core": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-2hCHMKjh3UNpLEjbAEaurrTGJyj/KpLtMSAraWgHA1vGY0kmk50BBSbgCDmXWUVm7lyh/SkCq4/GrGDZktEs3g==",
|
||||||
|
"requires": {
|
||||||
|
"@types/offscreencanvas": "~2019.3.0",
|
||||||
|
"@types/seedrandom": "2.4.27",
|
||||||
|
"@types/webgl-ext": "0.0.30",
|
||||||
|
"@types/webgl2": "0.0.4",
|
||||||
|
"node-fetch": "~2.1.2",
|
||||||
|
"rollup-plugin-visualizer": "~1.1.1",
|
||||||
|
"seedrandom": "2.4.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
|
||||||
|
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tensorflow/tfjs-data": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-oHGBoGdnCl2RyouLKplQqo+iil0iJgPbi/aoHizhpO77UBuJXlKMblH8w5GbxVAw3hKxWlqzYpxPo6rVRgehNA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/node-fetch": "^2.1.2",
|
||||||
|
"node-fetch": "~2.1.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
|
||||||
|
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tensorflow/tfjs-layers": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-yzWZaZrCVpEyTkSrzMe4OOP4aGUfaaROE/zR9fPsPGGF8wLlbLNZUJjeYUmjy3G3pXGaM0mQUbLR5Vd707CVtQ=="
|
||||||
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"version": "12.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.10.tgz",
|
||||||
|
"integrity": "sha512-LcsGbPomWsad6wmMNv7nBLw7YYYyfdYcz6xryKYQhx89c3XXan+8Q6AJ43G5XDIaklaVkK3mE4fCb0SBvMiPSQ=="
|
||||||
|
},
|
||||||
|
"@types/node-fetch": {
|
||||||
|
"version": "2.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.3.7.tgz",
|
||||||
|
"integrity": "sha512-+bKtuxhj/TYSSP1r4CZhfmyA0vm/aDRQNo7vbAgf6/cZajn0SAniGGST07yvI4Q+q169WTa2/x9gEHfJrkcALw==",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/offscreencanvas": {
|
||||||
|
"version": "2019.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz",
|
||||||
|
"integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q=="
|
||||||
|
},
|
||||||
|
"@types/seedrandom": {
|
||||||
|
"version": "2.4.27",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.27.tgz",
|
||||||
|
"integrity": "sha1-nbVjk33YaRX2kJK8QyWdL0hXjkE="
|
||||||
|
},
|
||||||
|
"@types/webgl-ext": {
|
||||||
|
"version": "0.0.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz",
|
||||||
|
"integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg=="
|
||||||
|
},
|
||||||
|
"@types/webgl2": {
|
||||||
|
"version": "0.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz",
|
||||||
|
"integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw=="
|
||||||
|
},
|
||||||
"@webassemblyjs/ast": {
|
"@webassemblyjs/ast": {
|
||||||
"version": "1.7.11",
|
"version": "1.7.11",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz",
|
||||||
|
@ -13187,6 +13283,35 @@
|
||||||
"inherits": "^2.0.1"
|
"inherits": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rollup-plugin-visualizer": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-7xkSKp+dyJmSC7jg2LXqViaHuOnF1VvIFCnsZEKjrgT5ZVyiLLSbeszxFcQSfNJILphqgAEmWAUz0Z4xYScrRw==",
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"mkdirp": "^0.5.1",
|
||||||
|
"opn": "^5.4.0",
|
||||||
|
"source-map": "^0.7.3",
|
||||||
|
"typeface-oswald": "0.0.54"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"opn": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==",
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"is-wsl": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||||
|
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"rsvp": {
|
"rsvp": {
|
||||||
"version": "3.6.2",
|
"version": "3.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz",
|
||||||
|
@ -13775,6 +13900,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.3.0.tgz",
|
||||||
"integrity": "sha1-V6lXWUIEHYV3qGnXx01MOgvYiPY="
|
"integrity": "sha1-V6lXWUIEHYV3qGnXx01MOgvYiPY="
|
||||||
},
|
},
|
||||||
|
"seedrandom": {
|
||||||
|
"version": "2.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz",
|
||||||
|
"integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw="
|
||||||
|
},
|
||||||
"select-hose": {
|
"select-hose": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||||
|
@ -15363,6 +15493,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
||||||
},
|
},
|
||||||
|
"typeface-oswald": {
|
||||||
|
"version": "0.0.54",
|
||||||
|
"resolved": "https://registry.npmjs.org/typeface-oswald/-/typeface-oswald-0.0.54.tgz",
|
||||||
|
"integrity": "sha512-U1WMNp4qfy4/3khIfHMVAIKnNu941MXUfs3+H9R8PFgnoz42Hh9pboSFztWr86zut0eXC8byalmVhfkiKON/8Q==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"ua-parser-js": {
|
"ua-parser-js": {
|
||||||
"version": "0.7.17",
|
"version": "0.7.17",
|
||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz",
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz",
|
||||||
|
|
|
@ -1,49 +1,48 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { getJitsiMeetGlobalNS } from '../base/util';
|
|
||||||
import { getLocalVideoTrack } from '../../features/base/tracks';
|
import { getLocalVideoTrack } from '../../features/base/tracks';
|
||||||
|
|
||||||
import {
|
import { BLUR_DISABLED, BLUR_ENABLED } from './actionTypes';
|
||||||
BLUR_DISABLED,
|
import { getBlurEffect } from './functions';
|
||||||
BLUR_ENABLED
|
|
||||||
} from './actionTypes';
|
|
||||||
|
|
||||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals the local participant is switching between blurred or
|
* Signals the local participant is switching between blurred or non blurred video.
|
||||||
* non blurred video.
|
|
||||||
*
|
|
||||||
* @param {boolean} enabled - If true enables video blur, false otherwise
|
|
||||||
*
|
*
|
||||||
|
* @param {boolean} enabled - If true enables video blur, false otherwise.
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
export function toggleBlurEffect(enabled: boolean) {
|
export function toggleBlurEffect(enabled: boolean) {
|
||||||
return function(dispatch: (Object) => Object, getState: () => any) {
|
return function(dispatch: (Object) => Object, getState: () => any) {
|
||||||
if (getState()['features/blur'].blurEnabled !== enabled) {
|
const state = getState();
|
||||||
const videoTrack = getLocalVideoTrack(getState()['features/base/tracks']).jitsiTrack;
|
|
||||||
|
|
||||||
return getJitsiMeetGlobalNS().effects.createBlurEffect()
|
if (state['features/blur'].blurEnabled !== enabled) {
|
||||||
|
const { jitsiTrack } = getLocalVideoTrack(state['features/base/tracks']);
|
||||||
|
|
||||||
|
return getBlurEffect()
|
||||||
.then(blurEffectInstance =>
|
.then(blurEffectInstance =>
|
||||||
videoTrack.enableEffect(enabled, blurEffectInstance)
|
jitsiTrack.setEffect(enabled ? blurEffectInstance : undefined)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
enabled ? dispatch(blurEnabled()) : dispatch(blurDisabled());
|
enabled ? dispatch(blurEnabled()) : dispatch(blurDisabled());
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
enabled ? dispatch(blurDisabled()) : dispatch(blurEnabled());
|
enabled ? dispatch(blurDisabled()) : dispatch(blurEnabled());
|
||||||
logger.log('enableEffect failed with error:', error);
|
logger.error('setEffect failed with error:', error);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
dispatch(blurDisabled());
|
dispatch(blurDisabled());
|
||||||
logger.log('createBlurEffect failed with error:', error);
|
logger.error('getBlurEffect failed with error:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals the local participant that the blur has been enabled
|
* Signals the local participant that the blur has been enabled.
|
||||||
*
|
*
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* type: BLUR_ENABLED
|
* type: BLUR_ENABLED
|
||||||
|
@ -56,7 +55,7 @@ export function blurEnabled() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals the local participant that the blur has been disabled
|
* Signals the local participant that the blur has been disabled.
|
||||||
*
|
*
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* type: BLUR_DISABLED
|
* type: BLUR_DISABLED
|
||||||
|
|
|
@ -5,15 +5,9 @@ import { translate } from '../../base/i18n';
|
||||||
import { connect } from '../../base/redux';
|
import { connect } from '../../base/redux';
|
||||||
import { AbstractButton } from '../../base/toolbox';
|
import { AbstractButton } from '../../base/toolbox';
|
||||||
import type { AbstractButtonProps } from '../../base/toolbox';
|
import type { AbstractButtonProps } from '../../base/toolbox';
|
||||||
import {
|
|
||||||
getJitsiMeetGlobalNS,
|
|
||||||
loadScript
|
|
||||||
} from '../../base/util';
|
|
||||||
|
|
||||||
import { toggleBlurEffect } from '../actions';
|
import { toggleBlurEffect } from '../actions';
|
||||||
|
|
||||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} props of {@link VideoBlurButton}.
|
* The type of the React {@code Component} props of {@link VideoBlurButton}.
|
||||||
*/
|
*/
|
||||||
|
@ -49,27 +43,11 @@ class VideoBlurButton extends AbstractButton<Props, *> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_handleClick() {
|
_handleClick() {
|
||||||
const {
|
const { _isVideoBlurred, dispatch } = this.props;
|
||||||
_isVideoBlurred,
|
const value = !_isVideoBlurred;
|
||||||
dispatch
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (!getJitsiMeetGlobalNS().effects
|
sendAnalytics(createVideoBlurEvent(value ? 'started' : 'stopped'));
|
||||||
|| !getJitsiMeetGlobalNS().effects.createBlurEffect) {
|
dispatch(toggleBlurEffect(value));
|
||||||
|
|
||||||
loadScript('libs/video-blur-effect.min.js')
|
|
||||||
.then(() => {
|
|
||||||
this._handleClick();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
logger.error('Failed to load script with error: ', error);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
sendAnalytics(createVideoBlurEvent(_isVideoBlurred ? 'started' : 'stopped'));
|
|
||||||
|
|
||||||
dispatch(toggleBlurEffect(!_isVideoBlurred));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,16 +58,7 @@ class VideoBlurButton extends AbstractButton<Props, *> {
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
_isToggled() {
|
_isToggled() {
|
||||||
const {
|
return this.props._isVideoBlurred;
|
||||||
_isVideoBlurred
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (!getJitsiMeetGlobalNS().effects
|
|
||||||
|| !getJitsiMeetGlobalNS().effects.createBlurEffect) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _isVideoBlurred;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { getJitsiMeetGlobalNS, loadScript } from '../base/util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns promise that resolves with the blur effect instance.
|
||||||
|
*
|
||||||
|
* @returns {Promise<JitsiStreamBlurEffect>} - Resolves with the blur effect instance.
|
||||||
|
*/
|
||||||
|
export function getBlurEffect() {
|
||||||
|
const ns = getJitsiMeetGlobalNS();
|
||||||
|
|
||||||
|
if (ns.effects && ns.effects.createBlurEffect) {
|
||||||
|
return ns.effects.createBlurEffect();
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadScript('libs/video-blur-effect.min.js').then(() => ns.effects.createBlurEffect());
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
export * from './actions';
|
export * from './actions';
|
||||||
export * from './components';
|
export * from './components';
|
||||||
|
export * from './functions';
|
||||||
|
|
||||||
import './reducer';
|
import './reducer';
|
||||||
|
|
|
@ -1,237 +0,0 @@
|
||||||
|
|
||||||
import { getLogger } from 'jitsi-meet-logger';
|
|
||||||
import {
|
|
||||||
drawBokehEffect,
|
|
||||||
load
|
|
||||||
} from '@tensorflow-models/body-pix';
|
|
||||||
|
|
||||||
import {
|
|
||||||
CLEAR_INTERVAL,
|
|
||||||
INTERVAL_TIMEOUT,
|
|
||||||
SET_INTERVAL,
|
|
||||||
timerWorkerScript
|
|
||||||
} from './TimerWorker';
|
|
||||||
|
|
||||||
const logger = getLogger(__filename);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This promise represents the loading of the BodyPix model that is used
|
|
||||||
* to extract person segmentation. A multiplier of 0.25 is used to for
|
|
||||||
* improved performance on a larger range of CPUs.
|
|
||||||
*/
|
|
||||||
const bpModelPromise = load(0.25);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a modified MediaStream that adds blur to video background.
|
|
||||||
* <tt>JitsiStreamBlurEffect</tt> does the processing of the original
|
|
||||||
* video stream.
|
|
||||||
*/
|
|
||||||
class JitsiStreamBlurEffect {
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Represents a modified video MediaStream track.
|
|
||||||
*
|
|
||||||
* @class
|
|
||||||
* @param {BodyPix} bpModel - BodyPix model
|
|
||||||
*/
|
|
||||||
constructor(bpModel) {
|
|
||||||
this._bpModel = bpModel;
|
|
||||||
|
|
||||||
this._outputCanvasElement = document.createElement('canvas');
|
|
||||||
this._maskCanvasElement = document.createElement('canvas');
|
|
||||||
this._inputVideoElement = document.createElement('video');
|
|
||||||
|
|
||||||
this._renderVideo = this._renderVideo.bind(this);
|
|
||||||
this._renderMask = this._renderMask.bind(this);
|
|
||||||
|
|
||||||
this._videoFrameTimerWorker = new Worker(timerWorkerScript);
|
|
||||||
this._maskFrameTimerWorker = new Worker(timerWorkerScript);
|
|
||||||
|
|
||||||
this._onMaskFrameTimer = this._onMaskFrameTimer.bind(this);
|
|
||||||
this._onVideoFrameTimer = this._onVideoFrameTimer.bind(this);
|
|
||||||
this._videoFrameTimerWorker.onmessage = this._onVideoFrameTimer;
|
|
||||||
this._maskFrameTimerWorker.onmessage = this._onMaskFrameTimer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EventHandler onmessage for the videoFrameTimerWorker WebWorker
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {EventHandler} response - onmessage EventHandler parameter
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_onVideoFrameTimer(response) {
|
|
||||||
switch (response.data.id) {
|
|
||||||
case INTERVAL_TIMEOUT: {
|
|
||||||
this._renderVideo();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EventHandler onmessage for the maskFrameTimerWorker WebWorker
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {EventHandler} response - onmessage EventHandler parameter
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_onMaskFrameTimer(response) {
|
|
||||||
switch (response.data.id) {
|
|
||||||
case INTERVAL_TIMEOUT: {
|
|
||||||
this._renderMask();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts loop to capture video frame and render the segmentation mask.
|
|
||||||
*
|
|
||||||
* @param {MediaStream} stream - Stream to be used for processing
|
|
||||||
*
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
startEffect(stream) {
|
|
||||||
this._stream = stream;
|
|
||||||
|
|
||||||
const firstVideoTrack = this._stream.getVideoTracks()[0];
|
|
||||||
const { height, frameRate, width } = firstVideoTrack.getSettings
|
|
||||||
? firstVideoTrack.getSettings() : firstVideoTrack.getConstraints();
|
|
||||||
|
|
||||||
if (!firstVideoTrack.getSettings && !firstVideoTrack.getConstraints) {
|
|
||||||
throw new Error('JitsiStreamBlurEffect not supported!');
|
|
||||||
}
|
|
||||||
|
|
||||||
this._frameRate = frameRate;
|
|
||||||
this._height = height;
|
|
||||||
this._width = width;
|
|
||||||
|
|
||||||
this._outputCanvasElement.width = width;
|
|
||||||
this._outputCanvasElement.height = height;
|
|
||||||
|
|
||||||
this._maskCanvasElement.width = this._width;
|
|
||||||
this._maskCanvasElement.height = this._height;
|
|
||||||
|
|
||||||
this._inputVideoElement.width = width;
|
|
||||||
this._inputVideoElement.height = height;
|
|
||||||
|
|
||||||
this._maskCanvasContext = this._maskCanvasElement.getContext('2d');
|
|
||||||
|
|
||||||
this._inputVideoElement.autoplay = true;
|
|
||||||
this._inputVideoElement.srcObject = this._stream;
|
|
||||||
|
|
||||||
this._videoFrameTimerWorker.postMessage({
|
|
||||||
id: SET_INTERVAL,
|
|
||||||
timeMs: 1000 / this._frameRate
|
|
||||||
});
|
|
||||||
|
|
||||||
this._maskFrameTimerWorker.postMessage({
|
|
||||||
id: SET_INTERVAL,
|
|
||||||
timeMs: 200
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the capture and render loop.
|
|
||||||
*
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
stopEffect() {
|
|
||||||
this._videoFrameTimerWorker.postMessage({
|
|
||||||
id: CLEAR_INTERVAL
|
|
||||||
});
|
|
||||||
|
|
||||||
this._maskFrameTimerWorker.postMessage({
|
|
||||||
id: CLEAR_INTERVAL
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the modified stream.
|
|
||||||
*
|
|
||||||
* @returns {MediaStream}
|
|
||||||
*/
|
|
||||||
getStreamWithEffect() {
|
|
||||||
return this._outputCanvasElement.captureStream(this._frameRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loop function to render the video frame input and draw blur effect.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_renderVideo() {
|
|
||||||
if (this._bpModel) {
|
|
||||||
this._maskCanvasContext.drawImage(this._inputVideoElement,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
this._width,
|
|
||||||
this._height);
|
|
||||||
|
|
||||||
if (this._segmentationData) {
|
|
||||||
|
|
||||||
drawBokehEffect(this._outputCanvasElement,
|
|
||||||
this._inputVideoElement,
|
|
||||||
this._segmentationData,
|
|
||||||
7, // Constant for background blur, integer values between 0-20
|
|
||||||
7); // Constant for edge blur, integer values between 0-20
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this._outputCanvasElement
|
|
||||||
.getContext('2d')
|
|
||||||
.drawImage(this._inputVideoElement,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
this._width,
|
|
||||||
this._height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loop function to render the background mask.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_renderMask() {
|
|
||||||
if (this._bpModel) {
|
|
||||||
this._bpModel.estimatePersonSegmentation(this._maskCanvasElement,
|
|
||||||
32, // Chose 32 for better performance
|
|
||||||
0.75) // Represents probability that a pixel belongs to a person
|
|
||||||
.then(value => {
|
|
||||||
this._segmentationData = value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the local track supports this effect.
|
|
||||||
*
|
|
||||||
* @param {JitsiLocalTrack} jitsiLocalTrack - Track to apply effect
|
|
||||||
*
|
|
||||||
* @returns {boolean} Returns true if this effect can run on the specified track
|
|
||||||
* false otherwise
|
|
||||||
*/
|
|
||||||
isEnabled(jitsiLocalTrack) {
|
|
||||||
return jitsiLocalTrack.isVideoTrack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance of JitsiStreamBlurEffect.
|
|
||||||
*
|
|
||||||
* @returns {Promise<JitsiStreamBlurEffect>}
|
|
||||||
*/
|
|
||||||
export function createBlurEffect() {
|
|
||||||
return bpModelPromise
|
|
||||||
.then(bpmodel =>
|
|
||||||
Promise.resolve(new JitsiStreamBlurEffect(bpmodel))
|
|
||||||
)
|
|
||||||
.catch(error => {
|
|
||||||
logger.error('Failed to load BodyPix model. Fallback to original stream!', error);
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
|
||||||
|
import { drawBokehEffect } from '@tensorflow-models/body-pix';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CLEAR_INTERVAL,
|
||||||
|
INTERVAL_TIMEOUT,
|
||||||
|
SET_INTERVAL,
|
||||||
|
timerWorkerScript
|
||||||
|
} from './TimerWorker';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a modified MediaStream that adds blur to video background.
|
||||||
|
* <tt>JitsiStreamBlurEffect</tt> does the processing of the original
|
||||||
|
* video stream.
|
||||||
|
*/
|
||||||
|
export default class JitsiStreamBlurEffect {
|
||||||
|
/**
|
||||||
|
* Represents a modified video MediaStream track.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @param {BodyPix} bpModel - BodyPix model.
|
||||||
|
*/
|
||||||
|
constructor(bpModel) {
|
||||||
|
this._bpModel = bpModel;
|
||||||
|
|
||||||
|
// Bind event handler so it is only bound once for every instance.
|
||||||
|
this._onMaskFrameTimer = this._onMaskFrameTimer.bind(this);
|
||||||
|
this._onVideoFrameTimer = this._onVideoFrameTimer.bind(this);
|
||||||
|
|
||||||
|
this._outputCanvasElement = document.createElement('canvas');
|
||||||
|
this._maskCanvasElement = document.createElement('canvas');
|
||||||
|
this._inputVideoElement = document.createElement('video');
|
||||||
|
|
||||||
|
this._videoFrameTimerWorker = new Worker(timerWorkerScript);
|
||||||
|
this._maskFrameTimerWorker = new Worker(timerWorkerScript);
|
||||||
|
this._videoFrameTimerWorker.onmessage = this._onVideoFrameTimer;
|
||||||
|
this._maskFrameTimerWorker.onmessage = this._onMaskFrameTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EventHandler onmessage for the videoFrameTimerWorker WebWorker.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {EventHandler} response - The onmessage EventHandler parameter.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onVideoFrameTimer(response) {
|
||||||
|
if (response.data.id === INTERVAL_TIMEOUT) {
|
||||||
|
this._renderVideo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EventHandler onmessage for the maskFrameTimerWorker WebWorker.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {EventHandler} response - The onmessage EventHandler parameter.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onMaskFrameTimer(response) {
|
||||||
|
if (response.data.id === INTERVAL_TIMEOUT) {
|
||||||
|
this._renderMask();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts loop to capture video frame and render the segmentation mask.
|
||||||
|
*
|
||||||
|
* @param {MediaStream} stream - Stream to be used for processing.
|
||||||
|
* @returns {MediaStream} - The stream with the applied effect.
|
||||||
|
*/
|
||||||
|
startEffect(stream) {
|
||||||
|
const firstVideoTrack = stream.getVideoTracks()[0];
|
||||||
|
const { height, frameRate, width }
|
||||||
|
= firstVideoTrack.getSettings ? firstVideoTrack.getSettings() : firstVideoTrack.getConstraints();
|
||||||
|
|
||||||
|
this._frameRate = frameRate;
|
||||||
|
this._height = height;
|
||||||
|
this._width = width;
|
||||||
|
|
||||||
|
this._outputCanvasElement.width = width;
|
||||||
|
this._outputCanvasElement.height = height;
|
||||||
|
|
||||||
|
this._maskCanvasElement.width = width;
|
||||||
|
this._maskCanvasElement.height = height;
|
||||||
|
|
||||||
|
this._maskCanvasContext = this._maskCanvasElement.getContext('2d');
|
||||||
|
this._inputVideoElement.width = width;
|
||||||
|
this._inputVideoElement.height = height;
|
||||||
|
this._inputVideoElement.autoplay = true;
|
||||||
|
this._inputVideoElement.srcObject = stream;
|
||||||
|
|
||||||
|
this._videoFrameTimerWorker.postMessage({
|
||||||
|
id: SET_INTERVAL,
|
||||||
|
timeMs: 1000 / this._frameRate
|
||||||
|
});
|
||||||
|
this._maskFrameTimerWorker.postMessage({
|
||||||
|
id: SET_INTERVAL,
|
||||||
|
timeMs: 200
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._outputCanvasElement.captureStream(this._frameRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the capture and render loop.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
stopEffect() {
|
||||||
|
this._videoFrameTimerWorker.postMessage({
|
||||||
|
id: CLEAR_INTERVAL
|
||||||
|
});
|
||||||
|
this._maskFrameTimerWorker.postMessage({
|
||||||
|
id: CLEAR_INTERVAL
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop function to render the video frame input and draw blur effect.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_renderVideo() {
|
||||||
|
this._maskCanvasContext.drawImage(this._inputVideoElement, 0, 0, this._width, this._height);
|
||||||
|
if (this._segmentationData) {
|
||||||
|
drawBokehEffect(
|
||||||
|
this._outputCanvasElement,
|
||||||
|
this._inputVideoElement,
|
||||||
|
this._segmentationData,
|
||||||
|
7, // Constant for background blur, integer values between 0-20
|
||||||
|
7 // Constant for edge blur, integer values between 0-20
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop function to render the background mask.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_renderMask() {
|
||||||
|
this._bpModel.estimatePersonSegmentation(
|
||||||
|
this._maskCanvasElement,
|
||||||
|
32, // Chose 32 for better performance
|
||||||
|
0.75 // Represents probability that a pixel belongs to a person
|
||||||
|
)
|
||||||
|
.then(value => {
|
||||||
|
this._segmentationData = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the local track supports this effect.
|
||||||
|
*
|
||||||
|
* @param {JitsiLocalTrack} jitsiLocalTrack - Track to apply effect.
|
||||||
|
* @returns {boolean} - Returns true if this effect can run on the specified track
|
||||||
|
* false otherwise.
|
||||||
|
*/
|
||||||
|
isEnabled(jitsiLocalTrack) {
|
||||||
|
return jitsiLocalTrack.isVideoTrack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
* timeMs: 33
|
* timeMs: 33
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export const SET_INTERVAL = 2;
|
export const SET_INTERVAL = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CLEAR_INTERVAL constant is used to clear the interval and it is set in
|
* CLEAR_INTERVAL constant is used to clear the interval and it is set in
|
||||||
|
@ -19,7 +19,7 @@ export const SET_INTERVAL = 2;
|
||||||
* id: CLEAR_INTERVAL
|
* id: CLEAR_INTERVAL
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export const CLEAR_INTERVAL = 3;
|
export const CLEAR_INTERVAL = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* INTERVAL_TIMEOUT constant is used as response and it is set in the id property.
|
* INTERVAL_TIMEOUT constant is used as response and it is set in the id property.
|
||||||
|
@ -28,15 +28,15 @@ export const CLEAR_INTERVAL = 3;
|
||||||
* id: INTERVAL_TIMEOUT
|
* id: INTERVAL_TIMEOUT
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export const INTERVAL_TIMEOUT = 22;
|
export const INTERVAL_TIMEOUT = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The following code is needed as string to create a URL from a Blob.
|
* The following code is needed as string to create a URL from a Blob.
|
||||||
* The URL is then passed to a WebWorker. Reason for this is to enable
|
* The URL is then passed to a WebWorker. Reason for this is to enable
|
||||||
* use of setInterval that is not throttled when tab is inactive.
|
* use of setInterval that is not throttled when tab is inactive.
|
||||||
*/
|
*/
|
||||||
const code
|
const code = `
|
||||||
= ` let timer = null;
|
var timer;
|
||||||
|
|
||||||
onmessage = function(request) {
|
onmessage = function(request) {
|
||||||
switch (request.data.id) {
|
switch (request.data.id) {
|
||||||
|
@ -47,13 +47,13 @@ const code
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ${CLEAR_INTERVAL}: {
|
case ${CLEAR_INTERVAL}: {
|
||||||
|
if (timer) {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const blob = new Blob([ code ], { type: 'application/javascript' });
|
export const timerWorkerScript = URL.createObjectURL(new Blob([ code ], { type: 'application/javascript' }));
|
||||||
|
|
||||||
export const timerWorkerScript = URL.createObjectURL(blob);
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { load } from '@tensorflow-models/body-pix';
|
||||||
|
|
||||||
|
import JitsiStreamBlurEffect from './JitsiStreamBlurEffect';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This promise represents the loading of the BodyPix model that is used
|
||||||
|
* to extract person segmentation. A multiplier of 0.25 is used to for
|
||||||
|
* improved performance on a larger range of CPUs.
|
||||||
|
*/
|
||||||
|
const bpModelPromise = load(0.25);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of JitsiStreamBlurEffect.
|
||||||
|
*
|
||||||
|
* @returns {Promise<JitsiStreamBlurEffect>}
|
||||||
|
*/
|
||||||
|
export function createBlurEffect() {
|
||||||
|
if (!MediaStreamTrack.prototype.getSettings && !MediaStreamTrack.prototype.getConstraints) {
|
||||||
|
return Promise.reject(new Error('JitsiStreamBlurEffect not supported!'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return bpModelPromise.then(bpmodel => new JitsiStreamBlurEffect(bpmodel));
|
||||||
|
}
|
|
@ -18,9 +18,7 @@ import {
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
import { OverflowMenuItem } from '../../../base/toolbox';
|
import { OverflowMenuItem } from '../../../base/toolbox';
|
||||||
import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
|
import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
|
||||||
import {
|
import { VideoBlurButton } from '../../../blur';
|
||||||
VideoBlurButton
|
|
||||||
} from '../../../blur';
|
|
||||||
import { ChatCounter, toggleChat } from '../../../chat';
|
import { ChatCounter, toggleChat } from '../../../chat';
|
||||||
import { toggleDocument } from '../../../etherpad';
|
import { toggleDocument } from '../../../etherpad';
|
||||||
import { openFeedbackDialog } from '../../../feedback';
|
import { openFeedbackDialog } from '../../../feedback';
|
||||||
|
|
|
@ -153,8 +153,7 @@ module.exports = [
|
||||||
}),
|
}),
|
||||||
Object.assign({}, config, {
|
Object.assign({}, config, {
|
||||||
entry: {
|
entry: {
|
||||||
'video-blur-effect':
|
'video-blur-effect': './react/features/stream-effects/blur/index.js'
|
||||||
'./react/features/stream-effects/JitsiStreamBlurEffect.js'
|
|
||||||
},
|
},
|
||||||
output: Object.assign({}, config.output, {
|
output: Object.assign({}, config.output, {
|
||||||
library: [ 'JitsiMeetJS', 'app', 'effects' ],
|
library: [ 'JitsiMeetJS', 'app', 'effects' ],
|
||||||
|
|
Loading…
Reference in New Issue