├── BanubaClientToken.js ├── BanubaPlayer.js ├── LICENSE ├── README.md ├── assets ├── effects │ ├── BG.zip │ ├── ConfusedRabbit.zip │ ├── DebugFRX.zip │ ├── Detection_gestures.zip │ ├── Eye_lenses.zip │ ├── Eye_lenses_Blue.zip │ ├── Eye_lenses_Green.zip │ ├── EyesWitening_Toggle.zip │ ├── FG.zip │ ├── FlappyPlane_mouth.zip │ ├── Full_Body.zip │ ├── Gangster.zip │ ├── Hades.zip │ ├── Hair.zip │ ├── Lips.zip │ ├── Low_look_clubs.zip │ ├── MinnieMouse7_multi.zip │ ├── Morphings_1.7.0.zip │ ├── Regular_blur.zip │ ├── Retrowave.zip │ ├── Ring_01.zip │ ├── Skin.zip │ ├── SkinSoftening.zip │ ├── Spider2.zip │ ├── Sunset.zip │ ├── TeethWitening_Toggle.zip │ ├── TrollGrandma.zip │ ├── VTO_Hair_blue.zip │ ├── VTO_Hair_green.zip │ ├── VTO_Hair_strand.zip │ ├── VTO_Headdresse_01.zip │ ├── What_Animal_Are_You.zip │ ├── WhooshBeautyFemale.zip │ ├── dialect.zip │ ├── earrings_01.zip │ ├── glasses_RayBan4165_Dark.zip │ ├── heart_rate.zip │ ├── necklace_01.zip │ ├── test_Ruler.zip │ └── video_BG_RainyCafe.zip └── icons │ ├── controls │ ├── icon-arrow.svg │ ├── icon-record-active.svg │ ├── icon-record.svg │ ├── icon-reset.svg │ ├── icon-screenshot-active.svg │ ├── icon-screenshot.svg │ ├── icon-sound-active.svg │ └── icon-sound.svg │ ├── effects │ ├── BG.png │ ├── Eye_lenses_Blue.png │ ├── Eye_lenses_Green.png │ ├── FG.png │ ├── Glasses_Dark.png │ ├── Regular_blur.png │ ├── VTO_Hair_blue.png │ ├── VTO_Hair_green.png │ ├── VTO_Hair_strand.png │ ├── WhooshBeautyFemale.png │ ├── dialect.png │ ├── earrings_01.png │ ├── eyebrows_bend.svg │ ├── eyebrows_height.svg │ ├── eyebrows_spacing.svg │ ├── eyes_enlargement.svg │ ├── eyes_height.svg │ ├── eyes_lower_eyelid_pos.svg │ ├── eyes_lower_eyelid_size.svg │ ├── eyes_rounding.svg │ ├── eyes_spacing.svg │ ├── eyes_squint.svg │ ├── face_cheekbones_narrowing.svg │ ├── face_cheeks_jaw_narrowing.svg │ ├── face_cheeks_narrowing.svg │ ├── face_chin_narrowing.svg │ ├── face_chin_shortening.svg │ ├── face_jaw_narrowing.svg │ ├── face_narrowing.svg │ ├── face_sunken_cheeks.svg │ ├── face_v_shape.svg │ ├── lips_height.svg │ ├── lips_mouth_size.svg │ ├── lips_shape.svg │ ├── lips_size.svg │ ├── lips_smile.svg │ ├── lips_thickness.svg │ ├── necklace_01.png │ ├── nose_length.svg │ ├── nose_tip_width.svg │ ├── nose_width.svg │ └── video_BG_RainyCafe.png │ └── hand_gestures │ ├── Like.svg │ ├── Ok.svg │ ├── Palm.svg │ ├── Rock.svg │ └── Victory.svg ├── effectsConfig.js ├── import └── effectsList.js ├── index.html ├── main.js ├── range-requests.sw.js ├── src ├── effect.js ├── elements.js ├── image-source.js ├── index.js ├── state.js ├── toolbar.js └── viewer-controls.js └── styles.css /BanubaClientToken.js: -------------------------------------------------------------------------------- 1 | window.BANUBA_CLIENT_TOKEN = "PUT YOUR CLIENT TOKEN HERE" -------------------------------------------------------------------------------- /BanubaPlayer.js: -------------------------------------------------------------------------------- 1 | let SDK_VERSION = "1.17.0"; 2 | 3 | const urlParams = new URLSearchParams(window.location.search); 4 | 5 | if (urlParams.has("sdk")) { 6 | SDK_VERSION = urlParams.get("sdk"); 7 | } 8 | 9 | const { 10 | Dom, 11 | Effect, 12 | Image, 13 | ImageCapture, 14 | Module, 15 | Player, 16 | VideoRecorder, 17 | Webcam, 18 | VERSION, 19 | } = await import( 20 | `https://cdn.jsdelivr.net/npm/@banuba/webar@${SDK_VERSION}/dist/BanubaSDK.browser.esm.min.js` 21 | ); 22 | 23 | if (VERSION != SDK_VERSION) { 24 | console.warn( 25 | `Version dont match: requested ${SDK_VERSION} - received ${VERSION}` 26 | ); 27 | 28 | SDK_VERSION = VERSION; 29 | 30 | if (SDK_VERSION.includes("-")) { 31 | console.warn("SDK version includes '-'. Removing it..."); 32 | SDK_VERSION = SDK_VERSION.slice(0, SDK_VERSION.indexOf("-")); 33 | } 34 | } 35 | 36 | const sdkUrl = "https://cdn.jsdelivr.net/npm/@banuba/webar"; 37 | 38 | const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); 39 | const modulesList = [ 40 | "background", 41 | "body", 42 | "eyes", 43 | "face_tracker", 44 | "hair", 45 | "hands", 46 | "lips", 47 | "skin", 48 | ]; 49 | 50 | export const fps = { 51 | cam: 0, 52 | processing: 0, 53 | render: 0, 54 | }; 55 | 56 | const fpsCounter = { 57 | cam: 0, 58 | processing: 0, 59 | render: 0, 60 | }; 61 | 62 | let currentEffect; 63 | 64 | console.log("Load player with SDK: ", SDK_VERSION); 65 | 66 | // Fixes video range requests in Safari that cause AR effects animation delay 67 | // https://docs.banuba.com/far-sdk/tutorials/development/known_issues/web/#effect-animations-are-delayed-in-safari 68 | navigator.serviceWorker.register("./range-requests.sw.js") 69 | 70 | const player = await Player.create({ 71 | clientToken: window.BANUBA_CLIENT_TOKEN, 72 | proxyVideoRequestsTo: isSafari ? "___range-requests___/" : null, 73 | useFutureInterpolate: false, 74 | locateFile: `${sdkUrl}@${SDK_VERSION}/dist`, 75 | }); 76 | 77 | await Promise.all( 78 | modulesList.map((moduleId) => { 79 | return new Promise(async (resolve) => { 80 | try { 81 | const module = await Module.preload( 82 | `https://cdn.jsdelivr.net/npm/@banuba/webar@${SDK_VERSION}/dist/modules/${moduleId}.zip` 83 | ); 84 | await player.addModule(module); 85 | } catch (error) { 86 | console.warn(`Load module ${moduleId} error: `, error); 87 | } 88 | 89 | return resolve(); 90 | }); 91 | }) 92 | ); 93 | 94 | const startFpsTracking = () => { 95 | player.addEventListener("framereceived", () => fpsCounter.cam++); 96 | player.addEventListener( 97 | "frameprocessed", 98 | ({ detail }) => (fpsCounter.processing = 1000 / detail.averagedDuration) 99 | ); 100 | player.addEventListener("framerendered", () => fpsCounter.render++); 101 | 102 | setInterval(() => { 103 | fps.cam = fpsCounter.cam; 104 | fps.render = fpsCounter.render; 105 | fps.processing = fpsCounter.processing; 106 | fpsCounter.cam = 0; 107 | fpsCounter.render = 0; 108 | }, 1000); 109 | }; 110 | 111 | let curResult; 112 | let analyseFunc; 113 | const renderAnalysisResultFuncs = { 114 | Detection_gestures: async (paramString, resultBlock) => { 115 | const res = await currentEffect.evalJs(paramString); 116 | 117 | if (!(curResult !== res && res !== undefined)) { 118 | return false; 119 | } 120 | 121 | curResult = res; 122 | 123 | const icon = 124 | res === "No Gesture" 125 | ? "" 126 | : `${curResult}`; 127 | 128 | resultBlock.innerHTML = `${icon}${curResult}`; 129 | }, 130 | 131 | heart_rate: async (paramString, resultBlock) => { 132 | const res = await currentEffect.evalJs(paramString); 133 | if (!(curResult !== res && res !== undefined)) { 134 | return false; 135 | } 136 | 137 | curResult = res; 138 | 139 | if (curResult.includes("calculation")) { 140 | resultBlock.classList.add("heart-rate__analyse"); 141 | } else { 142 | resultBlock.classList.remove("heart-rate__analyse"); 143 | } 144 | 145 | resultBlock.innerText = curResult; 146 | 147 | return true; 148 | }, 149 | 150 | test_Ruler: async (paramString, resultBlock) => { 151 | const res = await currentEffect.evalJs(paramString); 152 | if (curResult !== res && res !== undefined) { 153 | curResult = res; 154 | resultBlock.innerText = curResult; 155 | } 156 | }, 157 | }; 158 | 159 | /** 160 | * __analyticsState can be "enabled" or "disabled" 161 | */ 162 | const __analyticsActive = "active"; 163 | const __analyticsInActive = "inactive"; 164 | let _analyticsState = __analyticsInActive; 165 | 166 | export const startAnalysis = async (effectName, paramString, resultBlock) => { 167 | analyseFunc = () => 168 | renderAnalysisResultFuncs[effectName.split(".")[0]]( 169 | paramString, 170 | resultBlock 171 | ); 172 | player.addEventListener("framedata", analyseFunc); 173 | _analyticsState = __analyticsActive; 174 | }; 175 | 176 | export const stopAnalysis = () => { 177 | if (_analyticsState === __analyticsActive) 178 | player.removeEventListener("framedata", analyseFunc); 179 | _analyticsState = __analyticsInActive; 180 | }; 181 | 182 | export const clearEffect = async () => { 183 | await player.clearEffect(); 184 | }; 185 | 186 | export const muteToggle = (value) => { 187 | player.setVolume(value); 188 | }; 189 | 190 | export const getSource = (sourceType, file) => { 191 | return sourceType === "webcam" ? new Webcam() : new Image(file); 192 | }; 193 | 194 | export const getPlayer = () => { 195 | return player; 196 | }; 197 | 198 | export const startPlayer = (source) => { 199 | player.use(source); 200 | Dom.render(player, "#webar"); 201 | startFpsTracking(); 202 | }; 203 | 204 | export const applyEffect = async (effectName) => { 205 | currentEffect = new Effect(effectName); 206 | await player.applyEffect(currentEffect); 207 | }; 208 | 209 | export const applyEffectParam = async (paramString) => { 210 | await currentEffect.evalJs(paramString); 211 | }; 212 | 213 | export const startGame = () => { 214 | currentEffect.evalJs("isButtonTouched").then((isButtonTouched) => { 215 | if (isButtonTouched === "false") { 216 | currentEffect.evalJs("onClick()"); 217 | } 218 | }); 219 | }; 220 | 221 | export const getScreenshot = async () => { 222 | const capture = new ImageCapture(player); 223 | return await capture.takePhoto(); 224 | }; 225 | 226 | let recorder; 227 | const getRecorder = () => { 228 | if (recorder) return recorder; 229 | 230 | recorder = new VideoRecorder(player); 231 | return recorder; 232 | }; 233 | 234 | export const startRecord = () => { 235 | getRecorder().start(); 236 | }; 237 | 238 | export const stopRecord = async () => { 239 | return await getRecorder().stop(); 240 | }; 241 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Banuba Limited 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Banuba SDK Web AR demo app 2 | 3 | ## Requirements 4 | 5 | - Banuba [client token](#obtaining-banuba-client-token) 6 | - [Nodejs](https://nodejs.org/en/) installed 7 | - Browser with support of [WebGL 2.0](https://caniuse.com/#feat=webgl2) 8 | 9 | ### Obtaining Banuba SDK Web AR 10 | 11 | The example uses CDN version of the [@banuba/webar](https://www.npmjs.com/package/@banuba/webar) npm package for simplicity. 12 | Please use the npm package mentioned above for real world projects. 13 | Check out the [Integration tutorials](https://docs.banuba.com/far-sdk/tutorials/development/basic_integration?platform=web) for more ways of consuming [@banuba/webar](https://www.npmjs.com/package/@banuba/webar) package. 14 | 15 | ### Obtaining Banuba Client token 16 | 17 | Banuba Client token is required to get Banuba SDK Web AR working. 18 | 19 | To receive a new **trial** client token please fill in the [form on banuba.com](https://www.banuba.com/face-filters-sdk) website, or contact us via [info@banuba.com](mailto:info@banuba.com). 20 | 21 | ## Environment setup and local run 22 | 23 | Clone the repository 24 | 25 | ```sh 26 | git clone git@github.com:Banuba/quickstart-web.git 27 | ``` 28 | 29 | Insert Banuba [client token](#obtaining-banuba-client-token) into `BanubaClientToken.js` 30 | 31 | ```js 32 | window.BANUBA_CLIENT_TOKEN = "PUT YOUR CLIENT TOKEN HERE"; 33 | ``` 34 | 35 | Run the live server in the cloned folder 36 | 37 | ```sh 38 | npx live-server 39 | ``` 40 | 41 | Open [localhost:8080](http://localhost:8080) 42 | 43 | ## Adding a new effect 44 | 45 | Put effect zip file to `import/` folder and add zip file name to `./import/effectsList.js` file. 46 | Example: 47 | 48 | ```js 49 | export const importedEffectsList = ["your_effect_1.zip", "your_effect_2.zip"]; 50 | ``` 51 | 52 | You can obtain more effects on the [Demo Face Filters](https://docs.banuba.com/far-sdk/tutorials/capabilities/demo_face_filters) page. 53 | 54 | --- 55 | 56 | Learn more about Banuba WebAR SDK on the [Web](https://docs.banuba.com/far-sdk/tutorials/development/basic_integration?platform=web) section of [docs.banuba.com](https://docs.banuba.com). 57 | -------------------------------------------------------------------------------- /assets/effects/BG.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/BG.zip -------------------------------------------------------------------------------- /assets/effects/ConfusedRabbit.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/ConfusedRabbit.zip -------------------------------------------------------------------------------- /assets/effects/DebugFRX.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/DebugFRX.zip -------------------------------------------------------------------------------- /assets/effects/Detection_gestures.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Detection_gestures.zip -------------------------------------------------------------------------------- /assets/effects/Eye_lenses.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Eye_lenses.zip -------------------------------------------------------------------------------- /assets/effects/Eye_lenses_Blue.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Eye_lenses_Blue.zip -------------------------------------------------------------------------------- /assets/effects/Eye_lenses_Green.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Eye_lenses_Green.zip -------------------------------------------------------------------------------- /assets/effects/EyesWitening_Toggle.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/EyesWitening_Toggle.zip -------------------------------------------------------------------------------- /assets/effects/FG.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/FG.zip -------------------------------------------------------------------------------- /assets/effects/FlappyPlane_mouth.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/FlappyPlane_mouth.zip -------------------------------------------------------------------------------- /assets/effects/Full_Body.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Full_Body.zip -------------------------------------------------------------------------------- /assets/effects/Gangster.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Gangster.zip -------------------------------------------------------------------------------- /assets/effects/Hades.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Hades.zip -------------------------------------------------------------------------------- /assets/effects/Hair.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Hair.zip -------------------------------------------------------------------------------- /assets/effects/Lips.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Lips.zip -------------------------------------------------------------------------------- /assets/effects/Low_look_clubs.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Low_look_clubs.zip -------------------------------------------------------------------------------- /assets/effects/MinnieMouse7_multi.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/MinnieMouse7_multi.zip -------------------------------------------------------------------------------- /assets/effects/Morphings_1.7.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Morphings_1.7.0.zip -------------------------------------------------------------------------------- /assets/effects/Regular_blur.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Regular_blur.zip -------------------------------------------------------------------------------- /assets/effects/Retrowave.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Retrowave.zip -------------------------------------------------------------------------------- /assets/effects/Ring_01.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Ring_01.zip -------------------------------------------------------------------------------- /assets/effects/Skin.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Skin.zip -------------------------------------------------------------------------------- /assets/effects/SkinSoftening.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/SkinSoftening.zip -------------------------------------------------------------------------------- /assets/effects/Spider2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Spider2.zip -------------------------------------------------------------------------------- /assets/effects/Sunset.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/Sunset.zip -------------------------------------------------------------------------------- /assets/effects/TeethWitening_Toggle.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/TeethWitening_Toggle.zip -------------------------------------------------------------------------------- /assets/effects/TrollGrandma.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/TrollGrandma.zip -------------------------------------------------------------------------------- /assets/effects/VTO_Hair_blue.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/VTO_Hair_blue.zip -------------------------------------------------------------------------------- /assets/effects/VTO_Hair_green.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/VTO_Hair_green.zip -------------------------------------------------------------------------------- /assets/effects/VTO_Hair_strand.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/VTO_Hair_strand.zip -------------------------------------------------------------------------------- /assets/effects/VTO_Headdresse_01.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/VTO_Headdresse_01.zip -------------------------------------------------------------------------------- /assets/effects/What_Animal_Are_You.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/What_Animal_Are_You.zip -------------------------------------------------------------------------------- /assets/effects/WhooshBeautyFemale.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/WhooshBeautyFemale.zip -------------------------------------------------------------------------------- /assets/effects/dialect.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/dialect.zip -------------------------------------------------------------------------------- /assets/effects/earrings_01.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/earrings_01.zip -------------------------------------------------------------------------------- /assets/effects/glasses_RayBan4165_Dark.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/glasses_RayBan4165_Dark.zip -------------------------------------------------------------------------------- /assets/effects/heart_rate.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/heart_rate.zip -------------------------------------------------------------------------------- /assets/effects/necklace_01.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/necklace_01.zip -------------------------------------------------------------------------------- /assets/effects/test_Ruler.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/test_Ruler.zip -------------------------------------------------------------------------------- /assets/effects/video_BG_RainyCafe.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/effects/video_BG_RainyCafe.zip -------------------------------------------------------------------------------- /assets/icons/controls/icon-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/controls/icon-record-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/controls/icon-record.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/controls/icon-reset.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/controls/icon-screenshot-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/controls/icon-screenshot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/controls/icon-sound-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/controls/icon-sound.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/effects/BG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/icons/effects/BG.png -------------------------------------------------------------------------------- /assets/icons/effects/Eye_lenses_Blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/icons/effects/Eye_lenses_Blue.png -------------------------------------------------------------------------------- /assets/icons/effects/Eye_lenses_Green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/icons/effects/Eye_lenses_Green.png -------------------------------------------------------------------------------- /assets/icons/effects/FG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/icons/effects/FG.png -------------------------------------------------------------------------------- /assets/icons/effects/Glasses_Dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/icons/effects/Glasses_Dark.png -------------------------------------------------------------------------------- /assets/icons/effects/Regular_blur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/icons/effects/Regular_blur.png -------------------------------------------------------------------------------- /assets/icons/effects/VTO_Hair_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/icons/effects/VTO_Hair_blue.png -------------------------------------------------------------------------------- /assets/icons/effects/VTO_Hair_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/icons/effects/VTO_Hair_green.png -------------------------------------------------------------------------------- /assets/icons/effects/VTO_Hair_strand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/icons/effects/VTO_Hair_strand.png -------------------------------------------------------------------------------- /assets/icons/effects/WhooshBeautyFemale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/icons/effects/WhooshBeautyFemale.png -------------------------------------------------------------------------------- /assets/icons/effects/dialect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/icons/effects/dialect.png -------------------------------------------------------------------------------- /assets/icons/effects/earrings_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/icons/effects/earrings_01.png -------------------------------------------------------------------------------- /assets/icons/effects/eyebrows_bend.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /assets/icons/effects/eyebrows_height.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /assets/icons/effects/eyebrows_spacing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /assets/icons/effects/eyes_enlargement.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /assets/icons/effects/eyes_height.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /assets/icons/effects/eyes_lower_eyelid_pos.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /assets/icons/effects/eyes_lower_eyelid_size.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /assets/icons/effects/eyes_rounding.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /assets/icons/effects/eyes_spacing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /assets/icons/effects/eyes_squint.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /assets/icons/effects/face_cheekbones_narrowing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /assets/icons/effects/face_cheeks_jaw_narrowing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /assets/icons/effects/face_cheeks_narrowing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /assets/icons/effects/face_chin_narrowing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /assets/icons/effects/face_chin_shortening.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/effects/face_jaw_narrowing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/icons/effects/face_narrowing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /assets/icons/effects/face_sunken_cheeks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /assets/icons/effects/face_v_shape.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /assets/icons/effects/lips_height.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /assets/icons/effects/lips_mouth_size.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/effects/lips_shape.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /assets/icons/effects/lips_size.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/icons/effects/lips_smile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /assets/icons/effects/lips_thickness.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/effects/necklace_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/icons/effects/necklace_01.png -------------------------------------------------------------------------------- /assets/icons/effects/nose_length.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /assets/icons/effects/nose_tip_width.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /assets/icons/effects/nose_width.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /assets/icons/effects/video_BG_RainyCafe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banuba/quickstart-web/a26f113f849657114dd1d5ced4b05ee39919a085/assets/icons/effects/video_BG_RainyCafe.png -------------------------------------------------------------------------------- /effectsConfig.js: -------------------------------------------------------------------------------- 1 | export const effectsList = { 2 | ar_games: { 3 | label: "AR Games", 4 | categories: { 5 | flappy_plane: { 6 | label: "Flappy Plane", 7 | effects: [{ name: "FlappyPlane_mouth.zip" }], 8 | }, 9 | what_animal_are_you: { 10 | label: "What Animal Are You", 11 | effects: [{ name: "What_Animal_Are_You.zip", control: "game" }], 12 | }, 13 | }, 14 | }, 15 | 16 | ar_videocall: { 17 | label: "AR VideoCall", 18 | categories: { 19 | background_separation: { 20 | label: "Background separation", 21 | effects: [ 22 | { name: "Regular_blur.zip", icon: "Regular_blur.png" }, 23 | { name: "video_BG_RainyCafe.zip", icon: "video_BG_RainyCafe.png" }, 24 | ], 25 | }, 26 | beauty_filter: { 27 | label: "Beauty Filter", 28 | effects: [ 29 | { name: "dialect.zip", icon: "dialect.png" }, 30 | { name: "WhooshBeautyFemale.zip", icon: "WhooshBeautyFemale.png" }, 31 | ], 32 | }, 33 | lightning_color_correction: { 34 | label: "Lightning and Color correction", 35 | effects: [{ name: "Sunset.zip" }], 36 | }, 37 | }, 38 | }, 39 | 40 | avatar: { 41 | label: "Avatar", 42 | categories: { 43 | hades: { 44 | label: "Hades", 45 | effects: [{ name: "Hades.zip" }], 46 | }, 47 | }, 48 | }, 49 | 50 | beauty_touch_up: { 51 | label: "Beauty Touch UP", 52 | categories: { 53 | facemorphing: { 54 | label: "Face morphing", 55 | effects: [ 56 | { 57 | name: "Morphings_1.7.0.zip", 58 | control: "slider", 59 | params: ["FaceMorph.face"], 60 | arg: "narrowing", 61 | direction: 1, 62 | icon: "face_narrowing.svg", 63 | }, 64 | { 65 | name: "Morphings_1.7.0.zip", 66 | control: "slider", 67 | params: ["FaceMorph.face"], 68 | arg: "v_shape", 69 | direction: 1, 70 | icon: "face_v_shape.svg", 71 | }, 72 | { 73 | name: "Morphings_1.7.0.zip", 74 | control: "slider", 75 | params: ["FaceMorph.face"], 76 | arg: "cheekbones_narrowing", 77 | direction: 1, 78 | icon: "face_cheekbones_narrowing.svg", 79 | }, 80 | { 81 | name: "Morphings_1.7.0.zip", 82 | control: "slider", 83 | params: ["FaceMorph.face"], 84 | arg: "cheeks_narrowing", 85 | direction: 1, 86 | icon: "face_cheeks_narrowing.svg", 87 | }, 88 | { 89 | name: "Morphings_1.7.0.zip", 90 | control: "slider", 91 | params: ["FaceMorph.face"], 92 | arg: "jaw_narrowing", 93 | direction: 1, 94 | icon: "face_jaw_narrowing.svg", 95 | }, 96 | { 97 | name: "Morphings_1.7.0.zip", 98 | control: "slider", 99 | params: ["FaceMorph.face"], 100 | arg: "chin_shortening", 101 | direction: 1, 102 | icon: "face_chin_shortening.svg", 103 | }, 104 | { 105 | name: "Morphings_1.7.0.zip", 106 | control: "slider", 107 | params: ["FaceMorph.face"], 108 | arg: "chin_narrowing", 109 | direction: 1, 110 | icon: "face_chin_narrowing.svg", 111 | }, 112 | { 113 | name: "Morphings_1.7.0.zip", 114 | control: "slider", 115 | params: ["FaceMorph.face"], 116 | arg: "sunken_cheeks", 117 | minValue: "0", 118 | direction: 1, 119 | icon: "face_sunken_cheeks.svg", 120 | }, 121 | { 122 | name: "Morphings_1.7.0.zip", 123 | control: "slider", 124 | params: ["FaceMorph.face"], 125 | arg: "cheeks_jaw_narrowing", 126 | direction: 1, 127 | icon: "face_cheeks_jaw_narrowing.svg", 128 | }, 129 | ], 130 | }, 131 | nose: { 132 | label: "Nose", 133 | effects: [ 134 | { 135 | name: "Morphings_1.7.0.zip", 136 | control: "slider", 137 | params: ["FaceMorph.nose"], 138 | arg: "width", 139 | direction: 1, 140 | icon: "nose_width.svg", 141 | }, 142 | { 143 | name: "Morphings_1.7.0.zip", 144 | control: "slider", 145 | params: ["FaceMorph.nose"], 146 | arg: "length", 147 | direction: 1, 148 | icon: "nose_length.svg", 149 | }, 150 | { 151 | name: "Morphings_1.7.0.zip", 152 | control: "slider", 153 | params: ["FaceMorph.nose"], 154 | arg: "tip_width", 155 | direction: -1, 156 | icon: "nose_tip_width.svg", 157 | }, 158 | ], 159 | }, 160 | eyes: { 161 | label: "Eyes", 162 | effects: [ 163 | { 164 | name: "Morphings_1.7.0.zip", 165 | control: "slider", 166 | params: ["FaceMorph.eyes"], 167 | arg: "rounding", 168 | minValue: "0", 169 | direction: 1, 170 | icon: "eyes_rounding.svg", 171 | }, 172 | { 173 | name: "Morphings_1.7.0.zip", 174 | control: "slider", 175 | params: ["FaceMorph.eyes"], 176 | arg: "enlargement", 177 | direction: 1, 178 | icon: "eyes_enlargement.svg", 179 | }, 180 | { 181 | name: "Morphings_1.7.0.zip", 182 | control: "slider", 183 | params: ["FaceMorph.eyes"], 184 | arg: "height", 185 | direction: 1, 186 | icon: "eyes_height.svg", 187 | }, 188 | { 189 | name: "Morphings_1.7.0.zip", 190 | control: "slider", 191 | params: ["FaceMorph.eyes"], 192 | arg: "spacing", 193 | direction: 1, 194 | icon: "eyes_spacing.svg", 195 | }, 196 | { 197 | name: "Morphings_1.7.0.zip", 198 | control: "slider", 199 | params: ["FaceMorph.eyes"], 200 | arg: "squint", 201 | direction: -1, 202 | icon: "eyes_squint.svg", 203 | }, 204 | { 205 | name: "Morphings_1.7.0.zip", 206 | control: "slider", 207 | params: ["FaceMorph.eyes"], 208 | arg: "lower_eyelid_pos", 209 | direction: 1, 210 | icon: "eyes_lower_eyelid_pos.svg", 211 | }, 212 | { 213 | name: "Morphings_1.7.0.zip", 214 | control: "slider", 215 | params: ["FaceMorph.eyes"], 216 | arg: "lower_eyelid_size", 217 | direction: -1, 218 | icon: "eyes_lower_eyelid_size.svg", 219 | }, 220 | ], 221 | }, 222 | eyebrows: { 223 | label: "Eyebrows", 224 | effects: [ 225 | { 226 | name: "Morphings_1.7.0.zip", 227 | control: "slider", 228 | params: ["FaceMorph.eyebrows"], 229 | arg: "spacing", 230 | direction: -1, 231 | icon: "eyebrows_spacing.svg", 232 | }, 233 | { 234 | name: "Morphings_1.7.0.zip", 235 | control: "slider", 236 | params: ["FaceMorph.eyebrows"], 237 | arg: "height", 238 | direction: -1, 239 | icon: "eyebrows_height.svg", 240 | }, 241 | { 242 | name: "Morphings_1.7.0.zip", 243 | control: "slider", 244 | params: ["FaceMorph.eyebrows"], 245 | arg: "bend", 246 | direction: 1, 247 | icon: "eyebrows_bend.svg", 248 | }, 249 | ], 250 | }, 251 | lips: { 252 | label: "Lips", 253 | effects: [ 254 | { 255 | name: "Morphings_1.7.0.zip", 256 | control: "slider", 257 | params: ["FaceMorph.lips"], 258 | direction: 1, 259 | arg: "size", 260 | icon: "lips_size.svg", 261 | }, 262 | { 263 | name: "Morphings_1.7.0.zip", 264 | control: "slider", 265 | params: ["FaceMorph.lips"], 266 | direction: 1, 267 | arg: "height", 268 | icon: "lips_height.svg", 269 | }, 270 | { 271 | name: "Morphings_1.7.0.zip", 272 | control: "slider", 273 | params: ["FaceMorph.lips"], 274 | direction: 1, 275 | arg: "thickness", 276 | icon: "lips_thickness.svg", 277 | }, 278 | { 279 | name: "Morphings_1.7.0.zip", 280 | control: "slider", 281 | params: ["FaceMorph.lips"], 282 | direction: -1, 283 | arg: "mouth_size", 284 | icon: "lips_mouth_size.svg", 285 | }, 286 | { 287 | name: "Morphings_1.7.0.zip", 288 | control: "slider", 289 | params: ["FaceMorph.lips"], 290 | direction: 1, 291 | minValue: "0", 292 | arg: "smile", 293 | icon: "lips_smile.svg", 294 | }, 295 | { 296 | name: "Morphings_1.7.0.zip", 297 | control: "slider", 298 | params: ["FaceMorph.lips"], 299 | direction: -1, 300 | arg: "shape", 301 | icon: "lips_shape.svg", 302 | }, 303 | ], 304 | }, 305 | skin: { 306 | label: "Skin", 307 | effects: [ 308 | { 309 | name: "SkinSoftening.zip", 310 | control: "slider", 311 | params: ["Skin.softening"], 312 | minValue: 0, 313 | direction: 1, 314 | }, 315 | ], 316 | }, 317 | eye_whitening: { 318 | label: "Eye Whitening", 319 | effects: [ 320 | { 321 | name: "EyesWitening_Toggle.zip", 322 | control: "toggle", 323 | params: ["onDataUpdate"], 324 | }, 325 | ], 326 | }, 327 | tooth_whitening: { 328 | label: "Tooth Whitening", 329 | effects: [ 330 | { 331 | name: "TeethWitening_Toggle.zip", 332 | control: "toggle", 333 | params: ["onDataUpdate"], 334 | }, 335 | ], 336 | }, 337 | }, 338 | }, 339 | 340 | face_tracking: { 341 | label: "Face Tracking", 342 | categories: { 343 | background_foreground: { 344 | label: "Background/Foreground", 345 | effects: [ 346 | { name: "BG.zip", icon: "BG.png" }, 347 | { name: "FG.zip", icon: "FG.png" }, 348 | ], 349 | }, 350 | body_segmentation: { 351 | label: "Body segmentation", 352 | effects: [{ name: "Full_Body.zip" }], 353 | }, 354 | distance_to_phone: { 355 | label: "Distance to camera", 356 | effects: [ 357 | { 358 | name: "test_Ruler.zip", 359 | control: "analyze", 360 | params: ["onDataUpdate()"], 361 | }, 362 | ], 363 | }, 364 | eye_segmentation: { 365 | label: "Eye segmentation", 366 | effects: [{ name: "Eye_lenses.zip" }], 367 | }, 368 | hair_segmentation: { 369 | label: "Hair segmentation", 370 | effects: [{ name: "Hair.zip" }], 371 | }, 372 | landmarks: { 373 | label: "Landmarks", 374 | effects: [{ name: "DebugFRX.zip" }], 375 | }, 376 | lips_segmentation: { 377 | label: "Lips segmentation", 378 | effects: [{ name: "Lips.zip" }], 379 | }, 380 | skin_segmentation: { 381 | label: "Skin Segmentation", 382 | effects: [{ name: "Skin.zip" }], 383 | }, 384 | analytics: { 385 | label: "Analytics", 386 | effects: [ 387 | { 388 | name: "heart_rate.zip", 389 | control: "analyze", 390 | params: ["onDataUpdate()"], 391 | }, 392 | ], 393 | }, 394 | }, 395 | }, 396 | 397 | face_masks: { 398 | label: "Face Masks", 399 | categories: { 400 | animation: { 401 | label: "Animation", 402 | effects: [{ name: "Spider2.zip" }], 403 | }, 404 | foreground_effects: { 405 | label: "Foreground effects", 406 | effects: [{ name: "Retrowave.zip" }], 407 | }, 408 | masks_morphing: { 409 | label: "Masks with Morphing", 410 | effects: [{ name: "TrollGrandma.zip" }], 411 | }, 412 | multiple_face_detection: { 413 | label: "Multiple Face Detection", 414 | effects: [{ name: "MinnieMouse7_multi.zip" }], 415 | }, 416 | physics: { 417 | label: "Physics", 418 | effects: [{ name: "ConfusedRabbit.zip" }], 419 | }, 420 | triggers: { 421 | label: "Triggers", 422 | effects: [ 423 | { 424 | name: "Gangster.zip", 425 | control: "trigger", 426 | params: ["GetMouthStatus()"], 427 | tip: "Open your mouth", 428 | icon: "Gangster.svg", 429 | }, 430 | ], 431 | }, 432 | }, 433 | }, 434 | 435 | hand_tracking: { 436 | label: "Hand Tracking", 437 | categories: { 438 | gestures_detection: { 439 | label: "Gestures Detection", 440 | effects: [ 441 | { 442 | name: "Detection_gestures.zip", 443 | control: "analyze", 444 | params: ["getGesture()"], 445 | }, 446 | ], 447 | }, 448 | rings_try_on: { 449 | label: "Rings try on", 450 | effects: [{ name: "Ring_01.zip" }], 451 | }, 452 | }, 453 | }, 454 | 455 | virtual_try_on: { 456 | label: "Virtual Try On", 457 | categories: { 458 | glasses_try_on: { 459 | label: "Glasses Try On", 460 | effects: [ 461 | { name: "Eye_lenses_Blue.zip", icon: "Eye_lenses_Blue.png" }, 462 | { name: "Eye_lenses_Green.zip", icon: "Eye_lenses_Green.png" }, 463 | { name: "glasses_RayBan4165_Dark.zip", icon: "Glasses_Dark.png" }, 464 | ], 465 | }, 466 | hair: { 467 | label: "Hair Coloring", 468 | effects: [ 469 | { name: "VTO_Hair_blue.zip", icon: "VTO_Hair_blue.png" }, 470 | { name: "VTO_Hair_green.zip", icon: "VTO_Hair_green.png" }, 471 | { name: "VTO_Hair_strand.zip", icon: "VTO_Hair_strand.png" }, 472 | ], 473 | }, 474 | head_wearings: { 475 | label: "Head wearings", 476 | effects: [{ name: "VTO_Headdresse_01.zip" }], 477 | }, 478 | jewelry: { 479 | label: "Jewelry", 480 | effects: [ 481 | { name: "earrings_01.zip", icon: "earrings_01.png" }, 482 | { name: "necklace_01.zip", icon: "necklace_01.png" }, 483 | ], 484 | }, 485 | makeup: { 486 | label: "Makeup", 487 | effects: [{ name: "Low_look_clubs.zip" }], 488 | }, 489 | }, 490 | }, 491 | 492 | import: { 493 | label: "Imported", 494 | effects: [], 495 | }, 496 | }; 497 | -------------------------------------------------------------------------------- /import/effectsList.js: -------------------------------------------------------------------------------- 1 | export const importedEffectsList = [ 2 | // Place your effect zip-file to import folder and add here zip-file name. Example: 3 | //'your_effect_1.zip', 4 | // 'your_effect_2.zip' 5 | ]; 6 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Banuba SDK Web AR demo 7 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 38 |
39 | 40 | 73 | 74 | 75 | 76 |
77 |
78 |
79 |
80 | Webcam FPS:
81 | Processing FPS:
82 | Render FPS: 83 |
84 | 91 | 92 |
93 | 94 | 100 | 106 | 112 |
113 | 120 | 138 |
139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import "./src/index.js"; 2 | -------------------------------------------------------------------------------- /range-requests.sw.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Safari video range request fix, inspired by 3 | * @see https://philna.sh/blog/2018/10/23/service-workers-beware-safaris-range-request 4 | * @see https://bugs.webkit.org/show_bug.cgi?id=232076#c5 5 | */ 6 | addEventListener("fetch", (event) => { 7 | const proxyVideoRequestsTo = "___range-requests___/"; 8 | 9 | const request = event.request; 10 | 11 | const [, url] = request.url.split(proxyVideoRequestsTo); 12 | if (!url) return; 13 | 14 | const cacheName = "___range-requests___"; 15 | 16 | const response = caches 17 | .open(cacheName) 18 | .then((cache) => cache.match(request.url)) 19 | .then( 20 | (res) => 21 | res || 22 | fetch(new Request(decodeURIComponent(url), request)).then((res) => 23 | caches 24 | .open(cacheName) 25 | .then((cache) => cache.put(request, res.clone())) 26 | .then(() => res), 27 | ), 28 | ) 29 | .then((res) => 30 | Promise.all([res.arrayBuffer(), res.headers.get("Content-Type")]), 31 | ) 32 | .then(([arrayBuffer, type]) => { 33 | const bytes = /^bytes\=(\d+)\-(\d+)?$/g.exec( 34 | request.headers.get("range"), 35 | ); 36 | 37 | if (bytes) { 38 | const length = arrayBuffer.byteLength; 39 | const start = +bytes[1] || 0; 40 | const end = +bytes[2] || length - 1; 41 | 42 | return new Response(arrayBuffer.slice(start, end + 1), { 43 | status: 206, 44 | statusText: "Partial Content", 45 | headers: { 46 | "Content-Type": type, 47 | "Content-Range": `bytes ${start}-${end}/${length}`, 48 | "Content-Length": end - start + 1, 49 | }, 50 | }); 51 | } else { 52 | return new Response(null, { 53 | status: 416, 54 | statusText: "Range Not Satisfiable", 55 | headers: { 56 | "Content-Range": `*/${length}`, 57 | }, 58 | }); 59 | } 60 | }); 61 | 62 | event.respondWith(response); 63 | }); 64 | 65 | /** 66 | * You may also want to add the `clients.claim()` to install the worker as early as possible 67 | * @see https://developer.mozilla.org/en-US/docs/Web/API/Clients/claim 68 | */ 69 | addEventListener("activate", (event) => event.waitUntil(clients.claim())); 70 | -------------------------------------------------------------------------------- /src/effect.js: -------------------------------------------------------------------------------- 1 | import { importedEffectsList } from "../import/effectsList.js"; 2 | import { 3 | getSelectedTechnology, 4 | getSelectedCategory, 5 | getSelectedEffect, 6 | setSelectedEffect, 7 | setControlBlock, 8 | setControlFunc, 9 | setCurEventType, 10 | getControlBlock, 11 | getControlFunc, 12 | getCurEventType, 13 | } from "./state.js"; 14 | import { 15 | applyEffect, 16 | applyEffectParam, 17 | startAnalysis, 18 | startGame, 19 | } from "../BanubaPlayer.js"; 20 | 21 | import { 22 | heartRateBlock, 23 | testRulerBlock, 24 | handGesturesTipBlock, 25 | effectControlBlock, 26 | handGesturesBlock, 27 | } from "./elements.js"; 28 | 29 | export const getImportedEffects = (effectsList) => { 30 | importedEffectsList.forEach((effect) => 31 | effectsList.import.effects.push({ name: effect }), 32 | ); 33 | }; 34 | 35 | export const setEffectParam = async (params, value, arg) => { 36 | for (const param of params) { 37 | const s = arg ? `${param}({${arg}:${value}})` : `${param}(${value})`; 38 | await applyEffectParam(s); 39 | } 40 | }; 41 | 42 | export const addEffectControlHandler = (control) => { 43 | setCurEventType(control); 44 | 45 | switch (control) { 46 | case "slider": 47 | const min = 48 | getSelectedEffect().minValue !== undefined 49 | ? getSelectedEffect().minValue 50 | : -10; 51 | 52 | effectControlBlock.innerHTML = ` 53 |
54 | 55 |
`; 56 | 57 | setControlBlock(document.querySelector(".effect-control__slider")); 58 | 59 | const value = ((0 - min) / (10 - min)) * 100; 60 | 61 | getControlBlock().style.background = 62 | "linear-gradient(to right, #4794FE 0%, #4794FE " + 63 | value + 64 | "%, #EEF2F7 " + 65 | value + 66 | "%, #EEF2F7 100%)"; 67 | 68 | setControlFunc(async (e) => { 69 | const value = 70 | ((e.target.value - e.target.min) / (e.target.max - e.target.min)) * 71 | 100; 72 | document.querySelector(".effect-control__slider").style.background = 73 | "linear-gradient(to right, #4794FE 0%, #4794FE " + 74 | value + 75 | "%, #EEF2F7 " + 76 | value + 77 | "%, #EEF2F7 100%)"; 78 | await setEffectParam( 79 | getSelectedEffect().params, 80 | (e.target.value * getSelectedEffect().direction) / 10, 81 | getSelectedEffect()?.arg, 82 | ); 83 | }); 84 | 85 | getControlBlock().addEventListener("input", getControlFunc()); 86 | break; 87 | 88 | case "toggle": 89 | effectControlBlock.innerHTML = 90 | ''; 91 | 92 | setControlBlock(document.querySelector(".effect-control__toggle")); 93 | 94 | setControlFunc(async (e) => { 95 | await setEffectParam( 96 | getSelectedEffect().params, 97 | e.target.checked ? 1 : 0, 98 | ); 99 | }); 100 | 101 | getControlBlock().addEventListener("change", getControlFunc()); 102 | break; 103 | 104 | case "analyze": 105 | if (getSelectedEffect().name === "Detection_gestures.zip") { 106 | handGesturesTipBlock.classList.remove("hidden"); 107 | setControlBlock(handGesturesBlock); 108 | } else if (getSelectedEffect().name === "heart_rate.zip") { 109 | heartRateBlock.classList.remove("hidden"); 110 | setControlBlock(heartRateBlock); 111 | } else if (getSelectedEffect().name === "test_Ruler.zip") { 112 | testRulerBlock.classList.remove("hidden"); 113 | setControlBlock(testRulerBlock); 114 | } 115 | setControlFunc( 116 | startAnalysis( 117 | getSelectedEffect().name, 118 | getSelectedEffect().params[0], 119 | getControlBlock(), 120 | ), 121 | ); 122 | break; 123 | 124 | case "game": 125 | setControlFunc(startGame); 126 | setControlBlock(document.querySelector("#webar")); 127 | getControlBlock().addEventListener("click", getControlFunc()); 128 | break; 129 | 130 | default: 131 | setControlBlock(null); 132 | setControlFunc(null); 133 | setCurEventType(null); 134 | } 135 | }; 136 | 137 | export const startEffect = (effectIndex) => { 138 | setSelectedEffect(getSelectedCategory().effects[effectIndex]); 139 | const effectPath = 140 | getSelectedTechnology().label === "Imported" 141 | ? "import/" 142 | : "assets/effects/"; 143 | applyEffect(effectPath + getSelectedEffect().name).then(() => 144 | addEffectControlHandler(getSelectedEffect()?.control), 145 | ); 146 | }; 147 | -------------------------------------------------------------------------------- /src/elements.js: -------------------------------------------------------------------------------- 1 | export const spinner = document.querySelector(".start-screen__spinner"); 2 | export const startScreen = document.querySelector(".start-screen"); 3 | export const categoriesBlock = document.querySelector(".categories"); 4 | export const categoriesButtonLeft = document.querySelector( 5 | ".categories-button__left", 6 | ); 7 | export const categoriesButtonRight = document.querySelector( 8 | ".categories-button__right", 9 | ); 10 | export const handGesturesBlock = document.querySelector(".hand-gestures"); 11 | export const handGesturesTipBlock = 12 | document.querySelector(".hand-gestures-tip"); 13 | export const techBlock = document.querySelector(".technologies"); 14 | export const effectControlBlock = document.querySelector(".effect-control"); 15 | export const importMessageBlock = document.querySelector(".import-message"); 16 | export const heartRateBlock = document.querySelector(".heart-rate"); 17 | export const testRulerBlock = document.querySelector(".test-ruler"); 18 | export const effectsBlock = document.querySelector(".effects-list"); 19 | export const resetButton = document.querySelector("#reset-button"); 20 | export const muteButton = document.querySelector("#mute-button"); 21 | export const screenshotButton = document.querySelector("#screenshot-button"); 22 | export const recDurationBlock = document.querySelector("#rec-duration"); 23 | export const recButton = document.querySelector("#rec-button"); 24 | export const popups = document.querySelector("#popups"); 25 | export const webcamSourceButton = document.querySelector("#webcam"); 26 | export const imageSourceButton = document.querySelector("#image"); 27 | export const overlay = document.querySelector(".overlay"); 28 | export const fpsBlock = document.querySelector("#fps"); 29 | 30 | categoriesButtonLeft 31 | .querySelector(".categories-button__left-icon") 32 | .addEventListener("click", () => { 33 | categoriesButtonLeft.classList.add("hidden"); 34 | categoriesButtonRight.classList.remove("hidden"); 35 | categoriesBlock.style.transform = "translateX(0px)"; 36 | }); 37 | 38 | categoriesButtonRight 39 | .querySelector(".categories-button__right-icon") 40 | .addEventListener("click", () => { 41 | categoriesButtonLeft.classList.remove("hidden"); 42 | categoriesButtonRight.classList.add("hidden"); 43 | categoriesBlock.style.transform = "translateX(-400px)"; 44 | }); 45 | 46 | document 47 | .querySelector(".hand-gestures-tip__button") 48 | .addEventListener("click", () => { 49 | handGesturesTipBlock.classList.add("hidden"); 50 | handGesturesBlock.classList.remove("hidden"); 51 | handGesturesBlock.classList.remove("hidden"); 52 | }); 53 | 54 | spinner.classList.add("hidden"); 55 | startScreen.querySelector(".start-screen__inner").classList.remove("hidden"); 56 | -------------------------------------------------------------------------------- /src/image-source.js: -------------------------------------------------------------------------------- 1 | import { fps, getSource, startPlayer } from "../BanubaPlayer.js"; 2 | 3 | import { 4 | webcamSourceButton, 5 | imageSourceButton, 6 | startScreen, 7 | overlay, 8 | fpsBlock, 9 | } from "./elements.js"; 10 | 11 | const onSourceSelect = () => { 12 | startScreen.classList.add("hidden"); 13 | overlay.classList.add("hidden"); 14 | setInterval(() => { 15 | fpsBlock.querySelectorAll("span").forEach((el) => { 16 | el.innerText = fps[el.id].toFixed(1); 17 | }); 18 | }); 19 | }; 20 | 21 | const onWebcamSelect = (e) => { 22 | const source = getSource(e.target.value); 23 | startPlayer(source); 24 | onSourceSelect(); 25 | }; 26 | 27 | const onImageSelect = (e) => { 28 | const source = getSource(e.target.value, e.target.files[0]); 29 | startPlayer(source); 30 | onSourceSelect(); 31 | }; 32 | 33 | webcamSourceButton.addEventListener("click", onWebcamSelect); 34 | imageSourceButton.addEventListener("change", onImageSelect); 35 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import "./elements.js"; 2 | 3 | import "./image-source.js"; 4 | import "./viewer-controls.js"; 5 | import "./toolbar.js"; 6 | -------------------------------------------------------------------------------- /src/state.js: -------------------------------------------------------------------------------- 1 | let _selectedTechnology; 2 | let _selectedCategory; 3 | 4 | let _selectedTechInput; 5 | let _selectedCategoryInput; 6 | 7 | let _selectedEffect; 8 | 9 | let _controlBlock; 10 | let _controlFunc; 11 | let _curEventType; 12 | 13 | export const getSelectedTechnology = () => _selectedTechnology; 14 | export const getSelectedCategory = () => _selectedCategory; 15 | export const getSelectedTechInput = () => _selectedTechInput; 16 | export const getSelectedCategoryInput = () => _selectedCategoryInput; 17 | export const getSelectedEffect = () => _selectedEffect; 18 | export const getControlBlock = () => _controlBlock; 19 | export const getControlFunc = () => _controlFunc; 20 | export const getCurEventType = () => _curEventType; 21 | 22 | export const setSelectedTechnology = (value) => (_selectedTechnology = value); 23 | export const setSelectedCategory = (value) => (_selectedCategory = value); 24 | export const setSelectedTechInput = (value) => (_selectedTechInput = value); 25 | export const setSelectedCategoryInput = (value) => 26 | (_selectedCategoryInput = value); 27 | export const setSelectedEffect = (value) => (_selectedEffect = value); 28 | export const setControlBlock = (value) => (_controlBlock = value); 29 | export const setControlFunc = (value) => (_controlFunc = value); 30 | export const setCurEventType = (value) => (_curEventType = value); 31 | -------------------------------------------------------------------------------- /src/toolbar.js: -------------------------------------------------------------------------------- 1 | import { effectsList } from "../effectsConfig.js"; 2 | import { 3 | getSelectedCategory, 4 | getSelectedTechInput, 5 | getSelectedTechnology, 6 | getSelectedCategoryInput, 7 | setSelectedCategory, 8 | setSelectedTechInput, 9 | setSelectedTechnology, 10 | setSelectedCategoryInput, 11 | setSelectedEffect, 12 | } from "./state.js"; 13 | import { clearEffect, stopAnalysis } from "../BanubaPlayer.js"; 14 | import { startEffect, getImportedEffects } from "./effect.js"; 15 | import { 16 | effectControlBlock, 17 | handGesturesBlock, 18 | handGesturesTipBlock, 19 | heartRateBlock, 20 | testRulerBlock, 21 | categoriesBlock, 22 | categoriesButtonRight, 23 | categoriesButtonLeft, 24 | resetButton, 25 | techBlock, 26 | importMessageBlock, 27 | effectsBlock, 28 | } from "./elements.js"; 29 | 30 | let controlBlock; 31 | let controlFunc; 32 | let curEventType; 33 | 34 | const removeEffectControlHandler = () => { 35 | effectControlBlock.innerHTML = ""; 36 | 37 | if (curEventType === "analyze") { 38 | stopAnalysis(); 39 | handGesturesBlock.classList.add("hidden"); 40 | handGesturesTipBlock.classList.add("hidden"); 41 | heartRateBlock.classList.add("hidden"); 42 | testRulerBlock.classList.add("hidden"); 43 | } else if (curEventType) { 44 | controlBlock.removeEventListener(curEventType, controlFunc); 45 | } 46 | }; 47 | 48 | const createEffectBlock = async (effects) => { 49 | let htmlBlock = ""; 50 | 51 | const onEffectSelect = async (e) => { 52 | removeEffectControlHandler(); 53 | await clearEffect(); 54 | startEffect(e?.target.value ?? 0); 55 | }; 56 | 57 | if (effects.length > 1) { 58 | for (let i in effects) { 59 | htmlBlock += ` 60 |
61 | 67 |
`; 68 | } 69 | 70 | effectsBlock.innerHTML = htmlBlock; 71 | effectsBlock.style.marginLeft = 72 | getSelectedCategoryInput().value === "facemorphing" ? "-60px" : null; 73 | 74 | document.querySelectorAll('input[name="effect"]').forEach((el, i) => { 75 | el.addEventListener("click", onEffectSelect); 76 | if (i === 0) el.click(); 77 | }); 78 | } else { 79 | effectsBlock.innerHTML = htmlBlock; 80 | await onEffectSelect(); 81 | } 82 | }; 83 | 84 | const createCategoryBlock = (categories) => { 85 | let htmlBlock = ""; 86 | 87 | for (let category in categories) { 88 | htmlBlock += ` 89 |
92 | 93 | 94 |
`; 95 | } 96 | 97 | categoriesBlock.innerHTML = htmlBlock; 98 | categoriesBlock.style.transform = ""; 99 | 100 | let children = categoriesBlock.children; 101 | let totalWidth = 0; 102 | 103 | for (let i = 0; i < children.length; i++) { 104 | totalWidth += parseInt(children[i].offsetWidth, 10); 105 | } 106 | 107 | if (totalWidth > 1088) { 108 | categoriesBlock.classList.add("categories-long"); 109 | categoriesButtonRight.classList.remove("hidden"); 110 | } else { 111 | categoriesBlock.classList.remove("categories-long"); 112 | categoriesButtonLeft.classList.add("hidden"); 113 | categoriesButtonRight.classList.add("hidden"); 114 | } 115 | 116 | const onCategorySelect = async (e) => { 117 | if (e.target === getSelectedCategoryInput()) { 118 | return; 119 | } 120 | 121 | stopAnalysis(); 122 | 123 | hidePopups(); 124 | 125 | setSelectedCategoryInput(e.target); 126 | setSelectedCategory( 127 | getSelectedTechnology().categories[getSelectedCategoryInput().value], 128 | ); 129 | await createEffectBlock(getSelectedCategory().effects); 130 | }; 131 | 132 | document.querySelectorAll('input[name="category"]').forEach((el, i) => { 133 | el.addEventListener("click", onCategorySelect); 134 | if (i === 0) el.click(); 135 | }); 136 | }; 137 | 138 | const createTechBlock = () => { 139 | for (let tech in effectsList) { 140 | techBlock.innerHTML += ` 141 |
142 | 143 | 144 |
`; 145 | } 146 | 147 | const onTechSelect = async (e) => { 148 | if (e.target === getSelectedTechInput()) { 149 | return; 150 | } 151 | 152 | hidePopups(); 153 | 154 | importMessageBlock.classList.add("hidden"); 155 | setSelectedTechInput(e.target); 156 | 157 | if (getSelectedTechInput().value === "import") { 158 | setSelectedTechnology({ label: "Imported", categories: {} }); 159 | effectsList[getSelectedTechInput().value].effects.forEach((effect) => { 160 | getSelectedTechnology().categories[effect.name] = { 161 | label: effect.name, 162 | effects: [effect], 163 | }; 164 | }); 165 | 166 | if (!effectsList[getSelectedTechInput().value].effects.length) { 167 | importMessageBlock.classList.remove("hidden"); 168 | removeEffectControlHandler(); 169 | await clearEffect(); 170 | effectsBlock.innerHTML = ""; 171 | } 172 | } else { 173 | setSelectedTechnology(effectsList[getSelectedTechInput().value]); 174 | } 175 | 176 | createCategoryBlock(getSelectedTechnology().categories); 177 | 178 | if (resetButton.disabled) { 179 | resetButton.disabled = false; 180 | } 181 | }; 182 | 183 | document.querySelectorAll('input[name="tech"]').forEach((el) => { 184 | el.addEventListener("click", onTechSelect); 185 | }); 186 | }; 187 | 188 | const hideElement = (element) => { 189 | element.classList.add("hidden"); 190 | }; 191 | 192 | const hidePopups = () => { 193 | hideElement(handGesturesBlock); 194 | hideElement(importMessageBlock); 195 | hideElement(heartRateBlock); 196 | hideElement(testRulerBlock); 197 | }; 198 | 199 | const onResetButtonClick = async () => { 200 | stopAnalysis(); 201 | await clearEffect(); 202 | 203 | setSelectedCategory(null); 204 | setSelectedTechnology(null); 205 | getSelectedTechInput().checked = false; 206 | setSelectedTechInput(null); 207 | setSelectedEffect(null); 208 | setSelectedCategoryInput(null); 209 | 210 | hidePopups(); 211 | 212 | effectsBlock.innerHTML = ""; 213 | categoriesBlock.innerHTML = ""; 214 | resetButton.disabled = !resetButton.disabled; 215 | }; 216 | 217 | resetButton.addEventListener("click", onResetButtonClick); 218 | 219 | createTechBlock(); 220 | getImportedEffects(effectsList); 221 | -------------------------------------------------------------------------------- /src/viewer-controls.js: -------------------------------------------------------------------------------- 1 | import { 2 | getScreenshot, 3 | muteToggle, 4 | startRecord, 5 | stopRecord, 6 | } from "../BanubaPlayer.js"; 7 | 8 | import { 9 | muteButton, 10 | screenshotButton, 11 | popups, 12 | recDurationBlock, 13 | recButton, 14 | } from "./elements.js"; 15 | 16 | let isSoundOn = 0; 17 | let recDurationInterval; 18 | let recDuration; 19 | let isRecording = 0; 20 | 21 | const onMuteButtonClick = () => { 22 | muteButton.src = !!isSoundOn 23 | ? "assets/icons/controls/icon-sound.svg" 24 | : "assets/icons/controls/icon-sound-active.svg"; 25 | isSoundOn = 1 - isSoundOn; 26 | muteToggle(isSoundOn); 27 | }; 28 | 29 | const onScreenshotButtonClick = async (e) => { 30 | if (e.type === "mousedown") { 31 | screenshotButton.src = "assets/icons/controls/icon-screenshot-active.svg"; 32 | } else { 33 | screenshotButton.src = "assets/icons/controls/icon-screenshot.svg"; 34 | const url = URL.createObjectURL(await getScreenshot()); 35 | const popup = document.createElement("div"); 36 | popup.classList.add("popup", "popup__hidden"); 37 | popup.innerHTML = `Screenshot is ready Check the link`; 38 | popups.prepend(popup); 39 | 40 | setTimeout(() => { 41 | popup.classList.remove("popup__hidden"); 42 | }, 20); 43 | 44 | setTimeout(() => { 45 | popup.classList.add("popup__hidden"); 46 | setTimeout(() => { 47 | popup.remove(); 48 | }, 5500); 49 | }, 5000); 50 | } 51 | }; 52 | 53 | const renderRecDuration = () => { 54 | const str_pad_left = (string) => { 55 | return (new Array(3).join("0") + string).slice(-2); 56 | }; 57 | 58 | const minutes = Math.floor(recDuration / 60); 59 | const seconds = recDuration - minutes * 60; 60 | 61 | recDurationBlock.innerText = 62 | str_pad_left(minutes) + ":" + str_pad_left(seconds); 63 | recDuration += 1; 64 | }; 65 | 66 | const onRecButtonClick = async () => { 67 | if (!!isRecording) { 68 | recButton.src = "assets/icons/controls/icon-record.svg"; 69 | recDurationBlock.classList.add("hidden"); 70 | clearInterval(recDurationInterval); 71 | 72 | const url = URL.createObjectURL(await stopRecord()); 73 | const popup = document.createElement("div"); 74 | 75 | popup.classList.add("popup", "popup__hidden"); 76 | popup.innerHTML = `Video is ready Check the link`; 77 | popups.prepend(popup); 78 | 79 | setTimeout(() => { 80 | popup.classList.remove("popup__hidden"); 81 | }, 20); 82 | setTimeout(() => { 83 | popup.classList.add("popup__hidden"); 84 | setTimeout(() => { 85 | popup.remove(); 86 | }, 5500); 87 | }, 5000); 88 | } else { 89 | recButton.src = "assets/icons/controls/icon-record-active.svg"; 90 | recDurationBlock.classList.remove("hidden"); 91 | recDurationBlock.innerText = "00:00"; 92 | 93 | startRecord(); 94 | 95 | recDuration = 0; 96 | renderRecDuration(); 97 | 98 | recDurationInterval = setInterval(renderRecDuration, 1000); 99 | } 100 | isRecording = 1 - isRecording; 101 | }; 102 | 103 | muteButton.addEventListener("click", onMuteButtonClick); 104 | screenshotButton.addEventListener("mousedown", onScreenshotButtonClick); 105 | screenshotButton.addEventListener("mouseup", onScreenshotButtonClick); 106 | recButton.addEventListener("click", onRecButtonClick); 107 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Inter:400,500,700,900&display=swap"); 2 | 3 | body { 4 | width: 100%; 5 | height: 100%; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | flex-direction: column; 10 | box-sizing: border-box; 11 | } 12 | 13 | main { 14 | width: 100%; 15 | height: 100%; 16 | margin-left: auto; 17 | margin-right: auto; 18 | font-family: Inter; 19 | position: relative; 20 | box-sizing: border-box; 21 | } 22 | 23 | #webar { 24 | width: 100%; 25 | height: 100%; 26 | max-width: 100%; 27 | max-height: 720px; 28 | aspect-ratio: 1280 / 720; 29 | background-color: #000000; 30 | } 31 | 32 | canvas { 33 | width: 100%; 34 | height: 100%; 35 | object-fit: contain !important; 36 | } 37 | 38 | #fps { 39 | font-size: 10px; 40 | font-weight: 700; 41 | line-height: 16px; 42 | letter-spacing: 0; 43 | text-align: left; 44 | color: #ffffff; 45 | position: absolute; 46 | width: 122px; 47 | height: 48px; 48 | top: 40px; 49 | left: 1120px; 50 | opacity: 0.75; 51 | } 52 | 53 | #image { 54 | overflow: hidden; 55 | width: 0; 56 | height: 0; 57 | } 58 | 59 | #reset-button { 60 | position: absolute; 61 | bottom: 142px; 62 | left: 40px; 63 | width: 40px; 64 | height: 40px; 65 | z-index: 1; 66 | } 67 | 68 | #reset-button:disabled { 69 | opacity: 0.5; 70 | } 71 | 72 | .import-message { 73 | position: absolute; 74 | bottom: 61px; 75 | width: 100%; 76 | } 77 | 78 | .import-message__inner span { 79 | font-weight: 700; 80 | } 81 | 82 | .import-message__inner { 83 | border-radius: 60px; 84 | background: #f5f5f5; 85 | width: fit-content; 86 | margin: 0 auto; 87 | padding: 12px 20px; 88 | font-size: 13px; 89 | font-weight: 400; 90 | line-height: 16px; 91 | letter-spacing: 0; 92 | text-align: center; 93 | } 94 | 95 | .inverted { 96 | filter: invert(100%); 97 | } 98 | 99 | .controls { 100 | position: absolute; 101 | bottom: 142px; 102 | right: 40px; 103 | width: 152px; 104 | display: flex; 105 | justify-content: space-between; 106 | z-index: 1; 107 | } 108 | 109 | .controls + input[type="image"] { 110 | cursor: pointer; 111 | } 112 | 113 | #rec-duration { 114 | position: absolute; 115 | bottom: 44px; 116 | left: 5px; 117 | color: #fff; 118 | text-align: center; 119 | font-size: 10px; 120 | font-style: normal; 121 | font-weight: 700; 122 | line-height: normal; 123 | text-transform: uppercase; 124 | } 125 | 126 | .start-screen { 127 | width: 350px; 128 | height: 230px; 129 | flex-shrink: 0; 130 | border-radius: 20px; 131 | background: #fff; 132 | top: calc(50% - 115px); 133 | left: calc(50% - 175px); 134 | text-align: center; 135 | position: absolute; 136 | z-index: 3; 137 | } 138 | 139 | .start-screen__title { 140 | color: #191428; 141 | font-size: 16px; 142 | font-style: normal; 143 | font-weight: 700; 144 | line-height: normal; 145 | margin: 40px auto 42px; 146 | } 147 | 148 | .start-screen__webcam { 149 | margin-bottom: 12px; 150 | } 151 | 152 | .start-screen__webcam label { 153 | color: #fff; 154 | font-size: 13px; 155 | font-style: normal; 156 | font-weight: 700; 157 | line-height: normal; 158 | text-transform: uppercase; 159 | border-radius: 20px; 160 | border: 1px solid #191428; 161 | background: #191428; 162 | display: inline-block; 163 | width: 164px; 164 | padding: 12px 0; 165 | cursor: pointer; 166 | } 167 | 168 | .start-screen__photo label { 169 | width: 164px; 170 | display: inline-block; 171 | color: #191428; 172 | font-size: 13px; 173 | font-style: normal; 174 | font-weight: 700; 175 | line-height: normal; 176 | text-transform: uppercase; 177 | border-radius: 20px; 178 | border: 1px solid #191428; 179 | background: #fff; 180 | padding: 12px 0; 181 | cursor: pointer; 182 | } 183 | 184 | .footer { 185 | overflow: hidden; 186 | } 187 | 188 | .technologies { 189 | display: flex; 190 | justify-content: space-between; 191 | height: 60px; 192 | margin: 0 55px; 193 | } 194 | 195 | .technology { 196 | display: flex; 197 | align-items: center; 198 | flex-wrap: nowrap; 199 | text-align: center; 200 | } 201 | 202 | input[type="radio"] { 203 | display: none; 204 | margin: 0; 205 | } 206 | 207 | input[name="tech"] + label { 208 | font-size: 13px; 209 | font-weight: 400; 210 | line-height: 16px; 211 | letter-spacing: 0; 212 | text-align: center; 213 | text-transform: uppercase; 214 | cursor: pointer; 215 | padding: 10px 5px; 216 | } 217 | 218 | input[name="tech"]:checked + label { 219 | text-shadow: 0 0 1px black; 220 | display: flex; 221 | flex-direction: column; 222 | align-items: center; 223 | margin-bottom: -6px; 224 | cursor: default; 225 | } 226 | 227 | input[name="tech"]:checked + label:after { 228 | background: #191428; 229 | content: ""; 230 | height: 2px; 231 | margin-top: 4px; 232 | width: 20px; 233 | } 234 | 235 | .categories { 236 | font-size: 13px; 237 | display: flex; 238 | height: 40px; 239 | justify-content: center; 240 | margin-top: 16px; 241 | padding: 2px 16px; 242 | } 243 | 244 | .categories.categories-long { 245 | justify-content: normal; 246 | transition: 0.3s; 247 | } 248 | 249 | .categories-button__left { 250 | height: 40px; 251 | position: absolute; 252 | bottom: 62px; 253 | width: 96px; 254 | padding-left: 16px; 255 | background: linear-gradient(90deg, #fff 60.13%, hsla(0, 0%, 100%, 0)); 256 | z-index: 2; 257 | } 258 | 259 | .categories-button__left-icon { 260 | display: flex; 261 | flex-wrap: wrap; 262 | align-content: center; 263 | justify-content: center; 264 | width: 40px; 265 | height: 40px; 266 | background-color: #ffffff; 267 | border-radius: 20px; 268 | box-shadow: 0 12px 32px rgba(51, 61, 83, 0.1); 269 | } 270 | 271 | .categories-button__right { 272 | height: 40px; 273 | position: absolute; 274 | bottom: 62px; 275 | width: 96px; 276 | right: 0; 277 | background: linear-gradient(270deg, #fff 60.13%, hsla(0, 0%, 100%, 0)); 278 | z-index: 2; 279 | } 280 | 281 | .categories-button__right-icon { 282 | display: flex; 283 | flex-wrap: wrap; 284 | align-content: center; 285 | justify-content: center; 286 | width: 40px; 287 | height: 40px; 288 | background-color: #ffffff; 289 | border-radius: 20px; 290 | box-shadow: 0 12px 32px rgba(51, 61, 83, 0.1); 291 | transform: matrix(-1, 0, 0, 1, 0, 0); 292 | float: right; 293 | margin-right: 16px; 294 | } 295 | 296 | .category { 297 | display: flex; 298 | align-items: center; 299 | flex-wrap: nowrap; 300 | text-align: center; 301 | } 302 | 303 | input[name="category"] + label { 304 | border: 1px solid transparent; 305 | border-radius: 60px; 306 | padding: 12px 20px; 307 | cursor: pointer; 308 | white-space: nowrap; 309 | } 310 | 311 | input[name="category"]:checked + label { 312 | border: 1px solid #191428; 313 | cursor: default; 314 | } 315 | 316 | .effects { 317 | position: absolute; 318 | bottom: 134px; 319 | width: 100%; 320 | overflow: hidden; 321 | display: flex; 322 | flex-direction: column; 323 | align-items: flex-start; 324 | z-index: 0; 325 | } 326 | 327 | .effects-list { 328 | display: flex; 329 | justify-content: center; 330 | width: 100%; 331 | /*width: 848px;*/ 332 | /*margin-left: 80px;*/ 333 | } 334 | 335 | .effect-icon { 336 | width: 60px; 337 | height: 60px; 338 | padding: 4px 0 0 4px; 339 | } 340 | 341 | .effect-icon__border { 342 | border: 1.5px solid transparent; 343 | border-radius: 50%; 344 | height: 68px; 345 | margin: 0 10px; 346 | width: 68px; 347 | } 348 | 349 | input[name="effect"] + .effect-icon__border { 350 | cursor: pointer; 351 | } 352 | 353 | input[name="effect"]:checked + .effect-icon__border { 354 | border: 1.5px solid #fff; 355 | cursor: default; 356 | } 357 | 358 | .effect-control { 359 | margin: 0 auto 15px; 360 | } 361 | 362 | .effect-control__toggle { 363 | -webkit-appearance: none; 364 | appearance: none; 365 | background: #eef2f7; 366 | border-radius: 30px; 367 | cursor: pointer; 368 | height: 33px; 369 | outline: none; 370 | position: relative; 371 | transition: 0.4s; 372 | vertical-align: top; 373 | width: 60px; 374 | } 375 | 376 | .effect-control__toggle:checked { 377 | background-color: #4794fe; 378 | } 379 | 380 | .effect-control__toggle:after { 381 | background-color: #4794fe; 382 | border-radius: 50%; 383 | content: ""; 384 | height: 28px; 385 | left: 3px; 386 | position: absolute; 387 | top: 2.5px; 388 | transform: translateX(0); 389 | transition: 0.4s; 390 | width: 28px; 391 | } 392 | 393 | .effect-control__toggle:checked:after { 394 | background-color: #fff; 395 | transform: translateX(calc(100% - 3px)); 396 | } 397 | 398 | .effect-control__slider-container { 399 | width: 202px; 400 | } 401 | 402 | .effect-control__slider { 403 | background: linear-gradient( 404 | to right, 405 | #4794fe 0%, 406 | #4794fe 50%, 407 | #eef2f7 50%, 408 | #eef2f7 100% 409 | ); 410 | border-radius: 30px; 411 | height: 4px; 412 | width: 100%; 413 | outline: none; 414 | transition: background 450ms ease-in; 415 | -webkit-appearance: none; 416 | 417 | &::-webkit-slider-thumb { 418 | -webkit-appearance: none; 419 | background: #ffffff; 420 | width: 24px; 421 | height: 24px; 422 | border-radius: 50%; 423 | } 424 | } 425 | 426 | .hand-gestures-tip { 427 | top: calc(50% - 207px); 428 | left: calc(50% - 170px); 429 | padding: 40px 60px; 430 | position: absolute; 431 | text-align: center; 432 | border-radius: 20px; 433 | background: #fff; 434 | box-shadow: 0 12px 32px 0 rgba(51, 61, 83, 0.1); 435 | } 436 | 437 | .hand-gestures-tip__title { 438 | font-size: 22px; 439 | font-weight: 700; 440 | line-height: 27px; 441 | margin-bottom: 12px; 442 | } 443 | 444 | .hand-gestures-tip__subtitle { 445 | font-style: normal; 446 | font-weight: 500; 447 | line-height: 19px; 448 | text-align: center; 449 | } 450 | 451 | .hand-gestures-tip__content { 452 | margin: 32px auto 32px 63px; 453 | } 454 | 455 | .hand-gestures-tip__content-item { 456 | align-items: center; 457 | display: flex; 458 | height: 20px; 459 | margin-bottom: 16px; 460 | text-align: center; 461 | } 462 | 463 | .hand-gestures-tip__content-item img { 464 | font-size: 16px; 465 | font-weight: 500; 466 | height: 20px; 467 | line-height: 19px; 468 | margin-right: 10px; 469 | width: 20px; 470 | } 471 | 472 | .hand-gestures-tip__button { 473 | background: #000; 474 | border-radius: 24px; 475 | border-width: 0; 476 | color: #fff; 477 | font-size: 16px; 478 | font-weight: 600; 479 | height: 48px; 480 | line-height: 19px; 481 | text-align: center; 482 | width: 220px; 483 | cursor: pointer; 484 | } 485 | 486 | .hand-gestures { 487 | position: absolute; 488 | top: calc(74%); 489 | left: calc(50% - 125px); 490 | background: #000; 491 | color: #fff; 492 | margin: 0 auto 15px; 493 | padding: 8px 32px; 494 | align-items: center; 495 | display: flex; 496 | font-size: 18px; 497 | font-weight: 700; 498 | height: 52px; 499 | justify-content: center; 500 | line-height: 22px; 501 | text-align: center; 502 | vertical-align: middle; 503 | width: 180px; 504 | } 505 | 506 | .hand-gestures img { 507 | margin-right: 12px; 508 | } 509 | 510 | .heart-rate { 511 | position: absolute; 512 | width: 130px; 513 | left: 495px; 514 | bottom: 156px; 515 | background: #000; 516 | color: #fff; 517 | font-size: 14px; 518 | font-weight: 700; 519 | line-height: 16px; 520 | padding: 8px 22px; 521 | } 522 | 523 | .heart-rate.heart-rate__analyse { 524 | width: 190px; 525 | left: 480px; 526 | } 527 | 528 | .test-ruler { 529 | position: absolute; 530 | bottom: 156px; 531 | left: 473px; 532 | width: 142px; 533 | background: #000000; 534 | padding: 8px 0 8px 32px; 535 | font-weight: 700; 536 | font-size: 14px; 537 | line-height: 16px; 538 | color: #ffffff; 539 | } 540 | 541 | .popups { 542 | position: absolute; 543 | left: 50%; 544 | top: 50%; 545 | display: flex; 546 | flex-direction: column; 547 | align-items: center; 548 | gap: 10px; 549 | transform: translate(-50%, -50%); 550 | } 551 | 552 | .popup { 553 | opacity: 1; 554 | color: #191428; 555 | font-size: 13px; 556 | font-style: normal; 557 | font-weight: 400; 558 | line-height: normal; 559 | padding: 12px 24px; 560 | border-radius: 12px; 561 | background: #fff; 562 | box-shadow: 0 12px 32px 0 rgba(51, 61, 83, 0.1); 563 | transition: all ease-in-out 0.5s; 564 | } 565 | 566 | .popup__hidden { 567 | opacity: 0; 568 | } 569 | .popup__bold { 570 | margin-right: 8px; 571 | font-weight: 700; 572 | } 573 | 574 | .overlay { 575 | position: fixed; 576 | top: 0; 577 | bottom: 0; 578 | left: 0; 579 | right: 0; 580 | width: 100%; 581 | height: 100%; 582 | background: rgba(0, 0, 0, 0.5); 583 | backdrop-filter: blur(3px); 584 | z-index: 2; 585 | } 586 | 587 | .hidden { 588 | display: none; 589 | } 590 | 591 | .start-screen__spinner { 592 | position: absolute; 593 | width: 80px; 594 | height: 80px; 595 | top: 75px; 596 | left: 135px; 597 | } 598 | 599 | .start-screen__spinner div { 600 | animation: start-screen__spinner 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; 601 | transform-origin: 40px 40px; 602 | } 603 | 604 | .start-screen__spinner div:after { 605 | content: " "; 606 | display: block; 607 | position: absolute; 608 | width: 7px; 609 | height: 7px; 610 | border-radius: 50%; 611 | background: #000; 612 | margin: -4px 0 0 -4px; 613 | } 614 | 615 | .start-screen__spinner div:nth-child(1) { 616 | animation-delay: -0.036s; 617 | } 618 | 619 | .start-screen__spinner div:nth-child(1):after { 620 | top: 63px; 621 | left: 63px; 622 | } 623 | 624 | .start-screen__spinner div:nth-child(2) { 625 | animation-delay: -0.072s; 626 | } 627 | 628 | .start-screen__spinner div:nth-child(2):after { 629 | top: 68px; 630 | left: 56px; 631 | } 632 | 633 | .start-screen__spinner div:nth-child(3) { 634 | animation-delay: -0.108s; 635 | } 636 | 637 | .start-screen__spinner div:nth-child(3):after { 638 | top: 71px; 639 | left: 48px; 640 | } 641 | 642 | .start-screen__spinner div:nth-child(4) { 643 | animation-delay: -0.144s; 644 | } 645 | 646 | .start-screen__spinner div:nth-child(4):after { 647 | top: 72px; 648 | left: 40px; 649 | } 650 | 651 | .start-screen__spinner div:nth-child(5) { 652 | animation-delay: -0.18s; 653 | } 654 | 655 | .start-screen__spinner div:nth-child(5):after { 656 | top: 71px; 657 | left: 32px; 658 | } 659 | 660 | .start-screen__spinner div:nth-child(6) { 661 | animation-delay: -0.216s; 662 | } 663 | 664 | .start-screen__spinner div:nth-child(6):after { 665 | top: 68px; 666 | left: 24px; 667 | } 668 | 669 | .start-screen__spinner div:nth-child(7) { 670 | animation-delay: -0.252s; 671 | } 672 | 673 | .start-screen__spinner div:nth-child(7):after { 674 | top: 63px; 675 | left: 17px; 676 | } 677 | 678 | .start-screen__spinner div:nth-child(8) { 679 | animation-delay: -0.288s; 680 | } 681 | 682 | .start-screen__spinner div:nth-child(8):after { 683 | top: 56px; 684 | left: 12px; 685 | } 686 | 687 | @keyframes start-screen__spinner { 688 | 0% { 689 | transform: rotate(0deg); 690 | } 691 | 100% { 692 | transform: rotate(360deg); 693 | } 694 | } 695 | --------------------------------------------------------------------------------