diff --git a/conference.js b/conference.js index 951dfe556..e49998866 100644 --- a/conference.js +++ b/conference.js @@ -134,7 +134,7 @@ import { } from './react/features/prejoin'; import { disableReceiver, stopReceiver } from './react/features/remote-control'; import { setScreenAudioShareState, isScreenAudioShared } from './react/features/screen-share/'; -import { toggleScreenshotCaptureEffect } from './react/features/screenshot-capture'; +import { toggleScreenshotCaptureSummary } from './react/features/screenshot-capture'; import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/AudioMixerEffect'; import { createPresenterEffect } from './react/features/stream-effects/presenter'; import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise'; @@ -1545,8 +1545,9 @@ export default { APP.store.dispatch(stopReceiver()); this._stopProxyConnection(); + if (config.enableScreenshotCapture) { - APP.store.dispatch(toggleScreenshotCaptureEffect(false)); + APP.store.dispatch(toggleScreenshotCaptureSummary(false)); } // 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 { .then(() => { this.videoSwitchInProgress = false; if (config.enableScreenshotCapture) { - APP.store.dispatch(toggleScreenshotCaptureEffect(true)); + APP.store.dispatch(toggleScreenshotCaptureSummary(true)); } sendAnalytics(createScreenSharingEvent('started')); logger.log('Screen sharing started'); diff --git a/package-lock.json b/package-lock.json index b28d9389d..48d986f0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2774,6 +2774,152 @@ "sdp-transform": "2.3.0" } }, + "@mapbox/node-pre-gyp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", + "integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==", + "optional": true, + "requires": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.1", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "rimraf": "^3.0.2", + "semver": "^7.3.4", + "tar": "^6.1.0" + }, + "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "optional": true + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "optional": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "optional": true + } + } + }, + "minipass": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", + "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "optional": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true + }, + "node-fetch": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", + "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", + "optional": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "optional": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "optional": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + } + } + }, "@material-ui/core": { "version": "4.11.3", "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.3.tgz", @@ -4400,6 +4546,12 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "dev": true }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -4447,6 +4599,32 @@ "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", "dev": true }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, "ajv": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", @@ -4867,8 +5045,17 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } }, "argparse": { "version": "1.0.10", @@ -6135,6 +6322,17 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz", "integrity": "sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==" }, + "canvas": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.8.0.tgz", + "integrity": "sha512-gLTi17X8WY9Cf5GZ2Yns8T5lfBOcGgFehDFb+JQwDqdOoBOcECS9ZWMEAqMSVcMYwXD659J8NyzjRY/2aE+C2Q==", + "optional": true, + "requires": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.14.0", + "simple-get": "^3.0.3" + } + }, "capture-exit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", @@ -6670,6 +6868,12 @@ "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", "dev": true }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "optional": true + }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -7202,6 +7406,15 @@ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "optional": true, + "requires": { + "mimic-response": "^2.0.0" + } + }, "deep-assign": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-3.0.0.tgz", @@ -7327,6 +7540,12 @@ "rimraf": "^2.2.8" } }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "optional": true + }, "denodeify": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", @@ -7358,6 +7577,12 @@ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", "dev": true }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "optional": true + }, "detect-node": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", @@ -9451,6 +9676,44 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -9644,6 +9907,12 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "optional": true + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -9889,6 +10158,33 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "optional": true, + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, "hyphenate-style-name": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", @@ -9984,6 +10280,11 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "image-capture": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/image-capture/-/image-capture-0.4.0.tgz", + "integrity": "sha512-6RWTfqC4ij0AldG+6sQ51XSHTSbwfqMSjVl1GtwNBzbW4UrcfGZeB1Kn749BccvtLb04g5+jSTf1D7q3qHcxpA==" + }, "image-size": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", @@ -10555,8 +10856,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -12240,6 +12540,12 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "optional": true + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -12606,6 +12912,15 @@ "semver": "^5.3.0" } }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "requires": { + "abbrev": "1" + } + }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -12643,6 +12958,18 @@ "path-key": "^2.0.0" } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", @@ -13486,14 +13813,6 @@ "node-modules-regexp": "^1.0.0" } }, - "pixelmatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.1.0.tgz", - "integrity": "sha512-HqtgvuWN12tBzKJf7jYsc38Ha28Q2NYpmBL9WostEGgDHJqbTLkjydZXL1ZHM02ZnB+Dkwlxo87HBY38kMiD6A==", - "requires": { - "pngjs": "^3.4.0" - } - }, "pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", @@ -13562,11 +13881,6 @@ "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, - "pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" - }, "popper.js": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", @@ -15437,7 +15751,6 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -15723,6 +16036,14 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "resemblejs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resemblejs/-/resemblejs-4.0.0.tgz", + "integrity": "sha512-vaGs/hFVx/941+RS4UJtd8DQvx5RuB61tPLOQCxPso3JpmjfDb6odH5HViT17S0d8DaZsexD01nRJI12giCz/A==", + "requires": { + "canvas": "2.8.0" + } + }, "resolve": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", @@ -16197,6 +16518,23 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "optional": true + }, + "simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "optional": true, + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "simple-plist": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.1.1.tgz", @@ -17133,7 +17471,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -17777,6 +18114,12 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", "dev": true }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "optional": true + }, "traverse": { "version": "0.6.6", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", @@ -19661,6 +20004,24 @@ "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "optional": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "optional": true + } + } + }, "whatwg-url-without-unicode": { "version": "8.0.0-3", "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz", @@ -19722,6 +20083,15 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, "windows-iana": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/windows-iana/-/windows-iana-3.1.0.tgz", diff --git a/package.json b/package.json index 126be181d..014f362ca 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "i18next": "17.0.6", "i18next-browser-languagedetector": "3.0.1", "i18next-xhr-backend": "3.0.0", + "image-capture": "0.4.0", "jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#v1.0.0", "jquery": "3.5.1", "jquery-i18next": "1.2.1", @@ -65,7 +66,6 @@ "moment": "2.29.1", "moment-duration-format": "2.2.2", "optional-require": "1.0.3", - "pixelmatch": "5.1.0", "promise.allsettled": "1.0.4", "punycode": "2.1.1", "react": "16.12", @@ -102,6 +102,7 @@ "react-youtube": "7.13.1", "redux": "4.0.4", "redux-thunk": "2.2.0", + "resemblejs": "4.0.0", "rnnoise-wasm": "github:jitsi/rnnoise-wasm#566a16885897704d6e6d67a1d5ac5d39781db2af", "rtcstats": "github:jitsi/rtcstats#v8.1.0", "styled-components": "3.4.9", diff --git a/react/features/analytics/AnalyticsEvents.js b/react/features/analytics/AnalyticsEvents.js index 825dd9aca..cf566b7a5 100644 --- a/react/features/analytics/AnalyticsEvents.js +++ b/react/features/analytics/AnalyticsEvents.js @@ -867,3 +867,15 @@ export function createWelcomePageEvent(action, actionSubject, attributes = {}) { source: 'welcomePage' }; } + +/** + * Creates an event which indicates a screenshot of the screensharing has been taken. + * + * @returns {Object} The event in a format suitable for sending via + * sendAnalytics. + */ +export function createScreensharingCaptureTakenEvent() { + return { + action: 'screen.sharing.capture' + }; +} diff --git a/react/features/base/tracks/loadEffects.web.js b/react/features/base/tracks/loadEffects.web.js index ac445eb92..2ba643c21 100644 --- a/react/features/base/tracks/loadEffects.web.js +++ b/react/features/base/tracks/loadEffects.web.js @@ -1,6 +1,5 @@ // @flow -import { createScreenshotCaptureEffect } from '../../stream-effects/screenshot-capture'; import { createVirtualBackgroundEffect } from '../../stream-effects/virtual-background'; import logger from './logger'; @@ -23,14 +22,6 @@ export default function loadEffects(store: Object): Promise { return Promise.resolve(); }) : Promise.resolve(); - const screenshotCapturePromise = state['features/screenshot-capture']?.capturesEnabled - ? createScreenshotCaptureEffect(state) - .catch(error => { - logger.error('Failed to obtain the screenshot capture effect effect instance with error: ', error); - return Promise.resolve(); - }) - : Promise.resolve(); - - return Promise.all([ backgroundPromise, screenshotCapturePromise ]); + return Promise.all([ backgroundPromise ]); } diff --git a/react/features/screenshot-capture/ScreenshotCaptureSummary.js b/react/features/screenshot-capture/ScreenshotCaptureSummary.js new file mode 100644 index 000000000..874c5e818 --- /dev/null +++ b/react/features/screenshot-capture/ScreenshotCaptureSummary.js @@ -0,0 +1,173 @@ +// @flow + +import resemble from 'resemblejs'; +import 'image-capture'; +import './createImageBitmap'; + +import { createScreensharingCaptureTakenEvent, sendAnalytics } from '../analytics'; +import { getCurrentConference } from '../base/conference'; + +import { + CLEAR_INTERVAL, + INTERVAL_TIMEOUT, + PERCENTAGE_LOWER_BOUND, + POLL_INTERVAL, + SET_INTERVAL +} from './constants'; +import { processScreenshot } from './processScreenshot'; +import { timerWorkerScript } from './worker'; + +declare var interfaceConfig: Object; +declare var ImageCapture: any; + +/** + * Effect that wraps {@code MediaStream} adding periodic screenshot captures. + * Manipulates the original desktop stream and performs custom processing operations, if implemented. + */ +export default class ScreenshotCaptureSummary { + _state: Object; + _currentCanvas: HTMLCanvasElement; + _currentCanvasContext: CanvasRenderingContext2D; + _handleWorkerAction: Function; + _initScreenshotCapture: Function; + _imageCapture: any; + _streamWorker: Worker; + _streamHeight: any; + _streamWidth: any; + _storedImageData: ImageData; + + /** + * Initializes a new {@code ScreenshotCaptureEffect} instance. + * + * @param {Object} state - The redux state. + */ + constructor(state: Object) { + this._state = state; + this._currentCanvas = document.createElement('canvas'); + this._currentCanvasContext = this._currentCanvas.getContext('2d'); + + // Bind handlers such that they access the same instance. + this._handleWorkerAction = this._handleWorkerAction.bind(this); + this._initScreenshotCapture = this._initScreenshotCapture.bind(this); + this._streamWorker = new Worker(timerWorkerScript, { name: 'Screenshot capture worker' }); + this._streamWorker.onmessage = this._handleWorkerAction; + } + + /** + * Starts the screenshot capture event on a loop. + * + * @param {Track} track - The track that contains the stream from which screenshots are to be sent. + * @returns {Promise} - Promise that resolves once effect has started or rejects if the + * videoType parameter is not desktop. + */ + start(track: Object) { + const { videoType } = track; + const stream = track.getOriginalStream(); + + if (videoType !== 'desktop') { + return; + } + const desktopTrack = stream.getVideoTracks()[0]; + const { height, width } + = desktopTrack.getSettings() ?? desktopTrack.getConstraints(); + + this._streamHeight = height; + this._streamWidth = width; + this._currentCanvas.height = parseInt(height, 10); + this._currentCanvas.width = parseInt(width, 10); + this._imageCapture = new ImageCapture(desktopTrack); + + this._initScreenshotCapture(); + } + + /** + * Stops the ongoing {@code ScreenshotCaptureEffect} by clearing the {@code Worker} interval. + * + * @returns {void} + */ + stop() { + this._streamWorker.postMessage({ id: CLEAR_INTERVAL }); + } + + /** + * Method that is called as soon as the first frame of the video loads from stream. + * The method is used to store the {@code ImageData} object from the first frames + * in order to use it for future comparisons based on which we can process only certain + * screenshots. + * + * @private + * @returns {void} + */ + async _initScreenshotCapture() { + const imageBitmap = await this._imageCapture.grabFrame(); + + this._currentCanvasContext.drawImage(imageBitmap, 0, 0, this._streamWidth, this._streamHeight); + const imageData = this._currentCanvasContext.getImageData(0, 0, this._streamWidth, this._streamHeight); + + this._storedImageData = imageData; + this._streamWorker.postMessage({ + id: SET_INTERVAL, + timeMs: POLL_INTERVAL + }); + } + + /** + * Handler of the {@code EventHandler} message that calls the appropriate method based on the parameter's id. + * + * @private + * @param {EventHandler} message - Message received from the Worker. + * @returns {void} + */ + _handleWorkerAction(message: Object) { + return message.data.id === INTERVAL_TIMEOUT && this._handleScreenshot(); + } + + /** + * Method that processes the screenshot. + * + * @private + * @param {ImageData} imageData - The image data of the new screenshot. + * @returns {void} + */ + _doProcessScreenshot(imageData) { + sendAnalytics(createScreensharingCaptureTakenEvent()); + + const conference = getCurrentConference(this._state); + const sessionId = conference.getMeetingUniqueId(); + const { connection, timeEstablished } = this._state['features/base/connection']; + const jid = connection.getJid(); + const timeLapseSeconds = timeEstablished && Math.floor((Date.now() - timeEstablished) / 1000); + const { jwt } = this._state['features/base/jwt']; + + this._storedImageData = imageData; + + processScreenshot(this._currentCanvas, { + jid, + jwt, + sessionId, + timeLapseSeconds + }); + } + + /** + * Screenshot handler. + * + * @private + * @returns {void} + */ + async _handleScreenshot() { + const imageBitmap = await this._imageCapture.grabFrame(); + + this._currentCanvasContext.drawImage(imageBitmap, 0, 0, this._streamWidth, this._streamHeight); + const imageData = this._currentCanvasContext.getImageData(0, 0, this._streamWidth, this._streamHeight); + + resemble(imageData) + .compareTo(this._storedImageData) + .setReturnEarlyThreshold(PERCENTAGE_LOWER_BOUND) + .onComplete(resultData => { + if (resultData.rawMisMatchPercentage > PERCENTAGE_LOWER_BOUND) { + this._doProcessScreenshot(imageData); + } + }); + } +} diff --git a/react/features/screenshot-capture/actions.js b/react/features/screenshot-capture/actions.js index dfd557e5e..a77caf63d 100644 --- a/react/features/screenshot-capture/actions.js +++ b/react/features/screenshot-capture/actions.js @@ -1,12 +1,13 @@ // @flow import { getLocalVideoTrack } from '../../features/base/tracks'; -import { createScreenshotCaptureEffect } from '../stream-effects/screenshot-capture'; + import { SET_SCREENSHOT_CAPTURE } from './actionTypes'; +import { createScreenshotCaptureSummary } from './functions'; import logger from './logger'; -let ongoingEffect; +let screenshotSummary; /** * Marks the on-off state of screenshot captures. @@ -30,33 +31,32 @@ function setScreenshotCapture(enabled) { * @param {boolean} enabled - Bool that represents the intention to start/stop screenshot captures. * @returns {Promise} */ -export function toggleScreenshotCaptureEffect(enabled: boolean) { +export function toggleScreenshotCaptureSummary(enabled: boolean) { return async function(dispatch: (Object) => Object, getState: () => any) { const state = getState(); if (state['features/screenshot-capture'].capturesEnabled !== enabled) { const { jitsiTrack } = getLocalVideoTrack(state['features/base/tracks']); - if (!ongoingEffect) { - ongoingEffect = await createScreenshotCaptureEffect(state); + if (!screenshotSummary) { + try { + screenshotSummary = await createScreenshotCaptureSummary(state); + } catch (err) { + logger.error('Cannot create screenshotCaptureSummary', err); + } } - // Screenshot capture effect doesn't return a modified stream. Therefore, we don't have to - // switch the stream at the conference level, starting/stopping the effect will suffice here. if (enabled) { try { - await ongoingEffect.startEffect( - jitsiTrack.getOriginalStream(), - jitsiTrack.videoType - ); + await screenshotSummary.start(jitsiTrack); dispatch(setScreenshotCapture(enabled)); } catch { - // Handle promise rejection from {@code startEffect} due to stream type not being desktop. + // Handle promise rejection from {@code start} due to stream type not being desktop. logger.error('Unsupported stream type.'); } } else { - ongoingEffect.stopEffect(); + screenshotSummary.stop(); dispatch(setScreenshotCapture(enabled)); } } diff --git a/react/features/stream-effects/screenshot-capture/constants.js b/react/features/screenshot-capture/constants.js similarity index 82% rename from react/features/stream-effects/screenshot-capture/constants.js rename to react/features/screenshot-capture/constants.js index 67299c7e4..9db81c077 100644 --- a/react/features/stream-effects/screenshot-capture/constants.js +++ b/react/features/screenshot-capture/constants.js @@ -1,14 +1,14 @@ // @flow /** - * Number of pixels that signal if two images should be considered different. + * Percent of pixels that signal if two images should be considered different. */ -export const PIXEL_LOWER_BOUND = 100000; +export const PERCENTAGE_LOWER_BOUND = 5; /** * Number of milliseconds that represent how often screenshots should be taken. */ -export const POLL_INTERVAL = 30000; +export const POLL_INTERVAL = 2000; /** * SET_INTERVAL constant is used to set interval and it is set in diff --git a/react/features/screenshot-capture/createImageBitmap.js b/react/features/screenshot-capture/createImageBitmap.js new file mode 100644 index 000000000..89f159ba0 --- /dev/null +++ b/react/features/screenshot-capture/createImageBitmap.js @@ -0,0 +1,25 @@ +/* +* Safari polyfill for createImageBitmap +* https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/createImageBitmap +* +* Support source image types: Canvas. +*/ +if (!('createImageBitmap' in window)) { + window.createImageBitmap = async function(data) { + return new Promise((resolve, reject) => { + let dataURL; + + if (data instanceof HTMLCanvasElement) { + dataURL = data.toDataURL(); + } else { + reject(new Error('createImageBitmap does not handle the provided image source type')); + } + const img = document.createElement('img'); + + img.addEventListener('load', () => { + resolve(img); + }); + img.src = dataURL; + }); + }; +} diff --git a/react/features/screenshot-capture/functions.js b/react/features/screenshot-capture/functions.js new file mode 100644 index 000000000..46a8c7556 --- /dev/null +++ b/react/features/screenshot-capture/functions.js @@ -0,0 +1,20 @@ +// @flow + +import { toState } from '../base/redux'; + +import ScreenshotCaptureSummary from './ScreenshotCaptureSummary'; + +/** + * Creates a new instance of ScreenshotCapture. + * + * @param {Object | Function} stateful - The redux store, state, or + * {@code getState} function. + * @returns {Promise} + */ +export function createScreenshotCaptureSummary(stateful: Object | Function) { + if (!MediaStreamTrack.prototype.getSettings && !MediaStreamTrack.prototype.getConstraints) { + return Promise.reject(new Error('ScreenshotCaptureSummary not supported!')); + } + + return new ScreenshotCaptureSummary(toState(stateful)); +} diff --git a/react/features/stream-effects/screenshot-capture/processScreenshot.js b/react/features/screenshot-capture/processScreenshot.js similarity index 100% rename from react/features/stream-effects/screenshot-capture/processScreenshot.js rename to react/features/screenshot-capture/processScreenshot.js diff --git a/react/features/stream-effects/screenshot-capture/worker.js b/react/features/screenshot-capture/worker.js similarity index 100% rename from react/features/stream-effects/screenshot-capture/worker.js rename to react/features/screenshot-capture/worker.js diff --git a/react/features/stream-effects/screenshot-capture/ScreenshotCaptureEffect.js b/react/features/stream-effects/screenshot-capture/ScreenshotCaptureEffect.js deleted file mode 100644 index dbf84201e..000000000 --- a/react/features/stream-effects/screenshot-capture/ScreenshotCaptureEffect.js +++ /dev/null @@ -1,168 +0,0 @@ -// @flow - -import pixelmatch from 'pixelmatch'; - -import { getCurrentConference } from '../../base/conference'; - -import { - CLEAR_INTERVAL, - INTERVAL_TIMEOUT, - PIXEL_LOWER_BOUND, - POLL_INTERVAL, - SET_INTERVAL -} from './constants'; -import { processScreenshot } from './processScreenshot'; -import { timerWorkerScript } from './worker'; - -declare var interfaceConfig: Object; - -/** - * Effect that wraps {@code MediaStream} adding periodic screenshot captures. - * Manipulates the original desktop stream and performs custom processing operations, if implemented. - */ -export default class ScreenshotCaptureEffect { - _state: Object; - _currentCanvas: HTMLCanvasElement; - _currentCanvasContext: CanvasRenderingContext2D; - _videoElement: HTMLVideoElement; - _handleWorkerAction: Function; - _initScreenshotCapture: Function; - _streamWorker: Worker; - _streamHeight: any; - _streamWidth: any; - _storedImageData: Uint8ClampedArray; - - /** - * Initializes a new {@code ScreenshotCaptureEffect} instance. - * - * @param {Object} state - The redux state. - */ - constructor(state: Object) { - this._state = state; - this._currentCanvas = document.createElement('canvas'); - this._currentCanvasContext = this._currentCanvas.getContext('2d'); - this._videoElement = document.createElement('video'); - - // Bind handlers such that they access the same instance. - this._handleWorkerAction = this._handleWorkerAction.bind(this); - this._initScreenshotCapture = this._initScreenshotCapture.bind(this); - this._streamWorker = new Worker(timerWorkerScript, { name: 'Screenshot capture worker' }); - this._streamWorker.onmessage = this._handleWorkerAction; - } - - /** - * Starts the screenshot capture event on a loop. - * - * @param {MediaStream} stream - The desktop stream from which screenshots are to be sent. - * @param {string} videoType - The type of the media stream. - * @returns {Promise} - Promise that resolves once effect has started or rejects if the - * videoType parameter is not desktop. - */ - startEffect(stream: MediaStream, videoType: string) { - return new Promise((resolve, reject) => { - if (videoType !== 'desktop') { - reject(); - } - const desktopTrack = stream.getVideoTracks()[0]; - const { height, width } - = desktopTrack.getSettings() ?? desktopTrack.getConstraints(); - - this._streamHeight = height; - this._streamWidth = width; - this._currentCanvas.height = parseInt(height, 10); - this._currentCanvas.width = parseInt(width, 10); - this._videoElement.height = parseInt(height, 10); - this._videoElement.width = parseInt(width, 10); - this._videoElement.srcObject = stream; - this._videoElement.play(); - - // Store first capture for comparisons in {@code this._handleScreenshot}. - this._videoElement.addEventListener('loadeddata', this._initScreenshotCapture); - resolve(); - }); - } - - /** - * Stops the ongoing {@code ScreenshotCaptureEffect} by clearing the {@code Worker} interval. - * - * @returns {void} - */ - stopEffect() { - this._streamWorker.postMessage({ id: CLEAR_INTERVAL }); - this._videoElement.removeEventListener('loadeddata', this._initScreenshotCapture); - } - - /** - * Method that is called as soon as the first frame of the video loads from stream. - * The method is used to store the {@code ImageData} object from the first frames - * in order to use it for future comparisons based on which we can process only certain - * screenshots. - * - * @private - * @returns {void} - */ - _initScreenshotCapture() { - const storedCanvas = document.createElement('canvas'); - const storedCanvasContext = storedCanvas.getContext('2d'); - - storedCanvasContext.drawImage(this._videoElement, 0, 0, this._streamWidth, this._streamHeight); - const { data } = storedCanvasContext.getImageData(0, 0, this._streamWidth, this._streamHeight); - - this._storedImageData = data; - this._streamWorker.postMessage({ - id: SET_INTERVAL, - timeMs: POLL_INTERVAL - }); - } - - /** - * Handler of the {@code EventHandler} message that calls the appropriate method based on the parameter's id. - * - * @private - * @param {EventHandler} message - Message received from the Worker. - * @returns {void} - */ - _handleWorkerAction(message: Object) { - return message.data.id === INTERVAL_TIMEOUT && this._handleScreenshot(); - } - - /** - * Method that decides whether an image should be processed based on a preset pixel lower bound. - * - * @private - * @param {integer} nbPixels - The number of pixels of the candidate image. - * @returns {boolean} - Whether the image should be processed or not. - */ - _shouldProcessScreenshot(nbPixels: number) { - return nbPixels >= PIXEL_LOWER_BOUND; - } - - /** - * Screenshot handler. - * - * @private - * @returns {void} - */ - _handleScreenshot() { - this._currentCanvasContext.drawImage(this._videoElement, 0, 0, this._streamWidth, this._streamHeight); - const { data } = this._currentCanvasContext.getImageData(0, 0, this._streamWidth, this._streamHeight); - const diffPixels = pixelmatch(data, this._storedImageData, null, this._streamWidth, this._streamHeight); - - if (this._shouldProcessScreenshot(diffPixels)) { - const conference = getCurrentConference(this._state); - const sessionId = conference.getMeetingUniqueId(); - const { connection, timeEstablished } = this._state['features/base/connection']; - const jid = connection.getJid(); - const timeLapseSeconds = timeEstablished && Math.floor((Date.now() - timeEstablished) / 1000); - const { jwt } = this._state['features/base/jwt']; - - this._storedImageData = data; - processScreenshot(this._currentCanvas, { - jid, - jwt, - sessionId, - timeLapseSeconds - }); - } - } -} diff --git a/react/features/stream-effects/screenshot-capture/index.js b/react/features/stream-effects/screenshot-capture/index.js deleted file mode 100644 index fc88ad144..000000000 --- a/react/features/stream-effects/screenshot-capture/index.js +++ /dev/null @@ -1,20 +0,0 @@ -// @flow - -import { toState } from '../../base/redux'; - -import ScreenshotCaptureEffect from './ScreenshotCaptureEffect'; - -/** - * Creates a new instance of ScreenshotCaptureEffect. - * - * @param {Object | Function} stateful - The redux store, state, or - * {@code getState} function. - * @returns {Promise} - */ -export function createScreenshotCaptureEffect(stateful: Object | Function) { - if (!MediaStreamTrack.prototype.getSettings && !MediaStreamTrack.prototype.getConstraints) { - return Promise.reject(new Error('ScreenshotCaptureEffect not supported!')); - } - - return Promise.resolve(new ScreenshotCaptureEffect(toState(stateful))); -}