From 1a94a2a25366edc77de73c0460c9cd9a5b3edb4b Mon Sep 17 00:00:00 2001 From: Nam Nguyen Date: Thu, 19 Dec 2024 03:55:38 +0700 Subject: [PATCH 1/6] Init vite replacement for gulp --- app/.babelrc | 13 - app/.eslintrc.js | 457 ++- app/.npmrc | 1 + app/.prettierrc.cjs | 17 + app/gulpfile.js | 454 --- app/index.html | 11 +- app/lib/Logger.js | 43 - app/lib/RoomClient.js | 2669 ----------------- app/lib/RoomContext.js | 14 - app/lib/components/ChatInput.jsx | 123 - app/lib/components/EditableInput.jsx | 51 - app/lib/components/Me.jsx | 237 -- app/lib/components/NetworkThrottle.jsx | 200 -- app/lib/components/Notifications.jsx | 68 - app/lib/components/Peer.jsx | 138 - app/lib/components/PeerView.jsx | 794 ----- app/lib/components/Peers.jsx | 64 - app/lib/components/Room.jsx | 196 -- app/lib/components/Stats.jsx | 531 ---- app/lib/components/appPropTypes.js | 80 - app/lib/components/transitions.jsx | 22 - app/lib/cookiesManager.js | 24 - app/lib/deviceInfo.js | 30 - app/lib/e2e.js | 111 - app/lib/index.jsx | 351 --- app/lib/randomName.js | 43 - app/lib/redux/reducers/consumers.js | 128 - app/lib/redux/reducers/dataConsumers.js | 41 - app/lib/redux/reducers/dataProducers.js | 41 - app/lib/redux/reducers/index.js | 23 - app/lib/redux/reducers/me.js | 123 - app/lib/redux/reducers/notifications.js | 31 - app/lib/redux/reducers/peers.js | 138 - app/lib/redux/reducers/producers.js | 81 - app/lib/redux/reducers/room.js | 100 - app/lib/redux/requestActions.js | 38 - app/lib/redux/stateActions.js | 342 --- app/lib/urlFactory.js | 11 - app/lib/utils.js | 20 - app/package.json | 101 +- .../tiny_face_detector_model-shard1 | Bin ..._face_detector_model-weights_manifest.json | 0 .../fonts/Roboto-light-ext.woff2 | Bin .../fonts/Roboto-light.ttf | Bin .../fonts/Roboto-light.woff2 | Bin .../fonts/Roboto-medium-ext.woff2 | Bin .../fonts/Roboto-medium.ttf | Bin .../fonts/Roboto-medium.woff2 | Bin .../fonts/Roboto-regular-ext.woff2 | Bin .../fonts/Roboto-regular.ttf | Bin .../fonts/Roboto-regular.woff2 | Bin .../images/body-bg-1.jpg | Bin .../images/body-bg-2.jpg | Bin app/{resources => public}/images/buddy.svg | 0 .../images/devices/broadcaster.svg | 0 .../images/devices/chrome_16x16.png | Bin .../images/devices/edge_16x16.png | Bin .../images/devices/firefox_16x16.png | Bin .../images/devices/opera_16x16.png | Bin .../images/devices/safari_16x16.png | Bin .../images/devices/unknown.svg | 0 .../images/icon_change_webcam_black.svg | 0 .../icon_change_webcam_white_unsupported.svg | 0 .../images/icon_close_black.svg | 0 .../images/icon_info_white_off.svg | 0 .../images/icon_info_white_on.svg | 0 .../images/icon_mic_black_on.svg | 0 .../images/icon_mic_white_off.svg | 0 .../images/icon_mic_white_unsupported.svg | 0 .../images/icon_notification_error_white.svg | 0 .../images/icon_notification_info_white.svg | 0 .../images/icon_remote_mic_white_off.svg | 0 .../images/icon_remote_webcam_white_off.svg | 0 .../images/icon_restart_ice_white.svg | 0 .../images/icon_share_black_on.svg | 0 .../images/icon_share_white_on.svg | 0 .../images/icon_share_white_unsupported.svg | 0 .../images/icon_stats_white_on.svg | 0 .../images/icon_video_black_off.svg | 0 .../images/icon_video_elem_paused.svg | 0 .../images/icon_video_white_on.svg | 0 .../images/icon_volume_black_off.svg | 0 .../images/icon_volume_white_on.svg | 0 .../images/icon_webcam_black_on.svg | 0 .../images/icon_webcam_black_unsupported.svg | 0 .../images/icon_webcam_white_on.svg | 0 .../images/icon_webcam_white_unsupported.svg | 0 ...paint-stains-spots-bright-5K-wallpaper.jpg | Bin app/{resources => public}/images/room.svg | 0 app/{resources => public}/images/stats.svg | 0 app/{resources => public}/js/antiglobal.js | 0 app/{resources => public}/js/e2e-worker.js | 0 .../videos/video-audio-stereo.mp4 | Bin app/src/Logger.js | 35 + app/src/RoomClient.js | 2422 +++++++++++++++ app/src/RoomContext.jsx | 14 + app/src/components/ChatInput.jsx | 110 + app/src/components/EditableInput.jsx | 46 + app/src/components/Me.jsx | 221 ++ app/src/components/NetworkThrottle.jsx | 195 ++ app/src/components/Notifications.jsx | 60 + app/src/components/Peer.jsx | 134 + app/src/components/PeerView.jsx | 751 +++++ app/src/components/Peers.jsx | 52 + app/src/components/Room.jsx | 188 ++ app/src/components/Stats.jsx | 523 ++++ app/src/components/appPropTypes.js | 71 + app/src/components/transitions.jsx | 16 + app/src/cookiesManager.js | 20 + app/src/deviceInfo.js | 23 + app/src/e2e.js | 101 + app/src/index.jsx | 315 ++ app/src/randomName.js | 41 + app/{lib => src}/redux/STATE.md | 0 app/src/redux/reducers/consumers.js | 108 + app/src/redux/reducers/dataConsumers.js | 33 + app/src/redux/reducers/dataProducers.js | 33 + app/src/redux/reducers/index.js | 22 + app/src/redux/reducers/me.js | 105 + app/src/redux/reducers/notifications.js | 26 + app/src/redux/reducers/peers.js | 116 + app/src/redux/reducers/producers.js | 68 + app/src/redux/reducers/room.js | 89 + app/src/redux/requestActions.js | 32 + app/src/redux/stateActions.js | 307 ++ app/src/scss/components/ChatInput.scss | 28 + app/src/scss/components/Me.scss | 114 + app/src/scss/components/NetworkThrottle.scss | 115 + app/src/scss/components/Notifications.scss | 129 + app/src/scss/components/Peer.scss | 72 + app/src/scss/components/PeerView.scss | 447 +++ app/src/scss/components/Peers.scss | 60 + app/src/scss/components/Room.scss | 322 ++ app/src/scss/components/Stats.scss | 204 ++ app/src/scss/fonts.scss | 74 + app/src/scss/index.scss | 66 + app/src/scss/mixins.scss | 52 + app/src/scss/reset.scss | 15 + app/src/scss/variables.scss | 2 + app/src/urlFactory.js | 9 + app/src/utils.js | 18 + app/stylus/components/ChatInput.styl | 28 - app/stylus/components/Me.styl | 114 - app/stylus/components/NetworkThrottle.styl | 115 - app/stylus/components/Notifications.styl | 127 - app/stylus/components/Peer.styl | 72 - app/stylus/components/PeerView.styl | 406 --- app/stylus/components/Peers.styl | 60 - app/stylus/components/Room.styl | 319 -- app/stylus/components/Stats.styl | 203 -- app/stylus/fonts.styl | 62 - app/stylus/index.styl | 69 - app/stylus/mixins.styl | 39 - app/stylus/reset.styl | 15 - app/stylus/variables.styl | 2 - app/tsconfig.app.json | 27 + app/tsconfig.json | 11 + app/tsconfig.node.json | 13 + app/vite.config.ts | 33 + 159 files changed, 8367 insertions(+), 9312 deletions(-) delete mode 100644 app/.babelrc create mode 100644 app/.prettierrc.cjs delete mode 100644 app/gulpfile.js delete mode 100644 app/lib/Logger.js delete mode 100644 app/lib/RoomClient.js delete mode 100644 app/lib/RoomContext.js delete mode 100644 app/lib/components/ChatInput.jsx delete mode 100644 app/lib/components/EditableInput.jsx delete mode 100644 app/lib/components/Me.jsx delete mode 100644 app/lib/components/NetworkThrottle.jsx delete mode 100644 app/lib/components/Notifications.jsx delete mode 100644 app/lib/components/Peer.jsx delete mode 100644 app/lib/components/PeerView.jsx delete mode 100644 app/lib/components/Peers.jsx delete mode 100644 app/lib/components/Room.jsx delete mode 100644 app/lib/components/Stats.jsx delete mode 100644 app/lib/components/appPropTypes.js delete mode 100644 app/lib/components/transitions.jsx delete mode 100644 app/lib/cookiesManager.js delete mode 100644 app/lib/deviceInfo.js delete mode 100644 app/lib/e2e.js delete mode 100644 app/lib/index.jsx delete mode 100644 app/lib/randomName.js delete mode 100644 app/lib/redux/reducers/consumers.js delete mode 100644 app/lib/redux/reducers/dataConsumers.js delete mode 100644 app/lib/redux/reducers/dataProducers.js delete mode 100644 app/lib/redux/reducers/index.js delete mode 100644 app/lib/redux/reducers/me.js delete mode 100644 app/lib/redux/reducers/notifications.js delete mode 100644 app/lib/redux/reducers/peers.js delete mode 100644 app/lib/redux/reducers/producers.js delete mode 100644 app/lib/redux/reducers/room.js delete mode 100644 app/lib/redux/requestActions.js delete mode 100644 app/lib/redux/stateActions.js delete mode 100644 app/lib/urlFactory.js delete mode 100644 app/lib/utils.js rename app/{resources => public}/face-detector-models/tiny_face_detector_model-shard1 (100%) rename app/{resources => public}/face-detector-models/tiny_face_detector_model-weights_manifest.json (100%) rename app/{resources => public}/fonts/Roboto-light-ext.woff2 (100%) rename app/{resources => public}/fonts/Roboto-light.ttf (100%) rename app/{resources => public}/fonts/Roboto-light.woff2 (100%) rename app/{resources => public}/fonts/Roboto-medium-ext.woff2 (100%) rename app/{resources => public}/fonts/Roboto-medium.ttf (100%) rename app/{resources => public}/fonts/Roboto-medium.woff2 (100%) rename app/{resources => public}/fonts/Roboto-regular-ext.woff2 (100%) rename app/{resources => public}/fonts/Roboto-regular.ttf (100%) rename app/{resources => public}/fonts/Roboto-regular.woff2 (100%) rename app/{resources => public}/images/body-bg-1.jpg (100%) rename app/{resources => public}/images/body-bg-2.jpg (100%) rename app/{resources => public}/images/buddy.svg (100%) rename app/{resources => public}/images/devices/broadcaster.svg (100%) rename app/{resources => public}/images/devices/chrome_16x16.png (100%) rename app/{resources => public}/images/devices/edge_16x16.png (100%) rename app/{resources => public}/images/devices/firefox_16x16.png (100%) rename app/{resources => public}/images/devices/opera_16x16.png (100%) rename app/{resources => public}/images/devices/safari_16x16.png (100%) rename app/{resources => public}/images/devices/unknown.svg (100%) rename app/{resources => public}/images/icon_change_webcam_black.svg (100%) rename app/{resources => public}/images/icon_change_webcam_white_unsupported.svg (100%) rename app/{resources => public}/images/icon_close_black.svg (100%) rename app/{resources => public}/images/icon_info_white_off.svg (100%) rename app/{resources => public}/images/icon_info_white_on.svg (100%) rename app/{resources => public}/images/icon_mic_black_on.svg (100%) rename app/{resources => public}/images/icon_mic_white_off.svg (100%) rename app/{resources => public}/images/icon_mic_white_unsupported.svg (100%) rename app/{resources => public}/images/icon_notification_error_white.svg (100%) rename app/{resources => public}/images/icon_notification_info_white.svg (100%) rename app/{resources => public}/images/icon_remote_mic_white_off.svg (100%) rename app/{resources => public}/images/icon_remote_webcam_white_off.svg (100%) rename app/{resources => public}/images/icon_restart_ice_white.svg (100%) rename app/{resources => public}/images/icon_share_black_on.svg (100%) rename app/{resources => public}/images/icon_share_white_on.svg (100%) rename app/{resources => public}/images/icon_share_white_unsupported.svg (100%) rename app/{resources => public}/images/icon_stats_white_on.svg (100%) rename app/{resources => public}/images/icon_video_black_off.svg (100%) rename app/{resources => public}/images/icon_video_elem_paused.svg (100%) rename app/{resources => public}/images/icon_video_white_on.svg (100%) rename app/{resources => public}/images/icon_volume_black_off.svg (100%) rename app/{resources => public}/images/icon_volume_white_on.svg (100%) rename app/{resources => public}/images/icon_webcam_black_on.svg (100%) rename app/{resources => public}/images/icon_webcam_black_unsupported.svg (100%) rename app/{resources => public}/images/icon_webcam_white_on.svg (100%) rename app/{resources => public}/images/icon_webcam_white_unsupported.svg (100%) rename app/{resources => public}/images/paint-stains-spots-bright-5K-wallpaper.jpg (100%) rename app/{resources => public}/images/room.svg (100%) rename app/{resources => public}/images/stats.svg (100%) rename app/{resources => public}/js/antiglobal.js (100%) rename app/{resources => public}/js/e2e-worker.js (100%) rename app/{resources => public}/videos/video-audio-stereo.mp4 (100%) create mode 100644 app/src/Logger.js create mode 100644 app/src/RoomClient.js create mode 100644 app/src/RoomContext.jsx create mode 100644 app/src/components/ChatInput.jsx create mode 100644 app/src/components/EditableInput.jsx create mode 100644 app/src/components/Me.jsx create mode 100644 app/src/components/NetworkThrottle.jsx create mode 100644 app/src/components/Notifications.jsx create mode 100644 app/src/components/Peer.jsx create mode 100644 app/src/components/PeerView.jsx create mode 100644 app/src/components/Peers.jsx create mode 100644 app/src/components/Room.jsx create mode 100644 app/src/components/Stats.jsx create mode 100644 app/src/components/appPropTypes.js create mode 100644 app/src/components/transitions.jsx create mode 100644 app/src/cookiesManager.js create mode 100644 app/src/deviceInfo.js create mode 100644 app/src/e2e.js create mode 100644 app/src/index.jsx create mode 100644 app/src/randomName.js rename app/{lib => src}/redux/STATE.md (100%) create mode 100644 app/src/redux/reducers/consumers.js create mode 100644 app/src/redux/reducers/dataConsumers.js create mode 100644 app/src/redux/reducers/dataProducers.js create mode 100644 app/src/redux/reducers/index.js create mode 100644 app/src/redux/reducers/me.js create mode 100644 app/src/redux/reducers/notifications.js create mode 100644 app/src/redux/reducers/peers.js create mode 100644 app/src/redux/reducers/producers.js create mode 100644 app/src/redux/reducers/room.js create mode 100644 app/src/redux/requestActions.js create mode 100644 app/src/redux/stateActions.js create mode 100644 app/src/scss/components/ChatInput.scss create mode 100644 app/src/scss/components/Me.scss create mode 100644 app/src/scss/components/NetworkThrottle.scss create mode 100644 app/src/scss/components/Notifications.scss create mode 100644 app/src/scss/components/Peer.scss create mode 100644 app/src/scss/components/PeerView.scss create mode 100644 app/src/scss/components/Peers.scss create mode 100644 app/src/scss/components/Room.scss create mode 100644 app/src/scss/components/Stats.scss create mode 100644 app/src/scss/fonts.scss create mode 100644 app/src/scss/index.scss create mode 100644 app/src/scss/mixins.scss create mode 100644 app/src/scss/reset.scss create mode 100644 app/src/scss/variables.scss create mode 100644 app/src/urlFactory.js create mode 100644 app/src/utils.js delete mode 100644 app/stylus/components/ChatInput.styl delete mode 100644 app/stylus/components/Me.styl delete mode 100644 app/stylus/components/NetworkThrottle.styl delete mode 100644 app/stylus/components/Notifications.styl delete mode 100644 app/stylus/components/Peer.styl delete mode 100644 app/stylus/components/PeerView.styl delete mode 100644 app/stylus/components/Peers.styl delete mode 100644 app/stylus/components/Room.styl delete mode 100644 app/stylus/components/Stats.styl delete mode 100644 app/stylus/fonts.styl delete mode 100644 app/stylus/index.styl delete mode 100644 app/stylus/mixins.styl delete mode 100644 app/stylus/reset.styl delete mode 100644 app/stylus/variables.styl create mode 100644 app/tsconfig.app.json create mode 100644 app/tsconfig.json create mode 100644 app/tsconfig.node.json create mode 100644 app/vite.config.ts diff --git a/app/.babelrc b/app/.babelrc deleted file mode 100644 index 20714a435..000000000 --- a/app/.babelrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "plugins": - [ - "@babel/plugin-transform-runtime", - "@babel/plugin-proposal-object-rest-spread", - "jsx-control-statements" - ], - "presets": - [ - "@babel/env", - "@babel/react" - ] -} diff --git a/app/.eslintrc.js b/app/.eslintrc.js index d24d9bf35..1bcf6ca65 100644 --- a/app/.eslintrc.js +++ b/app/.eslintrc.js @@ -1,236 +1,221 @@ -module.exports = -{ - env: - { - browser: true, - es6: true, - node: true - }, - plugins: - [ - 'import', - 'react', - 'jsx-control-statements' - ], - extends: - [ - 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:jsx-control-statements/recommended' - ], - settings: - { - react: - { - pragma: 'React', - version: '16' - } - }, - parserOptions: - { - ecmaVersion: 2018, - sourceType: 'module', - ecmaFeatures: - { - impliedStrict: true, - jsx: true - } - }, - rules: - { - 'array-bracket-spacing': [ 2, 'always', - { - objectsInArrays: true, - arraysInArrays: true - }], - 'arrow-parens': [ 2, 'always' ], - 'arrow-spacing': 2, - 'block-spacing': [ 2, 'always' ], - 'brace-style': [ 2, 'allman', { allowSingleLine: true } ], - 'camelcase': 2, - 'comma-dangle': 2, - 'comma-spacing': [ 2, { before: false, after: true } ], - 'comma-style': 2, - 'computed-property-spacing': 2, - 'constructor-super': 2, - 'func-call-spacing': 2, - 'generator-star-spacing': 2, - 'guard-for-in': 2, - 'indent': [ 2, 'tab', { 'SwitchCase': 1 } ], - 'key-spacing': [ 2, - { - singleLine: - { - beforeColon: false, - afterColon: true - }, - multiLine: - { - beforeColon: true, - afterColon: true, - align: 'colon' - } - }], - 'keyword-spacing': 2, - 'linebreak-style': [ 2, 'unix' ], - 'lines-around-comment': [ 2, - { - allowBlockStart: true, - allowObjectStart: true, - beforeBlockComment: true, - beforeLineComment: false - }], - 'max-len': [ 2, 94, - { - tabWidth: 2, - comments: 110, - ignoreUrls: true, - ignoreStrings: true, - ignoreTemplateLiterals: true, - ignoreRegExpLiterals: true - }], - 'newline-after-var': 2, - 'newline-before-return': 2, - 'newline-per-chained-call': 2, - 'no-alert': 2, - 'no-caller': 2, - 'no-case-declarations': 2, - 'no-catch-shadow': 2, - 'no-class-assign': 2, - 'no-confusing-arrow': 2, - 'no-console': 2, - 'no-const-assign': 2, - 'no-debugger': 2, - 'no-dupe-args': 2, - 'no-dupe-keys': 2, - 'no-duplicate-case': 2, - 'no-div-regex': 2, - 'no-empty': [ 2, { allowEmptyCatch: true } ], - 'no-empty-pattern': 2, - 'no-else-return': 0, - 'no-eval': 2, - 'no-extend-native': 2, - 'no-ex-assign': 2, - 'no-extra-bind': 2, - 'no-extra-boolean-cast': 2, - 'no-extra-label': 2, - 'no-extra-semi': 2, - 'no-fallthrough': 2, - 'no-func-assign': 2, - 'no-global-assign': 2, - 'no-implicit-coercion': 2, - 'no-implicit-globals': 2, - 'no-inner-declarations': 2, - 'no-invalid-regexp': 2, - 'no-invalid-this': 2, - 'no-irregular-whitespace': 2, - 'no-lonely-if': 2, - 'no-mixed-operators': 2, - 'no-mixed-spaces-and-tabs': 2, - 'no-multi-spaces': 2, - 'no-multi-str': 2, - 'no-multiple-empty-lines': [ 2, { max: 1, maxEOF: 0, maxBOF: 0 } ], - 'no-native-reassign': 2, - 'no-negated-in-lhs': 2, - 'no-new': 2, - 'no-new-func': 2, - 'no-new-wrappers': 2, - 'no-obj-calls': 2, - 'no-proto': 2, - 'no-prototype-builtins': 0, - 'no-redeclare': 2, - 'no-regex-spaces': 2, - 'no-restricted-imports': 2, - 'no-return-assign': 2, - 'no-self-assign': 2, - 'no-self-compare': 2, - 'no-sequences': 2, - 'no-shadow': 2, - 'no-shadow-restricted-names': 2, - 'no-spaced-func': 2, - 'no-sparse-arrays': 2, - 'no-this-before-super': 2, - 'no-throw-literal': 2, - 'no-undef': 2, - 'no-unexpected-multiline': 2, - 'no-unmodified-loop-condition': 2, - 'no-unreachable': 2, - 'no-unused-vars': [ 1, { vars: 'all', args: 'after-used' }], - 'no-use-before-define': [ 2, { functions: false } ], - 'no-useless-call': 2, - 'no-useless-computed-key': 2, - 'no-useless-concat': 2, - 'no-useless-rename': 2, - 'no-var': 2, - 'no-whitespace-before-property': 2, - 'object-curly-newline': 0, - 'object-curly-spacing': [ 2, 'always' ], - 'object-property-newline': [ 2, { allowMultiplePropertiesPerLine: true } ], - 'prefer-const': 2, - 'prefer-rest-params': 2, - 'prefer-spread': 2, - 'prefer-template': 2, - 'quotes': [ 2, 'single', { avoidEscape: true } ], - 'semi': [ 2, 'always' ], - 'semi-spacing': 2, - 'space-before-blocks': 2, - 'space-before-function-paren': [ 2, - { - anonymous : 'never', - named : 'never', - asyncArrow : 'always' - }], - 'space-in-parens': [ 2, 'never' ], - 'spaced-comment': [ 2, 'always' ], - 'strict': 2, - 'valid-typeof': 2, - 'eol-last': 2, - 'yoda': 2, - // eslint-plugin-import options. - 'import/extensions': 2, - 'import/no-duplicates': 2, - // eslint-plugin-react options. - 'jsx-quotes': [ 2, 'prefer-single' ], - 'react/display-name': [ 2, { ignoreTranspilerName: false } ], - 'react/forbid-prop-types': 0, - 'react/jsx-boolean-value': 2, - 'react/jsx-closing-bracket-location': 2, - 'react/jsx-curly-spacing': 2, - 'react/jsx-equals-spacing': 2, - 'react/jsx-handler-names': 2, - 'react/jsx-indent-props': [ 2, 'tab' ], - 'react/jsx-indent': [ 2, 'tab' ], - 'react/jsx-key': 2, - 'react/jsx-max-props-per-line': 0, - 'react/jsx-no-bind': 0, - 'react/jsx-no-duplicate-props': 2, - 'react/jsx-no-literals': 0, - 'react/jsx-no-undef': 0, - 'react/jsx-pascal-case': 2, - 'react/jsx-sort-prop-types': 0, - 'react/jsx-sort-props': 0, - 'react/jsx-uses-react': 2, - 'react/jsx-uses-vars': 2, - 'react/no-danger': 2, - 'react/no-deprecated': 2, - 'react/no-did-mount-set-state': 2, - 'react/no-did-update-set-state': 2, - 'react/no-direct-mutation-state': 2, - 'react/no-is-mounted': 2, - 'react/no-multi-comp': 0, - 'react/no-set-state': 0, - 'react/no-string-refs': 0, - 'react/no-unknown-property': 2, - 'react/prefer-es6-class': 2, - 'react/prop-types': [ 2, { skipUndeclared: true } ], - 'react/react-in-jsx-scope': 2, - 'react/self-closing-comp': 2, - 'react/sort-comp': 0, - 'react/jsx-wrap-multilines': [ 2, - { - declaration: false, - assignment: false, - return: true - }] - } -}; +const disabled = 0 +const warning = 1 +const error = 2 +const default_level = warning + +module.exports = { + env: { + browser: true, + es6: true, + node: true, + }, + plugins: ['import', 'react'], + extends: ['eslint:recommended', 'plugin:react/recommended'], + settings: { + react: { + pragma: 'React', + version: '18', + }, + }, + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + impliedStrict: true, + jsx: true, + }, + }, + rules: { + 'array-bracket-spacing': [ + default_level, + 'always', + { + objectsInArrays: true, + arraysInArrays: true, + }, + ], + 'arrow-parens': [default_level, 'always'], + 'arrow-spacing': default_level, + 'block-spacing': [default_level, 'always'], + 'brace-style': [default_level, 'allman', { allowSingleLine: true }], + camelcase: default_level, + 'comma-dangle': default_level, + 'comma-spacing': [default_level, { before: false, after: true }], + 'comma-style': default_level, + 'computed-property-spacing': default_level, + 'constructor-super': default_level, + 'func-call-spacing': default_level, + 'generator-star-spacing': default_level, + 'guard-for-in': default_level, + 'key-spacing': [ + default_level, + { + singleLine: { + beforeColon: false, + afterColon: true, + }, + multiLine: { + beforeColon: true, + afterColon: true, + align: 'colon', + }, + }, + ], + 'keyword-spacing': default_level, + 'linebreak-style': [default_level, 'unix'], + 'lines-around-comment': [ + default_level, + { + allowBlockStart: true, + allowObjectStart: true, + beforeBlockComment: true, + beforeLineComment: false, + }, + ], + 'newline-after-var': default_level, + 'newline-before-return': default_level, + 'newline-per-chained-call': default_level, + 'no-alert': default_level, + 'no-caller': default_level, + 'no-case-declarations': default_level, + 'no-catch-shadow': default_level, + 'no-class-assign': default_level, + 'no-confusing-arrow': default_level, + 'no-console': default_level, + 'no-const-assign': default_level, + 'no-debugger': default_level, + 'no-dupe-args': default_level, + 'no-dupe-keys': default_level, + 'no-duplicate-case': default_level, + 'no-div-regex': default_level, + 'no-empty': [default_level, { allowEmptyCatch: true }], + 'no-empty-pattern': default_level, + 'no-else-return': disabled, + 'no-eval': default_level, + 'no-extend-native': default_level, + 'no-ex-assign': default_level, + 'no-extra-bind': default_level, + 'no-extra-boolean-cast': default_level, + 'no-extra-label': default_level, + 'no-extra-semi': default_level, + 'no-fallthrough': default_level, + 'no-func-assign': default_level, + 'no-global-assign': default_level, + 'no-implicit-coercion': default_level, + 'no-implicit-globals': default_level, + 'no-inner-declarations': default_level, + 'no-invalid-regexp': default_level, + 'no-invalid-this': default_level, + 'no-irregular-whitespace': default_level, + 'no-lonely-if': default_level, + 'no-mixed-operators': default_level, + 'no-mixed-spaces-and-tabs': disabled, + 'no-multi-spaces': default_level, + 'no-multi-str': default_level, + 'no-multiple-empty-lines': [default_level, { max: 1, maxEOF: 0, maxBOF: 0 }], + 'no-native-reassign': default_level, + 'no-negated-in-lhs': default_level, + 'no-new': default_level, + 'no-new-func': default_level, + 'no-new-wrappers': default_level, + 'no-obj-calls': default_level, + 'no-proto': default_level, + 'no-prototype-builtins': disabled, + 'no-redeclare': default_level, + 'no-regex-spaces': default_level, + 'no-restricted-imports': default_level, + 'no-return-assign': default_level, + 'no-self-assign': default_level, + 'no-self-compare': default_level, + 'no-sequences': default_level, + 'no-shadow': default_level, + 'no-shadow-restricted-names': default_level, + 'no-spaced-func': default_level, + 'no-sparse-arrays': default_level, + 'no-this-before-super': default_level, + 'no-throw-literal': default_level, + 'no-undef': default_level, + 'no-unexpected-multiline': default_level, + 'no-unmodified-loop-condition': default_level, + 'no-unreachable': default_level, + 'no-unused-vars': [default_level, { vars: 'all', args: 'after-used' }], + 'no-use-before-define': [default_level, { functions: false }], + 'no-useless-call': default_level, + 'no-useless-computed-key': default_level, + 'no-useless-concat': default_level, + 'no-useless-rename': default_level, + 'no-var': default_level, + 'no-whitespace-before-property': default_level, + 'object-curly-newline': disabled, + 'object-curly-spacing': [default_level, 'always'], + 'object-property-newline': [default_level, { allowMultiplePropertiesPerLine: true }], + 'prefer-const': default_level, + 'prefer-rest-params': default_level, + 'prefer-spread': default_level, + 'prefer-template': default_level, + quotes: [default_level, 'single', { avoidEscape: true }], + semi: [default_level, 'always'], + 'semi-spacing': default_level, + 'space-before-blocks': default_level, + 'space-before-function-paren': [ + default_level, + { + anonymous: 'never', + named: 'never', + asyncArrow: 'always', + }, + ], + 'space-in-parens': [default_level, 'never'], + 'spaced-comment': [default_level, 'always'], + strict: default_level, + 'valid-typeof': default_level, + 'eol-last': default_level, + yoda: default_level, + // eslint-plugin-import options. + 'import/extensions': default_level, + 'import/no-duplicates': default_level, + // eslint-plugin-react options. + 'jsx-quotes': [default_level, 'prefer-single'], + 'react/display-name': [default_level, { ignoreTranspilerName: false }], + 'react/forbid-prop-types': disabled, + 'react/jsx-boolean-value': default_level, + 'react/jsx-closing-bracket-location': default_level, + 'react/jsx-curly-spacing': default_level, + 'react/jsx-equals-spacing': default_level, + 'react/jsx-handler-names': default_level, + 'react/jsx-key': default_level, + 'react/jsx-max-props-per-line': disabled, + 'react/jsx-no-bind': disabled, + 'react/jsx-no-duplicate-props': default_level, + 'react/jsx-no-literals': disabled, + 'react/jsx-no-undef': disabled, + 'react/jsx-pascal-case': default_level, + 'react/jsx-sort-prop-types': disabled, + 'react/jsx-sort-props': disabled, + 'react/jsx-uses-react': default_level, + 'react/jsx-uses-vars': default_level, + 'react/no-danger': default_level, + 'react/no-deprecated': default_level, + 'react/no-did-mount-set-state': default_level, + 'react/no-did-update-set-state': default_level, + 'react/no-direct-mutation-state': default_level, + 'react/no-is-mounted': default_level, + 'react/no-multi-comp': disabled, + 'react/no-set-state': disabled, + 'react/no-string-refs': disabled, + 'react/no-unknown-property': default_level, + 'react/prefer-es6-class': default_level, + 'react/prop-types': [default_level, { skipUndeclared: true }], + 'react/react-in-jsx-scope': default_level, + 'react/self-closing-comp': default_level, + 'react/sort-comp': disabled, + 'react/jsx-wrap-multilines': [ + default_level, + { + declaration: false, + assignment: false, + return: true, + }, + ], + }, +} diff --git a/app/.npmrc b/app/.npmrc index 43c97e719..4fd15ed11 100644 --- a/app/.npmrc +++ b/app/.npmrc @@ -1 +1,2 @@ package-lock=false +legacy-peer-deps=true diff --git a/app/.prettierrc.cjs b/app/.prettierrc.cjs new file mode 100644 index 000000000..e93ca4b46 --- /dev/null +++ b/app/.prettierrc.cjs @@ -0,0 +1,17 @@ +module.exports = { + printWidth: 80, + tabWidth: 2, + useTabs: false, + semi: false, + singleQuote: true, + quoteProps: 'as-needed', + jsxSingleQuote: true, + trailingComma: 'all', + bracketSpacing: true, + bracketSameLine: false, + arrowParens: 'avoid', + requirePragma: false, + insertPragma: false, + endOfLine: 'lf', + htmlWhitespaceSensitivity: 'ignore', +} diff --git a/app/gulpfile.js b/app/gulpfile.js deleted file mode 100644 index bcfc45829..000000000 --- a/app/gulpfile.js +++ /dev/null @@ -1,454 +0,0 @@ -/** - * Tasks: - * - * gulp dist - * Generates the browser app in development mode (unless NODE_ENV is set - * to 'production'). - * - * gulp live - * Generates the browser app in development mode (unless NODE_ENV is set - * to 'production'), opens it and watches for changes in the source code. - * - * gulp devel - * Generates the browser app in development mode (unless NODE_ENV is set - * to 'production'), opens two browsers and watches for changes in the source - * code. - * - * gulp devel:tcp - * Same as gulp devel, but forcing media over TCP. - * - * gulp devel:vp9 - * Generates the browser app in development mode (unless NODE_ENV is set - * to 'production'), opens two browsers forcing VP9 and watches for changes in - * the source code. - * - * gulp devel:h264 - * Generates the browser app in development mode (unless NODE_ENV is set - * to 'production'), opens two browsers forcing H264 and watches for changes in - * the source code. - - * gulp - * Alias for `gulp dist`. - */ - -const fs = require('fs'); -const path = require('path'); -const gulp = require('gulp'); -const gulpif = require('gulp-if'); -const gutil = require('gulp-util'); -const plumber = require('gulp-plumber'); -const rename = require('gulp-rename'); -const header = require('gulp-header'); -const touch = require('gulp-touch-cmd'); -const browserify = require('browserify'); -const watchify = require('watchify'); -const envify = require('envify/custom'); -const uglify = require('gulp-uglify-es').default; -const source = require('vinyl-source-stream'); -const buffer = require('vinyl-buffer'); -const del = require('del'); -const mkdirp = require('mkdirp'); -const ncp = require('ncp'); -const eslint = require('gulp-eslint'); -const stylus = require('gulp-stylus'); -const cssBase64 = require('gulp-css-base64'); -const nib = require('nib'); -const browserSync = require('browser-sync'); - -const PKG = require('./package'); -const BANNER = fs.readFileSync('banner.txt').toString(); -const BANNER_OPTIONS = -{ - pkg : PKG, - currentYear : (new Date()).getFullYear() -}; -const OUTPUT_DIR = '../server/public'; - -// Set Node 'development' environment (unless externally set). -process.env.NODE_ENV = process.env.NODE_ENV || 'development'; - -gutil.log(`NODE_ENV: ${process.env.NODE_ENV}`); - -function logError(error) -{ - gutil.log(gutil.colors.red(error.stack)); -} - -function bundle(options) -{ - options = options || {}; - - const watch = Boolean(options.watch); - - let bundler = browserify( - { - entries : PKG.main, - extensions : [ '.js', '.jsx', 'mjs' ], - // required for sourcemaps (must be false otherwise). - debug : process.env.NODE_ENV === 'development', - // required for watchify. - cache : {}, - // required for watchify. - packageCache : {}, - // required to be true only for watchify. - fullPaths : watch - }) - .transform('babelify', { presets: [ '@babel/preset-env' ] }) - .transform(envify( - { - NODE_ENV : process.env.NODE_ENV, - _ : 'purge' - })); - - if (watch) - { - bundler = watchify(bundler); - - bundler.on('update', () => - { - const start = Date.now(); - - gutil.log('bundling...'); - rebundle(); - gutil.log('bundle took %sms', (Date.now() - start)); - }); - } - - function rebundle() - { - return bundler.bundle() - .on('error', logError) - .pipe(plumber()) - .pipe(source(`${PKG.name}.js`)) - .pipe(buffer()) - .pipe(rename(`${PKG.name}.js`)) - .pipe(gulpif(process.env.NODE_ENV === 'production', - uglify() - )) - .pipe(header(BANNER, BANNER_OPTIONS)) - .pipe(gulp.dest(OUTPUT_DIR)); - } - - return rebundle(); -} - -gulp.task('clean', () => del(OUTPUT_DIR, { force: true })); - -gulp.task('lint', () => -{ - const src = - [ - 'gulpfile.js', - 'lib/**/*.js', - 'lib/**/*.jsx' - ]; - - return gulp.src(src) - .pipe(plumber()) - .pipe(eslint()) - .pipe(eslint.format()); -}); - -gulp.task('css', () => -{ - return gulp.src('stylus/index.styl') - .pipe(plumber()) - .pipe(stylus( - { - use : nib(), - compress : process.env.NODE_ENV === 'production' - })) - .on('error', logError) - .pipe(cssBase64( - { - baseDir : '.', - maxWeightResource : 50000 // So big ttf fonts are not included, nice. - })) - .pipe(rename(`${PKG.name}.css`)) - .pipe(gulp.dest(OUTPUT_DIR)) - .pipe(touch()); -}); - -gulp.task('html', () => -{ - return gulp.src('index.html') - .pipe(gulp.dest(OUTPUT_DIR)); -}); - -gulp.task('resources', (done) => -{ - const dst = path.join(OUTPUT_DIR, 'resources'); - - mkdirp.sync(dst); - ncp('resources', dst, { stopOnErr: true }, (error) => - { - if (error && error[0].code !== 'ENOENT') - throw new Error(`resources copy failed: ${error}`); - - done(); - }); -}); - -gulp.task('bundle', () => -{ - return bundle({ watch: false }); -}); - -gulp.task('bundle:watch', () => -{ - return bundle({ watch: true }); -}); - -gulp.task('dist', gulp.series( - 'clean', - 'lint', - 'bundle', - 'html', - 'css', - 'resources' -)); - -gulp.task('watch', (done) => -{ - // Watch changes in HTML. - gulp.watch([ 'index.html' ], gulp.series( - 'html' - )); - - // Watch changes in Stylus files. - gulp.watch([ 'stylus/**/*.styl' ], gulp.series( - 'css' - )); - - // Watch changes in resources. - gulp.watch([ 'resources/**/*' ], gulp.series( - 'resources', 'css' - )); - - // Watch changes in JS files. - gulp.watch([ 'gulpfile.js', 'lib/**/*.js', 'lib/**/*.jsx' ], gulp.series( - 'lint' - )); - - done(); -}); - -gulp.task('browser:base', gulp.series( - 'clean', - 'lint', - 'bundle:watch', - 'html', - 'css', - 'resources', - 'watch' -)); - -gulp.task('live', gulp.series( - 'browser:base', - (done) => - { - const config = require('../server/config'); - - browserSync( - { - open : 'external', - host : config.domain, - startPath : '/?info=true', - server : - { - baseDir : OUTPUT_DIR - }, - https : config.https.tls, - ghostMode : false, - files : path.join(OUTPUT_DIR, '**', '*') - }); - - done(); - } -)); - -gulp.task('devel', gulp.series( - 'browser:base', - async (done) => - { - const config = require('../server/config'); - - await new Promise((resolve) => - { - browserSync.create('producer1').init( - { - open : 'external', - host : config.domain, - startPath : '/?roomId=devel&info=true&_throttleSecret=foo&consume=false', - server : - { - baseDir : OUTPUT_DIR - }, - https : config.https.tls, - ghostMode : false, - files : path.join(OUTPUT_DIR, '**', '*') - }, - resolve); - }); - - await new Promise((resolve) => - { - browserSync.create('consumer1').init( - { - open : 'external', - host : config.domain, - startPath : '/?roomId=devel&info=true&_throttleSecret=foo&produce=false', - server : - { - baseDir : OUTPUT_DIR - }, - https : config.https.tls, - ghostMode : false, - files : path.join(OUTPUT_DIR, '**', '*') - }, - resolve); - }); - - done(); - } -)); - -gulp.task('devel:tcp', gulp.series( - 'browser:base', - async (done) => - { - const config = require('../server/config'); - - await new Promise((resolve) => - { - browserSync.create('producer1').init( - { - open : 'external', - host : config.domain, - startPath : '/?roomId=devel:tcp&info=true&_throttleSecret=foo&forceTcp=true&consume=false', - server : - { - baseDir : OUTPUT_DIR - }, - https : config.https.tls, - ghostMode : false, - files : path.join(OUTPUT_DIR, '**', '*') - }, - resolve); - }); - - await new Promise((resolve) => - { - browserSync.create('consumer1').init( - { - open : 'external', - host : config.domain, - startPath : '/?roomId=devel:tcp&info=true&_throttleSecret=foo&forceTcp=true&produce=false', - server : - { - baseDir : OUTPUT_DIR - }, - https : config.https.tls, - ghostMode : false, - files : path.join(OUTPUT_DIR, '**', '*') - }, - resolve); - }); - - done(); - } -)); - -gulp.task('devel:vp9', gulp.series( - 'browser:base', - async (done) => - { - const config = require('../server/config'); - - await new Promise((resolve) => - { - browserSync.create('producer1').init( - { - open : 'external', - host : config.domain, - startPath : '/?roomId=devel:vp9&info=true&_throttleSecret=foo&forceVP9=true&numSimulcastStreams=3&webcamScalabilityMode=L1T3&consume=false', - server : - { - baseDir : OUTPUT_DIR - }, - https : config.https.tls, - ghostMode : false, - files : path.join(OUTPUT_DIR, '**', '*') - }, - resolve); - }); - - await new Promise((resolve) => - { - browserSync.create('consumer1').init( - { - open : 'external', - host : config.domain, - startPath : '/?roomId=devel:vp9&info=true&_throttleSecret=foo&forceVP9=true&produce=false', - server : - { - baseDir : OUTPUT_DIR - }, - https : config.https.tls, - ghostMode : false, - files : path.join(OUTPUT_DIR, '**', '*') - }, - resolve); - }); - - done(); - } -)); - -gulp.task('devel:h264', gulp.series( - 'browser:base', - async (done) => - { - const config = require('../server/config'); - - await new Promise((resolve) => - { - browserSync.create('producer1').init( - { - open : 'external', - host : config.domain, - startPath : '/?roomId=devel:h264&info=true&_throttleSecret=foo&forceH264=true&consume=false', - server : - { - baseDir : OUTPUT_DIR - }, - https : config.https.tls, - ghostMode : false, - files : path.join(OUTPUT_DIR, '**', '*') - }, - resolve); - }); - - await new Promise((resolve) => - { - browserSync.create('consumer1').init( - { - open : 'external', - host : config.domain, - startPath : '/?roomId=devel:h264&info=true&_throttleSecret=foo&forceH264=true&produce=false', - server : - { - baseDir : OUTPUT_DIR - }, - https : config.https.tls, - ghostMode : false, - files : path.join(OUTPUT_DIR, '**', '*') - }, - resolve); - }); - - done(); - } -)); - -gulp.task('default', gulp.series('dist')); diff --git a/app/index.html b/app/index.html index a65435268..78e6248f1 100644 --- a/app/index.html +++ b/app/index.html @@ -7,19 +7,10 @@ - - - - + diff --git a/app/lib/Logger.js b/app/lib/Logger.js deleted file mode 100644 index 7d1baa10e..000000000 --- a/app/lib/Logger.js +++ /dev/null @@ -1,43 +0,0 @@ -import debug from 'debug'; - -const APP_NAME = 'mediasoup-demo'; - -export default class Logger -{ - constructor(prefix) - { - if (prefix) - { - this._debug = debug(`${APP_NAME}:${prefix}`); - this._warn = debug(`${APP_NAME}:WARN:${prefix}`); - this._error = debug(`${APP_NAME}:ERROR:${prefix}`); - } - else - { - this._debug = debug(APP_NAME); - this._warn = debug(`${APP_NAME}:WARN`); - this._error = debug(`${APP_NAME}:ERROR`); - } - - /* eslint-disable no-console */ - this._debug.log = console.info.bind(console); - this._warn.log = console.warn.bind(console); - this._error.log = console.error.bind(console); - /* eslint-enable no-console */ - } - - get debug() - { - return this._debug; - } - - get warn() - { - return this._warn; - } - - get error() - { - return this._error; - } -} diff --git a/app/lib/RoomClient.js b/app/lib/RoomClient.js deleted file mode 100644 index 85794c07b..000000000 --- a/app/lib/RoomClient.js +++ /dev/null @@ -1,2669 +0,0 @@ -import protooClient from 'protoo-client'; -import * as mediasoupClient from 'mediasoup-client'; -import Logger from './Logger'; -import { getProtooUrl } from './urlFactory'; -import * as cookiesManager from './cookiesManager'; -import * as requestActions from './redux/requestActions'; -import * as stateActions from './redux/stateActions'; -import * as e2e from './e2e'; - -const VIDEO_CONSTRAINS = -{ - qvga : { width: { ideal: 320 }, height: { ideal: 240 } }, - vga : { width: { ideal: 640 }, height: { ideal: 480 } }, - hd : { width: { ideal: 1280 }, height: { ideal: 720 } } -}; - -const PC_PROPRIETARY_CONSTRAINTS = -{ - // optional : [ { googDscp: true } ] -}; - -const EXTERNAL_VIDEO_SRC = '/resources/videos/video-audio-stereo.mp4'; - -const logger = new Logger('RoomClient'); - -let store; - -export default class RoomClient -{ - /** - * @param {Object} data - * @param {Object} data.store - The Redux store. - */ - static init(data) - { - store = data.store; - } - - constructor( - { - roomId, - peerId, - displayName, - device, - handlerName, - forceTcp, - produce, - consume, - datachannel, - enableWebcamLayers, - enableSharingLayers, - webcamScalabilityMode, - sharingScalabilityMode, - numSimulcastStreams, - forceVP8, - forceH264, - forceVP9, - externalVideo, - e2eKey, - consumerReplicas - } - ) - { - logger.debug( - 'constructor() [roomId:"%s", peerId:"%s", displayName:"%s", device:%s]', - roomId, peerId, displayName, device.flag); - - // Closed flag. - // @type {Boolean} - this._closed = false; - - // Display name. - // @type {String} - this._displayName = displayName; - - // Device info. - // @type {Object} - this._device = device; - - // Custom mediasoup-client handler name (to override default browser - // detection if desired). - // @type {String} - this._handlerName = handlerName; - - // Whether we want to force RTC over TCP. - // @type {Boolean} - this._forceTcp = forceTcp; - - // Whether we want to produce audio/video. - // @type {Boolean} - this._produce = produce; - - // Whether we should consume. - // @type {Boolean} - this._consume = consume; - - // Whether we want DataChannels. - // @type {Boolean} - this._useDataChannel = Boolean(datachannel); - - // Force VP8 codec for sending. - // @type {Boolean} - this._forceVP8 = Boolean(forceVP8); - - // Force H264 codec for sending. - // @type {Boolean} - this._forceH264 = Boolean(forceH264); - - // Force VP9 codec for sending. - // @type {Boolean} - this._forceVP9 = Boolean(forceVP9); - - // Whether simulcast or SVC should be used for webcam. - // @type {Boolean} - this._enableWebcamLayers = Boolean(enableWebcamLayers); - - // Whether simulcast or SVC should be used in desktop sharing. - // @type {Boolean} - this._enableSharingLayers = Boolean(enableSharingLayers); - - // Scalability mode for webcam. - // @type {String} - this._webcamScalabilityMode = webcamScalabilityMode; - - // Scalability mode for sharing. - // @type {String} - this._sharingScalabilityMode = sharingScalabilityMode; - - // Number of simuclast streams for webcam and sharing. - // @type {Number} - this._numSimulcastStreams = numSimulcastStreams; - - // External video. - // @type {HTMLVideoElement} - this._externalVideo = null; - - // Enabled end-to-end encryption. - this._e2eKey = e2eKey; - - // MediaStream of the external video. - // @type {MediaStream} - this._externalVideoStream = null; - - // Next expected dataChannel test number. - // @type {Number} - this._nextDataChannelTestNumber = 0; - - if (externalVideo) - { - this._externalVideo = document.createElement('video'); - - this._externalVideo.controls = true; - this._externalVideo.muted = true; - this._externalVideo.loop = true; - this._externalVideo.setAttribute('playsinline', ''); - this._externalVideo.src = EXTERNAL_VIDEO_SRC; - - this._externalVideo.play() - .catch((error) => logger.warn('externalVideo.play() failed:%o', error)); - } - - // Protoo URL. - // @type {String} - this._protooUrl = getProtooUrl({ roomId, peerId, consumerReplicas }); - - // protoo-client Peer instance. - // @type {protooClient.Peer} - this._protoo = null; - - // mediasoup-client Device instance. - // @type {mediasoupClient.Device} - this._mediasoupDevice = null; - - // mediasoup Transport for sending. - // @type {mediasoupClient.Transport} - this._sendTransport = null; - - // mediasoup Transport for receiving. - // @type {mediasoupClient.Transport} - this._recvTransport = null; - - // Local mic mediasoup Producer. - // @type {mediasoupClient.Producer} - this._micProducer = null; - - // Local webcam mediasoup Producer. - // @type {mediasoupClient.Producer} - this._webcamProducer = null; - - // Local share mediasoup Producer. - // @type {mediasoupClient.Producer} - this._shareProducer = null; - - // Local chat DataProducer. - // @type {mediasoupClient.DataProducer} - this._chatDataProducer = null; - - // Local bot DataProducer. - // @type {mediasoupClient.DataProducer} - this._botDataProducer = null; - - // mediasoup Consumers. - // @type {Map} - this._consumers = new Map(); - - // mediasoup DataConsumers. - // @type {Map} - this._dataConsumers = new Map(); - - // Map of webcam MediaDeviceInfos indexed by deviceId. - // @type {Map} - this._webcams = new Map(); - - // Local Webcam. - // @type {Object} with: - // - {MediaDeviceInfo} [device] - // - {String} [resolution] - 'qvga' / 'vga' / 'hd'. - this._webcam = - { - device : null, - resolution : 'hd' - }; - - if (this._e2eKey && e2e.isSupported()) - { - e2e.setCryptoKey('setCryptoKey', this._e2eKey, true); - } - } - - close() - { - if (this._closed) - return; - - this._closed = true; - - logger.debug('close()'); - - // Close protoo Peer - this._protoo.close(); - - // Close mediasoup Transports. - if (this._sendTransport) - this._sendTransport.close(); - - if (this._recvTransport) - this._recvTransport.close(); - - store.dispatch( - stateActions.setRoomState('closed')); - } - - async join() - { - store.dispatch( - stateActions.setMediasoupClientVersion(mediasoupClient.version)); - - const protooTransport = new protooClient.WebSocketTransport(this._protooUrl); - - this._protoo = new protooClient.Peer(protooTransport); - - store.dispatch( - stateActions.setRoomState('connecting')); - - this._protoo.on('open', () => this._joinRoom()); - - this._protoo.on('failed', () => - { - store.dispatch(requestActions.notify( - { - type : 'error', - text : 'WebSocket connection failed' - })); - }); - - this._protoo.on('disconnected', () => - { - store.dispatch(requestActions.notify( - { - type : 'error', - text : 'WebSocket disconnected' - })); - - // Close mediasoup Transports. - if (this._sendTransport) - { - this._sendTransport.close(); - this._sendTransport = null; - } - - if (this._recvTransport) - { - this._recvTransport.close(); - this._recvTransport = null; - } - - store.dispatch( - stateActions.setRoomState('closed')); - }); - - this._protoo.on('close', () => - { - if (this._closed) - return; - - this.close(); - }); - - // eslint-disable-next-line no-unused-vars - this._protoo.on('request', async (request, accept, reject) => - { - logger.debug( - 'proto "request" event [method:%s, data:%o]', - request.method, request.data); - - switch (request.method) - { - case 'newConsumer': - { - if (!this._consume) - { - reject(403, 'I do not want to consume'); - - break; - } - - const { - peerId, - producerId, - id, - kind, - rtpParameters, - type, - appData, - producerPaused - } = request.data; - - try - { - const consumer = await this._recvTransport.consume( - { - id, - producerId, - kind, - rtpParameters, - // NOTE: Force streamId to be same in mic and webcam and different - // in screen sharing so libwebrtc will just try to sync mic and - // webcam streams from the same remote peer. - streamId : `${peerId}-${appData.share ? 'share' : 'mic-webcam'}`, - appData : { ...appData, peerId } // Trick. - }); - - if (this._e2eKey && e2e.isSupported()) - { - e2e.setupReceiverTransform(consumer.rtpReceiver); - } - - // Store in the map. - this._consumers.set(consumer.id, consumer); - - consumer.on('transportclose', () => - { - this._consumers.delete(consumer.id); - }); - - const { spatialLayers, temporalLayers } = - mediasoupClient.parseScalabilityMode( - consumer.rtpParameters.encodings[0].scalabilityMode); - - store.dispatch(stateActions.addConsumer( - { - id : consumer.id, - type : type, - locallyPaused : false, - remotelyPaused : producerPaused, - rtpParameters : consumer.rtpParameters, - spatialLayers : spatialLayers, - temporalLayers : temporalLayers, - preferredSpatialLayer : spatialLayers - 1, - preferredTemporalLayer : temporalLayers - 1, - priority : 1, - codec : consumer.rtpParameters.codecs[0].mimeType.split('/')[1], - track : consumer.track - }, - peerId)); - - // We are ready. Answer the protoo request so the server will - // resume this Consumer (which was paused for now if video). - accept(); - - // If audio-only mode is enabled, pause it. - if (consumer.kind === 'video' && store.getState().me.audioOnly) - this._pauseConsumer(consumer); - } - catch (error) - { - logger.error('"newConsumer" request failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error creating a Consumer: ${error}` - })); - - throw error; - } - - break; - } - - case 'newDataConsumer': - { - if (!this._consume) - { - reject(403, 'I do not want to data consume'); - - break; - } - - if (!this._useDataChannel) - { - reject(403, 'I do not want DataChannels'); - - break; - } - - const { - peerId, // NOTE: Null if bot. - dataProducerId, - id, - sctpStreamParameters, - label, - protocol, - appData - } = request.data; - - try - { - const dataConsumer = await this._recvTransport.consumeData( - { - id, - dataProducerId, - sctpStreamParameters, - label, - protocol, - appData : { ...appData, peerId } // Trick. - }); - - // Store in the map. - this._dataConsumers.set(dataConsumer.id, dataConsumer); - - dataConsumer.on('transportclose', () => - { - this._dataConsumers.delete(dataConsumer.id); - }); - - dataConsumer.on('open', () => - { - logger.debug('DataConsumer "open" event'); - }); - - dataConsumer.on('close', () => - { - logger.warn('DataConsumer "close" event'); - - this._dataConsumers.delete(dataConsumer.id); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : 'DataConsumer closed' - })); - }); - - dataConsumer.on('error', (error) => - { - logger.error('DataConsumer "error" event:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `DataConsumer error: ${error}` - })); - }); - - dataConsumer.on('message', (message) => - { - logger.debug( - 'DataConsumer "message" event [streamId:%d]', - dataConsumer.sctpStreamParameters.streamId); - - // TODO: For debugging. - window.DC_MESSAGE = message; - - if (message instanceof ArrayBuffer) - { - const view = new DataView(message); - const number = view.getUint32(); - - if (number == Math.pow(2, 32) - 1) - { - logger.warn('dataChannelTest finished!'); - - this._nextDataChannelTestNumber = 0; - - return; - } - - if (number > this._nextDataChannelTestNumber) - { - logger.warn( - 'dataChannelTest: %s packets missing', - number - this._nextDataChannelTestNumber); - } - - this._nextDataChannelTestNumber = number + 1; - - return; - } - else if (typeof message !== 'string') - { - logger.warn('ignoring DataConsumer "message" (not a string)'); - - return; - } - - switch (dataConsumer.label) - { - case 'chat': - { - const { peers } = store.getState(); - const peersArray = Object.keys(peers) - .map((_peerId) => peers[_peerId]); - const sendingPeer = peersArray - .find((peer) => peer.dataConsumers.includes(dataConsumer.id)); - - if (!sendingPeer) - { - logger.warn('DataConsumer "message" from unknown peer'); - - break; - } - - store.dispatch(requestActions.notify( - { - title : `${sendingPeer.displayName} says:`, - text : message, - timeout : 5000 - })); - - break; - } - - case 'bot': - { - store.dispatch(requestActions.notify( - { - title : 'Message from Bot:', - text : message, - timeout : 5000 - })); - - break; - } - } - }); - - // TODO: REMOVE - window.DC = dataConsumer; - - store.dispatch(stateActions.addDataConsumer( - { - id : dataConsumer.id, - sctpStreamParameters : dataConsumer.sctpStreamParameters, - label : dataConsumer.label, - protocol : dataConsumer.protocol - }, - peerId)); - - // We are ready. Answer the protoo request. - accept(); - } - catch (error) - { - logger.error('"newDataConsumer" request failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error creating a DataConsumer: ${error}` - })); - - throw error; - } - - break; - } - } - }); - - this._protoo.on('notification', (notification) => - { - logger.debug( - 'proto "notification" event [method:%s, data:%o]', - notification.method, notification.data); - - switch (notification.method) - { - case 'mediasoup-version': - { - const { version } = notification.data; - - store.dispatch( - stateActions.setMediasoupVersion(version)); - - break; - } - - case 'producerScore': - { - const { producerId, score } = notification.data; - - store.dispatch( - stateActions.setProducerScore(producerId, score)); - - break; - } - - case 'newPeer': - { - const peer = notification.data; - - store.dispatch( - stateActions.addPeer( - { ...peer, consumers: [], dataConsumers: [] })); - - store.dispatch(requestActions.notify( - { - text : `${peer.displayName} has joined the room` - })); - - break; - } - - case 'peerClosed': - { - const { peerId } = notification.data; - - store.dispatch( - stateActions.removePeer(peerId)); - - break; - } - - case 'peerDisplayNameChanged': - { - const { peerId, displayName, oldDisplayName } = notification.data; - - store.dispatch( - stateActions.setPeerDisplayName(displayName, peerId)); - - store.dispatch(requestActions.notify( - { - text : `${oldDisplayName} is now ${displayName}` - })); - - break; - } - - case 'downlinkBwe': - { - logger.debug('\'downlinkBwe\' event:%o', notification.data); - - break; - } - - case 'consumerClosed': - { - const { consumerId } = notification.data; - const consumer = this._consumers.get(consumerId); - - if (!consumer) - break; - - consumer.close(); - this._consumers.delete(consumerId); - - const { peerId } = consumer.appData; - - store.dispatch( - stateActions.removeConsumer(consumerId, peerId)); - - break; - } - - case 'consumerPaused': - { - const { consumerId } = notification.data; - const consumer = this._consumers.get(consumerId); - - if (!consumer) - break; - - consumer.pause(); - - store.dispatch( - stateActions.setConsumerPaused(consumerId, 'remote')); - - break; - } - - case 'consumerResumed': - { - const { consumerId } = notification.data; - const consumer = this._consumers.get(consumerId); - - if (!consumer) - break; - - consumer.resume(); - - store.dispatch( - stateActions.setConsumerResumed(consumerId, 'remote')); - - break; - } - - case 'consumerLayersChanged': - { - const { consumerId, spatialLayer, temporalLayer } = notification.data; - const consumer = this._consumers.get(consumerId); - - if (!consumer) - break; - - store.dispatch(stateActions.setConsumerCurrentLayers( - consumerId, spatialLayer, temporalLayer)); - - break; - } - - case 'consumerScore': - { - const { consumerId, score } = notification.data; - - store.dispatch( - stateActions.setConsumerScore(consumerId, score)); - - break; - } - - case 'dataConsumerClosed': - { - const { dataConsumerId } = notification.data; - const dataConsumer = this._dataConsumers.get(dataConsumerId); - - if (!dataConsumer) - break; - - dataConsumer.close(); - this._dataConsumers.delete(dataConsumerId); - - const { peerId } = dataConsumer.appData; - - store.dispatch( - stateActions.removeDataConsumer(dataConsumerId, peerId)); - - break; - } - - case 'activeSpeaker': - { - const { peerId } = notification.data; - - store.dispatch( - stateActions.setRoomActiveSpeaker(peerId)); - - break; - } - - default: - { - logger.error( - 'unknown protoo notification.method "%s"', notification.method); - } - } - }); - } - - async enableMic() - { - logger.debug('enableMic()'); - - if (this._micProducer) - return; - - if (!this._mediasoupDevice.canProduce('audio')) - { - logger.error('enableMic() | cannot produce audio'); - - return; - } - - let track; - - try - { - if (!this._externalVideo) - { - logger.debug('enableMic() | calling getUserMedia()'); - - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - - track = stream.getAudioTracks()[0]; - } - else - { - const stream = await this._getExternalVideoStream(); - - track = stream.getAudioTracks()[0].clone(); - } - - this._micProducer = await this._sendTransport.produce( - { - track, - codecOptions : - { - opusStereo : true, - opusDtx : true, - opusFec : true, - opusNack : true - } - // NOTE: for testing codec selection. - // codec : this._mediasoupDevice.rtpCapabilities.codecs - // .find((codec) => codec.mimeType.toLowerCase() === 'audio/pcma') - }); - - if (this._e2eKey && e2e.isSupported()) - { - e2e.setupSenderTransform(this._micProducer.rtpSender); - } - - store.dispatch(stateActions.addProducer( - { - id : this._micProducer.id, - paused : this._micProducer.paused, - track : this._micProducer.track, - rtpParameters : this._micProducer.rtpParameters, - codec : this._micProducer.rtpParameters.codecs[0].mimeType.split('/')[1] - })); - - this._micProducer.on('transportclose', () => - { - this._micProducer = null; - }); - - this._micProducer.on('trackended', () => - { - store.dispatch(requestActions.notify( - { - type : 'error', - text : 'Microphone disconnected!' - })); - - this.disableMic() - .catch(() => {}); - }); - } - catch (error) - { - logger.error('enableMic() | failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error enabling microphone: ${error}` - })); - - if (track) - track.stop(); - } - } - - async disableMic() - { - logger.debug('disableMic()'); - - if (!this._micProducer) - return; - - this._micProducer.close(); - - store.dispatch( - stateActions.removeProducer(this._micProducer.id)); - - try - { - await this._protoo.request( - 'closeProducer', { producerId: this._micProducer.id }); - } - catch (error) - { - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error closing server-side mic Producer: ${error}` - })); - } - - this._micProducer = null; - } - - async muteMic() - { - logger.debug('muteMic()'); - - this._micProducer.pause(); - - try - { - await this._protoo.request( - 'pauseProducer', { producerId: this._micProducer.id }); - - store.dispatch( - stateActions.setProducerPaused(this._micProducer.id)); - } - catch (error) - { - logger.error('muteMic() | failed: %o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error pausing server-side mic Producer: ${error}` - })); - } - } - - async unmuteMic() - { - logger.debug('unmuteMic()'); - - this._micProducer.resume(); - - try - { - await this._protoo.request( - 'resumeProducer', { producerId: this._micProducer.id }); - - store.dispatch( - stateActions.setProducerResumed(this._micProducer.id)); - } - catch (error) - { - logger.error('unmuteMic() | failed: %o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error resuming server-side mic Producer: ${error}` - })); - } - } - - async enableWebcam() - { - logger.debug('enableWebcam()'); - - if (this._webcamProducer) - return; - else if (this._shareProducer) - await this.disableShare(); - - if (!this._mediasoupDevice.canProduce('video')) - { - logger.error('enableWebcam() | cannot produce video'); - - return; - } - - let track; - let device; - - store.dispatch( - stateActions.setWebcamInProgress(true)); - - try - { - if (!this._externalVideo) - { - await this._updateWebcams(); - device = this._webcam.device; - - const { resolution } = this._webcam; - - if (!device) - throw new Error('no webcam devices'); - - logger.debug('enableWebcam() | calling getUserMedia()'); - - const stream = await navigator.mediaDevices.getUserMedia( - { - video : - { - deviceId : { ideal: device.deviceId }, - ...VIDEO_CONSTRAINS[resolution] - } - }); - - track = stream.getVideoTracks()[0]; - } - else - { - device = { label: 'external video' }; - - const stream = await this._getExternalVideoStream(); - - track = stream.getVideoTracks()[0].clone(); - } - - let encodings; - let codec; - const codecOptions = - { - videoGoogleStartBitrate : 1000 - }; - - if (this._forceVP8) - { - codec = this._mediasoupDevice.rtpCapabilities.codecs - .find((c) => c.mimeType.toLowerCase() === 'video/vp8'); - - if (!codec) - { - throw new Error('desired VP8 codec+configuration is not supported'); - } - } - else if (this._forceH264) - { - codec = this._mediasoupDevice.rtpCapabilities.codecs - .find((c) => c.mimeType.toLowerCase() === 'video/h264'); - - if (!codec) - { - throw new Error('desired H264 codec+configuration is not supported'); - } - } - else if (this._forceVP9) - { - codec = this._mediasoupDevice.rtpCapabilities.codecs - .find((c) => c.mimeType.toLowerCase() === 'video/vp9'); - - if (!codec) - { - throw new Error('desired VP9 codec+configuration is not supported'); - } - } - - if (this._enableWebcamLayers) - { - // If VP9 is the only available video codec then use SVC. - const firstVideoCodec = this._mediasoupDevice - .rtpCapabilities - .codecs - .find((c) => c.kind === 'video'); - - // VP9 with SVC. - if ( - (this._forceVP9 && codec) || - firstVideoCodec.mimeType.toLowerCase() === 'video/vp9' - ) - { - encodings = - [ - { - maxBitrate : 5000000, - scalabilityMode : this._webcamScalabilityMode || 'L3T3_KEY' - } - ]; - } - // VP8 or H264 with simulcast. - else - { - encodings = - [ - { - scaleResolutionDownBy : 1, - maxBitrate : 5000000, - scalabilityMode : this._webcamScalabilityMode || 'L1T3' - } - ]; - - if (this._numSimulcastStreams > 1) - { - encodings.unshift( - { - scaleResolutionDownBy : 2, - maxBitrate : 1000000, - scalabilityMode : this._webcamScalabilityMode || 'L1T3' - } - ); - } - - if (this._numSimulcastStreams > 2) - { - encodings.unshift( - { - scaleResolutionDownBy : 4, - maxBitrate : 500000, - scalabilityMode : this._webcamScalabilityMode || 'L1T3' - } - ); - } - } - } - - this._webcamProducer = await this._sendTransport.produce( - { - track, - encodings, - codecOptions, - codec - }); - - if (this._e2eKey && e2e.isSupported()) - { - e2e.setupSenderTransform(this._webcamProducer.rtpSender); - } - - store.dispatch(stateActions.addProducer( - { - id : this._webcamProducer.id, - deviceLabel : device.label, - type : this._getWebcamType(device), - paused : this._webcamProducer.paused, - track : this._webcamProducer.track, - rtpParameters : this._webcamProducer.rtpParameters, - codec : this._webcamProducer.rtpParameters.codecs[0].mimeType.split('/')[1] - })); - - this._webcamProducer.on('transportclose', () => - { - this._webcamProducer = null; - }); - - this._webcamProducer.on('trackended', () => - { - store.dispatch(requestActions.notify( - { - type : 'error', - text : 'Webcam disconnected!' - })); - - this.disableWebcam() - .catch(() => {}); - }); - } - catch (error) - { - logger.error('enableWebcam() | failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error enabling webcam: ${error}` - })); - - if (track) - track.stop(); - } - - store.dispatch( - stateActions.setWebcamInProgress(false)); - } - - async disableWebcam() - { - logger.debug('disableWebcam()'); - - if (!this._webcamProducer) - return; - - this._webcamProducer.close(); - - store.dispatch( - stateActions.removeProducer(this._webcamProducer.id)); - - try - { - await this._protoo.request( - 'closeProducer', { producerId: this._webcamProducer.id }); - } - catch (error) - { - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error closing server-side webcam Producer: ${error}` - })); - } - - this._webcamProducer = null; - } - - async changeWebcam() - { - logger.debug('changeWebcam()'); - - store.dispatch( - stateActions.setWebcamInProgress(true)); - - try - { - await this._updateWebcams(); - - const array = Array.from(this._webcams.keys()); - const len = array.length; - const deviceId = - this._webcam.device ? this._webcam.device.deviceId : undefined; - let idx = array.indexOf(deviceId); - - if (idx < len - 1) - idx++; - else - idx = 0; - - this._webcam.device = this._webcams.get(array[idx]); - - logger.debug( - 'changeWebcam() | new selected webcam [device:%o]', - this._webcam.device); - - // Reset video resolution to HD. - this._webcam.resolution = 'hd'; - - if (!this._webcam.device) - throw new Error('no webcam devices'); - - // Closing the current video track before asking for a new one (mobiles do not like - // having both front/back cameras open at the same time). - this._webcamProducer.track.stop(); - - logger.debug('changeWebcam() | calling getUserMedia()'); - - const stream = await navigator.mediaDevices.getUserMedia( - { - video : - { - deviceId : { exact: this._webcam.device.deviceId }, - ...VIDEO_CONSTRAINS[this._webcam.resolution] - } - }); - - const track = stream.getVideoTracks()[0]; - - await this._webcamProducer.replaceTrack({ track }); - - store.dispatch( - stateActions.setProducerTrack(this._webcamProducer.id, track)); - } - catch (error) - { - logger.error('changeWebcam() | failed: %o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Could not change webcam: ${error}` - })); - } - - store.dispatch( - stateActions.setWebcamInProgress(false)); - } - - async changeWebcamResolution() - { - logger.debug('changeWebcamResolution()'); - - store.dispatch( - stateActions.setWebcamInProgress(true)); - - try - { - switch (this._webcam.resolution) - { - case 'qvga': - this._webcam.resolution = 'vga'; - break; - case 'vga': - this._webcam.resolution = 'hd'; - break; - case 'hd': - this._webcam.resolution = 'qvga'; - break; - default: - this._webcam.resolution = 'hd'; - } - - logger.debug('changeWebcamResolution() | calling getUserMedia()'); - - const stream = await navigator.mediaDevices.getUserMedia( - { - video : - { - deviceId : { exact: this._webcam.device.deviceId }, - ...VIDEO_CONSTRAINS[this._webcam.resolution] - } - }); - - const track = stream.getVideoTracks()[0]; - - await this._webcamProducer.replaceTrack({ track }); - - store.dispatch( - stateActions.setProducerTrack(this._webcamProducer.id, track)); - } - catch (error) - { - logger.error('changeWebcamResolution() | failed: %o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Could not change webcam resolution: ${error}` - })); - } - - store.dispatch( - stateActions.setWebcamInProgress(false)); - } - - async enableShare() - { - logger.debug('enableShare()'); - - if (this._shareProducer) - return; - else if (this._webcamProducer) - await this.disableWebcam(); - - if (!this._mediasoupDevice.canProduce('video')) - { - logger.error('enableShare() | cannot produce video'); - - return; - } - - let track; - - store.dispatch( - stateActions.setShareInProgress(true)); - - try - { - logger.debug('enableShare() | calling getUserMedia()'); - - const stream = await navigator.mediaDevices.getDisplayMedia( - { - audio : false, - video : - { - displaySurface : 'monitor', - logicalSurface : true, - cursor : true, - width : { max: 1920 }, - height : { max: 1080 }, - frameRate : { max: 30 } - } - }); - - // May mean cancelled (in some implementations). - if (!stream) - { - store.dispatch( - stateActions.setShareInProgress(true)); - - return; - } - - track = stream.getVideoTracks()[0]; - - let encodings; - let codec; - const codecOptions = - { - videoGoogleStartBitrate : 1000 - }; - - if (this._forceVP8) - { - codec = this._mediasoupDevice.rtpCapabilities.codecs - .find((c) => c.mimeType.toLowerCase() === 'video/vp8'); - - if (!codec) - { - throw new Error('desired VP8 codec+configuration is not supported'); - } - } - else if (this._forceH264) - { - codec = this._mediasoupDevice.rtpCapabilities.codecs - .find((c) => c.mimeType.toLowerCase() === 'video/h264'); - - if (!codec) - { - throw new Error('desired H264 codec+configuration is not supported'); - } - } - else if (this._forceVP9) - { - codec = this._mediasoupDevice.rtpCapabilities.codecs - .find((c) => c.mimeType.toLowerCase() === 'video/vp9'); - - if (!codec) - { - throw new Error('desired VP9 codec+configuration is not supported'); - } - } - - if (this._enableSharingLayers) - { - // If VP9 is the only available video codec then use SVC. - const firstVideoCodec = this._mediasoupDevice - .rtpCapabilities - .codecs - .find((c) => c.kind === 'video'); - - // VP9 with SVC. - if ( - (this._forceVP9 && codec) || - firstVideoCodec.mimeType.toLowerCase() === 'video/vp9' - ) - { - encodings = - [ - { - maxBitrate : 5000000, - scalabilityMode : this._sharingScalabilityMode || 'L3T3', - dtx : true - } - ]; - } - // VP8 or H264 with simulcast. - else - { - encodings = - [ - { - scaleResolutionDownBy : 1, - maxBitrate : 5000000, - scalabilityMode : this._sharingScalabilityMode || 'L1T3', - dtx : true - } - ]; - - if (this._numSimulcastStreams > 1) - { - encodings.unshift( - { - scaleResolutionDownBy : 2, - maxBitrate : 1000000, - scalabilityMode : this._sharingScalabilityMode || 'L1T3', - dtx : true - } - ); - } - - if (this._numSimulcastStreams > 2) - { - encodings.unshift( - { - scaleResolutionDownBy : 4, - maxBitrate : 500000, - scalabilityMode : this._sharingScalabilityMode || 'L1T3', - dtx : true - } - ); - } - } - } - - this._shareProducer = await this._sendTransport.produce( - { - track, - encodings, - codecOptions, - codec, - appData : - { - share : true - } - }); - - if (this._e2eKey && e2e.isSupported()) - { - e2e.setupSenderTransform(this._shareProducer.rtpSender); - } - - store.dispatch(stateActions.addProducer( - { - id : this._shareProducer.id, - type : 'share', - paused : this._shareProducer.paused, - track : this._shareProducer.track, - rtpParameters : this._shareProducer.rtpParameters, - codec : this._shareProducer.rtpParameters.codecs[0].mimeType.split('/')[1] - })); - - this._shareProducer.on('transportclose', () => - { - this._shareProducer = null; - }); - - this._shareProducer.on('trackended', () => - { - store.dispatch(requestActions.notify( - { - type : 'error', - text : 'Share disconnected!' - })); - - this.disableShare() - .catch(() => {}); - }); - } - catch (error) - { - logger.error('enableShare() | failed:%o', error); - - if (error.name !== 'NotAllowedError') - { - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error sharing: ${error}` - })); - } - - if (track) - track.stop(); - } - - store.dispatch( - stateActions.setShareInProgress(false)); - } - - async disableShare() - { - logger.debug('disableShare()'); - - if (!this._shareProducer) - return; - - this._shareProducer.close(); - - store.dispatch( - stateActions.removeProducer(this._shareProducer.id)); - - try - { - await this._protoo.request( - 'closeProducer', { producerId: this._shareProducer.id }); - } - catch (error) - { - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error closing server-side share Producer: ${error}` - })); - } - - this._shareProducer = null; - } - - async enableAudioOnly() - { - logger.debug('enableAudioOnly()'); - - store.dispatch( - stateActions.setAudioOnlyInProgress(true)); - - this.disableWebcam(); - - for (const consumer of this._consumers.values()) - { - if (consumer.kind !== 'video') - continue; - - this._pauseConsumer(consumer); - } - - store.dispatch( - stateActions.setAudioOnlyState(true)); - - store.dispatch( - stateActions.setAudioOnlyInProgress(false)); - } - - async disableAudioOnly() - { - logger.debug('disableAudioOnly()'); - - store.dispatch( - stateActions.setAudioOnlyInProgress(true)); - - if ( - !this._webcamProducer && - this._produce && - (cookiesManager.getDevices() || {}).webcamEnabled - ) - { - this.enableWebcam(); - } - - for (const consumer of this._consumers.values()) - { - if (consumer.kind !== 'video') - continue; - - this._resumeConsumer(consumer); - } - - store.dispatch( - stateActions.setAudioOnlyState(false)); - - store.dispatch( - stateActions.setAudioOnlyInProgress(false)); - } - - async muteAudio() - { - logger.debug('muteAudio()'); - - store.dispatch( - stateActions.setAudioMutedState(true)); - } - - async unmuteAudio() - { - logger.debug('unmuteAudio()'); - - store.dispatch( - stateActions.setAudioMutedState(false)); - } - - async restartIce() - { - logger.debug('restartIce()'); - - store.dispatch( - stateActions.setRestartIceInProgress(true)); - - try - { - if (this._sendTransport) - { - const iceParameters = await this._protoo.request( - 'restartIce', - { transportId: this._sendTransport.id }); - - await this._sendTransport.restartIce({ iceParameters }); - } - - if (this._recvTransport) - { - const iceParameters = await this._protoo.request( - 'restartIce', - { transportId: this._recvTransport.id }); - - await this._recvTransport.restartIce({ iceParameters }); - } - - store.dispatch(requestActions.notify( - { - text : 'ICE restarted' - })); - } - catch (error) - { - logger.error('restartIce() | failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `ICE restart failed: ${error}` - })); - } - - store.dispatch( - stateActions.setRestartIceInProgress(false)); - } - - async setMaxSendingSpatialLayer(spatialLayer) - { - logger.debug('setMaxSendingSpatialLayer() [spatialLayer:%s]', spatialLayer); - - try - { - if (this._webcamProducer) - await this._webcamProducer.setMaxSpatialLayer(spatialLayer); - else if (this._shareProducer) - await this._shareProducer.setMaxSpatialLayer(spatialLayer); - } - catch (error) - { - logger.error('setMaxSendingSpatialLayer() | failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error setting max sending video spatial layer: ${error}` - })); - } - } - - async setConsumerPreferredLayers(consumerId, spatialLayer, temporalLayer) - { - logger.debug( - 'setConsumerPreferredLayers() [consumerId:%s, spatialLayer:%s, temporalLayer:%s]', - consumerId, spatialLayer, temporalLayer); - - try - { - await this._protoo.request( - 'setConsumerPreferredLayers', { consumerId, spatialLayer, temporalLayer }); - - store.dispatch(stateActions.setConsumerPreferredLayers( - consumerId, spatialLayer, temporalLayer)); - } - catch (error) - { - logger.error('setConsumerPreferredLayers() | failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error setting Consumer preferred layers: ${error}` - })); - } - } - - async setConsumerPriority(consumerId, priority) - { - logger.debug( - 'setConsumerPriority() [consumerId:%s, priority:%d]', - consumerId, priority); - - try - { - await this._protoo.request('setConsumerPriority', { consumerId, priority }); - - store.dispatch(stateActions.setConsumerPriority(consumerId, priority)); - } - catch (error) - { - logger.error('setConsumerPriority() | failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error setting Consumer priority: ${error}` - })); - } - } - - async requestConsumerKeyFrame(consumerId) - { - logger.debug('requestConsumerKeyFrame() [consumerId:%s]', consumerId); - - try - { - await this._protoo.request('requestConsumerKeyFrame', { consumerId }); - - store.dispatch(requestActions.notify( - { - text : 'Keyframe requested for video consumer' - })); - } - catch (error) - { - logger.error('requestConsumerKeyFrame() | failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error requesting key frame for Consumer: ${error}` - })); - } - } - - async enableChatDataProducer() - { - logger.debug('enableChatDataProducer()'); - - if (!this._useDataChannel) - return; - - // NOTE: Should enable this code but it's useful for testing. - // if (this._chatDataProducer) - // return; - - try - { - // Create chat DataProducer. - this._chatDataProducer = await this._sendTransport.produceData( - { - ordered : false, - maxRetransmits : 1, - label : 'chat', - priority : 'medium', - appData : { info: 'my-chat-DataProducer' } - }); - - store.dispatch(stateActions.addDataProducer( - { - id : this._chatDataProducer.id, - sctpStreamParameters : this._chatDataProducer.sctpStreamParameters, - label : this._chatDataProducer.label, - protocol : this._chatDataProducer.protocol - })); - - this._chatDataProducer.on('transportclose', () => - { - this._chatDataProducer = null; - }); - - this._chatDataProducer.on('open', () => - { - logger.debug('chat DataProducer "open" event'); - }); - - this._chatDataProducer.on('close', () => - { - logger.error('chat DataProducer "close" event'); - - this._chatDataProducer = null; - - store.dispatch(requestActions.notify( - { - type : 'error', - text : 'Chat DataProducer closed' - })); - }); - - this._chatDataProducer.on('error', (error) => - { - logger.error('chat DataProducer "error" event:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Chat DataProducer error: ${error}` - })); - }); - - this._chatDataProducer.on('bufferedamountlow', () => - { - logger.debug('chat DataProducer "bufferedamountlow" event'); - }); - } - catch (error) - { - logger.error('enableChatDataProducer() | failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error enabling chat DataProducer: ${error}` - })); - - throw error; - } - } - - async enableBotDataProducer() - { - logger.debug('enableBotDataProducer()'); - - if (!this._useDataChannel) - return; - - // NOTE: Should enable this code but it's useful for testing. - // if (this._botDataProducer) - // return; - - try - { - // Create chat DataProducer. - this._botDataProducer = await this._sendTransport.produceData( - { - ordered : false, - maxPacketLifeTime : 2000, - label : 'bot', - priority : 'medium', - appData : { info: 'my-bot-DataProducer' } - }); - - store.dispatch(stateActions.addDataProducer( - { - id : this._botDataProducer.id, - sctpStreamParameters : this._botDataProducer.sctpStreamParameters, - label : this._botDataProducer.label, - protocol : this._botDataProducer.protocol - })); - - this._botDataProducer.on('transportclose', () => - { - this._botDataProducer = null; - }); - - this._botDataProducer.on('open', () => - { - logger.debug('bot DataProducer "open" event'); - }); - - this._botDataProducer.on('close', () => - { - logger.error('bot DataProducer "close" event'); - - this._botDataProducer = null; - - store.dispatch(requestActions.notify( - { - type : 'error', - text : 'Bot DataProducer closed' - })); - }); - - this._botDataProducer.on('error', (error) => - { - logger.error('bot DataProducer "error" event:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Bot DataProducer error: ${error}` - })); - }); - - this._botDataProducer.on('bufferedamountlow', () => - { - logger.debug('bot DataProducer "bufferedamountlow" event'); - }); - } - catch (error) - { - logger.error('enableBotDataProducer() | failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error enabling bot DataProducer: ${error}` - })); - - throw error; - } - } - - async sendChatMessage(text) - { - logger.debug('sendChatMessage() [text:"%s]', text); - - if (!this._chatDataProducer) - { - store.dispatch(requestActions.notify( - { - type : 'error', - text : 'No chat DataProducer' - })); - - return; - } - - try - { - this._chatDataProducer.send(text); - } - catch (error) - { - logger.error('chat DataProducer.send() failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `chat DataProducer.send() failed: ${error}` - })); - } - } - - async sendBotMessage(text) - { - logger.debug('sendBotMessage() [text:"%s]', text); - - if (!this._botDataProducer) - { - store.dispatch(requestActions.notify( - { - type : 'error', - text : 'No bot DataProducer' - })); - - return; - } - - try - { - this._botDataProducer.send(text); - } - catch (error) - { - logger.error('bot DataProducer.send() failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `bot DataProducer.send() failed: ${error}` - })); - } - } - - async changeDisplayName(displayName) - { - logger.debug('changeDisplayName() [displayName:"%s"]', displayName); - - // Store in cookie. - cookiesManager.setUser({ displayName }); - - try - { - await this._protoo.request('changeDisplayName', { displayName }); - - this._displayName = displayName; - - store.dispatch( - stateActions.setDisplayName(displayName)); - - store.dispatch(requestActions.notify( - { - text : 'Display name changed' - })); - } - catch (error) - { - logger.error('changeDisplayName() | failed: %o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Could not change display name: ${error}` - })); - - // We need to refresh the component for it to render the previous - // displayName again. - store.dispatch( - stateActions.setDisplayName()); - } - } - - async getSendTransportRemoteStats() - { - logger.debug('getSendTransportRemoteStats()'); - - if (!this._sendTransport) - return; - - return this._protoo.request( - 'getTransportStats', { transportId: this._sendTransport.id }); - } - - async getRecvTransportRemoteStats() - { - logger.debug('getRecvTransportRemoteStats()'); - - if (!this._recvTransport) - return; - - return this._protoo.request( - 'getTransportStats', { transportId: this._recvTransport.id }); - } - - async getAudioRemoteStats() - { - logger.debug('getAudioRemoteStats()'); - - if (!this._micProducer) - return; - - return this._protoo.request( - 'getProducerStats', { producerId: this._micProducer.id }); - } - - async getVideoRemoteStats() - { - logger.debug('getVideoRemoteStats()'); - - const producer = this._webcamProducer || this._shareProducer; - - if (!producer) - return; - - return this._protoo.request( - 'getProducerStats', { producerId: producer.id }); - } - - async getConsumerRemoteStats(consumerId) - { - logger.debug('getConsumerRemoteStats()'); - - const consumer = this._consumers.get(consumerId); - - if (!consumer) - return; - - return this._protoo.request('getConsumerStats', { consumerId }); - } - - async getChatDataProducerRemoteStats() - { - logger.debug('getChatDataProducerRemoteStats()'); - - const dataProducer = this._chatDataProducer; - - if (!dataProducer) - return; - - return this._protoo.request( - 'getDataProducerStats', { dataProducerId: dataProducer.id }); - } - - async getBotDataProducerRemoteStats() - { - logger.debug('getBotDataProducerRemoteStats()'); - - const dataProducer = this._botDataProducer; - - if (!dataProducer) - return; - - return this._protoo.request( - 'getDataProducerStats', { dataProducerId: dataProducer.id }); - } - - async getDataConsumerRemoteStats(dataConsumerId) - { - logger.debug('getDataConsumerRemoteStats()'); - - const dataConsumer = this._dataConsumers.get(dataConsumerId); - - if (!dataConsumer) - return; - - return this._protoo.request('getDataConsumerStats', { dataConsumerId }); - } - - async getSendTransportLocalStats() - { - logger.debug('getSendTransportLocalStats()'); - - if (!this._sendTransport) - return; - - return this._sendTransport.getStats(); - } - - async getRecvTransportLocalStats() - { - logger.debug('getRecvTransportLocalStats()'); - - if (!this._recvTransport) - return; - - return this._recvTransport.getStats(); - } - - async getAudioLocalStats() - { - logger.debug('getAudioLocalStats()'); - - if (!this._micProducer) - return; - - return this._micProducer.getStats(); - } - - async getVideoLocalStats() - { - logger.debug('getVideoLocalStats()'); - - const producer = this._webcamProducer || this._shareProducer; - - if (!producer) - return; - - return producer.getStats(); - } - - async getConsumerLocalStats(consumerId) - { - const consumer = this._consumers.get(consumerId); - - if (!consumer) - return; - - return consumer.getStats(); - } - - async applyNetworkThrottle({ uplink, downlink, rtt, secret, packetLoss }) - { - logger.debug( - 'applyNetworkThrottle() [uplink:%s, downlink:%s, rtt:%s, packetLoss:%s]', - uplink, downlink, rtt, packetLoss); - - try - { - await this._protoo.request( - 'applyNetworkThrottle', - { secret, uplink, downlink, rtt, packetLoss }); - } - catch (error) - { - logger.error('applyNetworkThrottle() | failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error applying network throttle: ${error}` - })); - } - } - - async resetNetworkThrottle({ silent = false, secret }) - { - logger.debug('resetNetworkThrottle()'); - - try - { - await this._protoo.request('resetNetworkThrottle', { secret }); - } - catch (error) - { - if (!silent) - { - logger.error('resetNetworkThrottle() | failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error resetting network throttle: ${error}` - })); - } - } - } - - async _joinRoom() - { - logger.debug('_joinRoom()'); - - try - { - this._mediasoupDevice = new mediasoupClient.Device( - { - handlerName : this._handlerName - }); - - store.dispatch(stateActions.setRoomMediasoupClientHandler( - this._mediasoupDevice.handlerName - )); - - const routerRtpCapabilities = - await this._protoo.request('getRouterRtpCapabilities'); - - await this._mediasoupDevice.load({ routerRtpCapabilities }); - - // NOTE: Stuff to play remote audios due to browsers' new autoplay policy. - // - // Just get access to the mic and DO NOT close the mic track for a while. - // Super hack! - { - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - const audioTrack = stream.getAudioTracks()[0]; - - audioTrack.enabled = false; - - setTimeout(() => audioTrack.stop(), 120000); - } - // Create mediasoup Transport for sending (unless we don't want to produce). - if (this._produce) - { - const transportInfo = await this._protoo.request( - 'createWebRtcTransport', - { - forceTcp : this._forceTcp, - producing : true, - consuming : false, - sctpCapabilities : this._useDataChannel - ? this._mediasoupDevice.sctpCapabilities - : undefined - }); - - const { - id, - iceParameters, - iceCandidates, - dtlsParameters, - sctpParameters - } = transportInfo; - - this._sendTransport = this._mediasoupDevice.createSendTransport( - { - id, - iceParameters, - iceCandidates, - dtlsParameters : - { - ...dtlsParameters, - // Remote DTLS role. We know it's always 'auto' by default so, if - // we want, we can force local WebRTC transport to be 'client' by - // indicating 'server' here and vice-versa. - role : 'auto' - }, - sctpParameters, - iceServers : [], - proprietaryConstraints : PC_PROPRIETARY_CONSTRAINTS, - additionalSettings : - { encodedInsertableStreams: this._e2eKey && e2e.isSupported() } - }); - - this._sendTransport.on( - 'connect', ({ iceParameters, dtlsParameters }, callback, errback) => // eslint-disable-line no-shadow - { - this._protoo.request( - 'connectWebRtcTransport', - { - transportId : this._sendTransport.id, - iceParameters, - dtlsParameters - }) - .then(callback) - .catch(errback); - }); - - this._sendTransport.on( - 'produce', async ({ kind, rtpParameters, appData }, callback, errback) => - { - try - { - // eslint-disable-next-line no-shadow - const { id } = await this._protoo.request( - 'produce', - { - transportId : this._sendTransport.id, - kind, - rtpParameters, - appData - }); - - callback({ id }); - } - catch (error) - { - errback(error); - } - }); - - this._sendTransport.on('producedata', async ( - { - sctpStreamParameters, - label, - protocol, - appData - }, - callback, - errback - ) => - { - logger.debug( - '"producedata" event: [sctpStreamParameters:%o, appData:%o]', - sctpStreamParameters, appData); - - try - { - // eslint-disable-next-line no-shadow - const { id } = await this._protoo.request( - 'produceData', - { - transportId : this._sendTransport.id, - sctpStreamParameters, - label, - protocol, - appData - }); - - callback({ id }); - } - catch (error) - { - errback(error); - } - }); - } - - // Create mediasoup Transport for receiving (unless we don't want to consume). - if (this._consume) - { - const transportInfo = await this._protoo.request( - 'createWebRtcTransport', - { - forceTcp : this._forceTcp, - producing : false, - consuming : true, - sctpCapabilities : this._useDataChannel - ? this._mediasoupDevice.sctpCapabilities - : undefined - }); - - const { - id, - iceParameters, - iceCandidates, - dtlsParameters, - sctpParameters - } = transportInfo; - - this._recvTransport = this._mediasoupDevice.createRecvTransport( - { - id, - iceParameters, - iceCandidates, - dtlsParameters : - { - ...dtlsParameters, - // Remote DTLS role. We know it's always 'auto' by default so, if - // we want, we can force local WebRTC transport to be 'client' by - // indicating 'server' here and vice-versa. - role : 'auto' - }, - sctpParameters, - iceServers : [], - additionalSettings : - { encodedInsertableStreams: this._e2eKey && e2e.isSupported() } - }); - - this._recvTransport.on( - 'connect', ({ iceParameters, dtlsParameters }, callback, errback) => // eslint-disable-line no-shadow - { - this._protoo.request( - 'connectWebRtcTransport', - { - transportId : this._recvTransport.id, - iceParameters, - dtlsParameters - }) - .then(callback) - .catch(errback); - }); - } - - // Join now into the room. - // NOTE: Don't send our RTP capabilities if we don't want to consume. - const { peers } = await this._protoo.request( - 'join', - { - displayName : this._displayName, - device : this._device, - rtpCapabilities : this._consume - ? this._mediasoupDevice.rtpCapabilities - : undefined, - sctpCapabilities : this._useDataChannel && this._consume - ? this._mediasoupDevice.sctpCapabilities - : undefined - }); - - store.dispatch( - stateActions.setRoomState('connected')); - - // Clean all the existing notifcations. - store.dispatch( - stateActions.removeAllNotifications()); - - store.dispatch(requestActions.notify( - { - text : 'You are in the room!', - timeout : 3000 - })); - - for (const peer of peers) - { - store.dispatch( - stateActions.addPeer( - { ...peer, consumers: [], dataConsumers: [] })); - } - - // Enable mic/webcam. - if (this._produce) - { - // Set our media capabilities. - store.dispatch(stateActions.setMediaCapabilities( - { - canSendMic : this._mediasoupDevice.canProduce('audio'), - canSendWebcam : this._mediasoupDevice.canProduce('video') - })); - - this.enableMic(); - - const devicesCookie = cookiesManager.getDevices(); - - if (!devicesCookie || devicesCookie.webcamEnabled || this._externalVideo) - this.enableWebcam(); - - this._sendTransport.on('connectionstatechange', (connectionState) => - { - if (connectionState === 'connected') - { - this.enableChatDataProducer(); - this.enableBotDataProducer(); - } - }); - } - - // NOTE: For testing. - if (window.SHOW_INFO) - { - const { me } = store.getState(); - - store.dispatch( - stateActions.setRoomStatsPeerId(me.id)); - } - } - catch (error) - { - logger.error('_joinRoom() failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Could not join the room: ${error}` - })); - - this.close(); - } - } - - async _updateWebcams() - { - logger.debug('_updateWebcams()'); - - // Reset the list. - this._webcams = new Map(); - - logger.debug('_updateWebcams() | calling enumerateDevices()'); - - const devices = await navigator.mediaDevices.enumerateDevices(); - - for (const device of devices) - { - if (device.kind !== 'videoinput') - continue; - - this._webcams.set(device.deviceId, device); - } - - const array = Array.from(this._webcams.values()); - const len = array.length; - const currentWebcamId = - this._webcam.device ? this._webcam.device.deviceId : undefined; - - logger.debug('_updateWebcams() [webcams:%o]', array); - - if (len === 0) - this._webcam.device = null; - else if (!this._webcams.has(currentWebcamId)) - this._webcam.device = array[0]; - - store.dispatch( - stateActions.setCanChangeWebcam(this._webcams.size > 1)); - } - - _getWebcamType(device) - { - if (/(back|rear)/i.test(device.label)) - { - logger.debug('_getWebcamType() | it seems to be a back camera'); - - return 'back'; - } - else - { - logger.debug('_getWebcamType() | it seems to be a front camera'); - - return 'front'; - } - } - - async _pauseConsumer(consumer) - { - if (consumer.paused) - return; - - try - { - await this._protoo.request('pauseConsumer', { consumerId: consumer.id }); - - consumer.pause(); - - store.dispatch( - stateActions.setConsumerPaused(consumer.id, 'local')); - } - catch (error) - { - logger.error('_pauseConsumer() | failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error pausing Consumer: ${error}` - })); - } - } - - async _resumeConsumer(consumer) - { - if (!consumer.paused) - return; - - try - { - await this._protoo.request('resumeConsumer', { consumerId: consumer.id }); - - consumer.resume(); - - store.dispatch( - stateActions.setConsumerResumed(consumer.id, 'local')); - } - catch (error) - { - logger.error('_resumeConsumer() | failed:%o', error); - - store.dispatch(requestActions.notify( - { - type : 'error', - text : `Error resuming Consumer: ${error}` - })); - } - } - - async _getExternalVideoStream() - { - if (this._externalVideoStream) - return this._externalVideoStream; - - if (this._externalVideo.readyState < 3) - { - await new Promise((resolve) => ( - this._externalVideo.addEventListener('canplay', resolve) - )); - } - - if (this._externalVideo.captureStream) - this._externalVideoStream = this._externalVideo.captureStream(); - else if (this._externalVideo.mozCaptureStream) - this._externalVideoStream = this._externalVideo.mozCaptureStream(); - else - throw new Error('video.captureStream() not supported'); - - return this._externalVideoStream; - } -} diff --git a/app/lib/RoomContext.js b/app/lib/RoomContext.js deleted file mode 100644 index d4b79adae..000000000 --- a/app/lib/RoomContext.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -const RoomContext = React.createContext(); - -export default RoomContext; - -export function withRoomContext(Component) -{ - return (props) => ( // eslint-disable-line react/display-name - - {(roomClient) => } - - ); -} diff --git a/app/lib/components/ChatInput.jsx b/app/lib/components/ChatInput.jsx deleted file mode 100644 index 626d6e458..000000000 --- a/app/lib/components/ChatInput.jsx +++ /dev/null @@ -1,123 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import { withRoomContext } from '../RoomContext'; - -const BotMessageRegex = new RegExp('^@bot (.*)'); - -class ChatInput extends React.Component -{ - constructor(props) - { - super(props); - - this.state = - { - text : '' - }; - - // TextArea element got via React ref. - // @type {HTMLElement} - this._textareaElem = null; - } - - render() - { - const { - connected, - chatDataProducer, - botDataProducer - } = this.props; - - const { text } = this.state; - - const disabled = !connected || (!chatDataProducer && !botDataProducer); - - return ( -
-