diff --git a/package-lock.json b/package-lock.json
index 3f94f071e..331215544 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,9 @@
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",
+ "@tensorflow-models/face-landmarks-detection": "^1.0.2",
+ "@tensorflow/tfjs-backend-wasm": "^4.0.0",
+ "@tensorflow/tfjs-core": "^4.0.0",
"@twilio-labs/plugin-rtc": "^0.8.4",
"@twilio/conversations": "^2.1.0",
"@twilio/video-processors": "^1.0.1",
@@ -4522,6 +4525,18 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/@mediapipe/face_detection": {
+ "version": "0.4.1646425229",
+ "resolved": "https://registry.npmjs.org/@mediapipe/face_detection/-/face_detection-0.4.1646425229.tgz",
+ "integrity": "sha512-aeCN+fRAojv9ch3NXorP6r5tcGVLR3/gC1HmtqB0WEZBRXrdP6/3W/sGR0dHr1iT6ueiK95G9PVjbzFosf/hrg==",
+ "peer": true
+ },
+ "node_modules/@mediapipe/face_mesh": {
+ "version": "0.4.1633559619",
+ "resolved": "https://registry.npmjs.org/@mediapipe/face_mesh/-/face_mesh-0.4.1633559619.tgz",
+ "integrity": "sha512-Vc8cdjxS5+O2gnjWH9KncYpUCVXT0h714KlWAsyqJvJbIgUJBqpppbIx8yWcAzBDxm/5cYSuBI5p5ySIPxzcEg==",
+ "peer": true
+ },
"node_modules/@mrmlnc/readdir-enhanced": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@@ -11160,76 +11175,128 @@
"node": ">=10"
}
},
- "node_modules/@tensorflow-models/body-pix": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@tensorflow-models/body-pix/-/body-pix-2.2.0.tgz",
- "integrity": "sha512-e8PEf8xWlWhXJtGvlTQpdRyUuDXKAFJT/f8SWiBfvim5BAUyFp2dTeVV3gjDDp8MUEo7T3lofTZUexy/aG4YEA==",
+ "node_modules/@tensorflow-models/face-detection": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@tensorflow-models/face-detection/-/face-detection-1.0.1.tgz",
+ "integrity": "sha512-oIEEqHy4qbkGn5xT1ldzwxK5s948+t7ts0+WoIfTPKwScpNa8YY4GJf+fpyhtOxUGGAZymXx25jrvF9YlAt9dg==",
+ "peer": true,
+ "dependencies": {
+ "rimraf": "^3.0.2"
+ },
"peerDependencies": {
- "@tensorflow/tfjs-backend-webgl": "^3.6.0",
- "@tensorflow/tfjs-converter": "^3.6.0",
- "@tensorflow/tfjs-core": "^3.6.0"
+ "@mediapipe/face_detection": "~0.4.0",
+ "@tensorflow/tfjs-backend-webgl": "^3.14.0",
+ "@tensorflow/tfjs-converter": "^3.14.0",
+ "@tensorflow/tfjs-core": "^3.14.0"
+ }
+ },
+ "node_modules/@tensorflow-models/face-landmarks-detection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@tensorflow-models/face-landmarks-detection/-/face-landmarks-detection-1.0.2.tgz",
+ "integrity": "sha512-e10xKlBssv2nYg8hV93rNg7ne8NIJVT9Y1d/bpUCcBpPLJpykLw2DQ3nfPnoBpqhKDykFtKDGQVmeXvqc7H+KA==",
+ "dependencies": {
+ "rimraf": "^3.0.2"
+ },
+ "peerDependencies": {
+ "@mediapipe/face_mesh": "~0.4.0",
+ "@tensorflow-models/face-detection": "~1.0.0",
+ "@tensorflow/tfjs-backend-webgl": "^3.12.0",
+ "@tensorflow/tfjs-converter": "^3.12.0",
+ "@tensorflow/tfjs-core": "^3.12.0"
+ }
+ },
+ "node_modules/@tensorflow/tfjs-backend-wasm": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-wasm/-/tfjs-backend-wasm-4.0.0.tgz",
+ "integrity": "sha512-Z5gqdmqywauB2ZrWFduvFc82aMMTBFN7EtWEhGAGRXVcVH2L4nIXx0bs/5aUBA0Qs9B7hiy83b+NquWrDdBeGQ==",
+ "dependencies": {
+ "@tensorflow/tfjs-backend-cpu": "4.0.0",
+ "@types/emscripten": "~0.0.34"
+ },
+ "peerDependencies": {
+ "@tensorflow/tfjs-core": "4.0.0"
}
},
- "node_modules/@tensorflow/tfjs-backend-cpu": {
- "version": "3.18.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.18.0.tgz",
- "integrity": "sha512-LcSqlylzGtpgngcMFIL3q9Q3eVaPRJ7ITZt7ivhzkCj4R5ZsnPa9qM3DCVihkQ77heAwSw4hPTo2jp5C4mJ4Cg==",
+ "node_modules/@tensorflow/tfjs-backend-wasm/node_modules/@tensorflow/tfjs-backend-cpu": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.0.0.tgz",
+ "integrity": "sha512-Y9ok6VBMir1MVbkcK+h34hF4ZzHZKOrdjAOeE5eFbSuegJHY/AXWcZEU589VTnEE4AU+SUdjjfp6yvAtU17GAA==",
"dependencies": {
- "@types/seedrandom": "2.4.27",
- "seedrandom": "2.4.3"
+ "@types/seedrandom": "^2.4.28",
+ "seedrandom": "^3.0.5"
},
"engines": {
"yarn": ">= 1.3.2"
},
"peerDependencies": {
- "@tensorflow/tfjs-core": "3.18.0"
+ "@tensorflow/tfjs-core": "4.0.0"
}
},
"node_modules/@tensorflow/tfjs-backend-webgl": {
- "version": "3.18.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.18.0.tgz",
- "integrity": "sha512-3NknSzS1oX2BEBOrpjPMZl823S12RgshQthmIbG6QADHb4bCJA8aM4UjWpw+3bNQnRKbRDQdFbuvj10Un79s2A==",
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.21.0.tgz",
+ "integrity": "sha512-N4zitIAT9IX8B8oe489qM3f3VcESxGZIZvHmVP8varOQakTvTX859aaPo1s8hK1qCy4BjSGbweooZe4U8D4kTQ==",
"dependencies": {
- "@tensorflow/tfjs-backend-cpu": "3.18.0",
+ "@tensorflow/tfjs-backend-cpu": "3.21.0",
"@types/offscreencanvas": "~2019.3.0",
- "@types/seedrandom": "2.4.27",
+ "@types/seedrandom": "^2.4.28",
"@types/webgl-ext": "0.0.30",
"@types/webgl2": "0.0.6",
- "seedrandom": "2.4.3"
+ "seedrandom": "^3.0.5"
+ },
+ "engines": {
+ "yarn": ">= 1.3.2"
+ },
+ "peerDependencies": {
+ "@tensorflow/tfjs-core": "3.21.0"
+ }
+ },
+ "node_modules/@tensorflow/tfjs-backend-webgl/node_modules/@tensorflow/tfjs-backend-cpu": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.21.0.tgz",
+ "integrity": "sha512-88S21UAdzyK0CsLUrH17GPTD+26E85OP9CqmLZslaWjWUmBkeTQ5Zqyp6iK+gELnLxPx6q7JsNEeFuPv4254lQ==",
+ "dependencies": {
+ "@types/seedrandom": "^2.4.28",
+ "seedrandom": "^3.0.5"
},
"engines": {
"yarn": ">= 1.3.2"
},
"peerDependencies": {
- "@tensorflow/tfjs-core": "3.18.0"
+ "@tensorflow/tfjs-core": "3.21.0"
}
},
"node_modules/@tensorflow/tfjs-converter": {
- "version": "3.18.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-3.18.0.tgz",
- "integrity": "sha512-hpChA+zVNQOVwRnCfqDb1WI9jbEAKA6DuEm4m75Zb3dIlE6VVooDmAaHBhlc++z2q2G1sBzF9A4Bv48SUpN6vA==",
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-3.21.0.tgz",
+ "integrity": "sha512-12Y4zVDq3yW+wSjSDpSv4HnpL2sDZrNiGSg8XNiDE4HQBdjdA+a+Q3sZF/8NV9y2yoBhL5L7V4mMLDdbZBd9/Q==",
"peerDependencies": {
- "@tensorflow/tfjs-core": "3.18.0"
+ "@tensorflow/tfjs-core": "3.21.0"
}
},
"node_modules/@tensorflow/tfjs-core": {
- "version": "3.18.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-3.18.0.tgz",
- "integrity": "sha512-gMxisZozqsr5sCKlphF/eVBLg91MjlBiN60tjX8hJAu0WlSn6Gi5k65GNIL+Pq6hrxpvImcfdCmTH/2XJVZ0Mg==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.0.0.tgz",
+ "integrity": "sha512-RrTgHPT8Xo6vsBvaRkee1YOoH3lv0lEtS3ISGRcHsUy8mkT8ZIMNou4zaJuIBQ7bVPXOeOFFeTbjDvWVtYypxQ==",
"dependencies": {
"@types/long": "^4.0.1",
- "@types/offscreencanvas": "~2019.3.0",
- "@types/seedrandom": "2.4.27",
+ "@types/offscreencanvas": "~2019.7.0",
+ "@types/seedrandom": "^2.4.28",
"@types/webgl-ext": "0.0.30",
- "@webgpu/types": "^0.1.16",
+ "@webgpu/types": "0.1.21",
"long": "4.0.0",
"node-fetch": "~2.6.1",
- "seedrandom": "2.4.3"
+ "seedrandom": "^3.0.5"
},
"engines": {
"yarn": ">= 1.3.2"
}
},
+ "node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": {
+ "version": "2019.7.0",
+ "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz",
+ "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg=="
+ },
"node_modules/@testing-library/dom": {
"version": "6.16.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-6.16.0.tgz",
@@ -11667,6 +11734,54 @@
"node": ">=14"
}
},
+ "node_modules/@twilio/video-processors/node_modules/@tensorflow-models/body-pix": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow-models/body-pix/-/body-pix-2.2.0.tgz",
+ "integrity": "sha512-e8PEf8xWlWhXJtGvlTQpdRyUuDXKAFJT/f8SWiBfvim5BAUyFp2dTeVV3gjDDp8MUEo7T3lofTZUexy/aG4YEA==",
+ "peerDependencies": {
+ "@tensorflow/tfjs-backend-webgl": "^3.6.0",
+ "@tensorflow/tfjs-converter": "^3.6.0",
+ "@tensorflow/tfjs-core": "^3.6.0"
+ }
+ },
+ "node_modules/@twilio/video-processors/node_modules/@tensorflow/tfjs-backend-cpu": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.21.0.tgz",
+ "integrity": "sha512-88S21UAdzyK0CsLUrH17GPTD+26E85OP9CqmLZslaWjWUmBkeTQ5Zqyp6iK+gELnLxPx6q7JsNEeFuPv4254lQ==",
+ "dependencies": {
+ "@types/seedrandom": "^2.4.28",
+ "seedrandom": "^3.0.5"
+ },
+ "engines": {
+ "yarn": ">= 1.3.2"
+ },
+ "peerDependencies": {
+ "@tensorflow/tfjs-core": "3.21.0"
+ }
+ },
+ "node_modules/@twilio/video-processors/node_modules/@tensorflow/tfjs-core": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-3.21.0.tgz",
+ "integrity": "sha512-YSfsswOqWfd+M4bXIhT3hwtAb+IV8+ODwIxwdFR/7jTAPZP1wMVnSlpKnXHAN64HFOiP+Tm3HmKusEZ0+09A0w==",
+ "dependencies": {
+ "@types/long": "^4.0.1",
+ "@types/offscreencanvas": "~2019.3.0",
+ "@types/seedrandom": "^2.4.28",
+ "@types/webgl-ext": "0.0.30",
+ "@webgpu/types": "0.1.16",
+ "long": "4.0.0",
+ "node-fetch": "~2.6.1",
+ "seedrandom": "^3.0.5"
+ },
+ "engines": {
+ "yarn": ">= 1.3.2"
+ }
+ },
+ "node_modules/@twilio/video-processors/node_modules/@webgpu/types": {
+ "version": "0.1.16",
+ "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.16.tgz",
+ "integrity": "sha512-9E61voMP4+Rze02jlTXud++Htpjyyk8vw5Hyw9FGRrmhHQg2GqbuOfwf5Klrb8vTxc2XWI3EfO7RUHMpxTj26A=="
+ },
"node_modules/@twilio/video-room-monitor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@twilio/video-room-monitor/-/video-room-monitor-1.0.1.tgz",
@@ -11835,6 +11950,11 @@
"dotenv": "*"
}
},
+ "node_modules/@types/emscripten": {
+ "version": "0.0.34",
+ "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-0.0.34.tgz",
+ "integrity": "sha512-QSb9ojDincskc+uKMI0KXp8e1NALFINCrMlp8VGKGcTSxeEyRTTKyjWw75NYrCZHUsVEEEpr1tYHpbtaC++/sQ=="
+ },
"node_modules/@types/enzyme": {
"version": "3.10.12",
"resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.12.tgz",
@@ -12358,9 +12478,9 @@
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
"node_modules/@types/seedrandom": {
- "version": "2.4.27",
- "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.27.tgz",
- "integrity": "sha512-YvMLqFak/7rt//lPBtEHv3M4sRNA+HGxrhFZ+DQs9K2IkYJbNwVIb8avtJfhDiuaUBX/AW0jnjv48FV8h3u9bQ=="
+ "version": "2.4.30",
+ "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.30.tgz",
+ "integrity": "sha512-AnxLHewubLVzoF/A4qdxBGHCKifw8cY32iro3DQX9TPcetE95zBeVt3jnsvtvAUf1vwzMfwzp4t/L2yqPlnjkQ=="
},
"node_modules/@types/serve-index": {
"version": "1.9.1",
@@ -36736,9 +36856,9 @@
"integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q=="
},
"node_modules/seedrandom": {
- "version": "2.4.3",
- "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz",
- "integrity": "sha512-2CkZ9Wn2dS4mMUWQaXLsOAfGD+irMlLEeSP3cMxpGbgyOOzJGFa+MWCOMTOCMyZinHRPxyOj/S/C57li/1to6Q=="
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
+ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
},
"node_modules/select-hose": {
"version": "2.0.0",
@@ -45408,6 +45528,18 @@
"integrity": "sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==",
"dev": true
},
+ "@mediapipe/face_detection": {
+ "version": "0.4.1646425229",
+ "resolved": "https://registry.npmjs.org/@mediapipe/face_detection/-/face_detection-0.4.1646425229.tgz",
+ "integrity": "sha512-aeCN+fRAojv9ch3NXorP6r5tcGVLR3/gC1HmtqB0WEZBRXrdP6/3W/sGR0dHr1iT6ueiK95G9PVjbzFosf/hrg==",
+ "peer": true
+ },
+ "@mediapipe/face_mesh": {
+ "version": "0.4.1633559619",
+ "resolved": "https://registry.npmjs.org/@mediapipe/face_mesh/-/face_mesh-0.4.1633559619.tgz",
+ "integrity": "sha512-Vc8cdjxS5+O2gnjWH9KncYpUCVXT0h714KlWAsyqJvJbIgUJBqpppbIx8yWcAzBDxm/5cYSuBI5p5ySIPxzcEg==",
+ "peer": true
+ },
"@mrmlnc/readdir-enhanced": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@@ -50400,53 +50532,93 @@
"defer-to-connect": "^2.0.0"
}
},
- "@tensorflow-models/body-pix": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@tensorflow-models/body-pix/-/body-pix-2.2.0.tgz",
- "integrity": "sha512-e8PEf8xWlWhXJtGvlTQpdRyUuDXKAFJT/f8SWiBfvim5BAUyFp2dTeVV3gjDDp8MUEo7T3lofTZUexy/aG4YEA==",
- "requires": {}
+ "@tensorflow-models/face-detection": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@tensorflow-models/face-detection/-/face-detection-1.0.1.tgz",
+ "integrity": "sha512-oIEEqHy4qbkGn5xT1ldzwxK5s948+t7ts0+WoIfTPKwScpNa8YY4GJf+fpyhtOxUGGAZymXx25jrvF9YlAt9dg==",
+ "peer": true,
+ "requires": {
+ "rimraf": "^3.0.2"
+ }
},
- "@tensorflow/tfjs-backend-cpu": {
- "version": "3.18.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.18.0.tgz",
- "integrity": "sha512-LcSqlylzGtpgngcMFIL3q9Q3eVaPRJ7ITZt7ivhzkCj4R5ZsnPa9qM3DCVihkQ77heAwSw4hPTo2jp5C4mJ4Cg==",
+ "@tensorflow-models/face-landmarks-detection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@tensorflow-models/face-landmarks-detection/-/face-landmarks-detection-1.0.2.tgz",
+ "integrity": "sha512-e10xKlBssv2nYg8hV93rNg7ne8NIJVT9Y1d/bpUCcBpPLJpykLw2DQ3nfPnoBpqhKDykFtKDGQVmeXvqc7H+KA==",
"requires": {
- "@types/seedrandom": "2.4.27",
- "seedrandom": "2.4.3"
+ "rimraf": "^3.0.2"
+ }
+ },
+ "@tensorflow/tfjs-backend-wasm": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-wasm/-/tfjs-backend-wasm-4.0.0.tgz",
+ "integrity": "sha512-Z5gqdmqywauB2ZrWFduvFc82aMMTBFN7EtWEhGAGRXVcVH2L4nIXx0bs/5aUBA0Qs9B7hiy83b+NquWrDdBeGQ==",
+ "requires": {
+ "@tensorflow/tfjs-backend-cpu": "4.0.0",
+ "@types/emscripten": "~0.0.34"
+ },
+ "dependencies": {
+ "@tensorflow/tfjs-backend-cpu": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.0.0.tgz",
+ "integrity": "sha512-Y9ok6VBMir1MVbkcK+h34hF4ZzHZKOrdjAOeE5eFbSuegJHY/AXWcZEU589VTnEE4AU+SUdjjfp6yvAtU17GAA==",
+ "requires": {
+ "@types/seedrandom": "^2.4.28",
+ "seedrandom": "^3.0.5"
+ }
+ }
}
},
"@tensorflow/tfjs-backend-webgl": {
- "version": "3.18.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.18.0.tgz",
- "integrity": "sha512-3NknSzS1oX2BEBOrpjPMZl823S12RgshQthmIbG6QADHb4bCJA8aM4UjWpw+3bNQnRKbRDQdFbuvj10Un79s2A==",
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.21.0.tgz",
+ "integrity": "sha512-N4zitIAT9IX8B8oe489qM3f3VcESxGZIZvHmVP8varOQakTvTX859aaPo1s8hK1qCy4BjSGbweooZe4U8D4kTQ==",
"requires": {
- "@tensorflow/tfjs-backend-cpu": "3.18.0",
+ "@tensorflow/tfjs-backend-cpu": "3.21.0",
"@types/offscreencanvas": "~2019.3.0",
- "@types/seedrandom": "2.4.27",
+ "@types/seedrandom": "^2.4.28",
"@types/webgl-ext": "0.0.30",
"@types/webgl2": "0.0.6",
- "seedrandom": "2.4.3"
+ "seedrandom": "^3.0.5"
+ },
+ "dependencies": {
+ "@tensorflow/tfjs-backend-cpu": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.21.0.tgz",
+ "integrity": "sha512-88S21UAdzyK0CsLUrH17GPTD+26E85OP9CqmLZslaWjWUmBkeTQ5Zqyp6iK+gELnLxPx6q7JsNEeFuPv4254lQ==",
+ "requires": {
+ "@types/seedrandom": "^2.4.28",
+ "seedrandom": "^3.0.5"
+ }
+ }
}
},
"@tensorflow/tfjs-converter": {
- "version": "3.18.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-3.18.0.tgz",
- "integrity": "sha512-hpChA+zVNQOVwRnCfqDb1WI9jbEAKA6DuEm4m75Zb3dIlE6VVooDmAaHBhlc++z2q2G1sBzF9A4Bv48SUpN6vA==",
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-3.21.0.tgz",
+ "integrity": "sha512-12Y4zVDq3yW+wSjSDpSv4HnpL2sDZrNiGSg8XNiDE4HQBdjdA+a+Q3sZF/8NV9y2yoBhL5L7V4mMLDdbZBd9/Q==",
"requires": {}
},
"@tensorflow/tfjs-core": {
- "version": "3.18.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-3.18.0.tgz",
- "integrity": "sha512-gMxisZozqsr5sCKlphF/eVBLg91MjlBiN60tjX8hJAu0WlSn6Gi5k65GNIL+Pq6hrxpvImcfdCmTH/2XJVZ0Mg==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.0.0.tgz",
+ "integrity": "sha512-RrTgHPT8Xo6vsBvaRkee1YOoH3lv0lEtS3ISGRcHsUy8mkT8ZIMNou4zaJuIBQ7bVPXOeOFFeTbjDvWVtYypxQ==",
"requires": {
"@types/long": "^4.0.1",
- "@types/offscreencanvas": "~2019.3.0",
- "@types/seedrandom": "2.4.27",
+ "@types/offscreencanvas": "~2019.7.0",
+ "@types/seedrandom": "^2.4.28",
"@types/webgl-ext": "0.0.30",
- "@webgpu/types": "^0.1.16",
+ "@webgpu/types": "0.1.21",
"long": "4.0.0",
"node-fetch": "~2.6.1",
- "seedrandom": "2.4.3"
+ "seedrandom": "^3.0.5"
+ },
+ "dependencies": {
+ "@types/offscreencanvas": {
+ "version": "2019.7.0",
+ "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz",
+ "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg=="
+ }
}
},
"@testing-library/dom": {
@@ -50798,6 +50970,43 @@
"@tensorflow/tfjs-backend-webgl": "^3.2.0",
"@tensorflow/tfjs-converter": "^3.2.0",
"@tensorflow/tfjs-core": "^3.2.0"
+ },
+ "dependencies": {
+ "@tensorflow-models/body-pix": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow-models/body-pix/-/body-pix-2.2.0.tgz",
+ "integrity": "sha512-e8PEf8xWlWhXJtGvlTQpdRyUuDXKAFJT/f8SWiBfvim5BAUyFp2dTeVV3gjDDp8MUEo7T3lofTZUexy/aG4YEA==",
+ "requires": {}
+ },
+ "@tensorflow/tfjs-backend-cpu": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.21.0.tgz",
+ "integrity": "sha512-88S21UAdzyK0CsLUrH17GPTD+26E85OP9CqmLZslaWjWUmBkeTQ5Zqyp6iK+gELnLxPx6q7JsNEeFuPv4254lQ==",
+ "requires": {
+ "@types/seedrandom": "^2.4.28",
+ "seedrandom": "^3.0.5"
+ }
+ },
+ "@tensorflow/tfjs-core": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-3.21.0.tgz",
+ "integrity": "sha512-YSfsswOqWfd+M4bXIhT3hwtAb+IV8+ODwIxwdFR/7jTAPZP1wMVnSlpKnXHAN64HFOiP+Tm3HmKusEZ0+09A0w==",
+ "requires": {
+ "@types/long": "^4.0.1",
+ "@types/offscreencanvas": "~2019.3.0",
+ "@types/seedrandom": "^2.4.28",
+ "@types/webgl-ext": "0.0.30",
+ "@webgpu/types": "0.1.16",
+ "long": "4.0.0",
+ "node-fetch": "~2.6.1",
+ "seedrandom": "^3.0.5"
+ }
+ },
+ "@webgpu/types": {
+ "version": "0.1.16",
+ "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.16.tgz",
+ "integrity": "sha512-9E61voMP4+Rze02jlTXud++Htpjyyk8vw5Hyw9FGRrmhHQg2GqbuOfwf5Klrb8vTxc2XWI3EfO7RUHMpxTj26A=="
+ }
}
},
"@twilio/video-room-monitor": {
@@ -50962,6 +51171,11 @@
"dotenv": "*"
}
},
+ "@types/emscripten": {
+ "version": "0.0.34",
+ "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-0.0.34.tgz",
+ "integrity": "sha512-QSb9ojDincskc+uKMI0KXp8e1NALFINCrMlp8VGKGcTSxeEyRTTKyjWw75NYrCZHUsVEEEpr1tYHpbtaC++/sQ=="
+ },
"@types/enzyme": {
"version": "3.10.12",
"resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.12.tgz",
@@ -51460,9 +51674,9 @@
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
"@types/seedrandom": {
- "version": "2.4.27",
- "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.27.tgz",
- "integrity": "sha512-YvMLqFak/7rt//lPBtEHv3M4sRNA+HGxrhFZ+DQs9K2IkYJbNwVIb8avtJfhDiuaUBX/AW0jnjv48FV8h3u9bQ=="
+ "version": "2.4.30",
+ "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.30.tgz",
+ "integrity": "sha512-AnxLHewubLVzoF/A4qdxBGHCKifw8cY32iro3DQX9TPcetE95zBeVt3jnsvtvAUf1vwzMfwzp4t/L2yqPlnjkQ=="
},
"@types/serve-index": {
"version": "1.9.1",
@@ -69989,9 +70203,9 @@
"integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q=="
},
"seedrandom": {
- "version": "2.4.3",
- "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz",
- "integrity": "sha512-2CkZ9Wn2dS4mMUWQaXLsOAfGD+irMlLEeSP3cMxpGbgyOOzJGFa+MWCOMTOCMyZinHRPxyOj/S/C57li/1to6Q=="
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
+ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
},
"select-hose": {
"version": "2.0.0",
diff --git a/package.json b/package.json
index 6f33c8781..d651ac3d5 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,9 @@
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",
+ "@tensorflow-models/face-landmarks-detection": "^1.0.2",
+ "@tensorflow/tfjs-backend-wasm": "^4.0.0",
+ "@tensorflow/tfjs-core": "^4.0.0",
"@twilio-labs/plugin-rtc": "^0.8.4",
"@twilio/conversations": "^2.1.0",
"@twilio/video-processors": "^1.0.1",
diff --git a/src/components/MaskSelectionDialog/MaskSelectionDialog.tsx b/src/components/MaskSelectionDialog/MaskSelectionDialog.tsx
new file mode 100644
index 000000000..737f75a0d
--- /dev/null
+++ b/src/components/MaskSelectionDialog/MaskSelectionDialog.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import MaskSelectionHeader from './MaskSelectionHeader/MaskSelectionHeader';
+import MaskThumbnail from './MaskThumbnail/MaskThumbnail';
+import Drawer from '@material-ui/core/Drawer';
+import { makeStyles, Theme } from '@material-ui/core/styles';
+import { maskConfig } from '../VideoProvider/useMaskSettings/useMaskSettings';
+import useVideoContext from '../../hooks/useVideoContext/useVideoContext';
+
+const useStyles = makeStyles((theme: Theme) => ({
+ drawer: {
+ display: 'flex',
+ width: theme.rightDrawerWidth,
+ height: `calc(100% - ${theme.footerHeight}px)`,
+ },
+ thumbnailContainer: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ padding: '5px',
+ overflowY: 'auto',
+ },
+}));
+
+function MaskSelectionDialog() {
+ const classes = useStyles();
+ const { isMaskSelectionOpen, setIsMaskSelectionOpen } = useVideoContext();
+
+ const imageNames = maskConfig.imageNames;
+ const images = maskConfig.images;
+
+ return (
+
+ setIsMaskSelectionOpen(false)} />
+
+
+ {images.map((image, index) => (
+
+ ))}
+
+
+ );
+}
+
+export default MaskSelectionDialog;
diff --git a/src/components/MaskSelectionDialog/MaskSelectionHeader/MaskSelectionHeader.test.tsx b/src/components/MaskSelectionDialog/MaskSelectionHeader/MaskSelectionHeader.test.tsx
new file mode 100644
index 000000000..397c78bab
--- /dev/null
+++ b/src/components/MaskSelectionDialog/MaskSelectionHeader/MaskSelectionHeader.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import CloseIcon from '../../../icons/CloseIcon';
+import MaskSelectionHeader from './MaskSelectionHeader';
+
+const mockCloseDialog = jest.fn();
+
+describe('The Background Selection Header Component', () => {
+ it('should close the selection dialog when "X" is clicked', () => {
+ const wrapper = shallow();
+ wrapper
+ .find(CloseIcon)
+ .parent()
+ .simulate('click');
+ expect(mockCloseDialog).toHaveBeenCalled();
+ });
+});
diff --git a/src/components/MaskSelectionDialog/MaskSelectionHeader/MaskSelectionHeader.tsx b/src/components/MaskSelectionDialog/MaskSelectionHeader/MaskSelectionHeader.tsx
new file mode 100644
index 000000000..a7ed38a2d
--- /dev/null
+++ b/src/components/MaskSelectionDialog/MaskSelectionHeader/MaskSelectionHeader.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import { makeStyles, createStyles } from '@material-ui/core/styles';
+import CloseIcon from '../../../icons/CloseIcon';
+
+const useStyles = makeStyles(() =>
+ createStyles({
+ container: {
+ minHeight: '56px',
+ background: '#F4F4F6',
+ borderBottom: '1px solid #E4E7E9',
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ padding: '0 1em',
+ },
+ text: {
+ fontWeight: 'bold',
+ },
+ closeMaskSelection: {
+ cursor: 'pointer',
+ display: 'flex',
+ background: 'transparent',
+ border: '0',
+ padding: '0.4em',
+ },
+ })
+);
+
+interface MaskSelectionHeaderProps {
+ onClose: () => void;
+}
+
+export default function MaskSelectionHeader({ onClose }: MaskSelectionHeaderProps) {
+ const classes = useStyles();
+ return (
+
+ );
+}
diff --git a/src/components/MaskSelectionDialog/MaskThumbnail/MaskThumbnail.test.tsx b/src/components/MaskSelectionDialog/MaskThumbnail/MaskThumbnail.test.tsx
new file mode 100644
index 000000000..bc50ba594
--- /dev/null
+++ b/src/components/MaskSelectionDialog/MaskThumbnail/MaskThumbnail.test.tsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import MaskThumbnail from './MaskThumbnail';
+import BlurIcon from '@material-ui/icons/BlurOnOutlined';
+import NoneIcon from '@material-ui/icons/NotInterestedOutlined';
+import { shallow } from 'enzyme';
+import useVideoContext from '../../../hooks/useVideoContext/useVideoContext';
+
+jest.mock('../../../hooks/useVideoContext/useVideoContext');
+const mockUseVideoContext = useVideoContext as jest.Mock;
+const mockSetMaskSettings = jest.fn();
+mockUseVideoContext.mockImplementation(() => ({
+ maskSettings: {
+ type: 'blur',
+ index: 0,
+ },
+ setMaskSettings: mockSetMaskSettings,
+}));
+
+describe('The MaskThumbnail component', () => {
+ it('should update the mask settings when clicked', () => {
+ const wrapper = shallow();
+ wrapper.simulate('click');
+ expect(mockSetMaskSettings).toHaveBeenCalledWith({ index: 5, type: 'none' });
+ });
+
+ it('should not be selected when thumbnail prop and maskSettings type are not equivalent (icon)', () => {
+ const wrapper = shallow();
+ expect(wrapper.find('.selected').exists()).toBe(false);
+ });
+
+ it('should be selected when thumbnail prop and maskSettings type are equivalent (image)', () => {
+ mockUseVideoContext.mockImplementationOnce(() => ({
+ maskSettings: {
+ type: 'image',
+ index: 1,
+ },
+ setMaskSettings: mockSetMaskSettings,
+ }));
+ const wrapper = shallow();
+ expect(wrapper.find('.selected').exists()).toBe(true);
+ });
+
+ it('should not be selected when thumbnail and maskSettings type are not equivlanet (image)', () => {
+ mockUseVideoContext.mockImplementationOnce(() => ({
+ maskSettings: {
+ type: 'image',
+ index: 1,
+ },
+ setMaskSettings: mockSetMaskSettings,
+ }));
+ const wrapper = shallow();
+ expect(wrapper.find('.selected').exists()).toBe(false);
+ });
+
+ it("should contain the NoneIcon when thumbnail is set to 'none'", () => {
+ const wrapper = shallow();
+ expect(wrapper.containsMatchingElement()).toBe(true);
+ });
+
+ it("should not have any icons when thumbnail is set to 'image'", () => {
+ const wrapper = shallow();
+ expect(wrapper.containsMatchingElement()).toBe(false);
+ expect(wrapper.containsMatchingElement()).toBe(false);
+ });
+});
diff --git a/src/components/MaskSelectionDialog/MaskThumbnail/MaskThumbnail.tsx b/src/components/MaskSelectionDialog/MaskThumbnail/MaskThumbnail.tsx
new file mode 100644
index 000000000..9f0e23514
--- /dev/null
+++ b/src/components/MaskSelectionDialog/MaskThumbnail/MaskThumbnail.tsx
@@ -0,0 +1,132 @@
+import React from 'react';
+import clsx from 'clsx';
+import BlurIcon from '@material-ui/icons/BlurOnOutlined';
+import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
+import NoneIcon from '@material-ui/icons/NotInterestedOutlined';
+import useVideoContext from '../../../hooks/useVideoContext/useVideoContext';
+
+export type Thumbnail = 'none' | 'image';
+
+interface MaskThumbnailProps {
+ thumbnail: Thumbnail;
+ imagePath?: string;
+ name?: string;
+ index?: number;
+}
+
+const useStyles = makeStyles((theme: Theme) =>
+ createStyles({
+ thumbContainer: {
+ margin: '5px',
+ width: 'calc(50% - 10px)',
+ display: 'flex',
+ position: 'relative',
+ '&::after': {
+ content: '""',
+ paddingBottom: '55.5%',
+ },
+ },
+ thumbIconContainer: {
+ width: '100%',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ borderRadius: '10px',
+ border: `solid ${theme.palette.grey[400]}`,
+ '&.selected': {
+ border: `solid ${theme.palette.primary.main}`,
+ '& svg': {
+ color: `${theme.palette.primary.main}`,
+ },
+ },
+ },
+ thumbIcon: {
+ height: 50,
+ width: 50,
+ color: `${theme.palette.grey[400]}`,
+ '&.selected': {
+ color: `${theme.palette.primary.main}`,
+ },
+ },
+ thumbImage: {
+ width: '100%',
+ height: '100%',
+ position: 'absolute',
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ objectFit: 'cover',
+ borderRadius: '10px',
+ border: `solid ${theme.palette.grey[400]}`,
+ '&:hover': {
+ cursor: 'pointer',
+ '& svg': {
+ color: `${theme.palette.primary.main}`,
+ },
+ '& $thumbOverlay': {
+ visibility: 'visible',
+ },
+ },
+ '&.selected': {
+ border: `solid ${theme.palette.primary.main}`,
+ '& svg': {
+ color: `${theme.palette.primary.main}`,
+ },
+ },
+ },
+ thumbOverlay: {
+ position: 'absolute',
+ color: 'transparent',
+ padding: '20px',
+ fontSize: '14px',
+ fontWeight: 'bold',
+ width: '100%',
+ height: '100%',
+ borderRadius: '10px',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ '&:hover': {
+ background: 'rgba(95, 93, 128, 0.6)',
+ color: 'white',
+ },
+ },
+ })
+);
+
+export default function MaskThumbnail({ thumbnail, imagePath, name, index }: MaskThumbnailProps) {
+ const classes = useStyles();
+ const { maskSettings, setMaskSettings } = useVideoContext();
+ const isImage = thumbnail === 'image';
+ const thumbnailSelected = isImage
+ ? maskSettings.index === index && maskSettings.type === 'image'
+ : maskSettings.type === thumbnail;
+ const icons = {
+ none: NoneIcon,
+ blur: BlurIcon,
+ image: null,
+ };
+ const ThumbnailIcon = icons[thumbnail];
+
+ return (
+
+ setMaskSettings({
+ type: thumbnail,
+ index: index,
+ })
+ }
+ >
+ {ThumbnailIcon ? (
+
+
+
+ ) : (
+
+ )}
+
{name}
+
+ );
+}
diff --git a/src/components/MenuBar/Menu/Menu.tsx b/src/components/MenuBar/Menu/Menu.tsx
index b023a6187..2501ee42f 100644
--- a/src/components/MenuBar/Menu/Menu.tsx
+++ b/src/components/MenuBar/Menu/Menu.tsx
@@ -1,6 +1,7 @@
import React, { useState, useRef } from 'react';
import AboutDialog from '../../AboutDialog/AboutDialog';
import BackgroundIcon from '../../../icons/BackgroundIcon';
+import MaskIcon from '../../../icons/MaskIcon';
import CollaborationViewIcon from '@material-ui/icons/AccountBox';
import DeviceSelectionDialog from '../../DeviceSelectionDialog/DeviceSelectionDialog';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
@@ -39,7 +40,7 @@ export default function Menu(props: { buttonClassName?: string }) {
const { isFetching, updateRecordingRules, roomType, setIsGalleryViewActive, isGalleryViewActive } = useAppState();
const { setIsChatWindowOpen } = useChatContext();
const isRecording = useIsRecording();
- const { room, setIsBackgroundSelectionOpen } = useVideoContext();
+ const { room, setIsBackgroundSelectionOpen, setIsMaskSelectionOpen } = useVideoContext();
const anchorRef = useRef(null);
const { flipCameraDisabled, toggleFacingMode, flipCameraSupported } = useFlipCameraToggle();
@@ -85,6 +86,7 @@ export default function Menu(props: { buttonClassName?: string }) {
)}
+ {isSupported && (
+
+ )}
+
{flipCameraSupported && (