Browse Source

feat(virtual-backgrounds) add virtual background support

master
tudordan7 4 years ago
parent
commit
194d357005
43 changed files with 439 additions and 237 deletions
  1. 1
    1
      .eslintignore
  2. 4
    4
      Makefile
  3. 1
    1
      config.js
  4. 1
    0
      css/main.scss
  5. 44
    0
      css/modals/virtual-background/_virtual-background.scss
  6. BIN
      images/virtual-background/background-1.jpg
  7. BIN
      images/virtual-background/background-2.jpg
  8. BIN
      images/virtual-background/background-3.jpg
  9. BIN
      images/virtual-background/background-4.jpg
  10. 1
    1
      interface_config.js
  11. 7
    4
      lang/main.json
  12. 0
    1
      react/features/app/reducers.any.js
  13. 1
    1
      react/features/app/reducers.web.js
  14. 1
    1
      react/features/base/config/constants.js
  15. 1
    0
      react/features/base/icons/svg/index.js
  16. 3
    0
      react/features/base/icons/svg/virtual-background.svg
  17. 5
    5
      react/features/base/tracks/loadEffects.web.js
  18. 0
    21
      react/features/blur/actionTypes.js
  19. 0
    67
      react/features/blur/actions.js
  20. 0
    1
      react/features/blur/components/index.js
  21. 0
    26
      react/features/blur/reducer.js
  22. 0
    61
      react/features/stream-effects/blur/index.js
  23. 17
    5
      react/features/stream-effects/virtual-background/JitsiStreamBackgroundEffect.js
  24. 0
    0
      react/features/stream-effects/virtual-background/TimerWorker.js
  25. 64
    0
      react/features/stream-effects/virtual-background/index.js
  26. 0
    0
      react/features/stream-effects/virtual-background/vendor/README.md
  27. 0
    0
      react/features/stream-effects/virtual-background/vendor/models/segm_full_v679.tflite
  28. 0
    0
      react/features/stream-effects/virtual-background/vendor/models/segm_lite_v681.tflite
  29. 0
    0
      react/features/stream-effects/virtual-background/vendor/tflite/tflite-simd.js
  30. 0
    0
      react/features/stream-effects/virtual-background/vendor/tflite/tflite-simd.wasm
  31. 0
    0
      react/features/stream-effects/virtual-background/vendor/tflite/tflite.js
  32. 0
    0
      react/features/stream-effects/virtual-background/vendor/tflite/tflite.wasm
  33. 5
    5
      react/features/toolbox/components/web/Toolbox.js
  34. 23
    0
      react/features/virtual-background/actionTypes.js
  35. 70
    0
      react/features/virtual-background/actions.js
  36. 21
    24
      react/features/virtual-background/components/VideoBackgroundButton.js
  37. 118
    0
      react/features/virtual-background/components/VirtualBackgroundDialog.js
  38. 2
    0
      react/features/virtual-background/components/index.js
  39. 8
    5
      react/features/virtual-background/functions.js
  40. 0
    0
      react/features/virtual-background/index.js
  41. 1
    1
      react/features/virtual-background/logger.js
  42. 38
    0
      react/features/virtual-background/reducer.js
  43. 2
    2
      webpack.config.js

+ 1
- 1
.eslintignore View File

@@ -6,7 +6,7 @@ build/*
6 6
 flow-typed/*
7 7
 libs/*
8 8
 resources/*
9
-react/features/stream-effects/blur/vendor/*
9
+react/features/stream-effects/virtual-background/vendor/*
10 10
 
11 11
 # ESLint will by default ignore its own configuration file. However, there does
12 12
 # not seem to be a reason why we will want to risk being inconsistent with our

+ 4
- 4
Makefile View File

@@ -5,8 +5,8 @@ LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
5 5
 LIBFLAC_DIR = node_modules/libflacjs/dist/min/
6 6
 OLM_DIR = node_modules/olm
7 7
 RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist/
8
-TFLITE_WASM = react/features/stream-effects/blur/vendor/tflite
9
-MEET_MODELS_DIR  = react/features/stream-effects/blur/vendor/models/
8
+TFLITE_WASM = react/features/stream-effects/virtual-background/vendor/tflite
9
+MEET_MODELS_DIR  = react/features/stream-effects/virtual-background/vendor/models/
10 10
 NODE_SASS = ./node_modules/.bin/sass
11 11
 NPM = npm
12 12
 OUTPUT_DIR = .
@@ -51,8 +51,8 @@ deploy-appbundle:
51 51
 		$(OUTPUT_DIR)/analytics-ga.js \
52 52
 		$(BUILD_DIR)/analytics-ga.min.js \
53 53
 		$(BUILD_DIR)/analytics-ga.min.map \
54
-		$(BUILD_DIR)/video-blur-effect.min.js \
55
-		$(BUILD_DIR)/video-blur-effect.min.map \
54
+		$(BUILD_DIR)/virtual-background-effect.min.js \
55
+		$(BUILD_DIR)/virtual-background-effect.min.map \
56 56
 		$(BUILD_DIR)/rnnoise-processor.min.js \
57 57
 		$(BUILD_DIR)/rnnoise-processor.min.map \
58 58
 		$(BUILD_DIR)/close3.min.js \

+ 1
- 1
config.js View File

@@ -430,7 +430,7 @@ var config = {
430 430
     //    'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
431 431
     //    'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
432 432
     //    'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
433
-    //    'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
433
+    //    'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
434 434
     // ],
435 435
 
436 436
     // Stats

+ 1
- 0
css/main.scss View File

@@ -43,6 +43,7 @@ $flagsImagePath: "../images/";
43 43
 @import 'modals/settings/settings';
44 44
 @import 'modals/speaker_stats/speaker_stats';
45 45
 @import 'modals/video-quality/video-quality';
46
+@import 'modals/virtual-background/virtual-background';
46 47
 @import 'modals/local-recording/local-recording';
47 48
 @import 'videolayout_default';
48 49
 @import 'notice';

+ 44
- 0
css/modals/virtual-background/_virtual-background.scss View File

@@ -0,0 +1,44 @@
1
+.virtual-background-dialog{
2
+  display: inline-flex;
3
+  cursor: pointer;
4
+  .thumbnail{
5
+    object-fit: cover;
6
+    padding: 5px;
7
+    height: 40px;
8
+    width: 40px;
9
+  }
10
+  .thumbnail-selected{
11
+    object-fit: cover;
12
+    padding: 5px;
13
+    height: 40px;
14
+    width: 40px;
15
+    border: 2px solid #a4b8d1;
16
+  }
17
+  .blur-selected{
18
+    border: 2px solid #a4b8d1;
19
+  }
20
+  .virtual-background-none{
21
+    font-weight: bold;
22
+    padding: 5px;
23
+    height: 35px;
24
+    width: 35px;
25
+    border-radius: 10px;
26
+    border: 1px solid #a4b8d1;
27
+    text-align: center;
28
+    vertical-align: middle;
29
+    line-height: 35px;
30
+    margin-right: 5px;
31
+  }
32
+  .none-selected{
33
+    font-weight: bold;
34
+    padding: 5px;
35
+    height: 35px;
36
+    width: 35px;
37
+    border-radius: 10px;
38
+    border: 2px solid #a4b8d1;
39
+    text-align: center;
40
+    vertical-align: middle;
41
+    line-height: 35px;
42
+    margin-right: 5px;
43
+  }
44
+}

BIN
images/virtual-background/background-1.jpg View File


BIN
images/virtual-background/background-2.jpg View File


BIN
images/virtual-background/background-3.jpg View File


BIN
images/virtual-background/background-4.jpg View File


+ 1
- 1
interface_config.js View File

@@ -206,7 +206,7 @@ var interfaceConfig = {
206 206
     //     'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
207 207
     //     'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
208 208
     //     'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
209
-    //     'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
209
+    //     'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
210 210
     // ],
211 211
 
212 212
     TOOLBAR_TIMEOUT: 4000,

+ 7
- 4
lang/main.json View File

@@ -332,6 +332,11 @@
332 332
     "embedMeeting": {
333 333
         "title": "Embed this meeting"
334 334
     },
335
+    "virtualBackground": {
336
+        "title": "Backgrounds",
337
+        "enableBlur": "Enable blur",
338
+        "removeBackground": "Remove background"
339
+    },
335 340
     "feedback": {
336 341
         "average": "Average",
337 342
         "bad": "Bad",
@@ -748,7 +753,7 @@
748 753
             "toggleCamera": "Toggle camera",
749 754
             "toggleFilmstrip": "Toggle filmstrip",
750 755
             "videomute": "Toggle mute video",
751
-            "videoblur": "Toggle video blur"
756
+            "selectBackground": "Select Background"
752 757
         },
753 758
         "addPeople": "Add people to your call",
754 759
         "audioSettings": "Audio settings",
@@ -810,9 +815,7 @@
810 815
         "tileViewToggle": "Toggle tile view",
811 816
         "toggleCamera": "Toggle camera",
812 817
         "videomute": "Start / Stop camera",
813
-        "videoSettings": "Video settings",
814
-        "startvideoblur": "Blur my background",
815
-        "stopvideoblur": "Disable background blur"
818
+        "selectBackground": "Select background"
816 819
     },
817 820
     "transcribing": {
818 821
         "ccButtonTooltip": "Start / Stop subtitles",

+ 0
- 1
react/features/app/reducers.any.js View File

@@ -25,7 +25,6 @@ import '../base/testing/reducer';
25 25
 import '../base/tracks/reducer';
26 26
 import '../base/user-interaction/reducer';
27 27
 import '../billing-counter/reducer';
28
-import '../blur/reducer';
29 28
 import '../calendar-sync/reducer';
30 29
 import '../chat/reducer';
31 30
 import '../deep-linking/reducer';

+ 1
- 1
react/features/app/reducers.web.js View File

@@ -12,5 +12,5 @@ import '../remote-control/reducer';
12 12
 import '../screenshot-capture/reducer';
13 13
 import '../shared-video/reducer';
14 14
 import '../talk-while-muted/reducer';
15
-
15
+import '../virtual-background/reducer';
16 16
 import './reducers.any';

+ 1
- 1
react/features/base/config/constants.js View File

@@ -18,5 +18,5 @@ export const TOOLBAR_BUTTONS = [
18 18
     'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
19 19
     'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
20 20
     'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
21
-    'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
21
+    'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
22 22
 ];

+ 1
- 0
react/features/base/icons/svg/index.js View File

@@ -109,6 +109,7 @@ export { default as IconVideoQualityAudioOnly } from './AUD.svg';
109 109
 export { default as IconVideoQualityHD } from './HD.svg';
110 110
 export { default as IconVideoQualityLD } from './LD.svg';
111 111
 export { default as IconVideoQualitySD } from './SD.svg';
112
+export { default as IconVirtualBackground } from './virtual-background.svg';
112 113
 export { default as IconVolume } from './volume.svg';
113 114
 export { default as IconVolumeEmpty } from './volume-empty.svg';
114 115
 export { default as IconVolumeOff } from './volume-off.svg';

+ 3
- 0
react/features/base/icons/svg/virtual-background.svg View File

@@ -0,0 +1,3 @@
1
+<svg width="20" height="20" viewBox="0 0 20 20" fill="white" xmlns="http://www.w3.org/2000/svg">
2
+<path fill-rule="evenodd" clip-rule="evenodd" d="M16.6666 1.66667H3.33329C2.41282 1.66667 1.66663 2.41286 1.66663 3.33334V16.6667C1.66663 17.5871 2.41282 18.3333 3.33329 18.3333H3.63257H8.56767H9.14753H15.6942H16.6666C17.5871 18.3333 18.3333 17.5871 18.3333 16.6667V3.33334C18.3333 2.41286 17.5871 1.66667 16.6666 1.66667ZM7.57977 12.3005L9.3687 14.893L12.0223 8.76728C12.2052 8.34496 12.6959 8.15091 13.1182 8.33385C13.2964 8.41106 13.4421 8.54811 13.53 8.72131L16.6666 14.9002V3.33334H3.33329V16.4665L6.208 12.3005C6.4694 11.9217 6.98838 11.8265 7.36718 12.0879C7.45035 12.1453 7.52238 12.2174 7.57977 12.3005ZM7.49996 10C6.11925 10 4.99996 8.88072 4.99996 7.5C4.99996 6.11929 6.11925 5.00001 7.49996 5.00001C8.88067 5.00001 9.99996 6.11929 9.99996 7.5C9.99996 8.88072 8.88067 10 7.49996 10ZM8.33329 7.5C8.33329 7.96024 7.9602 8.33334 7.49996 8.33334C7.03972 8.33334 6.66663 7.96024 6.66663 7.5C6.66663 7.03977 7.03972 6.66667 7.49996 6.66667C7.9602 6.66667 8.33329 7.03977 8.33329 7.5ZM12.8466 11.0572L15.6942 16.6667H10.4167L12.8466 11.0572ZM6.89389 14.2411L8.56767 16.6667H5.2201L6.89389 14.2411Z" />
3
+</svg>

+ 5
- 5
react/features/base/tracks/loadEffects.web.js View File

@@ -1,7 +1,7 @@
1 1
 // @flow
2 2
 
3
-import { getBlurEffect } from '../../blur';
4 3
 import { createScreenshotCaptureEffect } from '../../stream-effects/screenshot-capture';
4
+import { getBackgroundEffect } from '../../virtual-background';
5 5
 
6 6
 import logger from './logger';
7 7
 
@@ -14,10 +14,10 @@ import logger from './logger';
14 14
 export default function loadEffects(store: Object): Promise<any> {
15 15
     const state = store.getState();
16 16
 
17
-    const blurPromise = state['features/blur'].blurEnabled
18
-        ? getBlurEffect()
17
+    const backgroundPromise = state['features/virtual-background'].backgroundEffectEnabled
18
+        ? getBackgroundEffect()
19 19
             .catch(error => {
20
-                logger.error('Failed to obtain the blur effect instance with error: ', error);
20
+                logger.error('Failed to obtain the background effect instance with error: ', error);
21 21
 
22 22
                 return Promise.resolve();
23 23
             })
@@ -31,5 +31,5 @@ export default function loadEffects(store: Object): Promise<any> {
31 31
             })
32 32
         : Promise.resolve();
33 33
 
34
-    return Promise.all([ blurPromise, screenshotCapturePromise ]);
34
+    return Promise.all([ backgroundPromise, screenshotCapturePromise ]);
35 35
 }

+ 0
- 21
react/features/blur/actionTypes.js View File

@@ -1,21 +0,0 @@
1
-// @flow
2
-
3
-/**
4
- * The type of redux action dispatched which represents that the blur
5
- * is enabled.
6
- *
7
- * {
8
- *      type: BLUR_ENABLED
9
- * }
10
- */
11
-export const BLUR_ENABLED = 'BLUR_ENABLED';
12
-
13
-/**
14
- * The type of redux action dispatched which represents that the blur
15
- * is disabled.
16
- *
17
- * {
18
- *      type: BLUR_DISABLED
19
- * }
20
- */
21
-export const BLUR_DISABLED = 'BLUR_DISABLED';

+ 0
- 67
react/features/blur/actions.js View File

@@ -1,67 +0,0 @@
1
-// @flow
2
-
3
-import { getLocalVideoTrack } from '../../features/base/tracks';
4
-
5
-import { BLUR_DISABLED, BLUR_ENABLED } from './actionTypes';
6
-import { getBlurEffect } from './functions';
7
-import logger from './logger';
8
-
9
-/**
10
-* Signals the local participant is switching between blurred or non blurred video.
11
-*
12
-* @param {boolean} enabled - If true enables video blur, false otherwise.
13
-* @returns {Promise}
14
-*/
15
-export function toggleBlurEffect(enabled: boolean) {
16
-    return function(dispatch: (Object) => Object, getState: () => any) {
17
-        const state = getState();
18
-
19
-        if (state['features/blur'].blurEnabled !== enabled) {
20
-            const { jitsiTrack } = getLocalVideoTrack(state['features/base/tracks']);
21
-
22
-            return getBlurEffect()
23
-                .then(blurEffectInstance =>
24
-                    jitsiTrack.setEffect(enabled ? blurEffectInstance : undefined)
25
-                        .then(() => {
26
-                            enabled ? dispatch(blurEnabled()) : dispatch(blurDisabled());
27
-                        })
28
-                        .catch(error => {
29
-                            enabled ? dispatch(blurDisabled()) : dispatch(blurEnabled());
30
-                            logger.error('setEffect failed with error:', error);
31
-                        })
32
-                )
33
-                .catch(error => {
34
-                    dispatch(blurDisabled());
35
-                    logger.error('getBlurEffect failed with error:', error);
36
-                });
37
-        }
38
-
39
-        return Promise.resolve();
40
-    };
41
-}
42
-
43
-/**
44
- * Signals the local participant that the blur has been enabled.
45
- *
46
- * @returns {{
47
- *      type: BLUR_ENABLED
48
- * }}
49
- */
50
-export function blurEnabled() {
51
-    return {
52
-        type: BLUR_ENABLED
53
-    };
54
-}
55
-
56
-/**
57
- * Signals the local participant that the blur has been disabled.
58
- *
59
- * @returns {{
60
- *      type: BLUR_DISABLED
61
- * }}
62
- */
63
-export function blurDisabled() {
64
-    return {
65
-        type: BLUR_DISABLED
66
-    };
67
-}

+ 0
- 1
react/features/blur/components/index.js View File

@@ -1 +0,0 @@
1
-export { default as VideoBlurButton } from './VideoBlurButton';

+ 0
- 26
react/features/blur/reducer.js View File

@@ -1,26 +0,0 @@
1
-// @flow
2
-
3
-import { ReducerRegistry } from '../base/redux';
4
-
5
-import { BLUR_ENABLED, BLUR_DISABLED } from './actionTypes';
6
-
7
-
8
-ReducerRegistry.register('features/blur', (state = {}, action) => {
9
-
10
-    switch (action.type) {
11
-    case BLUR_ENABLED: {
12
-        return {
13
-            ...state,
14
-            blurEnabled: true
15
-        };
16
-    }
17
-    case BLUR_DISABLED: {
18
-        return {
19
-            ...state,
20
-            blurEnabled: false
21
-        };
22
-    }
23
-    }
24
-
25
-    return state;
26
-});

+ 0
- 61
react/features/stream-effects/blur/index.js View File

@@ -1,61 +0,0 @@
1
-// @flow
2
-
3
-import * as wasmCheck from 'wasm-check';
4
-
5
-import JitsiStreamBlurEffect from './JitsiStreamBlurEffect';
6
-import createTFLiteModule from './vendor/tflite/tflite';
7
-import createTFLiteSIMDModule from './vendor/tflite/tflite-simd';
8
-
9
-const models = {
10
-    'model96': 'libs/segm_lite_v681.tflite',
11
-    'model144': 'libs/segm_full_v679.tflite'
12
-};
13
-
14
-const segmentationDimensions = {
15
-    'model96': {
16
-        'height': 96,
17
-        'width': 160
18
-    },
19
-    'model144': {
20
-        'height': 144,
21
-        'width': 256
22
-    }
23
-};
24
-
25
-/**
26
- * Creates a new instance of JitsiStreamBlurEffect. This loads the bodyPix model that is used to
27
- * extract person segmentation.
28
- *
29
- * @returns {Promise<JitsiStreamBlurEffect>}
30
- */
31
-export async function createBlurEffect() {
32
-    if (!MediaStreamTrack.prototype.getSettings && !MediaStreamTrack.prototype.getConstraints) {
33
-        throw new Error('JitsiStreamBlurEffect not supported!');
34
-    }
35
-    let tflite;
36
-
37
-    if (wasmCheck.feature.simd) {
38
-        tflite = await createTFLiteSIMDModule();
39
-    } else {
40
-        tflite = await createTFLiteModule();
41
-    }
42
-
43
-    const modelBufferOffset = tflite._getModelBufferMemoryOffset();
44
-    const modelResponse = await fetch(
45
-        wasmCheck.feature.simd ? models.model144 : models.model96
46
-    );
47
-
48
-    if (!modelResponse.ok) {
49
-        throw new Error('Failed to download tflite model!');
50
-    }
51
-
52
-    const model = await modelResponse.arrayBuffer();
53
-
54
-    tflite.HEAPU8.set(new Uint8Array(model), modelBufferOffset);
55
-
56
-    tflite._loadModel(model.byteLength);
57
-
58
-    const options = wasmCheck.feature.simd ? segmentationDimensions.model144 : segmentationDimensions.model96;
59
-
60
-    return new JitsiStreamBlurEffect(tflite, options);
61
-}

react/features/stream-effects/blur/JitsiStreamBlurEffect.js → react/features/stream-effects/virtual-background/JitsiStreamBackgroundEffect.js View File

@@ -9,11 +9,11 @@ import {
9 9
 const blurValue = '25px';
10 10
 
11 11
 /**
12
- * Represents a modified MediaStream that adds blur to video background.
13
- * <tt>JitsiStreamBlurEffect</tt> does the processing of the original
12
+ * Represents a modified MediaStream that adds effects to video background.
13
+ * <tt>JitsiStreamBackgroundEffect</tt> does the processing of the original
14 14
  * video stream.
15 15
  */
16
-export default class JitsiStreamBlurEffect {
16
+export default class JitsiStreamBackgroundEffect {
17 17
     _model: Object;
18 18
     _options: Object;
19 19
     _segmentationPixelCount: number;
@@ -29,6 +29,7 @@ export default class JitsiStreamBlurEffect {
29 29
     isEnabled: Function;
30 30
     startEffect: Function;
31 31
     stopEffect: Function;
32
+    virtualImage: Image;
32 33
 
33 34
     /**
34 35
      * Represents a modified video MediaStream track.
@@ -38,6 +39,12 @@ export default class JitsiStreamBlurEffect {
38 39
      * @param {Object} options - Segmentation dimensions.
39 40
      */
40 41
     constructor(model: Object, options: Object) {
42
+        this._options = options;
43
+
44
+        if (this._options.virtualBackground.isVirtualBackground) {
45
+            this.virtualImage = new Image();
46
+            this.virtualImage.src = this._options.virtualBackground.virtualSource;
47
+        }
41 48
         this._model = model;
42 49
         this._options = options;
43 50
         this._segmentationPixelCount = this._options.width * this._options.height;
@@ -91,8 +98,12 @@ export default class JitsiStreamBlurEffect {
91 98
         this._outputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
92 99
 
93 100
         this._outputCanvasCtx.globalCompositeOperation = 'destination-over';
94
-        this._outputCanvasCtx.filter = `blur(${blurValue})`;
95
-        this._outputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
101
+        if (this._options.virtualBackground.isVirtualBackground) {
102
+            this._outputCanvasCtx.drawImage(this.virtualImage, 0, 0);
103
+        } else {
104
+            this._outputCanvasCtx.filter = `blur(${blurValue})`;
105
+            this._outputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
106
+        }
96 107
     }
97 108
 
98 109
     /**
@@ -196,6 +207,7 @@ export default class JitsiStreamBlurEffect {
196 207
         this._segmentationMaskCanvas.width = this._options.width;
197 208
         this._segmentationMaskCanvas.height = this._options.height;
198 209
         this._segmentationMaskCtx = this._segmentationMaskCanvas.getContext('2d');
210
+
199 211
         this._outputCanvasElement.width = parseInt(width, 10);
200 212
         this._outputCanvasElement.height = parseInt(height, 10);
201 213
         this._outputCanvasCtx = this._outputCanvasElement.getContext('2d');

react/features/stream-effects/blur/TimerWorker.js → react/features/stream-effects/virtual-background/TimerWorker.js View File


+ 64
- 0
react/features/stream-effects/virtual-background/index.js View File

@@ -0,0 +1,64 @@
1
+// @flow
2
+
3
+import * as wasmCheck from 'wasm-check';
4
+
5
+import JitsiStreamBackgroundEffect from './JitsiStreamBackgroundEffect';
6
+import createTFLiteModule from './vendor/tflite/tflite';
7
+import createTFLiteSIMDModule from './vendor/tflite/tflite-simd';
8
+
9
+const models = {
10
+    model96: 'libs/segm_lite_v681.tflite',
11
+    model144: 'libs/segm_full_v679.tflite'
12
+};
13
+
14
+const segmentationDimensions = {
15
+    model96: {
16
+        height: 96,
17
+        width: 160
18
+    },
19
+    model144: {
20
+        height: 144,
21
+        width: 256
22
+    }
23
+};
24
+
25
+/**
26
+ * Creates a new instance of JitsiStreamBackgroundEffect. This loads the Meet background model that is used to
27
+ * extract person segmentation.
28
+ *
29
+ * @param {Object} virtualBackground - The virtual object that contains the background image source and
30
+ * the isVirtualBackground flag that indicates if virtual image is activated.
31
+ * @returns {Promise<JitsiStreamBackgroundEffect>}
32
+ */
33
+export async function createVirtualBackgroundEffect(virtualBackground: Object) {
34
+    if (!MediaStreamTrack.prototype.getSettings && !MediaStreamTrack.prototype.getConstraints) {
35
+        throw new Error('JitsiStreamBackgroundEffect not supported!');
36
+    }
37
+    let tflite;
38
+
39
+    if (wasmCheck.feature.simd) {
40
+        tflite = await createTFLiteSIMDModule();
41
+    } else {
42
+        tflite = await createTFLiteModule();
43
+    }
44
+
45
+    const modelBufferOffset = tflite._getModelBufferMemoryOffset();
46
+    const modelResponse = await fetch(wasmCheck.feature.simd ? models.model144 : models.model96);
47
+
48
+    if (!modelResponse.ok) {
49
+        throw new Error('Failed to download tflite model!');
50
+    }
51
+
52
+    const model = await modelResponse.arrayBuffer();
53
+
54
+    tflite.HEAPU8.set(new Uint8Array(model), modelBufferOffset);
55
+
56
+    tflite._loadModel(model.byteLength);
57
+
58
+    const options = {
59
+        ...wasmCheck.feature.simd ? segmentationDimensions.model144 : segmentationDimensions.model96,
60
+        virtualBackground
61
+    };
62
+
63
+    return new JitsiStreamBackgroundEffect(tflite, options);
64
+}

react/features/stream-effects/blur/vendor/README.md → react/features/stream-effects/virtual-background/vendor/README.md View File


react/features/stream-effects/blur/vendor/models/segm_full_v679.tflite → react/features/stream-effects/virtual-background/vendor/models/segm_full_v679.tflite View File


react/features/stream-effects/blur/vendor/models/segm_lite_v681.tflite → react/features/stream-effects/virtual-background/vendor/models/segm_lite_v681.tflite View File


react/features/stream-effects/blur/vendor/tflite/tflite-simd.js → react/features/stream-effects/virtual-background/vendor/tflite/tflite-simd.js View File


react/features/stream-effects/blur/vendor/tflite/tflite-simd.wasm → react/features/stream-effects/virtual-background/vendor/tflite/tflite-simd.wasm View File


react/features/stream-effects/blur/vendor/tflite/tflite.js → react/features/stream-effects/virtual-background/vendor/tflite/tflite.js View File


react/features/stream-effects/blur/vendor/tflite/tflite.wasm → react/features/stream-effects/virtual-background/vendor/tflite/tflite.wasm View File


+ 5
- 5
react/features/toolbox/components/web/Toolbox.js View File

@@ -35,8 +35,6 @@ import { connect } from '../../../base/redux';
35 35
 import { OverflowMenuItem } from '../../../base/toolbox/components';
36 36
 import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
37 37
 import { isVpaasMeeting } from '../../../billing-counter/functions';
38
-import { VideoBlurButton } from '../../../blur';
39
-import { checkBlurSupport } from '../../../blur/functions';
40 38
 import { CHAT_SIZE, ChatCounter, toggleChat } from '../../../chat';
41 39
 import { EmbedMeetingDialog } from '../../../embed-meeting';
42 40
 import { SharedDocumentButton } from '../../../etherpad';
@@ -68,6 +66,8 @@ import {
68 66
     OverflowMenuVideoQualityItem,
69 67
     VideoQualityDialog
70 68
 } from '../../../video-quality';
69
+import { VideoBackgroundButton } from '../../../virtual-background';
70
+import { checkBlurSupport } from '../../../virtual-background/functions';
71 71
 import {
72 72
     setFullScreen,
73 73
     setOverflowMenuVisible,
@@ -1017,9 +1017,9 @@ class Toolbox extends Component<Props, State> {
1017 1017
                 && <SharedDocumentButton
1018 1018
                     key = 'etherpad'
1019 1019
                     showLabel = { true } />,
1020
-            this._shouldShowButton('videobackgroundblur')
1021
-                && <VideoBlurButton
1022
-                    key = 'videobackgroundblur'
1020
+            (this._shouldShowButton('select-background') || this._shouldShowButton('videobackgroundblur'))
1021
+                && <VideoBackgroundButton
1022
+                    key = { 'select-background' }
1023 1023
                     showLabel = { true }
1024 1024
                     visible = { !_screensharing && checkBlurSupport() } />,
1025 1025
             this._shouldShowButton('stats')

+ 23
- 0
react/features/virtual-background/actionTypes.js View File

@@ -0,0 +1,23 @@
1
+// @flow
2
+
3
+/**
4
+ * The type of redux action dispatched which represents that the background
5
+ * effect is enabled or not.
6
+ *
7
+ * @returns {{
8
+ *     type: BACKGROUND_ENABLED,
9
+ *     backgroundEffectEnabled: boolean,
10
+ * }}
11
+ */
12
+export const BACKGROUND_ENABLED = 'BACKGROUND_ENABLED';
13
+
14
+/**
15
+ * The type of the action which enables or disables virtual background
16
+ *
17
+ * @returns {{
18
+ *     type: SET_VIRTUAL_BACKGROUND,
19
+ *     isVirtualBackground: boolean,
20
+ *     virtualSource: string,
21
+ * }}
22
+ */
23
+export const SET_VIRTUAL_BACKGROUND = 'SET_VIRTUAL_BACKGROUND';

+ 70
- 0
react/features/virtual-background/actions.js View File

@@ -0,0 +1,70 @@
1
+// @flow
2
+
3
+import { getLocalVideoTrack } from '../../features/base/tracks';
4
+
5
+import { BACKGROUND_ENABLED, SET_VIRTUAL_BACKGROUND } from './actionTypes';
6
+import { getBackgroundEffect } from './functions';
7
+import logger from './logger';
8
+
9
+/**
10
+ * Signals the local participant activate the virtual background video or not.
11
+ *
12
+ * @param {boolean} enabled - If true enables video background, false otherwise.
13
+ * @returns {Promise}
14
+ */
15
+export function toggleBackgroundEffect(enabled: boolean) {
16
+    return async function(dispatch: Object => Object, getState: () => any) {
17
+        const state = getState();
18
+
19
+        const { jitsiTrack } = getLocalVideoTrack(state['features/base/tracks']);
20
+        const virtualBackground = state['features/virtual-background'];
21
+
22
+        try {
23
+            if (enabled) {
24
+                await jitsiTrack.setEffect(await getBackgroundEffect(virtualBackground));
25
+                dispatch(backgroundEnabled(true));
26
+            } else {
27
+                await jitsiTrack.setEffect(undefined);
28
+                dispatch(backgroundEnabled(false));
29
+            }
30
+        } catch (error) {
31
+            dispatch(backgroundEnabled(false));
32
+            logger.error('Error on apply backgroun effect:', error);
33
+        }
34
+    };
35
+}
36
+
37
+/**
38
+ * Sets the selected virtual background image object.
39
+ *
40
+ * @param {Object} virtualSource - Virtual background image source.
41
+ * @param {boolean} isVirtualBackground - Indicate if virtual image is activated.
42
+ * @returns {{
43
+ *     type: SET_VIRTUAL_BACKGROUND,
44
+ *     virtualSource: string,
45
+ *     isVirtualBackground: boolean,
46
+ * }}
47
+ */
48
+export function setVirtualBackground(virtualSource: string, isVirtualBackground: boolean) {
49
+    return {
50
+        type: SET_VIRTUAL_BACKGROUND,
51
+        virtualSource,
52
+        isVirtualBackground
53
+    };
54
+}
55
+
56
+/**
57
+ * Signals the local participant that the background effect has been enabled.
58
+ *
59
+ * @param {boolean} backgroundEffectEnabled - Indicate if virtual background effect is activated.
60
+ * @returns {{
61
+ *      type: BACKGROUND_ENABLED,
62
+ *      backgroundEffectEnabled: boolean,
63
+ * }}
64
+ */
65
+export function backgroundEnabled(backgroundEffectEnabled: boolean) {
66
+    return {
67
+        type: BACKGROUND_ENABLED,
68
+        backgroundEffectEnabled
69
+    };
70
+}

react/features/blur/components/VideoBlurButton.js → react/features/virtual-background/components/VideoBackgroundButton.js View File

@@ -1,23 +1,24 @@
1 1
 // @flow
2 2
 
3
-import { createVideoBlurEvent, sendAnalytics } from '../../analytics';
3
+import { openDialog } from '../../base/dialog';
4 4
 import { translate } from '../../base/i18n';
5
-import { IconBlurBackground } from '../../base/icons';
5
+import { IconVirtualBackground } from '../../base/icons';
6 6
 import { connect } from '../../base/redux';
7 7
 import { AbstractButton } from '../../base/toolbox/components';
8 8
 import type { AbstractButtonProps } from '../../base/toolbox/components';
9 9
 import { isLocalCameraTrackMuted } from '../../base/tracks';
10
-import { toggleBlurEffect } from '../actions';
10
+
11
+import { VirtualBackgroundDialog } from './index';
11 12
 
12 13
 /**
13
- * The type of the React {@code Component} props of {@link VideoBlurButton}.
14
+ * The type of the React {@code Component} props of {@link VideoBackgroundButton}.
14 15
  */
15 16
 type Props = AbstractButtonProps & {
16 17
 
17 18
     /**
18 19
      * True if the video background is blurred or false if it is not.
19 20
      */
20
-    _isVideoBlurred: boolean,
21
+    _isBackgroundEnabled: boolean,
21 22
 
22 23
     /**
23 24
      * Whether video is currently muted or not.
@@ -28,42 +29,39 @@ type Props = AbstractButtonProps & {
28 29
      * The redux {@code dispatch} function.
29 30
      */
30 31
     dispatch: Function
31
-
32 32
 };
33 33
 
34 34
 /**
35
- * An abstract implementation of a button that toggles the video blur effect.
35
+ * An abstract implementation of a button that toggles the video background dialog.
36 36
  */
37
-class VideoBlurButton extends AbstractButton<Props, *> {
38
-    accessibilityLabel = 'toolbar.accessibilityLabel.videoblur';
39
-    icon = IconBlurBackground;
40
-    label = 'toolbar.startvideoblur';
41
-    toggledLabel = 'toolbar.stopvideoblur';
37
+class VideoBackgroundButton extends AbstractButton<Props, *> {
38
+    accessibilityLabel = 'toolbar.accessibilityLabel.selectBackground';
39
+    icon = IconVirtualBackground;
40
+    label = 'toolbar.selectBackground';
41
+    tooltip = 'toolbar.selectBackground';
42 42
 
43 43
     /**
44
-     * Handles clicking / pressing the button, and toggles the blur effect
44
+     * Handles clicking / pressing the button, and toggles the virtual background dialog
45 45
      * state accordingly.
46 46
      *
47 47
      * @protected
48 48
      * @returns {void}
49 49
      */
50 50
     _handleClick() {
51
-        const { _isVideoBlurred, dispatch } = this.props;
52
-        const value = !_isVideoBlurred;
51
+        const { dispatch } = this.props;
53 52
 
54
-        sendAnalytics(createVideoBlurEvent(value ? 'started' : 'stopped'));
55
-        dispatch(toggleBlurEffect(value));
53
+        dispatch(openDialog(VirtualBackgroundDialog));
56 54
     }
57 55
 
58 56
     /**
59
-     * Returns {@code boolean} value indicating if the blur effect is
57
+     * Returns {@code boolean} value indicating if the background effect is
60 58
      * enabled or not.
61 59
      *
62 60
      * @protected
63 61
      * @returns {boolean}
64 62
      */
65 63
     _isToggled() {
66
-        return this.props._isVideoBlurred;
64
+        return this.props._isBackgroundEnabled;
67 65
     }
68 66
 
69 67
     /**
@@ -80,22 +78,21 @@ class VideoBlurButton extends AbstractButton<Props, *> {
80 78
 
81 79
 /**
82 80
  * Maps (parts of) the redux state to the associated props for the
83
- * {@code VideoBlurButton} component.
81
+ * {@code VideoBackgroundButton} component.
84 82
  *
85 83
  * @param {Object} state - The Redux state.
86 84
  * @private
87 85
  * @returns {{
88
- *     _isVideoBlurred: boolean
86
+ *     _isBackgroundEnabled: boolean
89 87
  * }}
90 88
  */
91 89
 function _mapStateToProps(state): Object {
92 90
     const tracks = state['features/base/tracks'];
93 91
 
94 92
     return {
95
-        _isVideoBlurred: Boolean(state['features/blur'].blurEnabled),
93
+        _isBackgroundEnabled: Boolean(state['features/virtual-background'].backgroundEffectEnabled),
96 94
         _videoMuted: isLocalCameraTrackMuted(tracks)
97 95
     };
98 96
 }
99 97
 
100
-export default translate(connect(_mapStateToProps)(VideoBlurButton));
101
-
98
+export default translate(connect(_mapStateToProps)(VideoBackgroundButton));

+ 118
- 0
react/features/virtual-background/components/VirtualBackgroundDialog.js View File

@@ -0,0 +1,118 @@
1
+// @flow
2
+/* eslint-disable react/jsx-no-bind, no-return-assign */
3
+import React, { useState } from 'react';
4
+
5
+import { Dialog } from '../../base/dialog';
6
+import { translate } from '../../base/i18n';
7
+import { Icon, IconBlurBackground } from '../../base/icons';
8
+import { connect } from '../../base/redux';
9
+import { Tooltip } from '../../base/tooltip';
10
+import { toggleBackgroundEffect, setVirtualBackground } from '../actions';
11
+
12
+const images = [
13
+    {
14
+        tooltip: 'Image 1',
15
+        name: 'background-1.jpg',
16
+        id: 1,
17
+        src: 'images/virtual-background/background-1.jpg'
18
+    },
19
+    {
20
+        tooltip: 'Image 2',
21
+        name: 'background-2.jpg',
22
+        id: 2,
23
+        src: 'images/virtual-background/background-2.jpg'
24
+    },
25
+    {
26
+        tooltip: 'Image 3',
27
+        name: 'background-3.jpg',
28
+        id: 3,
29
+        src: 'images/virtual-background/background-3.jpg'
30
+    },
31
+    {
32
+        tooltip: 'Image 4',
33
+        name: 'background-4.jpg',
34
+        id: 4,
35
+        src: 'images/virtual-background/background-4.jpg'
36
+    }
37
+];
38
+type Props = {
39
+
40
+    /**
41
+     * The redux {@code dispatch} function.
42
+     */
43
+    dispatch: Function,
44
+
45
+    /**
46
+     * Invoked to obtain translated strings.
47
+     */
48
+    t: Function
49
+};
50
+
51
+/**
52
+ * Renders virtual background dialog.
53
+ *
54
+ * @returns {ReactElement}
55
+ */
56
+function VirtualBackground({ dispatch, t }: Props) {
57
+    const [ selected, setSelected ] = useState('');
58
+    const enableBlur = () => {
59
+        setSelected('blur');
60
+        dispatch(setVirtualBackground('', false));
61
+        dispatch(toggleBackgroundEffect(true));
62
+    };
63
+
64
+    const removeBackground = () => {
65
+        setSelected('none');
66
+        dispatch(setVirtualBackground('', false));
67
+        dispatch(toggleBackgroundEffect(false));
68
+    };
69
+
70
+    const addImageBackground = image => {
71
+        setSelected(image.id);
72
+        dispatch(setVirtualBackground(image.src, true));
73
+        dispatch(toggleBackgroundEffect(true));
74
+    };
75
+
76
+    return (
77
+        <Dialog
78
+            hideCancelButton = { true }
79
+            submitDisabled = { false }
80
+            titleKey = { 'virtualBackground.title' }
81
+            width = 'small'>
82
+            <div className = 'virtual-background-dialog'>
83
+                <Tooltip
84
+                    content = { t('virtualBackground.removeBackground') }
85
+                    position = { 'top' }>
86
+                    <div
87
+                        className = { selected === 'none' ? 'none-selected' : 'virtual-background-none' }
88
+                        onClick = { () => removeBackground() }>
89
+                        None
90
+                    </div>
91
+                </Tooltip>
92
+                <Tooltip
93
+                    content = { t('virtualBackground.enableBlur') }
94
+                    position = { 'top' }>
95
+                    <Icon
96
+                        className = { selected === 'blur' ? 'blur-selected' : '' }
97
+                        onClick = { () => enableBlur() }
98
+                        size = { 50 }
99
+                        src = { IconBlurBackground } />
100
+                </Tooltip>
101
+                {images.map((image, index) => (
102
+                    <Tooltip
103
+                        content = { image.tooltip }
104
+                        key = { index }
105
+                        position = { 'top' }>
106
+                        <img
107
+                            className = { selected === image.id ? 'thumbnail-selected' : 'thumbnail' }
108
+                            onClick = { () => addImageBackground(image) }
109
+                            onError = { event => event.target.style.display = 'none' }
110
+                            src = { image.src } />
111
+                    </Tooltip>
112
+                ))}
113
+            </div>
114
+        </Dialog>
115
+    );
116
+}
117
+
118
+export default translate(connect()(VirtualBackground));

+ 2
- 0
react/features/virtual-background/components/index.js View File

@@ -0,0 +1,2 @@
1
+export { default as VideoBackgroundButton } from './VideoBackgroundButton';
2
+export { default as VirtualBackgroundDialog } from './VirtualBackgroundDialog';

react/features/blur/functions.js → react/features/virtual-background/functions.js View File

@@ -7,16 +7,19 @@ let filterSupport;
7 7
 /**
8 8
  * Returns promise that resolves with the blur effect instance.
9 9
  *
10
- * @returns {Promise<JitsiStreamBlurEffect>} - Resolves with the blur effect instance.
10
+ * @param {Object} virtualBackground - The virtual object that contains the background image source and
11
+ * the isVirtualBackground flag that indicates if virtual image is activated .
12
+ * @returns {Promise<JitsiStreamBackgroundEffect>} - Resolves with the background effect instance.
11 13
  */
12
-export function getBlurEffect() {
14
+export function getBackgroundEffect(virtualBackground: Object) {
13 15
     const ns = getJitsiMeetGlobalNS();
14 16
 
15
-    if (ns.effects && ns.effects.createBlurEffect) {
16
-        return ns.effects.createBlurEffect();
17
+    if (ns.effects && ns.effects.createVirtualBackgroundEffect) {
18
+        return ns.effects.createVirtualBackgroundEffect(virtualBackground);
17 19
     }
18 20
 
19
-    return loadScript('libs/video-blur-effect.min.js').then(() => ns.effects.createBlurEffect());
21
+    return loadScript('libs/virtual-background-effect.min.js').then(() =>
22
+        ns.effects.createVirtualBackgroundEffect(virtualBackground));
20 23
 }
21 24
 
22 25
 /**

react/features/blur/index.js → react/features/virtual-background/index.js View File


react/features/blur/logger.js → react/features/virtual-background/logger.js View File

@@ -2,4 +2,4 @@
2 2
 
3 3
 import { getLogger } from '../base/logging/functions';
4 4
 
5
-export default getLogger('features/blur');
5
+export default getLogger('features/virtual-background');

+ 38
- 0
react/features/virtual-background/reducer.js View File

@@ -0,0 +1,38 @@
1
+// @flow
2
+
3
+import { ReducerRegistry } from '../base/redux';
4
+
5
+import { BACKGROUND_ENABLED, SET_VIRTUAL_BACKGROUND } from './actionTypes';
6
+
7
+/**
8
+ * Reduces redux actions which activate/deactivate virtual background image, or
9
+ * indicate if the virtual image background is activated/deactivated. The
10
+ * backgroundEffectEnabled flag indicate if virtual background effect is activated.
11
+ *
12
+ * @param {State} state - The current redux state.
13
+ * @param {Action} action - The redux action to reduce.
14
+ * @param {string} action.type - The type of the redux action to reduce..
15
+ * @returns {State} The next redux state that is the result of reducing the
16
+ * specified action.
17
+ */
18
+ReducerRegistry.register('features/virtual-background', (state = {}, action) => {
19
+    const { virtualSource, isVirtualBackground, backgroundEffectEnabled } = action;
20
+
21
+    switch (action.type) {
22
+    case SET_VIRTUAL_BACKGROUND: {
23
+        return {
24
+            ...state,
25
+            virtualSource,
26
+            isVirtualBackground
27
+        };
28
+    }
29
+    case BACKGROUND_ENABLED: {
30
+        return {
31
+            ...state,
32
+            backgroundEffectEnabled
33
+        };
34
+    }
35
+    }
36
+
37
+    return state;
38
+});

+ 2
- 2
webpack.config.js View File

@@ -240,7 +240,7 @@ module.exports = [
240 240
         performance: getPerformanceHints(128 * 1024)
241 241
     }),
242 242
 
243
-    // Because both video-blur-effect and rnnoise-processor modules are loaded
243
+    // Because both virtual-background-effect and rnnoise-processor modules are loaded
244 244
     // in a lazy manner using the loadScript function with a hard coded name,
245 245
     // i.e.loadScript('libs/rnnoise-processor.min.js'), webpack dev server
246 246
     // won't know how to properly load them using the default config filename
@@ -249,7 +249,7 @@ module.exports = [
249 249
     // prod and dev mode.
250 250
     Object.assign({}, config, {
251 251
         entry: {
252
-            'video-blur-effect': './react/features/stream-effects/blur/index.js'
252
+            'virtual-background-effect': './react/features/stream-effects/virtual-background/index.js'
253 253
         },
254 254
         output: Object.assign({}, config.output, {
255 255
             library: [ 'JitsiMeetJS', 'app', 'effects' ],

Loading…
Cancel
Save