Browse Source

feat(screenshot-capture) Updated screensharing screenshot capture

Changed screen capture to non effect. Effects are used to alter the stream, this feature does not need to alter the stream, it just needs access to it

Changed image diff library. Previous library diff’ed the whole image, the new one has en early return threshold

Use ImageCaptureAPI to take the screenshot. Added polyfill for it and polyfill for createImageBitmap

Added analytics
master
robertpin 3 years ago
parent
commit
001ae54a7c

+ 4
- 3
conference.js View File

134
 } from './react/features/prejoin';
134
 } from './react/features/prejoin';
135
 import { disableReceiver, stopReceiver } from './react/features/remote-control';
135
 import { disableReceiver, stopReceiver } from './react/features/remote-control';
136
 import { setScreenAudioShareState, isScreenAudioShared } from './react/features/screen-share/';
136
 import { setScreenAudioShareState, isScreenAudioShared } from './react/features/screen-share/';
137
-import { toggleScreenshotCaptureEffect } from './react/features/screenshot-capture';
137
+import { toggleScreenshotCaptureSummary } from './react/features/screenshot-capture';
138
 import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/AudioMixerEffect';
138
 import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/AudioMixerEffect';
139
 import { createPresenterEffect } from './react/features/stream-effects/presenter';
139
 import { createPresenterEffect } from './react/features/stream-effects/presenter';
140
 import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise';
140
 import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise';
1545
         APP.store.dispatch(stopReceiver());
1545
         APP.store.dispatch(stopReceiver());
1546
 
1546
 
1547
         this._stopProxyConnection();
1547
         this._stopProxyConnection();
1548
+
1548
         if (config.enableScreenshotCapture) {
1549
         if (config.enableScreenshotCapture) {
1549
-            APP.store.dispatch(toggleScreenshotCaptureEffect(false));
1550
+            APP.store.dispatch(toggleScreenshotCaptureSummary(false));
1550
         }
1551
         }
1551
 
1552
 
1552
         // It can happen that presenter GUM is in progress while screensharing is being turned off. Here it needs to
1553
         // It can happen that presenter GUM is in progress while screensharing is being turned off. Here it needs to
1924
             .then(() => {
1925
             .then(() => {
1925
                 this.videoSwitchInProgress = false;
1926
                 this.videoSwitchInProgress = false;
1926
                 if (config.enableScreenshotCapture) {
1927
                 if (config.enableScreenshotCapture) {
1927
-                    APP.store.dispatch(toggleScreenshotCaptureEffect(true));
1928
+                    APP.store.dispatch(toggleScreenshotCaptureSummary(true));
1928
                 }
1929
                 }
1929
                 sendAnalytics(createScreenSharingEvent('started'));
1930
                 sendAnalytics(createScreenSharingEvent('started'));
1930
                 logger.log('Screen sharing started');
1931
                 logger.log('Screen sharing started');

+ 389
- 19
package-lock.json View File

2774
         "sdp-transform": "2.3.0"
2774
         "sdp-transform": "2.3.0"
2775
       }
2775
       }
2776
     },
2776
     },
2777
+    "@mapbox/node-pre-gyp": {
2778
+      "version": "1.0.5",
2779
+      "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz",
2780
+      "integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==",
2781
+      "optional": true,
2782
+      "requires": {
2783
+        "detect-libc": "^1.0.3",
2784
+        "https-proxy-agent": "^5.0.0",
2785
+        "make-dir": "^3.1.0",
2786
+        "node-fetch": "^2.6.1",
2787
+        "nopt": "^5.0.0",
2788
+        "npmlog": "^4.1.2",
2789
+        "rimraf": "^3.0.2",
2790
+        "semver": "^7.3.4",
2791
+        "tar": "^6.1.0"
2792
+      },
2793
+      "dependencies": {
2794
+        "chownr": {
2795
+          "version": "2.0.0",
2796
+          "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
2797
+          "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
2798
+          "optional": true
2799
+        },
2800
+        "fs-minipass": {
2801
+          "version": "2.1.0",
2802
+          "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
2803
+          "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
2804
+          "optional": true,
2805
+          "requires": {
2806
+            "minipass": "^3.0.0"
2807
+          }
2808
+        },
2809
+        "glob": {
2810
+          "version": "7.2.0",
2811
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
2812
+          "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
2813
+          "optional": true,
2814
+          "requires": {
2815
+            "fs.realpath": "^1.0.0",
2816
+            "inflight": "^1.0.4",
2817
+            "inherits": "2",
2818
+            "minimatch": "^3.0.4",
2819
+            "once": "^1.3.0",
2820
+            "path-is-absolute": "^1.0.0"
2821
+          }
2822
+        },
2823
+        "lru-cache": {
2824
+          "version": "6.0.0",
2825
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
2826
+          "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
2827
+          "optional": true,
2828
+          "requires": {
2829
+            "yallist": "^4.0.0"
2830
+          }
2831
+        },
2832
+        "make-dir": {
2833
+          "version": "3.1.0",
2834
+          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
2835
+          "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
2836
+          "optional": true,
2837
+          "requires": {
2838
+            "semver": "^6.0.0"
2839
+          },
2840
+          "dependencies": {
2841
+            "semver": {
2842
+              "version": "6.3.0",
2843
+              "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
2844
+              "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
2845
+              "optional": true
2846
+            }
2847
+          }
2848
+        },
2849
+        "minipass": {
2850
+          "version": "3.1.5",
2851
+          "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz",
2852
+          "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==",
2853
+          "optional": true,
2854
+          "requires": {
2855
+            "yallist": "^4.0.0"
2856
+          }
2857
+        },
2858
+        "minizlib": {
2859
+          "version": "2.1.2",
2860
+          "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
2861
+          "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
2862
+          "optional": true,
2863
+          "requires": {
2864
+            "minipass": "^3.0.0",
2865
+            "yallist": "^4.0.0"
2866
+          }
2867
+        },
2868
+        "mkdirp": {
2869
+          "version": "1.0.4",
2870
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
2871
+          "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
2872
+          "optional": true
2873
+        },
2874
+        "node-fetch": {
2875
+          "version": "2.6.5",
2876
+          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz",
2877
+          "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==",
2878
+          "optional": true,
2879
+          "requires": {
2880
+            "whatwg-url": "^5.0.0"
2881
+          }
2882
+        },
2883
+        "rimraf": {
2884
+          "version": "3.0.2",
2885
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
2886
+          "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
2887
+          "optional": true,
2888
+          "requires": {
2889
+            "glob": "^7.1.3"
2890
+          }
2891
+        },
2892
+        "semver": {
2893
+          "version": "7.3.5",
2894
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
2895
+          "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
2896
+          "optional": true,
2897
+          "requires": {
2898
+            "lru-cache": "^6.0.0"
2899
+          }
2900
+        },
2901
+        "tar": {
2902
+          "version": "6.1.11",
2903
+          "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz",
2904
+          "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==",
2905
+          "optional": true,
2906
+          "requires": {
2907
+            "chownr": "^2.0.0",
2908
+            "fs-minipass": "^2.0.0",
2909
+            "minipass": "^3.0.0",
2910
+            "minizlib": "^2.1.1",
2911
+            "mkdirp": "^1.0.3",
2912
+            "yallist": "^4.0.0"
2913
+          }
2914
+        },
2915
+        "yallist": {
2916
+          "version": "4.0.0",
2917
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
2918
+          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
2919
+          "optional": true
2920
+        }
2921
+      }
2922
+    },
2777
     "@material-ui/core": {
2923
     "@material-ui/core": {
2778
       "version": "4.11.3",
2924
       "version": "4.11.3",
2779
       "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.3.tgz",
2925
       "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.3.tgz",
4400
       "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
4546
       "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
4401
       "dev": true
4547
       "dev": true
4402
     },
4548
     },
4549
+    "abbrev": {
4550
+      "version": "1.1.1",
4551
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
4552
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
4553
+      "optional": true
4554
+    },
4403
     "abort-controller": {
4555
     "abort-controller": {
4404
       "version": "3.0.0",
4556
       "version": "3.0.0",
4405
       "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
4557
       "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
4447
       "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==",
4599
       "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==",
4448
       "dev": true
4600
       "dev": true
4449
     },
4601
     },
4602
+    "agent-base": {
4603
+      "version": "6.0.2",
4604
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
4605
+      "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
4606
+      "optional": true,
4607
+      "requires": {
4608
+        "debug": "4"
4609
+      },
4610
+      "dependencies": {
4611
+        "debug": {
4612
+          "version": "4.3.2",
4613
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
4614
+          "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
4615
+          "optional": true,
4616
+          "requires": {
4617
+            "ms": "2.1.2"
4618
+          }
4619
+        },
4620
+        "ms": {
4621
+          "version": "2.1.2",
4622
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
4623
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
4624
+          "optional": true
4625
+        }
4626
+      }
4627
+    },
4450
     "ajv": {
4628
     "ajv": {
4451
       "version": "5.5.2",
4629
       "version": "5.5.2",
4452
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
4630
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
4867
     "aproba": {
5045
     "aproba": {
4868
       "version": "1.2.0",
5046
       "version": "1.2.0",
4869
       "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
5047
       "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
4870
-      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
4871
-      "dev": true
5048
+      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
5049
+    },
5050
+    "are-we-there-yet": {
5051
+      "version": "1.1.7",
5052
+      "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz",
5053
+      "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==",
5054
+      "optional": true,
5055
+      "requires": {
5056
+        "delegates": "^1.0.0",
5057
+        "readable-stream": "^2.0.6"
5058
+      }
4872
     },
5059
     },
4873
     "argparse": {
5060
     "argparse": {
4874
       "version": "1.0.10",
5061
       "version": "1.0.10",
6135
       "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz",
6322
       "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz",
6136
       "integrity": "sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw=="
6323
       "integrity": "sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw=="
6137
     },
6324
     },
6325
+    "canvas": {
6326
+      "version": "2.8.0",
6327
+      "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.8.0.tgz",
6328
+      "integrity": "sha512-gLTi17X8WY9Cf5GZ2Yns8T5lfBOcGgFehDFb+JQwDqdOoBOcECS9ZWMEAqMSVcMYwXD659J8NyzjRY/2aE+C2Q==",
6329
+      "optional": true,
6330
+      "requires": {
6331
+        "@mapbox/node-pre-gyp": "^1.0.0",
6332
+        "nan": "^2.14.0",
6333
+        "simple-get": "^3.0.3"
6334
+      }
6335
+    },
6138
     "capture-exit": {
6336
     "capture-exit": {
6139
       "version": "2.0.0",
6337
       "version": "2.0.0",
6140
       "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz",
6338
       "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz",
6670
       "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==",
6868
       "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==",
6671
       "dev": true
6869
       "dev": true
6672
     },
6870
     },
6871
+    "console-control-strings": {
6872
+      "version": "1.1.0",
6873
+      "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
6874
+      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
6875
+      "optional": true
6876
+    },
6673
     "constants-browserify": {
6877
     "constants-browserify": {
6674
       "version": "1.0.0",
6878
       "version": "1.0.0",
6675
       "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
6879
       "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
7202
       "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
7406
       "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
7203
       "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
7407
       "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
7204
     },
7408
     },
7409
+    "decompress-response": {
7410
+      "version": "4.2.1",
7411
+      "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
7412
+      "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
7413
+      "optional": true,
7414
+      "requires": {
7415
+        "mimic-response": "^2.0.0"
7416
+      }
7417
+    },
7205
     "deep-assign": {
7418
     "deep-assign": {
7206
       "version": "3.0.0",
7419
       "version": "3.0.0",
7207
       "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-3.0.0.tgz",
7420
       "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-3.0.0.tgz",
7327
         "rimraf": "^2.2.8"
7540
         "rimraf": "^2.2.8"
7328
       }
7541
       }
7329
     },
7542
     },
7543
+    "delegates": {
7544
+      "version": "1.0.0",
7545
+      "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
7546
+      "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
7547
+      "optional": true
7548
+    },
7330
     "denodeify": {
7549
     "denodeify": {
7331
       "version": "1.2.1",
7550
       "version": "1.2.1",
7332
       "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz",
7551
       "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz",
7358
       "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
7577
       "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
7359
       "dev": true
7578
       "dev": true
7360
     },
7579
     },
7580
+    "detect-libc": {
7581
+      "version": "1.0.3",
7582
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
7583
+      "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
7584
+      "optional": true
7585
+    },
7361
     "detect-node": {
7586
     "detect-node": {
7362
       "version": "2.0.4",
7587
       "version": "2.0.4",
7363
       "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
7588
       "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
9451
       "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
9676
       "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
9452
       "dev": true
9677
       "dev": true
9453
     },
9678
     },
9679
+    "gauge": {
9680
+      "version": "2.7.4",
9681
+      "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
9682
+      "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
9683
+      "optional": true,
9684
+      "requires": {
9685
+        "aproba": "^1.0.3",
9686
+        "console-control-strings": "^1.0.0",
9687
+        "has-unicode": "^2.0.0",
9688
+        "object-assign": "^4.1.0",
9689
+        "signal-exit": "^3.0.0",
9690
+        "string-width": "^1.0.1",
9691
+        "strip-ansi": "^3.0.1",
9692
+        "wide-align": "^1.1.0"
9693
+      },
9694
+      "dependencies": {
9695
+        "is-fullwidth-code-point": {
9696
+          "version": "1.0.0",
9697
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
9698
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
9699
+          "optional": true,
9700
+          "requires": {
9701
+            "number-is-nan": "^1.0.0"
9702
+          }
9703
+        },
9704
+        "string-width": {
9705
+          "version": "1.0.2",
9706
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
9707
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
9708
+          "optional": true,
9709
+          "requires": {
9710
+            "code-point-at": "^1.0.0",
9711
+            "is-fullwidth-code-point": "^1.0.0",
9712
+            "strip-ansi": "^3.0.0"
9713
+          }
9714
+        }
9715
+      }
9716
+    },
9454
     "gensync": {
9717
     "gensync": {
9455
       "version": "1.0.0-beta.2",
9718
       "version": "1.0.0-beta.2",
9456
       "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
9719
       "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
9644
       "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
9907
       "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
9645
       "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q="
9908
       "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q="
9646
     },
9909
     },
9910
+    "has-unicode": {
9911
+      "version": "2.0.1",
9912
+      "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
9913
+      "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
9914
+      "optional": true
9915
+    },
9647
     "has-value": {
9916
     "has-value": {
9648
       "version": "1.0.0",
9917
       "version": "1.0.0",
9649
       "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
9918
       "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
9889
       "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
10158
       "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
9890
       "dev": true
10159
       "dev": true
9891
     },
10160
     },
10161
+    "https-proxy-agent": {
10162
+      "version": "5.0.0",
10163
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
10164
+      "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
10165
+      "optional": true,
10166
+      "requires": {
10167
+        "agent-base": "6",
10168
+        "debug": "4"
10169
+      },
10170
+      "dependencies": {
10171
+        "debug": {
10172
+          "version": "4.3.2",
10173
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
10174
+          "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
10175
+          "optional": true,
10176
+          "requires": {
10177
+            "ms": "2.1.2"
10178
+          }
10179
+        },
10180
+        "ms": {
10181
+          "version": "2.1.2",
10182
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
10183
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
10184
+          "optional": true
10185
+        }
10186
+      }
10187
+    },
9892
     "hyphenate-style-name": {
10188
     "hyphenate-style-name": {
9893
       "version": "1.0.4",
10189
       "version": "1.0.4",
9894
       "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
10190
       "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
9984
       "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
10280
       "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
9985
       "dev": true
10281
       "dev": true
9986
     },
10282
     },
10283
+    "image-capture": {
10284
+      "version": "0.4.0",
10285
+      "resolved": "https://registry.npmjs.org/image-capture/-/image-capture-0.4.0.tgz",
10286
+      "integrity": "sha512-6RWTfqC4ij0AldG+6sQ51XSHTSbwfqMSjVl1GtwNBzbW4UrcfGZeB1Kn749BccvtLb04g5+jSTf1D7q3qHcxpA=="
10287
+    },
9987
     "image-size": {
10288
     "image-size": {
9988
       "version": "0.6.3",
10289
       "version": "0.6.3",
9989
       "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz",
10290
       "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz",
10555
     "isarray": {
10856
     "isarray": {
10556
       "version": "1.0.0",
10857
       "version": "1.0.0",
10557
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
10858
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
10558
-      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
10559
-      "dev": true
10859
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
10560
     },
10860
     },
10561
     "isexe": {
10861
     "isexe": {
10562
       "version": "2.0.0",
10862
       "version": "2.0.0",
12240
       "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
12540
       "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
12241
       "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
12541
       "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
12242
     },
12542
     },
12543
+    "mimic-response": {
12544
+      "version": "2.1.0",
12545
+      "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
12546
+      "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
12547
+      "optional": true
12548
+    },
12243
     "minimalistic-assert": {
12549
     "minimalistic-assert": {
12244
       "version": "1.0.1",
12550
       "version": "1.0.1",
12245
       "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
12551
       "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
12606
         "semver": "^5.3.0"
12912
         "semver": "^5.3.0"
12607
       }
12913
       }
12608
     },
12914
     },
12915
+    "nopt": {
12916
+      "version": "5.0.0",
12917
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
12918
+      "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
12919
+      "optional": true,
12920
+      "requires": {
12921
+        "abbrev": "1"
12922
+      }
12923
+    },
12609
     "normalize-package-data": {
12924
     "normalize-package-data": {
12610
       "version": "2.4.0",
12925
       "version": "2.4.0",
12611
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
12926
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
12643
         "path-key": "^2.0.0"
12958
         "path-key": "^2.0.0"
12644
       }
12959
       }
12645
     },
12960
     },
12961
+    "npmlog": {
12962
+      "version": "4.1.2",
12963
+      "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
12964
+      "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
12965
+      "optional": true,
12966
+      "requires": {
12967
+        "are-we-there-yet": "~1.1.2",
12968
+        "console-control-strings": "~1.1.0",
12969
+        "gauge": "~2.7.3",
12970
+        "set-blocking": "~2.0.0"
12971
+      }
12972
+    },
12646
     "nth-check": {
12973
     "nth-check": {
12647
       "version": "1.0.2",
12974
       "version": "1.0.2",
12648
       "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
12975
       "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
13486
         "node-modules-regexp": "^1.0.0"
13813
         "node-modules-regexp": "^1.0.0"
13487
       }
13814
       }
13488
     },
13815
     },
13489
-    "pixelmatch": {
13490
-      "version": "5.1.0",
13491
-      "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.1.0.tgz",
13492
-      "integrity": "sha512-HqtgvuWN12tBzKJf7jYsc38Ha28Q2NYpmBL9WostEGgDHJqbTLkjydZXL1ZHM02ZnB+Dkwlxo87HBY38kMiD6A==",
13493
-      "requires": {
13494
-        "pngjs": "^3.4.0"
13495
-      }
13496
-    },
13497
     "pkg-dir": {
13816
     "pkg-dir": {
13498
       "version": "2.0.0",
13817
       "version": "2.0.0",
13499
       "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
13818
       "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
13562
       "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==",
13881
       "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==",
13563
       "dev": true
13882
       "dev": true
13564
     },
13883
     },
13565
-    "pngjs": {
13566
-      "version": "3.4.0",
13567
-      "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
13568
-      "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="
13569
-    },
13570
     "popper.js": {
13884
     "popper.js": {
13571
       "version": "1.16.1",
13885
       "version": "1.16.1",
13572
       "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
13886
       "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
15437
       "version": "2.3.7",
15751
       "version": "2.3.7",
15438
       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
15752
       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
15439
       "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
15753
       "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
15440
-      "dev": true,
15441
       "requires": {
15754
       "requires": {
15442
         "core-util-is": "~1.0.0",
15755
         "core-util-is": "~1.0.0",
15443
         "inherits": "~2.0.3",
15756
         "inherits": "~2.0.3",
15723
       "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
16036
       "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
15724
       "dev": true
16037
       "dev": true
15725
     },
16038
     },
16039
+    "resemblejs": {
16040
+      "version": "4.0.0",
16041
+      "resolved": "https://registry.npmjs.org/resemblejs/-/resemblejs-4.0.0.tgz",
16042
+      "integrity": "sha512-vaGs/hFVx/941+RS4UJtd8DQvx5RuB61tPLOQCxPso3JpmjfDb6odH5HViT17S0d8DaZsexD01nRJI12giCz/A==",
16043
+      "requires": {
16044
+        "canvas": "2.8.0"
16045
+      }
16046
+    },
15726
     "resolve": {
16047
     "resolve": {
15727
       "version": "1.8.1",
16048
       "version": "1.8.1",
15728
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
16049
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
16197
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
16518
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
16198
       "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
16519
       "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
16199
     },
16520
     },
16521
+    "simple-concat": {
16522
+      "version": "1.0.1",
16523
+      "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
16524
+      "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
16525
+      "optional": true
16526
+    },
16527
+    "simple-get": {
16528
+      "version": "3.1.0",
16529
+      "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
16530
+      "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
16531
+      "optional": true,
16532
+      "requires": {
16533
+        "decompress-response": "^4.2.0",
16534
+        "once": "^1.3.1",
16535
+        "simple-concat": "^1.0.0"
16536
+      }
16537
+    },
16200
     "simple-plist": {
16538
     "simple-plist": {
16201
       "version": "1.1.1",
16539
       "version": "1.1.1",
16202
       "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.1.1.tgz",
16540
       "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.1.1.tgz",
17133
       "version": "1.1.1",
17471
       "version": "1.1.1",
17134
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
17472
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
17135
       "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
17473
       "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
17136
-      "dev": true,
17137
       "requires": {
17474
       "requires": {
17138
         "safe-buffer": "~5.1.0"
17475
         "safe-buffer": "~5.1.0"
17139
       }
17476
       }
17777
       "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
18114
       "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
17778
       "dev": true
18115
       "dev": true
17779
     },
18116
     },
18117
+    "tr46": {
18118
+      "version": "0.0.3",
18119
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
18120
+      "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
18121
+      "optional": true
18122
+    },
17780
     "traverse": {
18123
     "traverse": {
17781
       "version": "0.6.6",
18124
       "version": "0.6.6",
17782
       "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz",
18125
       "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz",
19661
       "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
20004
       "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
19662
       "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
20005
       "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
19663
     },
20006
     },
20007
+    "whatwg-url": {
20008
+      "version": "5.0.0",
20009
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
20010
+      "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
20011
+      "optional": true,
20012
+      "requires": {
20013
+        "tr46": "~0.0.3",
20014
+        "webidl-conversions": "^3.0.0"
20015
+      },
20016
+      "dependencies": {
20017
+        "webidl-conversions": {
20018
+          "version": "3.0.1",
20019
+          "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
20020
+          "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=",
20021
+          "optional": true
20022
+        }
20023
+      }
20024
+    },
19664
     "whatwg-url-without-unicode": {
20025
     "whatwg-url-without-unicode": {
19665
       "version": "8.0.0-3",
20026
       "version": "8.0.0-3",
19666
       "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz",
20027
       "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz",
19722
       "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
20083
       "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
19723
       "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
20084
       "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
19724
     },
20085
     },
20086
+    "wide-align": {
20087
+      "version": "1.1.3",
20088
+      "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
20089
+      "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
20090
+      "optional": true,
20091
+      "requires": {
20092
+        "string-width": "^1.0.2 || 2"
20093
+      }
20094
+    },
19725
     "windows-iana": {
20095
     "windows-iana": {
19726
       "version": "3.1.0",
20096
       "version": "3.1.0",
19727
       "resolved": "https://registry.npmjs.org/windows-iana/-/windows-iana-3.1.0.tgz",
20097
       "resolved": "https://registry.npmjs.org/windows-iana/-/windows-iana-3.1.0.tgz",

+ 2
- 1
package.json View File

54
     "i18next": "17.0.6",
54
     "i18next": "17.0.6",
55
     "i18next-browser-languagedetector": "3.0.1",
55
     "i18next-browser-languagedetector": "3.0.1",
56
     "i18next-xhr-backend": "3.0.0",
56
     "i18next-xhr-backend": "3.0.0",
57
+    "image-capture": "0.4.0",
57
     "jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#v1.0.0",
58
     "jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#v1.0.0",
58
     "jquery": "3.5.1",
59
     "jquery": "3.5.1",
59
     "jquery-i18next": "1.2.1",
60
     "jquery-i18next": "1.2.1",
65
     "moment": "2.29.1",
66
     "moment": "2.29.1",
66
     "moment-duration-format": "2.2.2",
67
     "moment-duration-format": "2.2.2",
67
     "optional-require": "1.0.3",
68
     "optional-require": "1.0.3",
68
-    "pixelmatch": "5.1.0",
69
     "promise.allsettled": "1.0.4",
69
     "promise.allsettled": "1.0.4",
70
     "punycode": "2.1.1",
70
     "punycode": "2.1.1",
71
     "react": "16.12",
71
     "react": "16.12",
102
     "react-youtube": "7.13.1",
102
     "react-youtube": "7.13.1",
103
     "redux": "4.0.4",
103
     "redux": "4.0.4",
104
     "redux-thunk": "2.2.0",
104
     "redux-thunk": "2.2.0",
105
+    "resemblejs": "4.0.0",
105
     "rnnoise-wasm": "github:jitsi/rnnoise-wasm#566a16885897704d6e6d67a1d5ac5d39781db2af",
106
     "rnnoise-wasm": "github:jitsi/rnnoise-wasm#566a16885897704d6e6d67a1d5ac5d39781db2af",
106
     "rtcstats": "github:jitsi/rtcstats#v8.1.0",
107
     "rtcstats": "github:jitsi/rtcstats#v8.1.0",
107
     "styled-components": "3.4.9",
108
     "styled-components": "3.4.9",

+ 12
- 0
react/features/analytics/AnalyticsEvents.js View File

867
         source: 'welcomePage'
867
         source: 'welcomePage'
868
     };
868
     };
869
 }
869
 }
870
+
871
+/**
872
+ * Creates an event which indicates a screenshot of the screensharing has been taken.
873
+ *
874
+ * @returns {Object} The event in a format suitable for sending via
875
+ * sendAnalytics.
876
+ */
877
+export function createScreensharingCaptureTakenEvent() {
878
+    return {
879
+        action: 'screen.sharing.capture'
880
+    };
881
+}

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

1
 // @flow
1
 // @flow
2
 
2
 
3
-import { createScreenshotCaptureEffect } from '../../stream-effects/screenshot-capture';
4
 import { createVirtualBackgroundEffect } from '../../stream-effects/virtual-background';
3
 import { createVirtualBackgroundEffect } from '../../stream-effects/virtual-background';
5
 
4
 
6
 import logger from './logger';
5
 import logger from './logger';
20
             .catch(error => {
19
             .catch(error => {
21
                 logger.error('Failed to obtain the background effect instance with error: ', error);
20
                 logger.error('Failed to obtain the background effect instance with error: ', error);
22
 
21
 
23
-                return Promise.resolve();
24
-            })
25
-        : Promise.resolve();
26
-    const screenshotCapturePromise = state['features/screenshot-capture']?.capturesEnabled
27
-        ? createScreenshotCaptureEffect(state)
28
-            .catch(error => {
29
-                logger.error('Failed to obtain the screenshot capture effect effect instance with error: ', error);
30
-
31
                 return Promise.resolve();
22
                 return Promise.resolve();
32
             })
23
             })
33
         : Promise.resolve();
24
         : Promise.resolve();
34
 
25
 
35
-    return Promise.all([ backgroundPromise, screenshotCapturePromise ]);
26
+    return Promise.all([ backgroundPromise ]);
36
 }
27
 }

+ 173
- 0
react/features/screenshot-capture/ScreenshotCaptureSummary.js View File

1
+// @flow
2
+
3
+import resemble from 'resemblejs';
4
+import 'image-capture';
5
+import './createImageBitmap';
6
+
7
+import { createScreensharingCaptureTakenEvent, sendAnalytics } from '../analytics';
8
+import { getCurrentConference } from '../base/conference';
9
+
10
+import {
11
+    CLEAR_INTERVAL,
12
+    INTERVAL_TIMEOUT,
13
+    PERCENTAGE_LOWER_BOUND,
14
+    POLL_INTERVAL,
15
+    SET_INTERVAL
16
+} from './constants';
17
+import { processScreenshot } from './processScreenshot';
18
+import { timerWorkerScript } from './worker';
19
+
20
+declare var interfaceConfig: Object;
21
+declare var ImageCapture: any;
22
+
23
+/**
24
+ * Effect that wraps {@code MediaStream} adding periodic screenshot captures.
25
+ * Manipulates the original desktop stream and performs custom processing operations, if implemented.
26
+ */
27
+export default class ScreenshotCaptureSummary {
28
+    _state: Object;
29
+    _currentCanvas: HTMLCanvasElement;
30
+    _currentCanvasContext: CanvasRenderingContext2D;
31
+    _handleWorkerAction: Function;
32
+    _initScreenshotCapture: Function;
33
+    _imageCapture: any;
34
+    _streamWorker: Worker;
35
+    _streamHeight: any;
36
+    _streamWidth: any;
37
+    _storedImageData: ImageData;
38
+
39
+    /**
40
+     * Initializes a new {@code ScreenshotCaptureEffect} instance.
41
+     *
42
+     * @param {Object} state - The redux state.
43
+     */
44
+    constructor(state: Object) {
45
+        this._state = state;
46
+        this._currentCanvas = document.createElement('canvas');
47
+        this._currentCanvasContext = this._currentCanvas.getContext('2d');
48
+
49
+        // Bind handlers such that they access the same instance.
50
+        this._handleWorkerAction = this._handleWorkerAction.bind(this);
51
+        this._initScreenshotCapture = this._initScreenshotCapture.bind(this);
52
+        this._streamWorker = new Worker(timerWorkerScript, { name: 'Screenshot capture worker' });
53
+        this._streamWorker.onmessage = this._handleWorkerAction;
54
+    }
55
+
56
+    /**
57
+     * Starts the screenshot capture event on a loop.
58
+     *
59
+     * @param {Track} track - The track that contains the stream from which screenshots are to be sent.
60
+     * @returns {Promise} - Promise that resolves once effect has started or rejects if the
61
+     * videoType parameter is not desktop.
62
+     */
63
+    start(track: Object) {
64
+        const { videoType } = track;
65
+        const stream = track.getOriginalStream();
66
+
67
+        if (videoType !== 'desktop') {
68
+            return;
69
+        }
70
+        const desktopTrack = stream.getVideoTracks()[0];
71
+        const { height, width }
72
+            = desktopTrack.getSettings() ?? desktopTrack.getConstraints();
73
+
74
+        this._streamHeight = height;
75
+        this._streamWidth = width;
76
+        this._currentCanvas.height = parseInt(height, 10);
77
+        this._currentCanvas.width = parseInt(width, 10);
78
+        this._imageCapture = new ImageCapture(desktopTrack);
79
+
80
+        this._initScreenshotCapture();
81
+    }
82
+
83
+    /**
84
+     * Stops the ongoing {@code ScreenshotCaptureEffect} by clearing the {@code Worker} interval.
85
+     *
86
+     * @returns {void}
87
+     */
88
+    stop() {
89
+        this._streamWorker.postMessage({ id: CLEAR_INTERVAL });
90
+    }
91
+
92
+    /**
93
+     * Method that is called as soon as the first frame of the video loads from stream.
94
+     * The method is used to store the {@code ImageData} object from the first frames
95
+     * in order to use it for future comparisons based on which we can process only certain
96
+     * screenshots.
97
+     *
98
+     * @private
99
+     * @returns {void}
100
+     */
101
+    async _initScreenshotCapture() {
102
+        const imageBitmap = await this._imageCapture.grabFrame();
103
+
104
+        this._currentCanvasContext.drawImage(imageBitmap, 0, 0, this._streamWidth, this._streamHeight);
105
+        const imageData = this._currentCanvasContext.getImageData(0, 0, this._streamWidth, this._streamHeight);
106
+
107
+        this._storedImageData = imageData;
108
+        this._streamWorker.postMessage({
109
+            id: SET_INTERVAL,
110
+            timeMs: POLL_INTERVAL
111
+        });
112
+    }
113
+
114
+    /**
115
+     * Handler of the {@code EventHandler} message that calls the appropriate method based on the parameter's id.
116
+     *
117
+     * @private
118
+     * @param {EventHandler} message - Message received from the Worker.
119
+     * @returns {void}
120
+     */
121
+    _handleWorkerAction(message: Object) {
122
+        return message.data.id === INTERVAL_TIMEOUT && this._handleScreenshot();
123
+    }
124
+
125
+    /**
126
+     * Method that processes the screenshot.
127
+     *
128
+     * @private
129
+     * @param {ImageData} imageData - The image data of the new screenshot.
130
+     * @returns {void}
131
+     */
132
+    _doProcessScreenshot(imageData) {
133
+        sendAnalytics(createScreensharingCaptureTakenEvent());
134
+
135
+        const conference = getCurrentConference(this._state);
136
+        const sessionId = conference.getMeetingUniqueId();
137
+        const { connection, timeEstablished } = this._state['features/base/connection'];
138
+        const jid = connection.getJid();
139
+        const timeLapseSeconds = timeEstablished && Math.floor((Date.now() - timeEstablished) / 1000);
140
+        const { jwt } = this._state['features/base/jwt'];
141
+
142
+        this._storedImageData = imageData;
143
+
144
+        processScreenshot(this._currentCanvas, {
145
+            jid,
146
+            jwt,
147
+            sessionId,
148
+            timeLapseSeconds
149
+        });
150
+    }
151
+
152
+    /**
153
+     * Screenshot handler.
154
+     *
155
+     * @private
156
+     * @returns {void}
157
+     */
158
+    async _handleScreenshot() {
159
+        const imageBitmap = await this._imageCapture.grabFrame();
160
+
161
+        this._currentCanvasContext.drawImage(imageBitmap, 0, 0, this._streamWidth, this._streamHeight);
162
+        const imageData = this._currentCanvasContext.getImageData(0, 0, this._streamWidth, this._streamHeight);
163
+
164
+        resemble(imageData)
165
+            .compareTo(this._storedImageData)
166
+            .setReturnEarlyThreshold(PERCENTAGE_LOWER_BOUND)
167
+            .onComplete(resultData => {
168
+                if (resultData.rawMisMatchPercentage > PERCENTAGE_LOWER_BOUND) {
169
+                    this._doProcessScreenshot(imageData);
170
+                }
171
+            });
172
+    }
173
+}

+ 13
- 13
react/features/screenshot-capture/actions.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
 import { getLocalVideoTrack } from '../../features/base/tracks';
3
 import { getLocalVideoTrack } from '../../features/base/tracks';
4
-import { createScreenshotCaptureEffect } from '../stream-effects/screenshot-capture';
4
+
5
 
5
 
6
 import { SET_SCREENSHOT_CAPTURE } from './actionTypes';
6
 import { SET_SCREENSHOT_CAPTURE } from './actionTypes';
7
+import { createScreenshotCaptureSummary } from './functions';
7
 import logger from './logger';
8
 import logger from './logger';
8
 
9
 
9
-let ongoingEffect;
10
+let screenshotSummary;
10
 
11
 
11
 /**
12
 /**
12
  * Marks the on-off state of screenshot captures.
13
  * Marks the on-off state of screenshot captures.
30
 * @param {boolean} enabled - Bool that represents the intention to start/stop screenshot captures.
31
 * @param {boolean} enabled - Bool that represents the intention to start/stop screenshot captures.
31
 * @returns {Promise}
32
 * @returns {Promise}
32
 */
33
 */
33
-export function toggleScreenshotCaptureEffect(enabled: boolean) {
34
+export function toggleScreenshotCaptureSummary(enabled: boolean) {
34
     return async function(dispatch: (Object) => Object, getState: () => any) {
35
     return async function(dispatch: (Object) => Object, getState: () => any) {
35
         const state = getState();
36
         const state = getState();
36
 
37
 
37
         if (state['features/screenshot-capture'].capturesEnabled !== enabled) {
38
         if (state['features/screenshot-capture'].capturesEnabled !== enabled) {
38
             const { jitsiTrack } = getLocalVideoTrack(state['features/base/tracks']);
39
             const { jitsiTrack } = getLocalVideoTrack(state['features/base/tracks']);
39
 
40
 
40
-            if (!ongoingEffect) {
41
-                ongoingEffect = await createScreenshotCaptureEffect(state);
41
+            if (!screenshotSummary) {
42
+                try {
43
+                    screenshotSummary = await createScreenshotCaptureSummary(state);
44
+                } catch (err) {
45
+                    logger.error('Cannot create screenshotCaptureSummary', err);
46
+                }
42
             }
47
             }
43
 
48
 
44
-            // Screenshot capture effect doesn't return a modified stream. Therefore, we don't have to
45
-            // switch the stream at the conference level, starting/stopping the effect will suffice here.
46
             if (enabled) {
49
             if (enabled) {
47
                 try {
50
                 try {
48
-                    await ongoingEffect.startEffect(
49
-                        jitsiTrack.getOriginalStream(),
50
-                        jitsiTrack.videoType
51
-                    );
51
+                    await screenshotSummary.start(jitsiTrack);
52
                     dispatch(setScreenshotCapture(enabled));
52
                     dispatch(setScreenshotCapture(enabled));
53
                 } catch {
53
                 } catch {
54
 
54
 
55
-                    // Handle promise rejection from {@code startEffect} due to stream type not being desktop.
55
+                    // Handle promise rejection from {@code start} due to stream type not being desktop.
56
                     logger.error('Unsupported stream type.');
56
                     logger.error('Unsupported stream type.');
57
                 }
57
                 }
58
             } else {
58
             } else {
59
-                ongoingEffect.stopEffect();
59
+                screenshotSummary.stop();
60
                 dispatch(setScreenshotCapture(enabled));
60
                 dispatch(setScreenshotCapture(enabled));
61
             }
61
             }
62
         }
62
         }

react/features/stream-effects/screenshot-capture/constants.js → react/features/screenshot-capture/constants.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
 /**
3
 /**
4
- * Number of pixels that signal if two images should be considered different.
4
+ * Percent of pixels that signal if two images should be considered different.
5
  */
5
  */
6
-export const PIXEL_LOWER_BOUND = 100000;
6
+export const PERCENTAGE_LOWER_BOUND = 5;
7
 
7
 
8
 /**
8
 /**
9
  * Number of milliseconds that represent how often screenshots should be taken.
9
  * Number of milliseconds that represent how often screenshots should be taken.
10
  */
10
  */
11
-export const POLL_INTERVAL = 30000;
11
+export const POLL_INTERVAL = 2000;
12
 
12
 
13
 /**
13
 /**
14
  * SET_INTERVAL constant is used to set interval and it is set in
14
  * SET_INTERVAL constant is used to set interval and it is set in

+ 25
- 0
react/features/screenshot-capture/createImageBitmap.js View File

1
+/*
2
+* Safari polyfill for createImageBitmap
3
+* https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/createImageBitmap
4
+*
5
+* Support source image types: Canvas.
6
+*/
7
+if (!('createImageBitmap' in window)) {
8
+    window.createImageBitmap = async function(data) {
9
+        return new Promise((resolve, reject) => {
10
+            let dataURL;
11
+
12
+            if (data instanceof HTMLCanvasElement) {
13
+                dataURL = data.toDataURL();
14
+            } else {
15
+                reject(new Error('createImageBitmap does not handle the provided image source type'));
16
+            }
17
+            const img = document.createElement('img');
18
+
19
+            img.addEventListener('load', () => {
20
+                resolve(img);
21
+            });
22
+            img.src = dataURL;
23
+        });
24
+    };
25
+}

+ 20
- 0
react/features/screenshot-capture/functions.js View File

1
+// @flow
2
+
3
+import { toState } from '../base/redux';
4
+
5
+import ScreenshotCaptureSummary from './ScreenshotCaptureSummary';
6
+
7
+/**
8
+ * Creates a new instance of ScreenshotCapture.
9
+ *
10
+ * @param {Object | Function} stateful - The redux store, state, or
11
+ * {@code getState} function.
12
+ * @returns {Promise<ScreenshotCapture>}
13
+ */
14
+export function createScreenshotCaptureSummary(stateful: Object | Function) {
15
+    if (!MediaStreamTrack.prototype.getSettings && !MediaStreamTrack.prototype.getConstraints) {
16
+        return Promise.reject(new Error('ScreenshotCaptureSummary not supported!'));
17
+    }
18
+
19
+    return new ScreenshotCaptureSummary(toState(stateful));
20
+}

react/features/stream-effects/screenshot-capture/processScreenshot.js → react/features/screenshot-capture/processScreenshot.js View File


react/features/stream-effects/screenshot-capture/worker.js → react/features/screenshot-capture/worker.js View File


+ 0
- 168
react/features/stream-effects/screenshot-capture/ScreenshotCaptureEffect.js View File

1
-// @flow
2
-
3
-import pixelmatch from 'pixelmatch';
4
-
5
-import { getCurrentConference } from '../../base/conference';
6
-
7
-import {
8
-    CLEAR_INTERVAL,
9
-    INTERVAL_TIMEOUT,
10
-    PIXEL_LOWER_BOUND,
11
-    POLL_INTERVAL,
12
-    SET_INTERVAL
13
-} from './constants';
14
-import { processScreenshot } from './processScreenshot';
15
-import { timerWorkerScript } from './worker';
16
-
17
-declare var interfaceConfig: Object;
18
-
19
-/**
20
- * Effect that wraps {@code MediaStream} adding periodic screenshot captures.
21
- * Manipulates the original desktop stream and performs custom processing operations, if implemented.
22
- */
23
-export default class ScreenshotCaptureEffect {
24
-    _state: Object;
25
-    _currentCanvas: HTMLCanvasElement;
26
-    _currentCanvasContext: CanvasRenderingContext2D;
27
-    _videoElement: HTMLVideoElement;
28
-    _handleWorkerAction: Function;
29
-    _initScreenshotCapture: Function;
30
-    _streamWorker: Worker;
31
-    _streamHeight: any;
32
-    _streamWidth: any;
33
-    _storedImageData: Uint8ClampedArray;
34
-
35
-    /**
36
-     * Initializes a new {@code ScreenshotCaptureEffect} instance.
37
-     *
38
-     * @param {Object} state - The redux state.
39
-     */
40
-    constructor(state: Object) {
41
-        this._state = state;
42
-        this._currentCanvas = document.createElement('canvas');
43
-        this._currentCanvasContext = this._currentCanvas.getContext('2d');
44
-        this._videoElement = document.createElement('video');
45
-
46
-        // Bind handlers such that they access the same instance.
47
-        this._handleWorkerAction = this._handleWorkerAction.bind(this);
48
-        this._initScreenshotCapture = this._initScreenshotCapture.bind(this);
49
-        this._streamWorker = new Worker(timerWorkerScript, { name: 'Screenshot capture worker' });
50
-        this._streamWorker.onmessage = this._handleWorkerAction;
51
-    }
52
-
53
-    /**
54
-     * Starts the screenshot capture event on a loop.
55
-     *
56
-     * @param {MediaStream} stream - The desktop stream from which screenshots are to be sent.
57
-     * @param {string} videoType - The type of the media stream.
58
-     * @returns {Promise} - Promise that resolves once effect has started or rejects if the
59
-     * videoType parameter is not desktop.
60
-     */
61
-    startEffect(stream: MediaStream, videoType: string) {
62
-        return new Promise<void>((resolve, reject) => {
63
-            if (videoType !== 'desktop') {
64
-                reject();
65
-            }
66
-            const desktopTrack = stream.getVideoTracks()[0];
67
-            const { height, width }
68
-                = desktopTrack.getSettings() ?? desktopTrack.getConstraints();
69
-
70
-            this._streamHeight = height;
71
-            this._streamWidth = width;
72
-            this._currentCanvas.height = parseInt(height, 10);
73
-            this._currentCanvas.width = parseInt(width, 10);
74
-            this._videoElement.height = parseInt(height, 10);
75
-            this._videoElement.width = parseInt(width, 10);
76
-            this._videoElement.srcObject = stream;
77
-            this._videoElement.play();
78
-
79
-            // Store first capture for comparisons in {@code this._handleScreenshot}.
80
-            this._videoElement.addEventListener('loadeddata', this._initScreenshotCapture);
81
-            resolve();
82
-        });
83
-    }
84
-
85
-    /**
86
-     * Stops the ongoing {@code ScreenshotCaptureEffect} by clearing the {@code Worker} interval.
87
-     *
88
-     * @returns {void}
89
-     */
90
-    stopEffect() {
91
-        this._streamWorker.postMessage({ id: CLEAR_INTERVAL });
92
-        this._videoElement.removeEventListener('loadeddata', this._initScreenshotCapture);
93
-    }
94
-
95
-    /**
96
-     * Method that is called as soon as the first frame of the video loads from stream.
97
-     * The method is used to store the {@code ImageData} object from the first frames
98
-     * in order to use it for future comparisons based on which we can process only certain
99
-     * screenshots.
100
-     *
101
-     * @private
102
-     * @returns {void}
103
-     */
104
-    _initScreenshotCapture() {
105
-        const storedCanvas = document.createElement('canvas');
106
-        const storedCanvasContext = storedCanvas.getContext('2d');
107
-
108
-        storedCanvasContext.drawImage(this._videoElement, 0, 0, this._streamWidth, this._streamHeight);
109
-        const { data } = storedCanvasContext.getImageData(0, 0, this._streamWidth, this._streamHeight);
110
-
111
-        this._storedImageData = data;
112
-        this._streamWorker.postMessage({
113
-            id: SET_INTERVAL,
114
-            timeMs: POLL_INTERVAL
115
-        });
116
-    }
117
-
118
-    /**
119
-     * Handler of the {@code EventHandler} message that calls the appropriate method based on the parameter's id.
120
-     *
121
-     * @private
122
-     * @param {EventHandler} message - Message received from the Worker.
123
-     * @returns {void}
124
-     */
125
-    _handleWorkerAction(message: Object) {
126
-        return message.data.id === INTERVAL_TIMEOUT && this._handleScreenshot();
127
-    }
128
-
129
-    /**
130
-     * Method that decides whether an image should be processed based on a preset pixel lower bound.
131
-     *
132
-     * @private
133
-     * @param {integer} nbPixels - The number of pixels of the candidate image.
134
-     * @returns {boolean} - Whether the image should be processed or not.
135
-     */
136
-    _shouldProcessScreenshot(nbPixels: number) {
137
-        return nbPixels >= PIXEL_LOWER_BOUND;
138
-    }
139
-
140
-    /**
141
-     * Screenshot handler.
142
-     *
143
-     * @private
144
-     * @returns {void}
145
-     */
146
-    _handleScreenshot() {
147
-        this._currentCanvasContext.drawImage(this._videoElement, 0, 0, this._streamWidth, this._streamHeight);
148
-        const { data } = this._currentCanvasContext.getImageData(0, 0, this._streamWidth, this._streamHeight);
149
-        const diffPixels = pixelmatch(data, this._storedImageData, null, this._streamWidth, this._streamHeight);
150
-
151
-        if (this._shouldProcessScreenshot(diffPixels)) {
152
-            const conference = getCurrentConference(this._state);
153
-            const sessionId = conference.getMeetingUniqueId();
154
-            const { connection, timeEstablished } = this._state['features/base/connection'];
155
-            const jid = connection.getJid();
156
-            const timeLapseSeconds = timeEstablished && Math.floor((Date.now() - timeEstablished) / 1000);
157
-            const { jwt } = this._state['features/base/jwt'];
158
-
159
-            this._storedImageData = data;
160
-            processScreenshot(this._currentCanvas, {
161
-                jid,
162
-                jwt,
163
-                sessionId,
164
-                timeLapseSeconds
165
-            });
166
-        }
167
-    }
168
-}

+ 0
- 20
react/features/stream-effects/screenshot-capture/index.js View File

1
-// @flow
2
-
3
-import { toState } from '../../base/redux';
4
-
5
-import ScreenshotCaptureEffect from './ScreenshotCaptureEffect';
6
-
7
-/**
8
- * Creates a new instance of ScreenshotCaptureEffect.
9
- *
10
- * @param {Object | Function} stateful - The redux store, state, or
11
- * {@code getState} function.
12
- * @returns {Promise<ScreenshotCaptureEffect>}
13
- */
14
-export function createScreenshotCaptureEffect(stateful: Object | Function) {
15
-    if (!MediaStreamTrack.prototype.getSettings && !MediaStreamTrack.prototype.getConstraints) {
16
-        return Promise.reject(new Error('ScreenshotCaptureEffect not supported!'));
17
-    }
18
-
19
-    return Promise.resolve(new ScreenshotCaptureEffect(toState(stateful)));
20
-}

Loading…
Cancel
Save