Add video background blur
This commit is contained in:
parent
6383d000a9
commit
3b750ddd5a
2
Makefile
2
Makefile
|
@ -45,6 +45,8 @@ deploy-appbundle:
|
||||||
$(OUTPUT_DIR)/analytics-ga.js \
|
$(OUTPUT_DIR)/analytics-ga.js \
|
||||||
$(BUILD_DIR)/analytics-ga.min.js \
|
$(BUILD_DIR)/analytics-ga.min.js \
|
||||||
$(BUILD_DIR)/analytics-ga.min.map \
|
$(BUILD_DIR)/analytics-ga.min.map \
|
||||||
|
$(BUILD_DIR)/video-blur-effect.min.js \
|
||||||
|
$(BUILD_DIR)/video-blur-effect.min.map \
|
||||||
$(DEPLOY_DIR)
|
$(DEPLOY_DIR)
|
||||||
|
|
||||||
deploy-lib-jitsi-meet:
|
deploy-lib-jitsi-meet:
|
||||||
|
|
103
conference.js
103
conference.js
|
@ -105,7 +105,10 @@ import {
|
||||||
trackAdded,
|
trackAdded,
|
||||||
trackRemoved
|
trackRemoved
|
||||||
} from './react/features/base/tracks';
|
} from './react/features/base/tracks';
|
||||||
import { getJitsiMeetGlobalNS } from './react/features/base/util';
|
import {
|
||||||
|
getJitsiMeetGlobalNS,
|
||||||
|
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';
|
||||||
|
@ -559,48 +562,74 @@ export default {
|
||||||
// Resolve with no tracks
|
// Resolve with no tracks
|
||||||
tryCreateLocalTracks = Promise.resolve([]);
|
tryCreateLocalTracks = Promise.resolve([]);
|
||||||
} else {
|
} else {
|
||||||
tryCreateLocalTracks = createLocalTracksF(
|
|
||||||
{ devices: initialDevices }, true)
|
|
||||||
.catch(err => {
|
|
||||||
if (requestedAudio && requestedVideo) {
|
|
||||||
|
|
||||||
// Try audio only...
|
const loadEffectsPromise = options.startWithBlurEnabled
|
||||||
audioAndVideoError = err;
|
? loadScript('libs/video-blur-effect.min.js')
|
||||||
|
.then(() =>
|
||||||
|
getJitsiMeetGlobalNS().effects.createBlurEffect()
|
||||||
|
.then(blurEffectInstance =>
|
||||||
|
Promise.resolve([ blurEffectInstance ])
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
logger.log('Failed to create JitsiStreamBlurEffect!', error);
|
||||||
|
|
||||||
return (
|
return Promise.resolve([]);
|
||||||
createLocalTracksF({ devices: [ 'audio' ] }, true));
|
})
|
||||||
} else if (requestedAudio && !requestedVideo) {
|
)
|
||||||
|
.catch(error => {
|
||||||
|
logger.error('loadScript failed with error: ', error);
|
||||||
|
|
||||||
|
return Promise.resolve([]);
|
||||||
|
})
|
||||||
|
: Promise.resolve([]);
|
||||||
|
|
||||||
|
tryCreateLocalTracks = loadEffectsPromise.then(trackEffects =>
|
||||||
|
createLocalTracksF(
|
||||||
|
{
|
||||||
|
devices: initialDevices,
|
||||||
|
effects: trackEffects
|
||||||
|
}, true)
|
||||||
|
.catch(err => {
|
||||||
|
if (requestedAudio && requestedVideo) {
|
||||||
|
|
||||||
|
// Try audio only...
|
||||||
|
audioAndVideoError = err;
|
||||||
|
|
||||||
|
return (
|
||||||
|
createLocalTracksF({ devices: [ 'audio' ] }, true));
|
||||||
|
} else if (requestedAudio && !requestedVideo) {
|
||||||
|
audioOnlyError = err;
|
||||||
|
|
||||||
|
return [];
|
||||||
|
} else if (requestedVideo && !requestedAudio) {
|
||||||
|
videoOnlyError = err;
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
logger.error('Should never happen');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
// Log this just in case...
|
||||||
|
if (!requestedAudio) {
|
||||||
|
logger.error('The impossible just happened', err);
|
||||||
|
}
|
||||||
audioOnlyError = err;
|
audioOnlyError = err;
|
||||||
|
|
||||||
return [];
|
// Try video only...
|
||||||
} else if (requestedVideo && !requestedAudio) {
|
return requestedVideo
|
||||||
|
? createLocalTracksF({ devices: [ 'video' ] }, true)
|
||||||
|
: [];
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
// Log this just in case...
|
||||||
|
if (!requestedVideo) {
|
||||||
|
logger.error('The impossible just happened', err);
|
||||||
|
}
|
||||||
videoOnlyError = err;
|
videoOnlyError = err;
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
})
|
||||||
logger.error('Should never happen');
|
);
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
// Log this just in case...
|
|
||||||
if (!requestedAudio) {
|
|
||||||
logger.error('The impossible just happened', err);
|
|
||||||
}
|
|
||||||
audioOnlyError = err;
|
|
||||||
|
|
||||||
// Try video only...
|
|
||||||
return requestedVideo
|
|
||||||
? createLocalTracksF({ devices: [ 'video' ] }, true)
|
|
||||||
: [];
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
// Log this just in case...
|
|
||||||
if (!requestedVideo) {
|
|
||||||
logger.error('The impossible just happened', err);
|
|
||||||
}
|
|
||||||
videoOnlyError = err;
|
|
||||||
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide the permissions prompt/overlay as soon as the tracks are
|
// Hide the permissions prompt/overlay as soon as the tracks are
|
||||||
|
@ -649,6 +678,7 @@ export default {
|
||||||
*/
|
*/
|
||||||
init(options) {
|
init(options) {
|
||||||
this.roomName = options.roomName;
|
this.roomName = options.roomName;
|
||||||
|
const videoBlurEffectEnabled = APP.store.getState()['features/blur'].blurEnabled;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
|
@ -662,6 +692,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,
|
||||||
startAudioOnly: config.startAudioOnly,
|
startAudioOnly: config.startAudioOnly,
|
||||||
startScreenSharing: config.startScreenSharing,
|
startScreenSharing: config.startScreenSharing,
|
||||||
startWithAudioMuted: config.startWithAudioMuted || config.startSilent,
|
startWithAudioMuted: config.startWithAudioMuted || config.startSilent,
|
||||||
|
|
|
@ -220,3 +220,7 @@
|
||||||
.icon-visibility-off:before {
|
.icon-visibility-off:before {
|
||||||
content: "\e924";
|
content: "\e924";
|
||||||
}
|
}
|
||||||
|
.icon-blur-background:before {
|
||||||
|
content: "\e901";
|
||||||
|
color: #a4b8d1;
|
||||||
|
}
|
||||||
|
|
BIN
fonts/jitsi.eot
BIN
fonts/jitsi.eot
Binary file not shown.
|
@ -28,16 +28,13 @@
|
||||||
<glyph unicode="" glyph-name="restore" d="M512 682h64v-180l150-90-32-52-182 110v212zM554 896c212 0 384-172 384-384s-172-384-384-384c-106 0-200 42-270 112l60 62c54-54 128-88 210-88 166 0 300 132 300 298s-134 298-300 298-298-132-298-298h128l-172-172-4 6-166 166h128c0 212 172 384 384 384z" />
|
<glyph unicode="" glyph-name="restore" d="M512 682h64v-180l150-90-32-52-182 110v212zM554 896c212 0 384-172 384-384s-172-384-384-384c-106 0-200 42-270 112l60 62c54-54 128-88 210-88 166 0 300 132 300 298s-134 298-300 298-298-132-298-298h128l-172-172-4 6-166 166h128c0 212 172 384 384 384z" />
|
||||||
<glyph unicode="" glyph-name="search" d="M406 426c106 0 192 86 192 192s-86 192-192 192-192-86-192-192 86-192 192-192zM662 426l212-212-64-64-212 212v34l-12 12c-48-42-112-66-180-66-154 0-278 122-278 276s124 278 278 278 276-124 276-278c0-68-24-132-66-180l12-12h34z" />
|
<glyph unicode="" glyph-name="search" d="M406 426c106 0 192 86 192 192s-86 192-192 192-192-86-192-192 86-192 192-192zM662 426l212-212-64-64-212 212v34l-12 12c-48-42-112-66-180-66-154 0-278 122-278 276s124 278 278 278 276-124 276-278c0-68-24-132-66-180l12-12h34z" />
|
||||||
<glyph unicode="" glyph-name="AUD" d="M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512c282.77 0 512-229.23 512-512s-229.23-512-512-512zM308.25 387.3h57.225l-87.675 252.525h-62.125l-87.675-252.525h53.025l19.425 60.2h88.725l19.075-60.2zM461.9 639.825h-52.85v-165.375c0-56 41.125-93.625 105.7-93.625 64.75 0 105.875 37.625 105.875 93.625v165.375h-52.85v-159.95c0-31.85-19.075-52.15-53.025-52.15-33.775 0-52.85 20.3-52.85 52.15v159.95zM682.225 640v-252.7h99.4c75.6 0 118.475 46.025 118.475 128.1 0 79.1-43.4 124.6-118.475 124.6h-99.4zM735.075 594.85v-162.4h38.15c46.725 0 72.975 28.7 72.975 82.075 0 51.1-27.125 80.325-72.975 80.325h-38.15zM243.5 587.325l-31.675-99.050h66.15l-31.325 99.050h-3.15z" />
|
<glyph unicode="" glyph-name="AUD" d="M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512c282.77 0 512-229.23 512-512s-229.23-512-512-512zM308.25 387.3h57.225l-87.675 252.525h-62.125l-87.675-252.525h53.025l19.425 60.2h88.725l19.075-60.2zM461.9 639.825h-52.85v-165.375c0-56 41.125-93.625 105.7-93.625 64.75 0 105.875 37.625 105.875 93.625v165.375h-52.85v-159.95c0-31.85-19.075-52.15-53.025-52.15-33.775 0-52.85 20.3-52.85 52.15v159.95zM682.225 640v-252.7h99.4c75.6 0 118.475 46.025 118.475 128.1 0 79.1-43.4 124.6-118.475 124.6h-99.4zM735.075 594.85v-162.4h38.15c46.725 0 72.975 28.7 72.975 82.075 0 51.1-27.125 80.325-72.975 80.325h-38.15zM243.5 587.325l-31.675-99.050h66.15l-31.325 99.050h-3.15z" />
|
||||||
<glyph unicode="" glyph-name="signal_cellular_0" d="M938 938v-852h-852zM854 732l-562-562h562v562z" />
|
<glyph unicode="" glyph-name="blur-background" d="M469.333 640c0-47.128-38.205-85.333-85.333-85.333s-85.333 38.205-85.333 85.333c0 47.128 38.205 85.333 85.333 85.333s85.333-38.205 85.333-85.333zM725.333 640c0-47.128-38.205-85.333-85.333-85.333s-85.333 38.205-85.333 85.333c0 47.128 38.205 85.333 85.333 85.333s85.333-38.205 85.333-85.333zM469.333 384c0-47.128-38.205-85.333-85.333-85.333s-85.333 38.205-85.333 85.333c0 47.128 38.205 85.333 85.333 85.333s85.333-38.205 85.333-85.333zM426.667 170.667c0-23.564-19.103-42.667-42.667-42.667s-42.667 19.103-42.667 42.667c0 23.564 19.103 42.667 42.667 42.667s42.667-19.103 42.667-42.667zM682.667 170.667c0-23.564-19.103-42.667-42.667-42.667s-42.667 19.103-42.667 42.667c0 23.564 19.103 42.667 42.667 42.667s42.667-19.103 42.667-42.667zM213.333 384c0-23.564-19.103-42.667-42.667-42.667s-42.667 19.103-42.667 42.667c0 23.564 19.103 42.667 42.667 42.667s42.667-19.103 42.667-42.667zM213.333 640c0-23.564-19.103-42.667-42.667-42.667s-42.667 19.103-42.667 42.667c0 23.564 19.103 42.667 42.667 42.667s42.667-19.103 42.667-42.667zM896 384c0-23.564-19.103-42.667-42.667-42.667s-42.667 19.103-42.667 42.667c0 23.564 19.103 42.667 42.667 42.667s42.667-19.103 42.667-42.667zM896 640c0-23.564-19.103-42.667-42.667-42.667s-42.667 19.103-42.667 42.667c0 23.564 19.103 42.667 42.667 42.667s42.667-19.103 42.667-42.667zM426.667 853.333c0-23.564-19.103-42.667-42.667-42.667s-42.667 19.103-42.667 42.667c0 23.564 19.103 42.667 42.667 42.667s42.667-19.103 42.667-42.667zM682.667 853.333c0-23.564-19.103-42.667-42.667-42.667s-42.667 19.103-42.667 42.667c0 23.564 19.103 42.667 42.667 42.667s42.667-19.103 42.667-42.667zM725.333 384c0-47.128-38.205-85.333-85.333-85.333s-85.333 38.205-85.333 85.333c0 47.128 38.205 85.333 85.333 85.333s85.333-38.205 85.333-85.333z" />
|
||||||
<glyph unicode="" glyph-name="signal_cellular_1" d="M86 86l852 852v-256h-170v-596h-682zM854 86v84h84v-84h-84zM854 256v342h84v-342h-84z" />
|
|
||||||
<glyph unicode="" glyph-name="mic-camera-combined" d="M756.704 628.138l267.296 202.213v-635.075l-267.296 202.213v-191.923c0-12.085-11.296-21.863-25.216-21.863h-706.272c-13.92 0-25.216 9.777-25.216 21.863v612.25c0 12.085 11.296 21.863 25.216 21.863h706.272c13.92 0 25.216-9.777 25.216-21.863v-189.679zM371.338 376.228c47.817 0 86.529 40.232 86.529 89.811v184.835c0 49.651-38.713 89.883-86.529 89.883-47.788 0-86.515-40.232-86.515-89.883v-184.835c0-49.579 38.756-89.811 86.515-89.811v0zM356.754 314.070v-32.78h33.718v33.412c73.858 9.606 131.235 73.73 131.235 151.351v88.232h-30.636v-88.232c0-67.57-53.696-122.534-119.734-122.534-66.024 0-119.691 54.964-119.691 122.534v88.232h-30.636v-88.232c0-79.215 59.674-144.502 135.744-151.969v-0.014z" />
|
<glyph unicode="" glyph-name="mic-camera-combined" d="M756.704 628.138l267.296 202.213v-635.075l-267.296 202.213v-191.923c0-12.085-11.296-21.863-25.216-21.863h-706.272c-13.92 0-25.216 9.777-25.216 21.863v612.25c0 12.085 11.296 21.863 25.216 21.863h706.272c13.92 0 25.216-9.777 25.216-21.863v-189.679zM371.338 376.228c47.817 0 86.529 40.232 86.529 89.811v184.835c0 49.651-38.713 89.883-86.529 89.883-47.788 0-86.515-40.232-86.515-89.883v-184.835c0-49.579 38.756-89.811 86.515-89.811v0zM356.754 314.070v-32.78h33.718v33.412c73.858 9.606 131.235 73.73 131.235 151.351v88.232h-30.636v-88.232c0-67.57-53.696-122.534-119.734-122.534-66.024 0-119.691 54.964-119.691 122.534v88.232h-30.636v-88.232c0-79.215 59.674-144.502 135.744-151.969v-0.014z" />
|
||||||
<glyph unicode="" glyph-name="kick" d="M512 810l284-426h-568zM214 298h596v-84h-596v84z" />
|
<glyph unicode="" glyph-name="kick" d="M512 810l284-426h-568zM214 298h596v-84h-596v84z" />
|
||||||
<glyph unicode="" glyph-name="hangup" d="M512 640c-68 0-134-10-196-30v-132c0-16-10-34-24-40-42-20-80-46-114-78-8-8-18-12-30-12s-22 4-30 12l-106 106c-8 8-12 18-12 30s4 22 12 30c130 124 306 200 500 200s370-76 500-200c8-8 12-18 12-30s-4-22-12-30l-106-106c-8-8-18-12-30-12s-22 4-30 12c-34 32-72 58-114 78-14 6-24 20-24 38v132c-62 20-128 32-196 32z" />
|
<glyph unicode="" glyph-name="hangup" d="M512 640c-68 0-134-10-196-30v-132c0-16-10-34-24-40-42-20-80-46-114-78-8-8-18-12-30-12s-22 4-30 12l-106 106c-8 8-12 18-12 30s4 22 12 30c130 124 306 200 500 200s370-76 500-200c8-8 12-18 12-30s-4-22-12-30l-106-106c-8-8-18-12-30-12s-22 4-30 12c-34 32-72 58-114 78-14 6-24 20-24 38v132c-62 20-128 32-196 32z" />
|
||||||
<glyph unicode="" glyph-name="chat" d="M854 342v512h-684v-598l86 86h598zM854 938c46 0 84-38 84-84v-512c0-46-38-86-84-86h-598l-170-170v768c0 46 38 84 84 84h684z" />
|
<glyph unicode="" glyph-name="chat" d="M854 342v512h-684v-598l86 86h598zM854 938c46 0 84-38 84-84v-512c0-46-38-86-84-86h-598l-170-170v768c0 46 38 84 84 84h684z" />
|
||||||
<glyph unicode="" glyph-name="signal_cellular_2" d="M86 86l852 852v-852h-852z" />
|
|
||||||
<glyph unicode="" glyph-name="share-doc" d="M554 640h236l-236 234v-234zM682 426v86h-340v-86h340zM682 256v86h-340v-86h340zM598 938l256-256v-512c0-46-40-84-86-84h-512c-46 0-86 38-86 84l2 684c0 46 38 84 84 84h342z" />
|
<glyph unicode="" glyph-name="share-doc" d="M554 640h236l-236 234v-234zM682 426v86h-340v-86h340zM682 256v86h-340v-86h340zM598 938l256-256v-512c0-46-40-84-86-84h-512c-46 0-86 38-86 84l2 684c0 46 38 84 84 84h342z" />
|
||||||
<glyph unicode="" glyph-name="ninja" d="M330.667 469.333c-0.427 14.933 6.4 29.44 17.92 39.253 32-6.827 61.867-20.053 88.747-39.253 0-29.013-23.893-52.907-53.333-52.907s-52.907 23.467-53.333 52.907zM586.667 469.333c26.88 18.773 56.747 32 88.747 38.827 11.52-9.813 18.347-24.32 17.92-38.827 0-29.867-23.893-53.76-53.333-53.76s-53.333 23.893-53.333 53.76v0zM512 640c-118.187 1.707-234.667-27.733-338.347-85.333l-2.987-42.667c0-52.48 12.373-104.107 35.84-151.040 101.12 15.36 203.093 23.040 305.493 23.040s204.373-7.68 305.493-23.040c23.467 46.933 35.84 98.56 35.84 151.040l-2.987 42.667c-103.68 57.6-220.16 87.040-338.347 85.333zM512 938.667c235.641 0 426.667-191.025 426.667-426.667s-191.025-426.667-426.667-426.667c-235.641 0-426.667 191.025-426.667 426.667s191.025 426.667 426.667 426.667z" />
|
<glyph unicode="" glyph-name="ninja" d="M330.667 469.333c-0.427 14.933 6.4 29.44 17.92 39.253 32-6.827 61.867-20.053 88.747-39.253 0-29.013-23.893-52.907-53.333-52.907s-52.907 23.467-53.333 52.907zM586.667 469.333c26.88 18.773 56.747 32 88.747 38.827 11.52-9.813 18.347-24.32 17.92-38.827 0-29.867-23.893-53.76-53.333-53.76s-53.333 23.893-53.333 53.76v0zM512 640c-118.187 1.707-234.667-27.733-338.347-85.333l-2.987-42.667c0-52.48 12.373-104.107 35.84-151.040 101.12 15.36 203.093 23.040 305.493 23.040s204.373-7.68 305.493-23.040c23.467 46.933 35.84 98.56 35.84 151.040l-2.987 42.667c-103.68 57.6-220.16 87.040-338.347 85.333zM512 938.667c235.641 0 426.667-191.025 426.667-426.667s-191.025-426.667-426.667-426.667c-235.641 0-426.667 191.025-426.667 426.667s191.025 426.667 426.667 426.667z" />
|
||||||
<glyph unicode="" glyph-name="enlarge" d="M896 212v600h-768v-600h768zM896 896q34 0 60-26t26-60v-596q0-34-26-60t-60-26h-768q-34 0-60 26t-26 60v596q0 34 26 60t60 26h768zM598 342l-86-108-86 108h172zM256 598v-172l-106 86zM768 598l106-86-106-86v172zM512 790l86-108h-172z" />
|
|
||||||
<glyph unicode="" glyph-name="full-screen" d="M598 810h212v-212h-84v128h-128v84zM726 298v128h84v-212h-212v84h128zM214 598v212h212v-84h-128v-128h-84zM298 426v-128h128v-84h-212v212h84z" />
|
<glyph unicode="" glyph-name="full-screen" d="M598 810h212v-212h-84v128h-128v84zM726 298v128h84v-212h-212v84h128zM214 598v212h212v-84h-128v-128h-84zM298 426v-128h128v-84h-212v212h84z" />
|
||||||
<glyph unicode="" glyph-name="exit-full-screen" d="M682 682h128v-84h-212v212h84v-128zM598 214v212h212v-84h-128v-128h-84zM342 682v128h84v-212h-212v84h128zM214 342v84h212v-212h-84v128h-128z" />
|
<glyph unicode="" glyph-name="exit-full-screen" d="M682 682h128v-84h-212v212h84v-128zM598 214v212h212v-84h-128v-128h-84zM342 682v128h84v-212h-212v84h128zM214 342v84h212v-212h-84v128h-128z" />
|
||||||
<glyph unicode="" glyph-name="security" d="M768 170v428h-512v-428h512zM768 682c46 0 86-38 86-84v-428c0-46-40-84-86-84h-512c-46 0-86 38-86 84v428c0 46 40 84 86 84h388v86c0 72-60 132-132 132s-132-60-132-132h-82c0 118 96 214 214 214s214-96 214-214v-86h42zM512 298c-46 0-86 40-86 86s40 86 86 86 86-40 86-86-40-86-86-86z" />
|
<glyph unicode="" glyph-name="security" d="M768 170v428h-512v-428h512zM768 682c46 0 86-38 86-84v-428c0-46-40-84-86-84h-512c-46 0-86 38-86 84v428c0 46 40 84 86 84h388v86c0 72-60 132-132 132s-132-60-132-132h-82c0 118 96 214 214 214s214-96 214-214v-86h42zM512 298c-46 0-86 40-86 86s40 86 86 86 86-40 86-86-40-86-86-86z" />
|
||||||
|
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 24 KiB |
BIN
fonts/jitsi.ttf
BIN
fonts/jitsi.ttf
Binary file not shown.
BIN
fonts/jitsi.woff
BIN
fonts/jitsi.woff
Binary file not shown.
File diff suppressed because one or more lines are too long
|
@ -50,7 +50,7 @@ var interfaceConfig = {
|
||||||
'fodeviceselection', 'hangup', 'profile', 'info', 'chat', 'recording',
|
'fodeviceselection', 'hangup', 'profile', 'info', 'chat', 'recording',
|
||||||
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
||||||
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
|
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
|
||||||
'tileview'
|
'tileview', 'videobackgroundblur'
|
||||||
],
|
],
|
||||||
|
|
||||||
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],
|
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],
|
||||||
|
|
|
@ -615,7 +615,8 @@
|
||||||
"speakerStats": "Toggle speaker statistics",
|
"speakerStats": "Toggle speaker statistics",
|
||||||
"tileView": "Toggle tile view",
|
"tileView": "Toggle tile view",
|
||||||
"toggleCamera": "Toggle camera",
|
"toggleCamera": "Toggle camera",
|
||||||
"videomute": "Toggle mute video"
|
"videomute": "Toggle mute video",
|
||||||
|
"videoblur": "Toggle video blur"
|
||||||
},
|
},
|
||||||
"addPeople": "Add people to your call",
|
"addPeople": "Add people to your call",
|
||||||
"audioonly": "Enable / Disable audio only mode",
|
"audioonly": "Enable / Disable audio only mode",
|
||||||
|
@ -668,7 +669,9 @@
|
||||||
"tileViewToggle": "Toggle tile view",
|
"tileViewToggle": "Toggle tile view",
|
||||||
"toggleCamera": "Toggle camera",
|
"toggleCamera": "Toggle camera",
|
||||||
"unableToUnmutePopup": "You cannot un-mute while the shared video is on.",
|
"unableToUnmutePopup": "You cannot un-mute while the shared video is on.",
|
||||||
"videomute": "Start / Stop camera"
|
"videomute": "Start / Stop camera",
|
||||||
|
"startvideoblur": "Blur my background",
|
||||||
|
"stopvideoblur": "Disable background blur"
|
||||||
},
|
},
|
||||||
"transcribing": {
|
"transcribing": {
|
||||||
"ccButtonTooltip": "Start / Stop subtitles",
|
"ccButtonTooltip": "Start / Stop subtitles",
|
||||||
|
|
|
@ -35,6 +35,8 @@
|
||||||
"@atlaskit/tooltip": "12.1.13",
|
"@atlaskit/tooltip": "12.1.13",
|
||||||
"@microsoft/microsoft-graph-client": "1.1.0",
|
"@microsoft/microsoft-graph-client": "1.1.0",
|
||||||
"@react-native-community/async-storage": "1.3.4",
|
"@react-native-community/async-storage": "1.3.4",
|
||||||
|
"@tensorflow-models/body-pix": "^1.0.1",
|
||||||
|
"@tensorflow/tfjs": "^1.1.2",
|
||||||
"@webcomponents/url": "0.7.1",
|
"@webcomponents/url": "0.7.1",
|
||||||
"amplitude-js": "4.5.2",
|
"amplitude-js": "4.5.2",
|
||||||
"bc-css-flags": "3.0.0",
|
"bc-css-flags": "3.0.0",
|
||||||
|
|
|
@ -467,6 +467,21 @@ export function createRemoteVideoMenuButtonEvent(buttonName, attributes) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an event indicating that an action related to video blur
|
||||||
|
* occurred (e.g. It was started or stopped).
|
||||||
|
*
|
||||||
|
* @param {string} action - The action which occurred.
|
||||||
|
* @returns {Object} The event in a format suitable for sending via
|
||||||
|
* sendAnalytics.
|
||||||
|
*/
|
||||||
|
export function createVideoBlurEvent(action) {
|
||||||
|
return {
|
||||||
|
action,
|
||||||
|
actionSubject: 'video.blur'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an event indicating that an action related to screen sharing
|
* Creates an event indicating that an action related to screen sharing
|
||||||
* occurred (e.g. It was started or stopped).
|
* occurred (e.g. It was started or stopped).
|
||||||
|
|
|
@ -71,6 +71,7 @@ export function createLocalTracksF(
|
||||||
|
|
||||||
// Copy array to avoid mutations inside library.
|
// Copy array to avoid mutations inside library.
|
||||||
devices: options.devices.slice(0),
|
devices: options.devices.slice(0),
|
||||||
|
effects: options.effects,
|
||||||
firefox_fake_device, // eslint-disable-line camelcase
|
firefox_fake_device, // eslint-disable-line camelcase
|
||||||
micDeviceId,
|
micDeviceId,
|
||||||
resolution
|
resolution
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of redux action dispatched which represents that the blur
|
||||||
|
* is enabled.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: BLUR_ENABLED
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const BLUR_ENABLED = 'BLUR_ENABLED';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of redux action dispatched which represents that the blur
|
||||||
|
* is disabled.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: BLUR_DISABLED
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const BLUR_DISABLED = 'BLUR_DISABLED';
|
|
@ -0,0 +1,69 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { getJitsiMeetGlobalNS } from '../base/util';
|
||||||
|
import { getLocalVideoTrack } from '../../features/base/tracks';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BLUR_DISABLED,
|
||||||
|
BLUR_ENABLED
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals the local participant is switching between blurred or
|
||||||
|
* non blurred video.
|
||||||
|
*
|
||||||
|
* @param {boolean} enabled - If true enables video blur, false otherwise
|
||||||
|
*
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export function toggleBlurEffect(enabled: boolean) {
|
||||||
|
return function(dispatch: (Object) => Object, getState: () => any) {
|
||||||
|
if (getState()['features/blur'].blurEnabled !== enabled) {
|
||||||
|
const videoTrack = getLocalVideoTrack(getState()['features/base/tracks']).jitsiTrack;
|
||||||
|
|
||||||
|
return getJitsiMeetGlobalNS().effects.createBlurEffect()
|
||||||
|
.then(blurEffectInstance =>
|
||||||
|
videoTrack.enableEffect(enabled, blurEffectInstance)
|
||||||
|
.then(() => {
|
||||||
|
enabled ? dispatch(blurEnabled()) : dispatch(blurDisabled());
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
enabled ? dispatch(blurDisabled()) : dispatch(blurEnabled());
|
||||||
|
logger.log('enableEffect failed with error:', error);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
dispatch(blurDisabled());
|
||||||
|
logger.log('createBlurEffect failed with error:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals the local participant that the blur has been enabled
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* type: BLUR_ENABLED
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function blurEnabled() {
|
||||||
|
return {
|
||||||
|
type: BLUR_ENABLED
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals the local participant that the blur has been disabled
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* type: BLUR_DISABLED
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function blurDisabled() {
|
||||||
|
return {
|
||||||
|
type: BLUR_DISABLED
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { createVideoBlurEvent, sendAnalytics } from '../../analytics';
|
||||||
|
import { translate } from '../../base/i18n';
|
||||||
|
import { connect } from '../../base/redux';
|
||||||
|
import { AbstractButton } from '../../base/toolbox';
|
||||||
|
import type { AbstractButtonProps } from '../../base/toolbox';
|
||||||
|
import {
|
||||||
|
getJitsiMeetGlobalNS,
|
||||||
|
loadScript
|
||||||
|
} from '../../base/util';
|
||||||
|
|
||||||
|
import { toggleBlurEffect } from '../actions';
|
||||||
|
|
||||||
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the React {@code Component} props of {@link VideoBlurButton}.
|
||||||
|
*/
|
||||||
|
type Props = AbstractButtonProps & {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the video background is blurred or false if it is not.
|
||||||
|
*/
|
||||||
|
_isVideoBlurred: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The redux {@code dispatch} function.
|
||||||
|
*/
|
||||||
|
dispatch: Function
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract implementation of a button that toggles the video blur effect.
|
||||||
|
*/
|
||||||
|
class VideoBlurButton extends AbstractButton<Props, *> {
|
||||||
|
accessibilityLabel = 'toolbar.accessibilityLabel.videoblur';
|
||||||
|
iconName = 'icon-blur-background';
|
||||||
|
label = 'toolbar.startvideoblur';
|
||||||
|
tooltip = 'toolbar.startvideoblur';
|
||||||
|
toggledLabel = 'toolbar.stopvideoblur';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles clicking / pressing the button, and toggles the blur effect
|
||||||
|
* state accordingly.
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_handleClick() {
|
||||||
|
const {
|
||||||
|
_isVideoBlurred,
|
||||||
|
dispatch
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (!getJitsiMeetGlobalNS().effects
|
||||||
|
|| !getJitsiMeetGlobalNS().effects.createBlurEffect) {
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code boolean} value indicating if the blur effect is
|
||||||
|
* enabled or not.
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
_isToggled() {
|
||||||
|
const {
|
||||||
|
_isVideoBlurred
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (!getJitsiMeetGlobalNS().effects
|
||||||
|
|| !getJitsiMeetGlobalNS().effects.createBlurEffect) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _isVideoBlurred;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the redux state to the associated props for the
|
||||||
|
* {@code VideoBlurButton} component.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _isVideoBlurred: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state): Object {
|
||||||
|
return {
|
||||||
|
_isVideoBlurred: Boolean(state['features/blur'].blurEnabled)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(connect(_mapStateToProps)(VideoBlurButton));
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as VideoBlurButton } from './VideoBlurButton';
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from './actions';
|
||||||
|
export * from './components';
|
||||||
|
|
||||||
|
import './reducer';
|
|
@ -0,0 +1,30 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { ReducerRegistry } from '../base/redux';
|
||||||
|
import { PersistenceRegistry } from '../base/storage';
|
||||||
|
|
||||||
|
import { BLUR_ENABLED, BLUR_DISABLED } from './actionTypes';
|
||||||
|
|
||||||
|
PersistenceRegistry.register('features/blur', true, {
|
||||||
|
blurEnabled: false
|
||||||
|
});
|
||||||
|
|
||||||
|
ReducerRegistry.register('features/blur', (state = {}, action) => {
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case BLUR_ENABLED: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
blurEnabled: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case BLUR_DISABLED: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
blurEnabled: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
});
|
|
@ -0,0 +1,237 @@
|
||||||
|
|
||||||
|
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,59 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SET_INTERVAL constant is used to set interval and it is set in
|
||||||
|
* the id property of the request.data property. timeMs property must
|
||||||
|
* also be set. request.data example:
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* id: SET_INTERVAL,
|
||||||
|
* timeMs: 33
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SET_INTERVAL = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLEAR_INTERVAL constant is used to clear the interval and it is set in
|
||||||
|
* the id property of the request.data property.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* id: CLEAR_INTERVAL
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const CLEAR_INTERVAL = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERVAL_TIMEOUT constant is used as response and it is set in the id property.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* id: INTERVAL_TIMEOUT
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const INTERVAL_TIMEOUT = 22;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* use of setInterval that is not throttled when tab is inactive.
|
||||||
|
*/
|
||||||
|
const code
|
||||||
|
= ` let timer = null;
|
||||||
|
|
||||||
|
onmessage = function(request) {
|
||||||
|
switch (request.data.id) {
|
||||||
|
case ${SET_INTERVAL}: {
|
||||||
|
timer = setInterval(() => {
|
||||||
|
postMessage({ id: ${INTERVAL_TIMEOUT} });
|
||||||
|
}, request.data.timeMs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ${CLEAR_INTERVAL}: {
|
||||||
|
clearInterval(timer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const blob = new Blob([ code ], { type: 'application/javascript' });
|
||||||
|
|
||||||
|
export const timerWorkerScript = URL.createObjectURL(blob);
|
|
@ -18,6 +18,9 @@ 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 {
|
||||||
|
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';
|
||||||
|
@ -220,7 +223,6 @@ class Toolbox extends Component<Props, State> {
|
||||||
= this._onShortcutToggleRaiseHand.bind(this);
|
= this._onShortcutToggleRaiseHand.bind(this);
|
||||||
this._onShortcutToggleScreenshare
|
this._onShortcutToggleScreenshare
|
||||||
= this._onShortcutToggleScreenshare.bind(this);
|
= this._onShortcutToggleScreenshare.bind(this);
|
||||||
|
|
||||||
this._onToolbarOpenFeedback
|
this._onToolbarOpenFeedback
|
||||||
= this._onToolbarOpenFeedback.bind(this);
|
= this._onToolbarOpenFeedback.bind(this);
|
||||||
this._onToolbarOpenInvite = this._onToolbarOpenInvite.bind(this);
|
this._onToolbarOpenInvite = this._onToolbarOpenInvite.bind(this);
|
||||||
|
@ -970,6 +972,10 @@ class Toolbox extends Component<Props, State> {
|
||||||
text = { _editingDocument
|
text = { _editingDocument
|
||||||
? t('toolbar.documentClose')
|
? t('toolbar.documentClose')
|
||||||
: t('toolbar.documentOpen') } />,
|
: t('toolbar.documentOpen') } />,
|
||||||
|
<VideoBlurButton
|
||||||
|
key = 'videobackgroundblur'
|
||||||
|
showLabel = { true }
|
||||||
|
visible = { this._shouldShowButton('videobackgroundblur') } />,
|
||||||
<SettingsButton
|
<SettingsButton
|
||||||
key = 'settings'
|
key = 'settings'
|
||||||
showLabel = { true }
|
showLabel = { true }
|
||||||
|
|
|
@ -151,6 +151,16 @@ module.exports = [
|
||||||
'./react/features/analytics/handlers/GoogleAnalyticsHandler.js'
|
'./react/features/analytics/handlers/GoogleAnalyticsHandler.js'
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
Object.assign({}, config, {
|
||||||
|
entry: {
|
||||||
|
'video-blur-effect':
|
||||||
|
'./react/features/stream-effects/JitsiStreamBlurEffect.js'
|
||||||
|
},
|
||||||
|
output: Object.assign({}, config.output, {
|
||||||
|
library: [ 'JitsiMeetJS', 'app', 'effects' ],
|
||||||
|
libraryTarget: 'window'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
// The Webpack configuration to bundle external_api.js (aka
|
// The Webpack configuration to bundle external_api.js (aka
|
||||||
// JitsiMeetExternalAPI).
|
// JitsiMeetExternalAPI).
|
||||||
|
|
Loading…
Reference in New Issue