瀏覽代碼

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 年之前
父節點
當前提交
001ae54a7c

+ 4
- 3
conference.js 查看文件

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

+ 389
- 19
package-lock.json 查看文件

@@ -2774,6 +2774,152 @@
2774 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 2923
     "@material-ui/core": {
2778 2924
       "version": "4.11.3",
2779 2925
       "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.3.tgz",
@@ -4400,6 +4546,12 @@
4400 4546
       "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
4401 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 4555
     "abort-controller": {
4404 4556
       "version": "3.0.0",
4405 4557
       "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -4447,6 +4599,32 @@
4447 4599
       "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==",
4448 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 4628
     "ajv": {
4451 4629
       "version": "5.5.2",
4452 4630
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
@@ -4867,8 +5045,17 @@
4867 5045
     "aproba": {
4868 5046
       "version": "1.2.0",
4869 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 5060
     "argparse": {
4874 5061
       "version": "1.0.10",
@@ -6135,6 +6322,17 @@
6135 6322
       "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz",
6136 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 6336
     "capture-exit": {
6139 6337
       "version": "2.0.0",
6140 6338
       "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz",
@@ -6670,6 +6868,12 @@
6670 6868
       "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==",
6671 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 6877
     "constants-browserify": {
6674 6878
       "version": "1.0.0",
6675 6879
       "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
@@ -7202,6 +7406,15 @@
7202 7406
       "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
7203 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 7418
     "deep-assign": {
7206 7419
       "version": "3.0.0",
7207 7420
       "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-3.0.0.tgz",
@@ -7327,6 +7540,12 @@
7327 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 7549
     "denodeify": {
7331 7550
       "version": "1.2.1",
7332 7551
       "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz",
@@ -7358,6 +7577,12 @@
7358 7577
       "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
7359 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 7586
     "detect-node": {
7362 7587
       "version": "2.0.4",
7363 7588
       "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
@@ -9451,6 +9676,44 @@
9451 9676
       "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
9452 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 9717
     "gensync": {
9455 9718
       "version": "1.0.0-beta.2",
9456 9719
       "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -9644,6 +9907,12 @@
9644 9907
       "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
9645 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 9916
     "has-value": {
9648 9917
       "version": "1.0.0",
9649 9918
       "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
@@ -9889,6 +10158,33 @@
9889 10158
       "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
9890 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 10188
     "hyphenate-style-name": {
9893 10189
       "version": "1.0.4",
9894 10190
       "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
@@ -9984,6 +10280,11 @@
9984 10280
       "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
9985 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 10288
     "image-size": {
9988 10289
       "version": "0.6.3",
9989 10290
       "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz",
@@ -10555,8 +10856,7 @@
10555 10856
     "isarray": {
10556 10857
       "version": "1.0.0",
10557 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 10861
     "isexe": {
10562 10862
       "version": "2.0.0",
@@ -12240,6 +12540,12 @@
12240 12540
       "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
12241 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 12549
     "minimalistic-assert": {
12244 12550
       "version": "1.0.1",
12245 12551
       "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -12606,6 +12912,15 @@
12606 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 12924
     "normalize-package-data": {
12610 12925
       "version": "2.4.0",
12611 12926
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
@@ -12643,6 +12958,18 @@
12643 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 12973
     "nth-check": {
12647 12974
       "version": "1.0.2",
12648 12975
       "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
@@ -13486,14 +13813,6 @@
13486 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 13816
     "pkg-dir": {
13498 13817
       "version": "2.0.0",
13499 13818
       "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
@@ -13562,11 +13881,6 @@
13562 13881
       "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==",
13563 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 13884
     "popper.js": {
13571 13885
       "version": "1.16.1",
13572 13886
       "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
@@ -15437,7 +15751,6 @@
15437 15751
       "version": "2.3.7",
15438 15752
       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
15439 15753
       "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
15440
-      "dev": true,
15441 15754
       "requires": {
15442 15755
         "core-util-is": "~1.0.0",
15443 15756
         "inherits": "~2.0.3",
@@ -15723,6 +16036,14 @@
15723 16036
       "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
15724 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 16047
     "resolve": {
15727 16048
       "version": "1.8.1",
15728 16049
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
@@ -16197,6 +16518,23 @@
16197 16518
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
16198 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 16538
     "simple-plist": {
16201 16539
       "version": "1.1.1",
16202 16540
       "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.1.1.tgz",
@@ -17133,7 +17471,6 @@
17133 17471
       "version": "1.1.1",
17134 17472
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
17135 17473
       "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
17136
-      "dev": true,
17137 17474
       "requires": {
17138 17475
         "safe-buffer": "~5.1.0"
17139 17476
       }
@@ -17777,6 +18114,12 @@
17777 18114
       "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
17778 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 18123
     "traverse": {
17781 18124
       "version": "0.6.6",
17782 18125
       "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz",
@@ -19661,6 +20004,24 @@
19661 20004
       "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
19662 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 20025
     "whatwg-url-without-unicode": {
19665 20026
       "version": "8.0.0-3",
19666 20027
       "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz",
@@ -19722,6 +20083,15 @@
19722 20083
       "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
19723 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 20095
     "windows-iana": {
19726 20096
       "version": "3.1.0",
19727 20097
       "resolved": "https://registry.npmjs.org/windows-iana/-/windows-iana-3.1.0.tgz",

+ 2
- 1
package.json 查看文件

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

+ 12
- 0
react/features/analytics/AnalyticsEvents.js 查看文件

@@ -867,3 +867,15 @@ export function createWelcomePageEvent(action, actionSubject, attributes = {}) {
867 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 查看文件

@@ -1,6 +1,5 @@
1 1
 // @flow
2 2
 
3
-import { createScreenshotCaptureEffect } from '../../stream-effects/screenshot-capture';
4 3
 import { createVirtualBackgroundEffect } from '../../stream-effects/virtual-background';
5 4
 
6 5
 import logger from './logger';
@@ -20,17 +19,9 @@ export default function loadEffects(store: Object): Promise<any> {
20 19
             .catch(error => {
21 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 22
                 return Promise.resolve();
32 23
             })
33 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 查看文件

@@ -0,0 +1,173 @@
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 查看文件

@@ -1,12 +1,13 @@
1 1
 // @flow
2 2
 
3 3
 import { getLocalVideoTrack } from '../../features/base/tracks';
4
-import { createScreenshotCaptureEffect } from '../stream-effects/screenshot-capture';
4
+
5 5
 
6 6
 import { SET_SCREENSHOT_CAPTURE } from './actionTypes';
7
+import { createScreenshotCaptureSummary } from './functions';
7 8
 import logger from './logger';
8 9
 
9
-let ongoingEffect;
10
+let screenshotSummary;
10 11
 
11 12
 /**
12 13
  * Marks the on-off state of screenshot captures.
@@ -30,33 +31,32 @@ function setScreenshotCapture(enabled) {
30 31
 * @param {boolean} enabled - Bool that represents the intention to start/stop screenshot captures.
31 32
 * @returns {Promise}
32 33
 */
33
-export function toggleScreenshotCaptureEffect(enabled: boolean) {
34
+export function toggleScreenshotCaptureSummary(enabled: boolean) {
34 35
     return async function(dispatch: (Object) => Object, getState: () => any) {
35 36
         const state = getState();
36 37
 
37 38
         if (state['features/screenshot-capture'].capturesEnabled !== enabled) {
38 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 49
             if (enabled) {
47 50
                 try {
48
-                    await ongoingEffect.startEffect(
49
-                        jitsiTrack.getOriginalStream(),
50
-                        jitsiTrack.videoType
51
-                    );
51
+                    await screenshotSummary.start(jitsiTrack);
52 52
                     dispatch(setScreenshotCapture(enabled));
53 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 56
                     logger.error('Unsupported stream type.');
57 57
                 }
58 58
             } else {
59
-                ongoingEffect.stopEffect();
59
+                screenshotSummary.stop();
60 60
                 dispatch(setScreenshotCapture(enabled));
61 61
             }
62 62
         }

react/features/stream-effects/screenshot-capture/constants.js → react/features/screenshot-capture/constants.js 查看文件

@@ -1,14 +1,14 @@
1 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 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 14
  * SET_INTERVAL constant is used to set interval and it is set in

+ 25
- 0
react/features/screenshot-capture/createImageBitmap.js 查看文件

@@ -0,0 +1,25 @@
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 查看文件

@@ -0,0 +1,20 @@
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 查看文件


react/features/stream-effects/screenshot-capture/worker.js → react/features/screenshot-capture/worker.js 查看文件


+ 0
- 168
react/features/stream-effects/screenshot-capture/ScreenshotCaptureEffect.js 查看文件

@@ -1,168 +0,0 @@
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 查看文件

@@ -1,20 +0,0 @@
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…
取消
儲存