Merge branch 'develop'
commit
0d705abf11
|
|
@ -1,5 +1,14 @@
|
|||
# Changelog
|
||||
|
||||
### 3.1
|
||||
* Browser session storage
|
||||
* Virtual lobby for rooms
|
||||
* Allow minimum TLSv1.2 and recommended ciphers
|
||||
* Code splitting for faster load times
|
||||
* Various GUI fixes
|
||||
* Internationalization support
|
||||
* Can require sign in for access
|
||||
|
||||
### 3.0
|
||||
* Updated to mediasoup v3
|
||||
* Replace lib "passport-datporten" with "openid-client" (a general OIDC certified client)
|
||||
|
|
|
|||
11
README.md
11
README.md
|
|
@ -10,8 +10,7 @@ Try it online at https://letsmeet.no. You can add /roomname to the URL for speci
|
|||
* Screen sharing
|
||||
* File sharing
|
||||
* Different layouts
|
||||
|
||||
There is also a SIP gateway that can be found [here](https://github.com/havfo/multiparty-meeting-sipgw). To try it, call: roomname@letsmeet.no.
|
||||
* Internationalization support
|
||||
|
||||
## Docker
|
||||
If you want the automatic approach, you can find a docker image [here](https://hub.docker.com/r/misi/mm/).
|
||||
|
|
@ -37,7 +36,7 @@ $ cp server/config/config.example.js server/config/config.js
|
|||
$ cp app/public/config/config.example.js app/public/config/config.js
|
||||
```
|
||||
|
||||
* Edit your two `config.js` with appropriate settings (listening IP/port, logging options, **valid** TLS certificate, etc).
|
||||
* Edit your two `config.js` with appropriate settings (listening IP/port, logging options, **valid** TLS certificate, don't forget ip setting in last section in server config: (webRtcTransport), etc).
|
||||
|
||||
* Set up the browser app:
|
||||
|
||||
|
|
@ -51,6 +50,7 @@ This will build the client application and copy everythink to `server/public` fr
|
|||
* Set up the server:
|
||||
|
||||
```bash
|
||||
$ sudo apt install redis
|
||||
$ cd ..
|
||||
$ cd server
|
||||
$ npm install
|
||||
|
|
@ -110,3 +110,8 @@ This started as a fork of the [work](https://github.com/versatica/mediasoup-demo
|
|||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
Contributions to this work were made on behalf of the GÉANT project, a project that has received funding from the European Union’s Horizon 2020 research and innovation programme under Grant Agreement No. 731122 (GN4-2). On behalf of GÉANT project, GÉANT Association is the sole owner of the copyright in all material which was developed by a member of the GÉANT project.
|
||||
|
||||
GÉANT Vereniging (Association) is registered with the Chamber of Commerce in Amsterdam with registration number 40535155 and operates in the UK as a branch of GÉANT Vereniging. Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK.
|
||||
|
|
@ -0,0 +1,333 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"plugins": [
|
||||
"import",
|
||||
"react"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"react-app"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "React",
|
||||
"version": "16"
|
||||
}
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"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,
|
||||
90,
|
||||
{
|
||||
"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": [
|
||||
"error",
|
||||
{
|
||||
"allowParens": true
|
||||
}
|
||||
],
|
||||
"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-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": 0,
|
||||
"yoda": 2,
|
||||
"import/extensions": 2,
|
||||
"import/no-duplicates": 2,
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
378
app/package.json
378
app/package.json
|
|
@ -1,37 +1,38 @@
|
|||
{
|
||||
"name": "multiparty-meeting",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"private": true,
|
||||
"description": "multiparty meeting service",
|
||||
"author": "Håvar Aambø Fosstveit <h@fosstveit.net>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.1.2",
|
||||
"@material-ui/icons": "^4.2.1",
|
||||
"bowser": "^2.4.0",
|
||||
"create-torrent": "^3.33.0",
|
||||
"@material-ui/core": "^4.5.1",
|
||||
"@material-ui/icons": "^4.5.1",
|
||||
"bowser": "^2.7.0",
|
||||
"dompurify": "^2.0.7",
|
||||
"domready": "^1.0.8",
|
||||
"file-saver": "^2.0.1",
|
||||
"file-saver": "^2.0.2",
|
||||
"hark": "^1.2.3",
|
||||
"marked": "^0.6.1",
|
||||
"mediasoup-client": "^3.0.6",
|
||||
"notistack": "^0.5.1",
|
||||
"marked": "^0.7.0",
|
||||
"mediasoup-client": "^3.2.7",
|
||||
"notistack": "^0.9.5",
|
||||
"prop-types": "^15.7.2",
|
||||
"random-string": "^0.2.0",
|
||||
"react": "^16.8.5",
|
||||
"react-cookie-consent": "^2.2.2",
|
||||
"react-dom": "^16.8.5",
|
||||
"react-redux": "^6.0.1",
|
||||
"react-scripts": "2.1.8",
|
||||
"redux": "^4.0.1",
|
||||
"react": "^16.10.2",
|
||||
"react-cookie-consent": "^2.5.0",
|
||||
"react-dom": "^16.10.2",
|
||||
"react-intl": "^3.4.0",
|
||||
"react-redux": "^7.1.1",
|
||||
"react-scripts": "3.2.0",
|
||||
"redux": "^4.0.4",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-persist": "^5.10.0",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"riek": "^1.1.0",
|
||||
"socket.io-client": "^2.2.0",
|
||||
"source-map-explorer": "^1.8.0",
|
||||
"webtorrent": "^0.103.1"
|
||||
"socket.io-client": "^2.3.0",
|
||||
"source-map-explorer": "^2.1.0",
|
||||
"webtorrent": "^0.107.16"
|
||||
},
|
||||
"scripts": {
|
||||
"analyze-main": "source-map-explorer build/static/js/main.*",
|
||||
|
|
@ -41,342 +42,15 @@
|
|||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"plugins": [
|
||||
"import",
|
||||
"react"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "React",
|
||||
"version": "16"
|
||||
}
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"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,
|
||||
90,
|
||||
{
|
||||
"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": [
|
||||
"error",
|
||||
{
|
||||
"allowParens": true
|
||||
}
|
||||
],
|
||||
"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-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": 0,
|
||||
"yoda": 2,
|
||||
"import/extensions": 2,
|
||||
"import/no-duplicates": 2,
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie > 0",
|
||||
"not op_mini all"
|
||||
]
|
||||
],
|
||||
"devDependencies": {
|
||||
"eslint": "^6.5.1",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"eslint-plugin-react": "^7.16.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,17 @@
|
|||
export const addUserMessage = (text) =>
|
||||
({
|
||||
type : 'ADD_NEW_USER_MESSAGE',
|
||||
payload : { text }
|
||||
});
|
||||
|
||||
export const addResponseMessage = (message) =>
|
||||
({
|
||||
type : 'ADD_NEW_RESPONSE_MESSAGE',
|
||||
payload : { message }
|
||||
});
|
||||
|
||||
export const addChatHistory = (chatHistory) =>
|
||||
({
|
||||
type : 'ADD_CHAT_HISTORY',
|
||||
payload : { chatHistory }
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
export const addConsumer = (consumer, peerId) =>
|
||||
({
|
||||
type : 'ADD_CONSUMER',
|
||||
payload : { consumer, peerId }
|
||||
});
|
||||
|
||||
export const removeConsumer = (consumerId, peerId) =>
|
||||
({
|
||||
type : 'REMOVE_CONSUMER',
|
||||
payload : { consumerId, peerId }
|
||||
});
|
||||
|
||||
export const setConsumerPaused = (consumerId, originator) =>
|
||||
({
|
||||
type : 'SET_CONSUMER_PAUSED',
|
||||
payload : { consumerId, originator }
|
||||
});
|
||||
|
||||
export const setConsumerResumed = (consumerId, originator) =>
|
||||
({
|
||||
type : 'SET_CONSUMER_RESUMED',
|
||||
payload : { consumerId, originator }
|
||||
});
|
||||
|
||||
export const setConsumerCurrentLayers = (consumerId, spatialLayer, temporalLayer) =>
|
||||
({
|
||||
type : 'SET_CONSUMER_CURRENT_LAYERS',
|
||||
payload : { consumerId, spatialLayer, temporalLayer }
|
||||
});
|
||||
|
||||
export const setConsumerPreferredLayers = (consumerId, spatialLayer, temporalLayer) =>
|
||||
({
|
||||
type : 'SET_CONSUMER_PREFERRED_LAYERS',
|
||||
payload : { consumerId, spatialLayer, temporalLayer }
|
||||
});
|
||||
|
||||
export const setConsumerTrack = (consumerId, track) =>
|
||||
({
|
||||
type : 'SET_CONSUMER_TRACK',
|
||||
payload : { consumerId, track }
|
||||
});
|
||||
|
||||
export const setConsumerScore = (consumerId, score) =>
|
||||
({
|
||||
type : 'SET_CONSUMER_SCORE',
|
||||
payload : { consumerId, score }
|
||||
});
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
export const addFile = (peerId, magnetUri) =>
|
||||
({
|
||||
type : 'ADD_FILE',
|
||||
payload : { peerId, magnetUri }
|
||||
});
|
||||
|
||||
export const addFileHistory = (fileHistory) =>
|
||||
({
|
||||
type : 'ADD_FILE_HISTORY',
|
||||
payload : { fileHistory }
|
||||
});
|
||||
|
||||
export const setFileActive = (magnetUri) =>
|
||||
({
|
||||
type : 'SET_FILE_ACTIVE',
|
||||
payload : { magnetUri }
|
||||
});
|
||||
|
||||
export const setFileInActive = (magnetUri) =>
|
||||
({
|
||||
type : 'SET_FILE_INACTIVE',
|
||||
payload : { magnetUri }
|
||||
});
|
||||
|
||||
export const setFileProgress = (magnetUri, progress) =>
|
||||
({
|
||||
type : 'SET_FILE_PROGRESS',
|
||||
payload : { magnetUri, progress }
|
||||
});
|
||||
|
||||
export const setFileDone = (magnetUri, sharedFiles) =>
|
||||
({
|
||||
type : 'SET_FILE_DONE',
|
||||
payload : { magnetUri, sharedFiles }
|
||||
});
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
export const addLobbyPeer = (peerId) =>
|
||||
({
|
||||
type : 'ADD_LOBBY_PEER',
|
||||
payload : { peerId }
|
||||
});
|
||||
|
||||
export const removeLobbyPeer = (peerId) =>
|
||||
({
|
||||
type : 'REMOVE_LOBBY_PEER',
|
||||
payload : { peerId }
|
||||
});
|
||||
|
||||
export const setLobbyPeerDisplayName = (displayName, peerId) =>
|
||||
({
|
||||
type : 'SET_LOBBY_PEER_DISPLAY_NAME',
|
||||
payload : { displayName, peerId }
|
||||
});
|
||||
|
||||
export const setLobbyPeerPicture = (picture, peerId) =>
|
||||
({
|
||||
type : 'SET_LOBBY_PEER_PICTURE',
|
||||
payload : { picture, peerId }
|
||||
});
|
||||
|
||||
export const setLobbyPeerPromotionInProgress = (peerId, flag) =>
|
||||
({
|
||||
type : 'SET_LOBBY_PEER_PROMOTION_IN_PROGRESS',
|
||||
payload : { peerId, flag }
|
||||
});
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
export const setMe = ({ peerId, loginEnabled }) =>
|
||||
({
|
||||
type : 'SET_ME',
|
||||
payload : { peerId, loginEnabled }
|
||||
});
|
||||
|
||||
export const loggedIn = (flag) =>
|
||||
({
|
||||
type : 'LOGGED_IN',
|
||||
payload : { flag }
|
||||
});
|
||||
|
||||
export const setPicture = (picture) =>
|
||||
({
|
||||
type : 'SET_PICTURE',
|
||||
payload : { picture }
|
||||
});
|
||||
|
||||
export const setMediaCapabilities = ({
|
||||
canSendMic,
|
||||
canSendWebcam,
|
||||
canShareScreen,
|
||||
canShareFiles
|
||||
}) =>
|
||||
({
|
||||
type : 'SET_MEDIA_CAPABILITIES',
|
||||
payload : { canSendMic, canSendWebcam, canShareScreen, canShareFiles }
|
||||
});
|
||||
|
||||
export const setAudioDevices = (devices) =>
|
||||
({
|
||||
type : 'SET_AUDIO_DEVICES',
|
||||
payload : { devices }
|
||||
});
|
||||
|
||||
export const setWebcamDevices = (devices) =>
|
||||
({
|
||||
type : 'SET_WEBCAM_DEVICES',
|
||||
payload : { devices }
|
||||
});
|
||||
|
||||
export const setMyRaiseHandState = (flag) =>
|
||||
({
|
||||
type : 'SET_MY_RAISE_HAND_STATE',
|
||||
payload : { flag }
|
||||
});
|
||||
|
||||
export const setAudioInProgress = (flag) =>
|
||||
({
|
||||
type : 'SET_AUDIO_IN_PROGRESS',
|
||||
payload : { flag }
|
||||
});
|
||||
|
||||
export const setWebcamInProgress = (flag) =>
|
||||
({
|
||||
type : 'SET_WEBCAM_IN_PROGRESS',
|
||||
payload : { flag }
|
||||
});
|
||||
|
||||
export const setScreenShareInProgress = (flag) =>
|
||||
({
|
||||
type : 'SET_SCREEN_SHARE_IN_PROGRESS',
|
||||
payload : { flag }
|
||||
});
|
||||
|
||||
export const setMyRaiseHandStateInProgress = (flag) =>
|
||||
({
|
||||
type : 'SET_MY_RAISE_HAND_STATE_IN_PROGRESS',
|
||||
payload : { flag }
|
||||
});
|
||||
|
||||
export const setDisplayNameInProgress = (flag) =>
|
||||
({
|
||||
type : 'SET_DISPLAY_NAME_IN_PROGRESS',
|
||||
payload : { flag }
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
export const addNotification = (notification) =>
|
||||
({
|
||||
type : 'ADD_NOTIFICATION',
|
||||
payload : { notification }
|
||||
});
|
||||
|
||||
export const removeNotification = (notificationId) =>
|
||||
({
|
||||
type : 'REMOVE_NOTIFICATION',
|
||||
payload : { notificationId }
|
||||
});
|
||||
|
||||
export const removeAllNotifications = () =>
|
||||
({
|
||||
type : 'REMOVE_ALL_NOTIFICATIONS'
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
export const addPeer = (peer) =>
|
||||
({
|
||||
type : 'ADD_PEER',
|
||||
payload : { peer }
|
||||
});
|
||||
|
||||
export const removePeer = (peerId) =>
|
||||
({
|
||||
type : 'REMOVE_PEER',
|
||||
payload : { peerId }
|
||||
});
|
||||
|
||||
export const setPeerDisplayName = (displayName, peerId) =>
|
||||
({
|
||||
type : 'SET_PEER_DISPLAY_NAME',
|
||||
payload : { displayName, peerId }
|
||||
});
|
||||
|
||||
export const setPeerVideoInProgress = (peerId, flag) =>
|
||||
({
|
||||
type : 'SET_PEER_VIDEO_IN_PROGRESS',
|
||||
payload : { peerId, flag }
|
||||
});
|
||||
|
||||
export const setPeerAudioInProgress = (peerId, flag) =>
|
||||
({
|
||||
type : 'SET_PEER_AUDIO_IN_PROGRESS',
|
||||
payload : { peerId, flag }
|
||||
});
|
||||
|
||||
export const setPeerScreenInProgress = (peerId, flag) =>
|
||||
({
|
||||
type : 'SET_PEER_SCREEN_IN_PROGRESS',
|
||||
payload : { peerId, flag }
|
||||
});
|
||||
|
||||
export const setPeerRaiseHandState = (peerId, raiseHandState) =>
|
||||
({
|
||||
type : 'SET_PEER_RAISE_HAND_STATE',
|
||||
payload : { peerId, raiseHandState }
|
||||
});
|
||||
|
||||
export const setPeerPicture = (peerId, picture) =>
|
||||
({
|
||||
type : 'SET_PEER_PICTURE',
|
||||
payload : { peerId, picture }
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export const setPeerVolume = (peerId, volume) =>
|
||||
({
|
||||
type : 'SET_PEER_VOLUME',
|
||||
payload : { peerId, volume }
|
||||
});
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
export const addProducer = (producer) =>
|
||||
({
|
||||
type : 'ADD_PRODUCER',
|
||||
payload : { producer }
|
||||
});
|
||||
|
||||
export const removeProducer = (producerId) =>
|
||||
({
|
||||
type : 'REMOVE_PRODUCER',
|
||||
payload : { producerId }
|
||||
});
|
||||
|
||||
export const setProducerPaused = (producerId, originator) =>
|
||||
({
|
||||
type : 'SET_PRODUCER_PAUSED',
|
||||
payload : { producerId, originator }
|
||||
});
|
||||
|
||||
export const setProducerResumed = (producerId, originator) =>
|
||||
({
|
||||
type : 'SET_PRODUCER_RESUMED',
|
||||
payload : { producerId, originator }
|
||||
});
|
||||
|
||||
export const setProducerTrack = (producerId, track) =>
|
||||
({
|
||||
type : 'SET_PRODUCER_TRACK',
|
||||
payload : { producerId, track }
|
||||
});
|
||||
|
||||
export const setProducerScore = (producerId, score) =>
|
||||
({
|
||||
type : 'SET_PRODUCER_SCORE',
|
||||
payload : { producerId, score }
|
||||
});
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import randomString from 'random-string';
|
||||
import * as stateActions from './stateActions';
|
||||
import * as notificationActions from './notificationActions';
|
||||
|
||||
// This returns a redux-thunk action (a function).
|
||||
export const notify = ({ type = 'info', text, timeout }) =>
|
||||
|
|
@ -30,11 +30,11 @@ export const notify = ({ type = 'info', text, timeout }) =>
|
|||
|
||||
return (dispatch) =>
|
||||
{
|
||||
dispatch(stateActions.addNotification(notification));
|
||||
dispatch(notificationActions.addNotification(notification));
|
||||
|
||||
setTimeout(() =>
|
||||
{
|
||||
dispatch(stateActions.removeNotification(notification.id));
|
||||
dispatch(notificationActions.removeNotification(notification.id));
|
||||
}, timeout);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
export const setRoomUrl = (url) =>
|
||||
({
|
||||
type : 'SET_ROOM_URL',
|
||||
payload : { url }
|
||||
});
|
||||
|
||||
export const setRoomName = (name) =>
|
||||
({
|
||||
type : 'SET_ROOM_NAME',
|
||||
payload : { name }
|
||||
|
||||
});
|
||||
|
||||
export const setRoomState = (state) =>
|
||||
({
|
||||
type : 'SET_ROOM_STATE',
|
||||
payload : { state }
|
||||
|
||||
});
|
||||
|
||||
export const setRoomActiveSpeaker = (peerId) =>
|
||||
({
|
||||
type : 'SET_ROOM_ACTIVE_SPEAKER',
|
||||
payload : { peerId }
|
||||
});
|
||||
|
||||
export const setRoomLocked = () =>
|
||||
({
|
||||
type : 'SET_ROOM_LOCKED'
|
||||
});
|
||||
|
||||
export const setRoomUnLocked = () =>
|
||||
({
|
||||
type : 'SET_ROOM_UNLOCKED'
|
||||
});
|
||||
|
||||
export const setInLobby = (inLobby) =>
|
||||
({
|
||||
type : 'SET_IN_LOBBY',
|
||||
payload : { inLobby }
|
||||
});
|
||||
|
||||
export const setSignInRequired = (signInRequired) =>
|
||||
({
|
||||
type : 'SET_SIGN_IN_REQUIRED',
|
||||
payload : { signInRequired }
|
||||
});
|
||||
|
||||
export const setAccessCode = (accessCode) =>
|
||||
({
|
||||
type : 'SET_ACCESS_CODE',
|
||||
payload : { accessCode }
|
||||
});
|
||||
|
||||
export const setJoinByAccessCode = (joinByAccessCode) =>
|
||||
({
|
||||
type : 'SET_JOIN_BY_ACCESS_CODE',
|
||||
payload : { joinByAccessCode }
|
||||
});
|
||||
|
||||
export const setSettingsOpen = ({ settingsOpen }) =>
|
||||
({
|
||||
type : 'SET_SETTINGS_OPEN',
|
||||
payload : { settingsOpen }
|
||||
});
|
||||
|
||||
export const setLockDialogOpen = ({ lockDialogOpen }) =>
|
||||
({
|
||||
type : 'SET_LOCK_DIALOG_OPEN',
|
||||
payload : { lockDialogOpen }
|
||||
});
|
||||
|
||||
export const setFileSharingSupported = (supported) =>
|
||||
({
|
||||
type : 'FILE_SHARING_SUPPORTED',
|
||||
payload : { supported }
|
||||
});
|
||||
|
||||
export const toggleConsumerWindow = (consumerId) =>
|
||||
({
|
||||
type : 'TOGGLE_WINDOW_CONSUMER',
|
||||
payload : { consumerId }
|
||||
});
|
||||
|
||||
export const setToolbarsVisible = (toolbarsVisible) =>
|
||||
({
|
||||
type : 'SET_TOOLBARS_VISIBLE',
|
||||
payload : { toolbarsVisible }
|
||||
});
|
||||
|
||||
export const setDisplayMode = (mode) =>
|
||||
({
|
||||
type : 'SET_DISPLAY_MODE',
|
||||
payload : { mode }
|
||||
});
|
||||
|
||||
export const setSelectedPeer = (selectedPeerId) =>
|
||||
({
|
||||
type : 'SET_SELECTED_PEER',
|
||||
payload : { selectedPeerId }
|
||||
});
|
||||
|
||||
export const setSpotlights = (spotlights) =>
|
||||
({
|
||||
type : 'SET_SPOTLIGHTS',
|
||||
payload : { spotlights }
|
||||
});
|
||||
|
||||
export const toggleJoined = () =>
|
||||
({
|
||||
type : 'TOGGLE_JOINED'
|
||||
});
|
||||
|
||||
export const toggleConsumerFullscreen = (consumerId) =>
|
||||
({
|
||||
type : 'TOGGLE_FULLSCREEN_CONSUMER',
|
||||
payload : { consumerId }
|
||||
});
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
export const setSelectedAudioDevice = (deviceId) =>
|
||||
({
|
||||
type : 'CHANGE_AUDIO_DEVICE',
|
||||
payload : { deviceId }
|
||||
});
|
||||
|
||||
export const setSelectedWebcamDevice = (deviceId) =>
|
||||
({
|
||||
type : 'CHANGE_WEBCAM',
|
||||
payload : { deviceId }
|
||||
});
|
||||
|
||||
export const setVideoResolution = (resolution) =>
|
||||
({
|
||||
type : 'SET_VIDEO_RESOLUTION',
|
||||
payload : { resolution }
|
||||
});
|
||||
|
||||
export const setDisplayName = (displayName) =>
|
||||
({
|
||||
type : 'SET_DISPLAY_NAME',
|
||||
payload : { displayName }
|
||||
});
|
||||
|
||||
export const toggleAdvancedMode = () =>
|
||||
({
|
||||
type : 'TOGGLE_ADVANCED_MODE'
|
||||
});
|
||||
|
|
@ -1,578 +0,0 @@
|
|||
export const setRoomUrl = (url) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_ROOM_URL',
|
||||
payload : { url }
|
||||
};
|
||||
};
|
||||
|
||||
export const setRoomState = (state) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_ROOM_STATE',
|
||||
payload : { state }
|
||||
};
|
||||
};
|
||||
|
||||
export const setRoomActiveSpeaker = (peerId) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_ROOM_ACTIVE_SPEAKER',
|
||||
payload : { peerId }
|
||||
};
|
||||
};
|
||||
|
||||
export const setRoomLocked = () =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_ROOM_LOCKED'
|
||||
};
|
||||
};
|
||||
|
||||
export const setRoomUnLocked = () =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_ROOM_UNLOCKED'
|
||||
};
|
||||
};
|
||||
|
||||
export const setRoomLockedOut = () =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_ROOM_LOCKED_OUT'
|
||||
};
|
||||
};
|
||||
|
||||
export const setSettingsOpen = ({ settingsOpen }) =>
|
||||
({
|
||||
type : 'SET_SETTINGS_OPEN',
|
||||
payload : { settingsOpen }
|
||||
});
|
||||
|
||||
export const setMe = ({ peerId, device, loginEnabled }) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_ME',
|
||||
payload : { peerId, device, loginEnabled }
|
||||
};
|
||||
};
|
||||
|
||||
export const setMediaCapabilities = ({
|
||||
canSendMic,
|
||||
canSendWebcam,
|
||||
canShareScreen,
|
||||
canShareFiles
|
||||
}) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_MEDIA_CAPABILITIES',
|
||||
payload : { canSendMic, canSendWebcam, canShareScreen, canShareFiles }
|
||||
};
|
||||
};
|
||||
|
||||
export const setAudioDevices = (devices) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_AUDIO_DEVICES',
|
||||
payload : { devices }
|
||||
};
|
||||
};
|
||||
|
||||
export const setWebcamDevices = (devices) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_WEBCAM_DEVICES',
|
||||
payload : { devices }
|
||||
};
|
||||
};
|
||||
|
||||
export const setSelectedAudioDevice = (deviceId) =>
|
||||
{
|
||||
return {
|
||||
type : 'CHANGE_AUDIO_DEVICE',
|
||||
payload : { deviceId }
|
||||
};
|
||||
};
|
||||
|
||||
export const setSelectedWebcamDevice = (deviceId) =>
|
||||
{
|
||||
return {
|
||||
type : 'CHANGE_WEBCAM',
|
||||
payload : { deviceId }
|
||||
};
|
||||
};
|
||||
|
||||
export const setVideoResolution = (resolution) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_VIDEO_RESOLUTION',
|
||||
payload : { resolution }
|
||||
};
|
||||
};
|
||||
|
||||
export const setFileSharingSupported = (supported) =>
|
||||
{
|
||||
return {
|
||||
type : 'FILE_SHARING_SUPPORTED',
|
||||
payload : { supported }
|
||||
};
|
||||
};
|
||||
|
||||
export const setDisplayName = (displayName) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_DISPLAY_NAME',
|
||||
payload : { displayName }
|
||||
};
|
||||
};
|
||||
|
||||
export const toggleAdvancedMode = () =>
|
||||
{
|
||||
return {
|
||||
type : 'TOGGLE_ADVANCED_MODE'
|
||||
};
|
||||
};
|
||||
|
||||
export const setDisplayMode = (mode) =>
|
||||
({
|
||||
type : 'SET_DISPLAY_MODE',
|
||||
payload : { mode }
|
||||
});
|
||||
|
||||
export const setPeerVideoInProgress = (peerId, flag) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_PEER_VIDEO_IN_PROGRESS',
|
||||
payload : { peerId, flag }
|
||||
};
|
||||
};
|
||||
|
||||
export const setPeerAudioInProgress = (peerId, flag) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_PEER_AUDIO_IN_PROGRESS',
|
||||
payload : { peerId, flag }
|
||||
};
|
||||
};
|
||||
|
||||
export const setPeerScreenInProgress = (peerId, flag) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_PEER_SCREEN_IN_PROGRESS',
|
||||
payload : { peerId, flag }
|
||||
};
|
||||
};
|
||||
|
||||
export const setMyRaiseHandState = (flag) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_MY_RAISE_HAND_STATE',
|
||||
payload : { flag }
|
||||
};
|
||||
};
|
||||
|
||||
export const toggleSettings = () =>
|
||||
{
|
||||
return {
|
||||
type : 'TOGGLE_SETTINGS'
|
||||
};
|
||||
};
|
||||
|
||||
export const toggleToolArea = () =>
|
||||
{
|
||||
return {
|
||||
type : 'TOGGLE_TOOL_AREA'
|
||||
};
|
||||
};
|
||||
|
||||
export const openToolArea = () =>
|
||||
{
|
||||
return {
|
||||
type : 'OPEN_TOOL_AREA'
|
||||
};
|
||||
};
|
||||
|
||||
export const closeToolArea = () =>
|
||||
{
|
||||
return {
|
||||
type : 'CLOSE_TOOL_AREA'
|
||||
};
|
||||
};
|
||||
|
||||
export const setToolTab = (toolTab) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_TOOL_TAB',
|
||||
payload : { toolTab }
|
||||
};
|
||||
};
|
||||
|
||||
export const setMyRaiseHandStateInProgress = (flag) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_MY_RAISE_HAND_STATE_IN_PROGRESS',
|
||||
payload : { flag }
|
||||
};
|
||||
};
|
||||
|
||||
export const setPeerRaiseHandState = (peerId, raiseHandState) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_PEER_RAISE_HAND_STATE',
|
||||
payload : { peerId, raiseHandState }
|
||||
};
|
||||
};
|
||||
|
||||
export const addProducer = (producer) =>
|
||||
{
|
||||
return {
|
||||
type : 'ADD_PRODUCER',
|
||||
payload : { producer }
|
||||
};
|
||||
};
|
||||
|
||||
export const removeProducer = (producerId) =>
|
||||
{
|
||||
return {
|
||||
type : 'REMOVE_PRODUCER',
|
||||
payload : { producerId }
|
||||
};
|
||||
};
|
||||
|
||||
export const setProducerPaused = (producerId, originator) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_PRODUCER_PAUSED',
|
||||
payload : { producerId, originator }
|
||||
};
|
||||
};
|
||||
|
||||
export const setProducerResumed = (producerId, originator) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_PRODUCER_RESUMED',
|
||||
payload : { producerId, originator }
|
||||
};
|
||||
};
|
||||
|
||||
export const setProducerTrack = (producerId, track) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_PRODUCER_TRACK',
|
||||
payload : { producerId, track }
|
||||
};
|
||||
};
|
||||
|
||||
export const setProducerScore = (producerId, score) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_PRODUCER_SCORE',
|
||||
payload : { producerId, score }
|
||||
};
|
||||
};
|
||||
|
||||
export const setAudioInProgress = (flag) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_AUDIO_IN_PROGRESS',
|
||||
payload : { flag }
|
||||
};
|
||||
};
|
||||
|
||||
export const setWebcamInProgress = (flag) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_WEBCAM_IN_PROGRESS',
|
||||
payload : { flag }
|
||||
};
|
||||
};
|
||||
|
||||
export const setScreenShareInProgress = (flag) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_SCREEN_SHARE_IN_PROGRESS',
|
||||
payload : { flag }
|
||||
};
|
||||
};
|
||||
|
||||
export const addPeer = (peer) =>
|
||||
{
|
||||
return {
|
||||
type : 'ADD_PEER',
|
||||
payload : { peer }
|
||||
};
|
||||
};
|
||||
|
||||
export const removePeer = (peerId) =>
|
||||
{
|
||||
return {
|
||||
type : 'REMOVE_PEER',
|
||||
payload : { peerId }
|
||||
};
|
||||
};
|
||||
|
||||
export const setPeerDisplayName = (displayName, peerId) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_PEER_DISPLAY_NAME',
|
||||
payload : { displayName, peerId }
|
||||
};
|
||||
};
|
||||
|
||||
export const addConsumer = (consumer, peerId) =>
|
||||
{
|
||||
return {
|
||||
type : 'ADD_CONSUMER',
|
||||
payload : { consumer, peerId }
|
||||
};
|
||||
};
|
||||
|
||||
export const removeConsumer = (consumerId, peerId) =>
|
||||
{
|
||||
return {
|
||||
type : 'REMOVE_CONSUMER',
|
||||
payload : { consumerId, peerId }
|
||||
};
|
||||
};
|
||||
|
||||
export const setConsumerPaused = (consumerId, originator) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_CONSUMER_PAUSED',
|
||||
payload : { consumerId, originator }
|
||||
};
|
||||
};
|
||||
|
||||
export const setConsumerResumed = (consumerId, originator) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_CONSUMER_RESUMED',
|
||||
payload : { consumerId, originator }
|
||||
};
|
||||
};
|
||||
|
||||
export const setConsumerCurrentLayers = (consumerId, spatialLayer, temporalLayer) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_CONSUMER_CURRENT_LAYERS',
|
||||
payload : { consumerId, spatialLayer, temporalLayer }
|
||||
};
|
||||
};
|
||||
|
||||
export const setConsumerPreferredLayers = (consumerId, spatialLayer, temporalLayer) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_CONSUMER_PREFERRED_LAYERS',
|
||||
payload : { consumerId, spatialLayer, temporalLayer }
|
||||
};
|
||||
};
|
||||
|
||||
export const setConsumerTrack = (consumerId, track) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_CONSUMER_TRACK',
|
||||
payload : { consumerId, track }
|
||||
};
|
||||
};
|
||||
|
||||
export const setConsumerScore = (consumerId, score) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_CONSUMER_SCORE',
|
||||
payload : { consumerId, score }
|
||||
};
|
||||
};
|
||||
|
||||
export const setPeerVolume = (peerId, volume) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_PEER_VOLUME',
|
||||
payload : { peerId, volume }
|
||||
};
|
||||
};
|
||||
|
||||
export const addNotification = (notification) =>
|
||||
{
|
||||
return {
|
||||
type : 'ADD_NOTIFICATION',
|
||||
payload : { notification }
|
||||
};
|
||||
};
|
||||
|
||||
export const removeNotification = (notificationId) =>
|
||||
{
|
||||
return {
|
||||
type : 'REMOVE_NOTIFICATION',
|
||||
payload : { notificationId }
|
||||
};
|
||||
};
|
||||
|
||||
export const removeAllNotifications = () =>
|
||||
{
|
||||
return {
|
||||
type : 'REMOVE_ALL_NOTIFICATIONS'
|
||||
};
|
||||
};
|
||||
|
||||
export const toggleChat = () =>
|
||||
{
|
||||
return {
|
||||
type : 'TOGGLE_CHAT'
|
||||
};
|
||||
};
|
||||
|
||||
export const toggleConsumerFullscreen = (consumerId) =>
|
||||
{
|
||||
return {
|
||||
type : 'TOGGLE_FULLSCREEN_CONSUMER',
|
||||
payload : { consumerId }
|
||||
};
|
||||
};
|
||||
|
||||
export const toggleConsumerWindow = (consumerId) =>
|
||||
{
|
||||
return {
|
||||
type : 'TOGGLE_WINDOW_CONSUMER',
|
||||
payload : { consumerId }
|
||||
};
|
||||
};
|
||||
|
||||
export const setToolbarsVisible = (toolbarsVisible) => ({
|
||||
type : 'SET_TOOLBARS_VISIBLE',
|
||||
payload : { toolbarsVisible }
|
||||
});
|
||||
|
||||
export const increaseBadge = () =>
|
||||
{
|
||||
return {
|
||||
type : 'INCREASE_BADGE'
|
||||
};
|
||||
};
|
||||
|
||||
export const toggleInputDisabled = () =>
|
||||
{
|
||||
return {
|
||||
type : 'TOGGLE_INPUT_DISABLED'
|
||||
};
|
||||
};
|
||||
|
||||
export const addUserMessage = (text) =>
|
||||
{
|
||||
return {
|
||||
type : 'ADD_NEW_USER_MESSAGE',
|
||||
payload : { text }
|
||||
};
|
||||
};
|
||||
|
||||
export const addUserFile = (file) =>
|
||||
{
|
||||
return {
|
||||
type : 'ADD_NEW_USER_FILE',
|
||||
payload : { file }
|
||||
};
|
||||
};
|
||||
|
||||
export const addResponseMessage = (message) =>
|
||||
{
|
||||
return {
|
||||
type : 'ADD_NEW_RESPONSE_MESSAGE',
|
||||
payload : { message }
|
||||
};
|
||||
};
|
||||
|
||||
export const addChatHistory = (chatHistory) =>
|
||||
{
|
||||
return {
|
||||
type : 'ADD_CHAT_HISTORY',
|
||||
payload : { chatHistory }
|
||||
};
|
||||
};
|
||||
|
||||
export const dropMessages = () =>
|
||||
{
|
||||
return {
|
||||
type : 'DROP_MESSAGES'
|
||||
};
|
||||
};
|
||||
|
||||
export const addFile = (peerId, magnetUri) =>
|
||||
{
|
||||
return {
|
||||
type : 'ADD_FILE',
|
||||
payload : { peerId, magnetUri }
|
||||
};
|
||||
};
|
||||
|
||||
export const addFileHistory = (fileHistory) =>
|
||||
{
|
||||
return {
|
||||
type : 'ADD_FILE_HISTORY',
|
||||
payload : { fileHistory }
|
||||
};
|
||||
};
|
||||
|
||||
export const setFileActive = (magnetUri) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_FILE_ACTIVE',
|
||||
payload : { magnetUri }
|
||||
};
|
||||
};
|
||||
|
||||
export const setFileInActive = (magnetUri) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_FILE_INACTIVE',
|
||||
payload : { magnetUri }
|
||||
};
|
||||
};
|
||||
|
||||
export const setFileProgress = (magnetUri, progress) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_FILE_PROGRESS',
|
||||
payload : { magnetUri, progress }
|
||||
};
|
||||
};
|
||||
|
||||
export const setFileDone = (magnetUri, sharedFiles) =>
|
||||
{
|
||||
return {
|
||||
type : 'SET_FILE_DONE',
|
||||
payload : { magnetUri, sharedFiles }
|
||||
};
|
||||
};
|
||||
|
||||
export const setPicture = (picture) =>
|
||||
({
|
||||
type : 'SET_PICTURE',
|
||||
payload : { picture }
|
||||
});
|
||||
|
||||
export const setPeerPicture = (peerId, picture) =>
|
||||
({
|
||||
type : 'SET_PEER_PICTURE',
|
||||
payload : { peerId, picture }
|
||||
});
|
||||
|
||||
export const loggedIn = () =>
|
||||
({
|
||||
type : 'LOGGED_IN'
|
||||
});
|
||||
|
||||
export const toggleJoined = () =>
|
||||
({
|
||||
type : 'TOGGLE_JOINED'
|
||||
});
|
||||
|
||||
export const setSelectedPeer = (selectedPeerId) =>
|
||||
({
|
||||
type : 'SET_SELECTED_PEER',
|
||||
payload : { selectedPeerId }
|
||||
});
|
||||
|
||||
export const setSpotlights = (spotlights) =>
|
||||
({
|
||||
type : 'SET_SPOTLIGHTS',
|
||||
payload : { spotlights }
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
export const toggleToolArea = () =>
|
||||
({
|
||||
type : 'TOGGLE_TOOL_AREA'
|
||||
});
|
||||
|
||||
export const openToolArea = () =>
|
||||
({
|
||||
type : 'OPEN_TOOL_AREA'
|
||||
});
|
||||
|
||||
export const closeToolArea = () =>
|
||||
({
|
||||
type : 'CLOSE_TOOL_AREA'
|
||||
});
|
||||
|
||||
export const setToolTab = (toolTab) =>
|
||||
({
|
||||
type : 'SET_TOOL_TAB',
|
||||
payload : { toolTab }
|
||||
});
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import { useIntl } from 'react-intl';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
||||
import PromoteIcon from '@material-ui/icons/OpenInBrowser';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
root :
|
||||
{
|
||||
padding : theme.spacing(1),
|
||||
width : '100%',
|
||||
overflow : 'hidden',
|
||||
cursor : 'auto',
|
||||
display : 'flex'
|
||||
},
|
||||
avatar :
|
||||
{
|
||||
borderRadius : '50%',
|
||||
height : '2rem'
|
||||
},
|
||||
peerInfo :
|
||||
{
|
||||
fontSize : '1rem',
|
||||
border : 'none',
|
||||
display : 'flex',
|
||||
paddingLeft : theme.spacing(1),
|
||||
flexGrow : 1,
|
||||
alignItems : 'center'
|
||||
},
|
||||
controls :
|
||||
{
|
||||
float : 'right',
|
||||
display : 'flex',
|
||||
flexDirection : 'row',
|
||||
justifyContent : 'flex-start',
|
||||
alignItems : 'center'
|
||||
},
|
||||
button :
|
||||
{
|
||||
flex : '0 0 auto',
|
||||
margin : '0.3rem',
|
||||
borderRadius : 2,
|
||||
backgroundColor : 'rgba(0, 0, 0, 0.5)',
|
||||
cursor : 'pointer',
|
||||
transitionProperty : 'opacity, background-color',
|
||||
transitionDuration : '0.15s',
|
||||
width : 'var(--media-control-button-size)',
|
||||
height : 'var(--media-control-button-size)',
|
||||
opacity : 0.85,
|
||||
'&:hover' :
|
||||
{
|
||||
opacity : 1
|
||||
},
|
||||
'&.disabled' :
|
||||
{
|
||||
pointerEvents : 'none',
|
||||
backgroundColor : 'var(--media-control-botton-disabled)'
|
||||
},
|
||||
'&.promote' :
|
||||
{
|
||||
backgroundColor : 'var(--media-control-botton-on)'
|
||||
}
|
||||
},
|
||||
ListItem :
|
||||
{
|
||||
alignItems : 'center'
|
||||
}
|
||||
});
|
||||
|
||||
const ListLobbyPeer = (props) =>
|
||||
{
|
||||
const {
|
||||
roomClient,
|
||||
peer,
|
||||
classes
|
||||
} = props;
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const picture = peer.picture || EmptyAvatar;
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
className={classnames(classes.ListItem)}
|
||||
key={peer.peerId}
|
||||
button
|
||||
alignItems='flex-start'
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<Avatar alt='Peer avatar' src={picture} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={peer.displayName}
|
||||
/>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.admitFromLobby',
|
||||
defaultMessage : 'Click to let them in'
|
||||
})}
|
||||
>
|
||||
<ListItemIcon
|
||||
className={classnames(classes.button, 'promote', {
|
||||
disabled : peer.promotionInProgress
|
||||
})}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
roomClient.promoteLobbyPeer(peer.id);
|
||||
}}
|
||||
>
|
||||
<PromoteIcon />
|
||||
</ListItemIcon>
|
||||
</Tooltip>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
ListLobbyPeer.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
advancedMode : PropTypes.bool,
|
||||
peer : PropTypes.object.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, { id }) =>
|
||||
{
|
||||
return {
|
||||
peer : state.lobbyPeers[id]
|
||||
};
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.lobbyPeers === next.lobbyPeers
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(ListLobbyPeer)));
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
lobbyPeersKeySelector
|
||||
} from '../../Selectors';
|
||||
import * as appPropTypes from '../../appPropTypes';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import * as roomActions from '../../../actions/roomActions';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import Button from '@material-ui/core/Button';
|
||||
// import FormLabel from '@material-ui/core/FormLabel';
|
||||
// import FormControl from '@material-ui/core/FormControl';
|
||||
// import FormGroup from '@material-ui/core/FormGroup';
|
||||
// import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
// import Checkbox from '@material-ui/core/Checkbox';
|
||||
// import InputLabel from '@material-ui/core/InputLabel';
|
||||
// import OutlinedInput from '@material-ui/core/OutlinedInput';
|
||||
// import Switch from '@material-ui/core/Switch';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListSubheader from '@material-ui/core/ListSubheader';
|
||||
import ListLobbyPeer from './ListLobbyPeer';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
root :
|
||||
{
|
||||
},
|
||||
dialogPaper :
|
||||
{
|
||||
width : '30vw',
|
||||
[theme.breakpoints.down('lg')] :
|
||||
{
|
||||
width : '40vw'
|
||||
},
|
||||
[theme.breakpoints.down('md')] :
|
||||
{
|
||||
width : '50vw'
|
||||
},
|
||||
[theme.breakpoints.down('sm')] :
|
||||
{
|
||||
width : '70vw'
|
||||
},
|
||||
[theme.breakpoints.down('xs')] :
|
||||
{
|
||||
width : '90vw'
|
||||
}
|
||||
},
|
||||
lock :
|
||||
{
|
||||
padding : theme.spacing(2)
|
||||
}
|
||||
});
|
||||
|
||||
const LockDialog = ({
|
||||
// roomClient,
|
||||
room,
|
||||
handleCloseLockDialog,
|
||||
// handleAccessCode,
|
||||
lobbyPeers,
|
||||
classes
|
||||
}) =>
|
||||
{
|
||||
return (
|
||||
<Dialog
|
||||
className={classes.root}
|
||||
open={room.lockDialogOpen}
|
||||
onClose={() => handleCloseLockDialog({ lockDialogOpen: false })}
|
||||
classes={{
|
||||
paper : classes.dialogPaper
|
||||
}}
|
||||
>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
<FormattedMessage
|
||||
id='room.lobbyAdministration'
|
||||
defaultMessage='Lobby administration'
|
||||
/>
|
||||
</DialogTitle>
|
||||
{/*
|
||||
<FormControl component='fieldset' className={classes.formControl}>
|
||||
<FormLabel component='legend'>Room lock</FormLabel>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch checked={room.locked} onChange={() =>
|
||||
{
|
||||
if (room.locked)
|
||||
{
|
||||
roomClient.unlockRoom();
|
||||
}
|
||||
else
|
||||
{
|
||||
roomClient.lockRoom();
|
||||
}
|
||||
}}
|
||||
/>}
|
||||
label='Lock'
|
||||
/>
|
||||
TODO: access code
|
||||
<FormControlLabel disabled={ room.locked ? false : true }
|
||||
control={
|
||||
<Checkbox checked={room.joinByAccessCode}
|
||||
onChange={
|
||||
(event) => roomClient.setJoinByAccessCode(event.target.checked)
|
||||
}
|
||||
/>}
|
||||
label='Join by Access code'
|
||||
/>
|
||||
<InputLabel htmlFor='access-code-input' />
|
||||
<OutlinedInput
|
||||
disabled={ room.locked ? false : true }
|
||||
id='acces-code-input'
|
||||
label='Access code'
|
||||
labelWidth={0}
|
||||
variant='outlined'
|
||||
value={room.accessCode}
|
||||
onChange={(event) => handleAccessCode(event.target.value)}
|
||||
>
|
||||
</OutlinedInput>
|
||||
<Button onClick={() => roomClient.setAccessCode(room.accessCode)} color='primary'>
|
||||
save
|
||||
</Button>
|
||||
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
*/}
|
||||
{ lobbyPeers.length > 0 ?
|
||||
<List
|
||||
dense
|
||||
subheader={
|
||||
<ListSubheader component='div'>
|
||||
<FormattedMessage
|
||||
id='room.peersInLobby'
|
||||
defaultMessage='Participants in Lobby'
|
||||
/>
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
{
|
||||
lobbyPeers.map((peerId) =>
|
||||
{
|
||||
return (<ListLobbyPeer key={peerId} id={peerId} />);
|
||||
})
|
||||
}
|
||||
</List>
|
||||
:
|
||||
<DialogContent>
|
||||
<DialogContentText gutterBottom>
|
||||
<FormattedMessage
|
||||
id='room.lobbyEmpty'
|
||||
defaultMessage='There are currently no one in the lobby'
|
||||
/>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
}
|
||||
<DialogActions>
|
||||
<Button onClick={() => handleCloseLockDialog({ lockDialogOpen: false })} color='primary'>
|
||||
<FormattedMessage
|
||||
id='label.close'
|
||||
defaultMessage='Close'
|
||||
/>
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
LockDialog.propTypes =
|
||||
{
|
||||
// roomClient : PropTypes.any.isRequired,
|
||||
room : appPropTypes.Room.isRequired,
|
||||
handleCloseLockDialog : PropTypes.func.isRequired,
|
||||
handleAccessCode : PropTypes.func.isRequired,
|
||||
lobbyPeers : PropTypes.array,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
room : state.room,
|
||||
lobbyPeers : lobbyPeersKeySelector(state)
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
handleCloseLockDialog : roomActions.setLockDialogOpen,
|
||||
handleAccessCode : roomActions.setAccessCode
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.room.locked === next.room.locked &&
|
||||
prev.room.joinByAccessCode === next.room.joinByAccessCode &&
|
||||
prev.room.accessCode === next.room.accessCode &&
|
||||
prev.room.code === next.room.code &&
|
||||
prev.room.lockDialogOpen === next.room.lockDialogOpen &&
|
||||
prev.room.codeHidden === next.room.codeHidden &&
|
||||
prev.lobbyPeers === next.lobbyPeers
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(LockDialog)));
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import React, { useEffect, Suspense } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import JoinDialog from './JoinDialog';
|
||||
import LoadingView from './LoadingView';
|
||||
import { ReactLazyPreload } from './ReactLazyPreload';
|
||||
|
||||
const Room = ReactLazyPreload(() => import(/* webpackChunkName: "room" */ './Room'));
|
||||
|
||||
const App = (props) =>
|
||||
{
|
||||
const {
|
||||
room
|
||||
} = props;
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
Room.preload();
|
||||
|
||||
return;
|
||||
}, []);
|
||||
|
||||
if (!room.joined)
|
||||
{
|
||||
return (
|
||||
<JoinDialog />
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (
|
||||
<Suspense fallback={<LoadingView />}>
|
||||
<Room />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
App.propTypes =
|
||||
{
|
||||
room : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
({
|
||||
room : state.room
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.room === next.room
|
||||
);
|
||||
}
|
||||
}
|
||||
)(App);
|
||||
|
|
@ -3,7 +3,8 @@ import { connect } from 'react-redux';
|
|||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import * as stateActions from '../../actions/stateActions';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import * as toolareaActions from '../../actions/toolareaActions';
|
||||
import BuddyImage from '../../images/buddy.svg';
|
||||
|
||||
const styles = () =>
|
||||
|
|
@ -95,8 +96,19 @@ class HiddenPeers extends React.PureComponent
|
|||
className={classnames(classes.root, this.state.className)}
|
||||
onClick={() => openUsersTab()}
|
||||
>
|
||||
<p>+{hiddenPeersCount} <br /> participant
|
||||
{(hiddenPeersCount === 1) ? null : 's'}
|
||||
<p>
|
||||
+{hiddenPeersCount} <br />
|
||||
<FormattedMessage
|
||||
id='room.hiddenPeers'
|
||||
defaultMessage={
|
||||
`{hiddenPeersCount, plural,
|
||||
one {participant}
|
||||
other {participants}}`
|
||||
}
|
||||
values={{
|
||||
hiddenPeersCount
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -115,8 +127,8 @@ const mapDispatchToProps = (dispatch) =>
|
|||
return {
|
||||
openUsersTab : () =>
|
||||
{
|
||||
dispatch(stateActions.openToolArea());
|
||||
dispatch(stateActions.setToolTab('users'));
|
||||
dispatch(toolareaActions.openToolArea());
|
||||
dispatch(toolareaActions.setToolTab('users'));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import useMediaQuery from '@material-ui/core/useMediaQuery';
|
|||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import VideoView from '../VideoContainers/VideoView';
|
||||
import Volume from './Volume';
|
||||
import Fab from '@material-ui/core/Fab';
|
||||
|
|
@ -96,6 +97,8 @@ const Me = (props) =>
|
|||
{
|
||||
const [ hover, setHover ] = useState(false);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
let touchTimeout = null;
|
||||
|
||||
const {
|
||||
|
|
@ -133,22 +136,34 @@ const Me = (props) =>
|
|||
if (!me.canSendMic)
|
||||
{
|
||||
micState = 'unsupported';
|
||||
micTip = 'Audio unsupported';
|
||||
micTip = intl.formatMessage({
|
||||
id : 'device.audioUnsupported',
|
||||
defaultMessage : 'Audio unsupported'
|
||||
});
|
||||
}
|
||||
else if (!micProducer)
|
||||
{
|
||||
micState = 'off';
|
||||
micTip = 'Activate audio';
|
||||
micTip = intl.formatMessage({
|
||||
id : 'device.activateAudio',
|
||||
defaultMessage : 'Activate audio'
|
||||
});
|
||||
}
|
||||
else if (!micProducer.locallyPaused && !micProducer.remotelyPaused)
|
||||
{
|
||||
micState = 'on';
|
||||
micTip = 'Mute audio';
|
||||
micTip = intl.formatMessage({
|
||||
id : 'device.muteAudio',
|
||||
defaultMessage : 'Mute audio'
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
micState = 'muted';
|
||||
micTip = 'Unmute audio';
|
||||
micTip = intl.formatMessage({
|
||||
id : 'device.unMuteAudio',
|
||||
defaultMessage : 'Unmute audio'
|
||||
});
|
||||
}
|
||||
|
||||
let webcamState;
|
||||
|
|
@ -158,17 +173,26 @@ const Me = (props) =>
|
|||
if (!me.canSendWebcam)
|
||||
{
|
||||
webcamState = 'unsupported';
|
||||
webcamTip = 'Video unsupported';
|
||||
webcamTip = intl.formatMessage({
|
||||
id : 'device.videoUnsupported',
|
||||
defaultMessage : 'Video unsupported'
|
||||
});
|
||||
}
|
||||
else if (webcamProducer)
|
||||
{
|
||||
webcamState = 'on';
|
||||
webcamTip = 'Stop video';
|
||||
webcamTip = intl.formatMessage({
|
||||
id : 'device.stopVideo',
|
||||
defaultMessage : 'Stop video'
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
webcamState = 'off';
|
||||
webcamTip = 'Start video';
|
||||
webcamTip = intl.formatMessage({
|
||||
id : 'device.startVideo',
|
||||
defaultMessage : 'Start video'
|
||||
});
|
||||
}
|
||||
|
||||
let screenState;
|
||||
|
|
@ -178,17 +202,26 @@ const Me = (props) =>
|
|||
if (!me.canShareScreen)
|
||||
{
|
||||
screenState = 'unsupported';
|
||||
screenTip = 'Screen sharing not supported';
|
||||
screenTip = intl.formatMessage({
|
||||
id : 'device.screenSharingUnsupported',
|
||||
defaultMessage : 'Screen sharing not supported'
|
||||
});
|
||||
}
|
||||
else if (screenProducer)
|
||||
{
|
||||
screenState = 'on';
|
||||
screenTip = 'Stop screen sharing';
|
||||
screenTip = intl.formatMessage({
|
||||
id : 'device.stopScreenSharing',
|
||||
defaultMessage : 'Stop screen sharing'
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
screenState = 'off';
|
||||
screenTip = 'Start screen sharing';
|
||||
screenTip = intl.formatMessage({
|
||||
id : 'device.startScreenSharing',
|
||||
defaultMessage : 'Start screen sharing'
|
||||
});
|
||||
}
|
||||
|
||||
const spacingStyle =
|
||||
|
|
@ -253,11 +286,19 @@ const Me = (props) =>
|
|||
}, 2000);
|
||||
}}
|
||||
>
|
||||
<p>ME</p>
|
||||
<Tooltip title={micTip} placement={smallScreen ? 'top' : 'right'}>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='room.me'
|
||||
defaultMessage='ME'
|
||||
/>
|
||||
</p>
|
||||
<Tooltip title={micTip} placement={smallScreen ? 'top' : 'left'}>
|
||||
<div>
|
||||
<Fab
|
||||
aria-label='Mute mic'
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'device.muteAudio',
|
||||
defaultMessage : 'Mute audio'
|
||||
})}
|
||||
className={classes.fab}
|
||||
disabled={!me.canSendMic || me.audioInProgress}
|
||||
color={micState === 'on' ? 'default' : 'secondary'}
|
||||
|
|
@ -280,10 +321,13 @@ const Me = (props) =>
|
|||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip title={webcamTip} placement={smallScreen ? 'top' : 'right'}>
|
||||
<Tooltip title={webcamTip} placement={smallScreen ? 'top' : 'left'}>
|
||||
<div>
|
||||
<Fab
|
||||
aria-label='Mute video'
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'device.startVideo',
|
||||
defaultMessage : 'Start video'
|
||||
})}
|
||||
className={classes.fab}
|
||||
disabled={!me.canSendWebcam || me.webcamInProgress}
|
||||
color={webcamState === 'on' ? 'default' : 'secondary'}
|
||||
|
|
@ -303,10 +347,13 @@ const Me = (props) =>
|
|||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip title={screenTip} placement={smallScreen ? 'top' : 'right'}>
|
||||
<Tooltip title={screenTip} placement={smallScreen ? 'top' : 'left'}>
|
||||
<div>
|
||||
<Fab
|
||||
aria-label='Share screen'
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'device.startScreenSharing',
|
||||
defaultMessage : 'Start screen sharing'
|
||||
})}
|
||||
className={classes.fab}
|
||||
disabled={!me.canShareScreen || me.screenShareInProgress}
|
||||
color={screenState === 'on' ? 'primary' : 'default'}
|
||||
|
|
@ -332,13 +379,11 @@ const Me = (props) =>
|
|||
}
|
||||
}}
|
||||
>
|
||||
{ screenState === 'on' || screenState === 'unsupported' ?
|
||||
{ (screenState === 'on' || screenState === 'unsupported') &&
|
||||
<ScreenOffIcon/>
|
||||
:null
|
||||
}
|
||||
{ screenState === 'off' ?
|
||||
{ screenState === 'off' &&
|
||||
<ScreenIcon/>
|
||||
:null
|
||||
}
|
||||
</Fab>
|
||||
</div>
|
||||
|
|
@ -351,10 +396,10 @@ const Me = (props) =>
|
|||
peer={me}
|
||||
displayName={settings.displayName}
|
||||
showPeerInfo
|
||||
videoTrack={webcamProducer ? webcamProducer.track : null}
|
||||
videoTrack={webcamProducer && webcamProducer.track}
|
||||
videoVisible={videoVisible}
|
||||
audioCodec={micProducer ? micProducer.codec : null}
|
||||
videoCodec={webcamProducer ? webcamProducer.codec : null}
|
||||
audioCodec={micProducer && micProducer.codec}
|
||||
videoCodec={webcamProducer && webcamProducer.codec}
|
||||
onChangeDisplayName={(displayName) =>
|
||||
{
|
||||
roomClient.changeDisplayName(displayName);
|
||||
|
|
@ -364,9 +409,9 @@ const Me = (props) =>
|
|||
</VideoView>
|
||||
</div>
|
||||
</div>
|
||||
{ screenProducer ?
|
||||
{ screenProducer &&
|
||||
<div
|
||||
className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
|
||||
className={classnames(classes.root, 'screen', hover && 'hover')}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
|
|
@ -390,7 +435,7 @@ const Me = (props) =>
|
|||
>
|
||||
<div className={classnames(classes.viewContainer)} style={style}>
|
||||
<div
|
||||
className={classnames(classes.controls, hover ? 'hover' : null)}
|
||||
className={classnames(classes.controls, hover && 'hover')}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
|
|
@ -412,7 +457,12 @@ const Me = (props) =>
|
|||
}, 2000);
|
||||
}}
|
||||
>
|
||||
<p>ME</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='room.me'
|
||||
defaultMessage='ME'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<VideoView
|
||||
|
|
@ -420,13 +470,12 @@ const Me = (props) =>
|
|||
isScreen
|
||||
advancedMode={advancedMode}
|
||||
videoContain
|
||||
videoTrack={screenProducer ? screenProducer.track : null}
|
||||
videoTrack={screenProducer && screenProducer.track}
|
||||
videoVisible={screenVisible}
|
||||
videoCodec={screenProducer ? screenProducer.codec : null}
|
||||
videoCodec={screenProducer && screenProducer.codec}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ import * as appPropTypes from '../appPropTypes';
|
|||
import { withRoomContext } from '../../RoomContext';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import useMediaQuery from '@material-ui/core/useMediaQuery';
|
||||
import * as stateActions from '../../actions/stateActions';
|
||||
import * as roomActions from '../../actions/roomActions';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import VideoView from '../VideoContainers/VideoView';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import Fab from '@material-ui/core/Fab';
|
||||
import MicIcon from '@material-ui/icons/Mic';
|
||||
import MicOffIcon from '@material-ui/icons/MicOff';
|
||||
|
|
@ -103,6 +105,8 @@ const Peer = (props) =>
|
|||
{
|
||||
const [ hover, setHover ] = useState(false);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
let touchTimeout = null;
|
||||
|
||||
const {
|
||||
|
|
@ -166,8 +170,8 @@ const Peer = (props) =>
|
|||
classnames(
|
||||
classes.root,
|
||||
'webcam',
|
||||
hover ? 'hover' : null,
|
||||
activeSpeaker ? 'active-speaker' : null
|
||||
hover && 'hover',
|
||||
activeSpeaker && 'active-speaker'
|
||||
)
|
||||
}
|
||||
onMouseOver={() => setHover(true)}
|
||||
|
|
@ -192,15 +196,19 @@ const Peer = (props) =>
|
|||
style={rootStyle}
|
||||
>
|
||||
<div className={classnames(classes.viewContainer)}>
|
||||
{ !videoVisible ?
|
||||
{ !videoVisible &&
|
||||
<div className={classes.videoInfo}>
|
||||
<p>this video is paused</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='room.videoPaused'
|
||||
defaultMessage='This video is paused'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
|
||||
<div
|
||||
className={classnames(classes.controls, hover ? 'hover' : null)}
|
||||
className={classnames(classes.controls, hover && 'hover')}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
|
|
@ -221,57 +229,95 @@ const Peer = (props) =>
|
|||
}, 2000);
|
||||
}}
|
||||
>
|
||||
<Fab
|
||||
aria-label='Mute mic'
|
||||
className={classes.fab}
|
||||
disabled={!micConsumer}
|
||||
color={micEnabled ? 'default' : 'secondary'}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
micEnabled ?
|
||||
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
|
||||
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
|
||||
}}
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'device.muteAudio',
|
||||
defaultMessage : 'Mute audio'
|
||||
})}
|
||||
placement={smallScreen ? 'top' : 'left'}
|
||||
>
|
||||
{ micEnabled ?
|
||||
<MicIcon />
|
||||
:
|
||||
<MicOffIcon />
|
||||
}
|
||||
</Fab>
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'device.muteAudio',
|
||||
defaultMessage : 'Mute audio'
|
||||
})}
|
||||
className={classes.fab}
|
||||
disabled={!micConsumer}
|
||||
color={micEnabled ? 'default' : 'secondary'}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
micEnabled ?
|
||||
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
|
||||
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
|
||||
}}
|
||||
>
|
||||
{ micEnabled ?
|
||||
<MicIcon />
|
||||
:
|
||||
<MicOffIcon />
|
||||
}
|
||||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{ !smallScreen ?
|
||||
<Fab
|
||||
aria-label='New window'
|
||||
className={classes.fab}
|
||||
disabled={
|
||||
!videoVisible ||
|
||||
(windowConsumer === webcamConsumer.id)
|
||||
}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerWindow(webcamConsumer);
|
||||
}}
|
||||
{ !smallScreen &&
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'label.newWindow',
|
||||
defaultMessage : 'New window'
|
||||
})}
|
||||
placement={smallScreen ? 'top' : 'left'}
|
||||
>
|
||||
<NewWindowIcon />
|
||||
</Fab>
|
||||
:null
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.newWindow',
|
||||
defaultMessage : 'New window'
|
||||
})}
|
||||
className={classes.fab}
|
||||
disabled={
|
||||
!videoVisible ||
|
||||
(windowConsumer === webcamConsumer.id)
|
||||
}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerWindow(webcamConsumer);
|
||||
}}
|
||||
>
|
||||
<NewWindowIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
<Fab
|
||||
aria-label='Fullscreen'
|
||||
className={classes.fab}
|
||||
disabled={!videoVisible}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerFullscreen(webcamConsumer);
|
||||
}}
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'label.fullscreen',
|
||||
defaultMessage : 'Fullscreen'
|
||||
})}
|
||||
placement={smallScreen ? 'top' : 'left'}
|
||||
>
|
||||
<FullScreenIcon />
|
||||
</Fab>
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.fullscreen',
|
||||
defaultMessage : 'Fullscreen'
|
||||
})}
|
||||
className={classes.fab}
|
||||
disabled={!videoVisible}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerFullscreen(webcamConsumer);
|
||||
}}
|
||||
>
|
||||
<FullScreenIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<VideoView
|
||||
|
|
@ -279,20 +325,20 @@ const Peer = (props) =>
|
|||
peer={peer}
|
||||
displayName={peer.displayName}
|
||||
showPeerInfo
|
||||
videoTrack={webcamConsumer ? webcamConsumer.track : null}
|
||||
videoTrack={webcamConsumer && webcamConsumer.track}
|
||||
videoVisible={videoVisible}
|
||||
videoProfile={videoProfile}
|
||||
audioCodec={micConsumer ? micConsumer.codec : null}
|
||||
videoCodec={webcamConsumer ? webcamConsumer.codec : null}
|
||||
audioCodec={micConsumer && micConsumer.codec}
|
||||
videoCodec={webcamConsumer && webcamConsumer.codec}
|
||||
>
|
||||
<Volume id={peer.id} />
|
||||
</VideoView>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ screenConsumer ?
|
||||
{ screenConsumer &&
|
||||
<div
|
||||
className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
|
||||
className={classnames(classes.root, 'screen', hover && 'hover')}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
|
|
@ -314,17 +360,21 @@ const Peer = (props) =>
|
|||
}}
|
||||
style={rootStyle}
|
||||
>
|
||||
{ !screenVisible ?
|
||||
{ !screenVisible &&
|
||||
<div className={classes.videoInfo}>
|
||||
<p>this video is paused</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='room.videoPaused'
|
||||
defaultMessage='This video is paused'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
|
||||
{ screenVisible ?
|
||||
{ screenVisible &&
|
||||
<div className={classnames(classes.viewContainer)}>
|
||||
<div
|
||||
className={classnames(classes.controls, hover ? 'hover' : null)}
|
||||
className={classnames(classes.controls, hover && 'hover')}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
|
|
@ -346,51 +396,74 @@ const Peer = (props) =>
|
|||
}, 2000);
|
||||
}}
|
||||
>
|
||||
{ !smallScreen ?
|
||||
<Fab
|
||||
aria-label='New window'
|
||||
className={classes.fab}
|
||||
disabled={
|
||||
!screenVisible ||
|
||||
(windowConsumer === screenConsumer.id)
|
||||
}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerWindow(screenConsumer);
|
||||
}}
|
||||
{ !smallScreen &&
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'label.newWindow',
|
||||
defaultMessage : 'New window'
|
||||
})}
|
||||
placement={smallScreen ? 'top' : 'left'}
|
||||
>
|
||||
<NewWindowIcon />
|
||||
</Fab>
|
||||
:null
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.newWindow',
|
||||
defaultMessage : 'New window'
|
||||
})}
|
||||
className={classes.fab}
|
||||
disabled={
|
||||
!screenVisible ||
|
||||
(windowConsumer === screenConsumer.id)
|
||||
}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerWindow(screenConsumer);
|
||||
}}
|
||||
>
|
||||
<NewWindowIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
<Fab
|
||||
aria-label='Fullscreen'
|
||||
className={classes.fab}
|
||||
disabled={!screenVisible}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerFullscreen(screenConsumer);
|
||||
}}
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'label.fullscreen',
|
||||
defaultMessage : 'Fullscreen'
|
||||
})}
|
||||
placement={smallScreen ? 'top' : 'left'}
|
||||
>
|
||||
<FullScreenIcon />
|
||||
</Fab>
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.fullscreen',
|
||||
defaultMessage : 'Fullscreen'
|
||||
})}
|
||||
className={classes.fab}
|
||||
disabled={!screenVisible}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerFullscreen(screenConsumer);
|
||||
}}
|
||||
>
|
||||
<FullScreenIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<VideoView
|
||||
advancedMode={advancedMode}
|
||||
videoContain
|
||||
videoTrack={screenConsumer ? screenConsumer.track : null}
|
||||
videoTrack={screenConsumer && screenConsumer.track}
|
||||
videoVisible={screenVisible}
|
||||
videoProfile={screenProfile}
|
||||
videoCodec={screenConsumer ? screenConsumer.codec : null}
|
||||
videoCodec={screenConsumer && screenConsumer.codec}
|
||||
/>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
@ -438,12 +511,12 @@ const mapDispatchToProps = (dispatch) =>
|
|||
toggleConsumerFullscreen : (consumer) =>
|
||||
{
|
||||
if (consumer)
|
||||
dispatch(stateActions.toggleConsumerFullscreen(consumer.id));
|
||||
dispatch(roomActions.toggleConsumerFullscreen(consumer.id));
|
||||
},
|
||||
toggleConsumerWindow : (consumer) =>
|
||||
{
|
||||
if (consumer)
|
||||
dispatch(stateActions.toggleConsumerWindow(consumer.id));
|
||||
dispatch(roomActions.toggleConsumerWindow(consumer.id));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
|
|||
import classnames from 'classnames';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import VideoView from '../VideoContainers/VideoView';
|
||||
import Volume from './Volume';
|
||||
|
||||
|
|
@ -117,11 +118,15 @@ const SpeakerPeer = (props) =>
|
|||
style={spacingStyle}
|
||||
>
|
||||
<div className={classnames(classes.viewContainer)} style={style}>
|
||||
{ !videoVisible ?
|
||||
{ !videoVisible &&
|
||||
<div className={classes.videoInfo}>
|
||||
<p>this video is paused</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='room.videoPaused'
|
||||
defaultMessage='This video is paused'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
|
||||
<VideoView
|
||||
|
|
@ -140,18 +145,22 @@ const SpeakerPeer = (props) =>
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{ screenConsumer ?
|
||||
{ screenConsumer &&
|
||||
<div
|
||||
className={classnames(classes.root, 'screen')}
|
||||
>
|
||||
{ !screenVisible ?
|
||||
{ !screenVisible &&
|
||||
<div className={classes.videoInfo} style={style}>
|
||||
<p>this video is paused</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='room.videoPaused'
|
||||
defaultMessage='This video is paused'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
|
||||
{ screenVisible ?
|
||||
{ screenVisible &&
|
||||
<div className={classnames(classes.viewContainer)} style={style}>
|
||||
<VideoView
|
||||
advancedMode={advancedMode}
|
||||
|
|
@ -162,10 +171,8 @@ const SpeakerPeer = (props) =>
|
|||
videoCodec={screenConsumer ? screenConsumer.codec : null}
|
||||
/>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,399 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
lobbyPeersKeySelector
|
||||
} from '../Selectors';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import { withRoomContext } from '../../RoomContext';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import * as roomActions from '../../actions/roomActions';
|
||||
import * as toolareaActions from '../../actions/toolareaActions';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Badge from '@material-ui/core/Badge';
|
||||
import AccountCircle from '@material-ui/icons/AccountCircle';
|
||||
import FullScreenIcon from '@material-ui/icons/Fullscreen';
|
||||
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
import SecurityIcon from '@material-ui/icons/Security';
|
||||
import LockIcon from '@material-ui/icons/Lock';
|
||||
import LockOpenIcon from '@material-ui/icons/LockOpen';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
menuButton :
|
||||
{
|
||||
margin : 0,
|
||||
padding : 0
|
||||
},
|
||||
logo :
|
||||
{
|
||||
display : 'none',
|
||||
marginLeft : 20,
|
||||
[theme.breakpoints.up('sm')] :
|
||||
{
|
||||
display : 'block'
|
||||
}
|
||||
},
|
||||
show :
|
||||
{
|
||||
opacity : 1,
|
||||
transition : 'opacity .5s'
|
||||
},
|
||||
hide :
|
||||
{
|
||||
opacity : 0,
|
||||
transition : 'opacity .5s'
|
||||
},
|
||||
grow :
|
||||
{
|
||||
flexGrow : 1
|
||||
},
|
||||
title :
|
||||
{
|
||||
display : 'none',
|
||||
marginLeft : 20,
|
||||
[theme.breakpoints.up('sm')] :
|
||||
{
|
||||
display : 'block'
|
||||
}
|
||||
},
|
||||
actionButtons :
|
||||
{
|
||||
display : 'flex'
|
||||
},
|
||||
actionButton :
|
||||
{
|
||||
margin : theme.spacing(1),
|
||||
padding : 0
|
||||
}
|
||||
});
|
||||
|
||||
const PulsingBadge = withStyles((theme) =>
|
||||
({
|
||||
badge :
|
||||
{
|
||||
backgroundColor : theme.palette.secondary.main,
|
||||
'&::after' :
|
||||
{
|
||||
position : 'absolute',
|
||||
width : '100%',
|
||||
height : '100%',
|
||||
borderRadius : '50%',
|
||||
animation : '$ripple 1.2s infinite ease-in-out',
|
||||
border : `3px solid ${theme.palette.secondary.main}`,
|
||||
content : '""'
|
||||
}
|
||||
},
|
||||
'@keyframes ripple' :
|
||||
{
|
||||
'0%' :
|
||||
{
|
||||
transform : 'scale(.8)',
|
||||
opacity : 1
|
||||
},
|
||||
'100%' :
|
||||
{
|
||||
transform : 'scale(2.4)',
|
||||
opacity : 0
|
||||
}
|
||||
}
|
||||
}))(Badge);
|
||||
|
||||
const TopBar = (props) =>
|
||||
{
|
||||
const intl = useIntl();
|
||||
|
||||
const {
|
||||
roomClient,
|
||||
room,
|
||||
lobbyPeers,
|
||||
myPicture,
|
||||
loggedIn,
|
||||
loginEnabled,
|
||||
fullscreenEnabled,
|
||||
fullscreen,
|
||||
onFullscreen,
|
||||
setSettingsOpen,
|
||||
setLockDialogOpen,
|
||||
toggleToolArea,
|
||||
unread,
|
||||
classes
|
||||
} = props;
|
||||
|
||||
const lockTooltip = room.locked ?
|
||||
intl.formatMessage({
|
||||
id : 'tooltip.unLockRoom',
|
||||
defaultMessage : 'Unlock room'
|
||||
})
|
||||
:
|
||||
intl.formatMessage({
|
||||
id : 'tooltip.lockRoom',
|
||||
defaultMessage : 'Lock room'
|
||||
});
|
||||
|
||||
const fullscreenTooltip = fullscreen ?
|
||||
intl.formatMessage({
|
||||
id : 'tooltip.leaveFullscreen',
|
||||
defaultMessage : 'Leave fullscreen'
|
||||
})
|
||||
:
|
||||
intl.formatMessage({
|
||||
id : 'tooltip.enterFullscreen',
|
||||
defaultMessage : 'Enter fullscreen'
|
||||
});
|
||||
|
||||
const loginTooltip = loggedIn ?
|
||||
intl.formatMessage({
|
||||
id : 'tooltip.logout',
|
||||
defaultMessage : 'Log out'
|
||||
})
|
||||
:
|
||||
intl.formatMessage({
|
||||
id : 'tooltip.login',
|
||||
defaultMessage : 'Log in'
|
||||
});
|
||||
|
||||
return (
|
||||
<AppBar
|
||||
position='fixed'
|
||||
className={room.toolbarsVisible ? classes.show : classes.hide}
|
||||
>
|
||||
<Toolbar>
|
||||
<PulsingBadge
|
||||
color='secondary'
|
||||
badgeContent={unread}
|
||||
>
|
||||
<IconButton
|
||||
color='inherit'
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.openDrawer',
|
||||
defaultMessage : 'Open drawer'
|
||||
})}
|
||||
onClick={() => toggleToolArea()}
|
||||
className={classes.menuButton}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</PulsingBadge>
|
||||
{ window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
|
||||
<Typography
|
||||
className={classes.title}
|
||||
variant='h6'
|
||||
color='inherit'
|
||||
noWrap
|
||||
>
|
||||
{ window.config.title }
|
||||
</Typography>
|
||||
<div className={classes.grow} />
|
||||
<div className={classes.actionButtons}>
|
||||
<Tooltip title={lockTooltip}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.lockRoom',
|
||||
defaultMessage : 'Lock room'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={() =>
|
||||
{
|
||||
if (room.locked)
|
||||
{
|
||||
roomClient.unlockRoom();
|
||||
}
|
||||
else
|
||||
{
|
||||
roomClient.lockRoom();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{ room.locked ?
|
||||
<LockIcon />
|
||||
:
|
||||
<LockOpenIcon />
|
||||
}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{ lobbyPeers.length > 0 &&
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.lobby',
|
||||
defaultMessage : 'Show lobby'
|
||||
})}
|
||||
>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.lobby',
|
||||
defaultMessage : 'Show lobby'
|
||||
})}
|
||||
color='inherit'
|
||||
onClick={() => setLockDialogOpen(!room.lockDialogOpen)}
|
||||
>
|
||||
<PulsingBadge
|
||||
color='secondary'
|
||||
badgeContent={lobbyPeers.length}
|
||||
>
|
||||
<SecurityIcon />
|
||||
</PulsingBadge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
{ fullscreenEnabled &&
|
||||
<Tooltip title={fullscreenTooltip}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.enterFullscreen',
|
||||
defaultMessage : 'Enter fullscreen'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={onFullscreen}
|
||||
>
|
||||
{ fullscreen ?
|
||||
<FullScreenExitIcon />
|
||||
:
|
||||
<FullScreenIcon />
|
||||
}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.settings',
|
||||
defaultMessage : 'Show settings'
|
||||
})}
|
||||
>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.settings',
|
||||
defaultMessage : 'Show settings'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={() => setSettingsOpen(!room.settingsOpen)}
|
||||
>
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{ loginEnabled &&
|
||||
<Tooltip title={loginTooltip}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.login',
|
||||
defaultMessage : 'Log in'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={() =>
|
||||
{
|
||||
loggedIn ? roomClient.logout() : roomClient.login();
|
||||
}}
|
||||
>
|
||||
{ myPicture ?
|
||||
<Avatar src={myPicture} />
|
||||
:
|
||||
<AccountCircle />
|
||||
}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
<Button
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.leave',
|
||||
defaultMessage : 'Leave'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
onClick={() => roomClient.close()}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='label.leave'
|
||||
defaultMessage='Leave'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
};
|
||||
|
||||
TopBar.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.object.isRequired,
|
||||
room : appPropTypes.Room.isRequired,
|
||||
lobbyPeers : PropTypes.array,
|
||||
myPicture : PropTypes.string,
|
||||
loggedIn : PropTypes.bool.isRequired,
|
||||
loginEnabled : PropTypes.bool.isRequired,
|
||||
fullscreenEnabled : PropTypes.bool,
|
||||
fullscreen : PropTypes.bool,
|
||||
onFullscreen : PropTypes.func.isRequired,
|
||||
setToolbarsVisible : PropTypes.func.isRequired,
|
||||
setSettingsOpen : PropTypes.func.isRequired,
|
||||
setLockDialogOpen : PropTypes.func.isRequired,
|
||||
toggleToolArea : PropTypes.func.isRequired,
|
||||
unread : PropTypes.number.isRequired,
|
||||
classes : PropTypes.object.isRequired,
|
||||
theme : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
({
|
||||
room : state.room,
|
||||
lobbyPeers : lobbyPeersKeySelector(state),
|
||||
advancedMode : state.settings.advancedMode,
|
||||
loggedIn : state.me.loggedIn,
|
||||
loginEnabled : state.me.loginEnabled,
|
||||
myPicture : state.me.picture,
|
||||
unread : state.toolarea.unreadMessages +
|
||||
state.toolarea.unreadFiles
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
({
|
||||
setToolbarsVisible : (visible) =>
|
||||
{
|
||||
dispatch(roomActions.setToolbarsVisible(visible));
|
||||
},
|
||||
setSettingsOpen : (settingsOpen) =>
|
||||
{
|
||||
dispatch(roomActions.setSettingsOpen({ settingsOpen }));
|
||||
},
|
||||
setLockDialogOpen : (lockDialogOpen) =>
|
||||
{
|
||||
dispatch(roomActions.setLockDialogOpen({ lockDialogOpen }));
|
||||
},
|
||||
toggleToolArea : () =>
|
||||
{
|
||||
dispatch(toolareaActions.toggleToolArea());
|
||||
}
|
||||
});
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.room === next.room &&
|
||||
prev.lobbyPeers === next.lobbyPeers &&
|
||||
prev.me.loggedIn === next.me.loggedIn &&
|
||||
prev.me.loginEnabled === next.me.loginEnabled &&
|
||||
prev.me.picture === next.me.picture &&
|
||||
prev.toolarea.unreadMessages === next.toolarea.unreadMessages &&
|
||||
prev.toolarea.unreadFiles === next.toolarea.unreadFiles
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles, { withTheme: true })(TopBar)));
|
||||
|
|
@ -1,90 +1,400 @@
|
|||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../RoomContext';
|
||||
import * as settingsActions from '../actions/settingsActions';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import AccountCircle from '@material-ui/icons/AccountCircle';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import CookieConsent from 'react-cookie-consent';
|
||||
import MuiDialogTitle from '@material-ui/core/DialogTitle';
|
||||
import MuiDialogContent from '@material-ui/core/DialogContent';
|
||||
import MuiDialogActions from '@material-ui/core/DialogActions';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
root :
|
||||
{
|
||||
display : 'flex',
|
||||
width : '100%',
|
||||
height : '100%',
|
||||
backgroundColor : 'var(--background-color)',
|
||||
backgroundImage : `url(${window.config.background})`,
|
||||
backgroundAttachment : 'fixed',
|
||||
backgroundPosition : 'center',
|
||||
backgroundSize : 'cover',
|
||||
backgroundRepeat : 'no-repeat'
|
||||
},
|
||||
dialogTitle :
|
||||
{
|
||||
},
|
||||
dialogPaper :
|
||||
{
|
||||
width : '20vw',
|
||||
width : '30vw',
|
||||
padding : theme.spacing(2),
|
||||
[theme.breakpoints.down('lg')] :
|
||||
{
|
||||
width : '30vw'
|
||||
width : '40vw'
|
||||
},
|
||||
[theme.breakpoints.down('md')] :
|
||||
{
|
||||
width : '40vw'
|
||||
width : '50vw'
|
||||
},
|
||||
[theme.breakpoints.down('sm')] :
|
||||
{
|
||||
width : '60vw'
|
||||
width : '70vw'
|
||||
},
|
||||
[theme.breakpoints.down('xs')] :
|
||||
{
|
||||
width : '80vw'
|
||||
width : '90vw'
|
||||
}
|
||||
},
|
||||
logo :
|
||||
{
|
||||
display : 'block'
|
||||
display : 'block',
|
||||
paddingBottom : '1vh'
|
||||
},
|
||||
loginButton :
|
||||
{
|
||||
position : 'absolute',
|
||||
right : theme.spacing(2),
|
||||
top : theme.spacing(2),
|
||||
padding : 0
|
||||
},
|
||||
largeIcon :
|
||||
{
|
||||
fontSize : '2em'
|
||||
},
|
||||
largeAvatar :
|
||||
{
|
||||
width : 50,
|
||||
height : 50
|
||||
},
|
||||
green :
|
||||
{
|
||||
color : 'rgba(0, 153, 0, 1)'
|
||||
}
|
||||
});
|
||||
|
||||
const DialogTitle = withStyles(styles)((props) =>
|
||||
{
|
||||
const [ open, setOpen ] = useState(false);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const openTimer = setTimeout(() => setOpen(true), 1000);
|
||||
const closeTimer = setTimeout(() => setOpen(false), 4000);
|
||||
|
||||
return () =>
|
||||
{
|
||||
clearTimeout(openTimer);
|
||||
clearTimeout(closeTimer);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { children, classes, myPicture, onLogin, ...other } = props;
|
||||
|
||||
const handleTooltipClose = () =>
|
||||
{
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleTooltipOpen = () =>
|
||||
{
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<MuiDialogTitle disableTypography className={classes.dialogTitle} {...other}>
|
||||
{ window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
|
||||
<Typography variant='h5'>{children}</Typography>
|
||||
{ window.config.loginEnabled &&
|
||||
<Tooltip
|
||||
onClose={handleTooltipClose}
|
||||
onOpen={handleTooltipOpen}
|
||||
open={open}
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.login',
|
||||
defaultMessage : 'Click to log in'
|
||||
})}
|
||||
placement='left'
|
||||
>
|
||||
<IconButton
|
||||
aria-label='Account'
|
||||
className={classes.loginButton}
|
||||
color='inherit'
|
||||
onClick={onLogin}
|
||||
>
|
||||
{ myPicture ?
|
||||
<Avatar src={myPicture} className={classes.largeAvatar} />
|
||||
:
|
||||
<AccountCircle className={classes.largeIcon} />
|
||||
}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
</MuiDialogTitle>
|
||||
);
|
||||
});
|
||||
|
||||
const DialogContent = withStyles((theme) => ({
|
||||
root :
|
||||
{
|
||||
padding : theme.spacing(2)
|
||||
}
|
||||
}))(MuiDialogContent);
|
||||
|
||||
const DialogActions = withStyles((theme) => ({
|
||||
root :
|
||||
{
|
||||
margin : 0,
|
||||
padding : theme.spacing(1)
|
||||
}
|
||||
}))(MuiDialogActions);
|
||||
|
||||
const JoinDialog = ({
|
||||
roomClient,
|
||||
room,
|
||||
displayName,
|
||||
displayNameInProgress,
|
||||
loggedIn,
|
||||
myPicture,
|
||||
changeDisplayName,
|
||||
classes
|
||||
}) =>
|
||||
{
|
||||
return (
|
||||
<Dialog
|
||||
className={classes.root}
|
||||
open
|
||||
classes={{
|
||||
paper : classes.dialogPaper
|
||||
}}
|
||||
>
|
||||
{ window.config.logo ?
|
||||
<img alt='Logo' className={classes.logo} src={window.config.logo} />
|
||||
:null
|
||||
const intl = useIntl();
|
||||
|
||||
const handleKeyDown = (event) =>
|
||||
{
|
||||
const { key } = event;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case 'Enter':
|
||||
case 'Escape':
|
||||
{
|
||||
if (displayName === '')
|
||||
changeDisplayName('Guest');
|
||||
if (room.inLobby)
|
||||
roomClient.changeDisplayName(displayName);
|
||||
break;
|
||||
}
|
||||
<Typography variant='subtitle1'>You are about to join a meeting, how would you like to join?</Typography>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() =>
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Dialog
|
||||
open
|
||||
classes={{
|
||||
paper : classes.dialogPaper
|
||||
}}
|
||||
>
|
||||
<DialogTitle
|
||||
myPicture={myPicture}
|
||||
onLogin={() =>
|
||||
{
|
||||
roomClient.join({ joinVideo: false });
|
||||
loggedIn ? roomClient.logout() : roomClient.login();
|
||||
}}
|
||||
variant='contained'
|
||||
>
|
||||
Audio only
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() =>
|
||||
{
|
||||
roomClient.join({ joinVideo: true });
|
||||
}}
|
||||
variant='contained'
|
||||
>
|
||||
Audio and Video
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
{ window.config.title }
|
||||
<hr />
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText gutterBottom>
|
||||
<FormattedMessage
|
||||
id='room.aboutToJoin'
|
||||
defaultMessage='You are about to join a meeting'
|
||||
/>
|
||||
</DialogContentText>
|
||||
|
||||
<DialogContentText variant='h6' gutterBottom align='center'>
|
||||
<FormattedMessage
|
||||
id='room.roomId'
|
||||
defaultMessage='Room ID: {roomName}'
|
||||
values={{
|
||||
roomName : room.name
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
|
||||
<DialogContentText gutterBottom>
|
||||
<FormattedMessage
|
||||
id='room.setYourName'
|
||||
defaultMessage={
|
||||
`Set your name for participation,
|
||||
and choose how you want to join:`
|
||||
}
|
||||
/>
|
||||
</DialogContentText>
|
||||
|
||||
<TextField
|
||||
id='displayname'
|
||||
label={intl.formatMessage({
|
||||
id : 'label.yourName',
|
||||
defaultMessage : 'Your name'
|
||||
})}
|
||||
value={displayName}
|
||||
variant='outlined'
|
||||
margin='normal'
|
||||
disabled={displayNameInProgress}
|
||||
onChange={(event) =>
|
||||
{
|
||||
const { value } = event.target;
|
||||
|
||||
changeDisplayName(value);
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={() =>
|
||||
{
|
||||
if (displayName === '')
|
||||
changeDisplayName('Guest');
|
||||
if (room.inLobby)
|
||||
roomClient.changeDisplayName(displayName);
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
</DialogContent>
|
||||
|
||||
{ !room.inLobby ?
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() =>
|
||||
{
|
||||
roomClient.join({ joinVideo: false });
|
||||
}}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='room.audioOnly'
|
||||
defaultMessage='Audio only'
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() =>
|
||||
{
|
||||
roomClient.join({ joinVideo: true });
|
||||
}}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='room.audioVideo'
|
||||
defaultMessage='Audio and Video'
|
||||
/>
|
||||
</Button>
|
||||
</DialogActions>
|
||||
:
|
||||
<DialogContent>
|
||||
<DialogContentText
|
||||
className={classes.green}
|
||||
gutterBottom
|
||||
variant='h6'
|
||||
align='center'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='room.youAreReady'
|
||||
defaultMessage='Ok, you are ready'
|
||||
/>
|
||||
</DialogContentText>
|
||||
{ room.signInRequired ?
|
||||
<DialogContentText gutterBottom>
|
||||
<FormattedMessage
|
||||
id='room.emptyRequireLogin'
|
||||
defaultMessage={
|
||||
`The room is empty! You can Log In to start
|
||||
the meeting or wait until the host joins`
|
||||
}
|
||||
/>
|
||||
</DialogContentText>
|
||||
:
|
||||
<DialogContentText gutterBottom>
|
||||
<FormattedMessage
|
||||
id='room.locketWait'
|
||||
defaultMessage='The room is locked - hang on until somebody lets you in ...'
|
||||
/>
|
||||
</DialogContentText>
|
||||
}
|
||||
</DialogContent>
|
||||
}
|
||||
|
||||
<CookieConsent>
|
||||
<FormattedMessage
|
||||
id='room.cookieConsent'
|
||||
defaultMessage='This website uses cookies to enhance the user experience'
|
||||
/>
|
||||
</CookieConsent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
JoinDialog.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
room : PropTypes.object.isRequired,
|
||||
displayName : PropTypes.string.isRequired,
|
||||
displayNameInProgress : PropTypes.bool.isRequired,
|
||||
loginEnabled : PropTypes.bool.isRequired,
|
||||
loggedIn : PropTypes.bool.isRequired,
|
||||
myPicture : PropTypes.string,
|
||||
changeDisplayName : PropTypes.func.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default withRoomContext(withStyles(styles)(JoinDialog));
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
room : state.room,
|
||||
displayName : state.settings.displayName,
|
||||
displayNameInProgress : state.me.displayNameInProgress,
|
||||
loginEnabled : state.me.loginEnabled,
|
||||
loggedIn : state.me.loggedIn,
|
||||
myPicture : state.me.picture
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
{
|
||||
return {
|
||||
changeDisplayName : (displayName) =>
|
||||
{
|
||||
dispatch(settingsActions.setDisplayName(displayName));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.room.inLobby === next.room.inLobby &&
|
||||
prev.room.signInRequired === next.room.signInRequired &&
|
||||
prev.settings.displayName === next.settings.displayName &&
|
||||
prev.me.displayNameInProgress === next.me.displayNameInProgress &&
|
||||
prev.me.loginEnabled === next.me.loginEnabled &&
|
||||
prev.me.loggedIn === next.me.loggedIn &&
|
||||
prev.me.picture === next.me.picture
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(JoinDialog)));
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import { useIntl } from 'react-intl';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import InputBase from '@material-ui/core/InputBase';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
|
|
@ -28,19 +29,13 @@ const styles = (theme) =>
|
|||
}
|
||||
});
|
||||
|
||||
class ChatInput extends React.PureComponent
|
||||
const ChatInput = (props) =>
|
||||
{
|
||||
constructor(props)
|
||||
{
|
||||
super(props);
|
||||
const [ message, setMessage ] = useState('');
|
||||
|
||||
this.state =
|
||||
{
|
||||
message : ''
|
||||
};
|
||||
}
|
||||
const intl = useIntl();
|
||||
|
||||
createNewMessage = (text, sender, name, picture) =>
|
||||
const createNewMessage = (text, sender, name, picture) =>
|
||||
({
|
||||
type : 'message',
|
||||
text,
|
||||
|
|
@ -50,67 +45,67 @@ class ChatInput extends React.PureComponent
|
|||
picture
|
||||
});
|
||||
|
||||
handleChange = (e) =>
|
||||
const handleChange = (e) =>
|
||||
{
|
||||
this.setState({ message: e.target.value });
|
||||
}
|
||||
setMessage(e.target.value);
|
||||
};
|
||||
|
||||
render()
|
||||
{
|
||||
const {
|
||||
roomClient,
|
||||
displayName,
|
||||
picture,
|
||||
classes
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Paper className={classes.root}>
|
||||
<InputBase
|
||||
className={classes.input}
|
||||
placeholder='Enter chat message...'
|
||||
value={this.state.message || ''}
|
||||
onChange={this.handleChange}
|
||||
onKeyPress={(ev) =>
|
||||
const {
|
||||
roomClient,
|
||||
displayName,
|
||||
picture,
|
||||
classes
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Paper className={classes.root}>
|
||||
<InputBase
|
||||
className={classes.input}
|
||||
placeholder={intl.formatMessage({
|
||||
id : 'label.chatInput',
|
||||
defaultMessage : 'Enter chat message...'
|
||||
})}
|
||||
value={message || ''}
|
||||
onChange={handleChange}
|
||||
onKeyPress={(ev) =>
|
||||
{
|
||||
if (ev.key === 'Enter')
|
||||
{
|
||||
if (ev.key === 'Enter')
|
||||
ev.preventDefault();
|
||||
|
||||
if (message && message !== '')
|
||||
{
|
||||
ev.preventDefault();
|
||||
const sendMessage = createNewMessage(message, 'response', displayName, picture);
|
||||
|
||||
if (this.state.message && this.state.message !== '')
|
||||
{
|
||||
const message = this.createNewMessage(this.state.message, 'response', displayName, picture);
|
||||
roomClient.sendChatMessage(sendMessage);
|
||||
|
||||
roomClient.sendChatMessage(message);
|
||||
|
||||
this.setState({ message: '' });
|
||||
}
|
||||
setMessage('');
|
||||
}
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
<IconButton
|
||||
color='primary'
|
||||
className={classes.iconButton}
|
||||
aria-label='Send'
|
||||
onClick={() =>
|
||||
}
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
<IconButton
|
||||
color='primary'
|
||||
className={classes.iconButton}
|
||||
aria-label='Send'
|
||||
onClick={() =>
|
||||
{
|
||||
if (message && message !== '')
|
||||
{
|
||||
if (this.state.message && this.state.message !== '')
|
||||
{
|
||||
const message = this.createNewMessage(this.state.message, 'response', displayName, picture);
|
||||
const sendMessage = this.createNewMessage(message, 'response', displayName, picture);
|
||||
|
||||
roomClient.sendChatMessage(message);
|
||||
roomClient.sendChatMessage(sendMessage);
|
||||
|
||||
this.setState({ message: '' });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SendIcon />
|
||||
</IconButton>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
}
|
||||
setMessage('');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SendIcon />
|
||||
</IconButton>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
ChatInput.propTypes =
|
||||
{
|
||||
|
|
@ -123,7 +118,7 @@ ChatInput.propTypes =
|
|||
const mapStateToProps = (state) =>
|
||||
({
|
||||
displayName : state.settings.displayName,
|
||||
picture : state.settings.picture
|
||||
picture : state.me.picture
|
||||
});
|
||||
|
||||
export default withRoomContext(
|
||||
|
|
@ -136,7 +131,7 @@ export default withRoomContext(
|
|||
{
|
||||
return (
|
||||
prev.settings.displayName === next.settings.displayName &&
|
||||
prev.settings.picture === next.settings.picture
|
||||
prev.me.picture === next.me.picture
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import DOMPurify from 'dompurify';
|
||||
import marked from 'marked';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
|
@ -76,9 +77,11 @@ const Message = (props) =>
|
|||
className={classes.text}
|
||||
variant='subtitle1'
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html : marked.parse(
|
||||
text,
|
||||
{ sanitize: true, renderer: linkRenderer }
|
||||
dangerouslySetInnerHTML={{ __html : DOMPurify.sanitize(
|
||||
marked.parse(
|
||||
text,
|
||||
{ renderer: linkRenderer }
|
||||
)
|
||||
) }}
|
||||
/>
|
||||
<Typography variant='caption'>{self ? 'Me' : name} - {time}</Typography>
|
||||
|
|
@ -92,7 +95,7 @@ Message.propTypes =
|
|||
self : PropTypes.bool,
|
||||
picture : PropTypes.string,
|
||||
text : PropTypes.string,
|
||||
time : PropTypes.string,
|
||||
time : PropTypes.object,
|
||||
name : PropTypes.string,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { FormattedTime } from 'react-intl';
|
||||
import Message from './Message';
|
||||
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
||||
|
||||
|
|
@ -33,7 +34,7 @@ class MessageList extends React.Component
|
|||
|
||||
shouldComponentUpdate(nextProps)
|
||||
{
|
||||
if (nextProps.chatmessages.length !== this.props.chatmessages.length)
|
||||
if (nextProps.chat.length !== this.props.chat.length)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
|
@ -49,13 +50,13 @@ class MessageList extends React.Component
|
|||
|
||||
getTimeString(time)
|
||||
{
|
||||
return `${(time.getHours() < 10 ? '0' : '')}${time.getHours()}:${(time.getMinutes() < 10 ? '0' : '')}${time.getMinutes()}`;
|
||||
return (<FormattedTime value={new Date(time)} />);
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
const {
|
||||
chatmessages,
|
||||
chat,
|
||||
myPicture,
|
||||
classes
|
||||
} = this.props;
|
||||
|
|
@ -63,10 +64,8 @@ class MessageList extends React.Component
|
|||
return (
|
||||
<div className={classes.root} ref={(node) => { this.node = node; }}>
|
||||
{
|
||||
chatmessages.map((message, index) =>
|
||||
chat.map((message, index) =>
|
||||
{
|
||||
const messageTime = new Date(message.time);
|
||||
|
||||
const picture = (message.sender === 'response' ?
|
||||
message.picture : myPicture) || EmptyAvatar;
|
||||
|
||||
|
|
@ -76,7 +75,7 @@ class MessageList extends React.Component
|
|||
self={message.sender === 'client'}
|
||||
picture={picture}
|
||||
text={message.text}
|
||||
time={this.getTimeString(messageTime)}
|
||||
time={this.getTimeString(message.time)}
|
||||
name={message.name}
|
||||
/>
|
||||
);
|
||||
|
|
@ -89,15 +88,15 @@ class MessageList extends React.Component
|
|||
|
||||
MessageList.propTypes =
|
||||
{
|
||||
chatmessages : PropTypes.array,
|
||||
myPicture : PropTypes.string,
|
||||
classes : PropTypes.object.isRequired
|
||||
chat : PropTypes.array,
|
||||
myPicture : PropTypes.string,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
({
|
||||
chatmessages : state.chatmessages,
|
||||
myPicture : state.settings.picture
|
||||
chat : state.chat,
|
||||
myPicture : state.me.picture
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
@ -108,8 +107,8 @@ export default connect(
|
|||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.chatmessages === next.chatmessages &&
|
||||
prev.settings.picture === next.settings.picture
|
||||
prev.chat === next.chat &&
|
||||
prev.me.picture === next.me.picture
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { connect } from 'react-redux';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import magnet from 'magnet-uri';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Button from '@material-ui/core/Button';
|
||||
|
|
@ -67,10 +68,13 @@ class File extends React.PureComponent
|
|||
<img alt='Avatar' className={classes.avatar} src={picture} />
|
||||
|
||||
<div className={classes.fileContent}>
|
||||
{ file.files ?
|
||||
{ file.files &&
|
||||
<Fragment>
|
||||
<Typography className={classes.text}>
|
||||
File finished downloading
|
||||
<FormattedMessage
|
||||
id='filesharing.finished'
|
||||
defaultMessage='File finished downloading'
|
||||
/>
|
||||
</Typography>
|
||||
|
||||
{ file.files.map((sharedFile, i) => (
|
||||
|
|
@ -87,18 +91,26 @@ class File extends React.PureComponent
|
|||
roomClient.saveFile(sharedFile);
|
||||
}}
|
||||
>
|
||||
Save
|
||||
<FormattedMessage
|
||||
id='filesharing.save'
|
||||
defaultMessage='Save'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</Fragment>
|
||||
:null
|
||||
}
|
||||
<Typography className={classes.text}>
|
||||
{ `${displayName} shared a file` }
|
||||
<FormattedMessage
|
||||
id='filesharing.sharedFile'
|
||||
defaultMessage='{displayName} shared a file'
|
||||
values={{
|
||||
displayName
|
||||
}}
|
||||
/>
|
||||
</Typography>
|
||||
|
||||
{ !file.active && !file.files ?
|
||||
{ (!file.active && !file.files) &&
|
||||
<div className={classes.fileInfo}>
|
||||
<Typography className={classes.text}>
|
||||
{ magnet.decode(magnetUri).dn }
|
||||
|
|
@ -113,28 +125,37 @@ class File extends React.PureComponent
|
|||
roomClient.handleDownload(magnetUri);
|
||||
}}
|
||||
>
|
||||
Download
|
||||
<FormattedMessage
|
||||
id='filesharing.download'
|
||||
defaultMessage='Download'
|
||||
/>
|
||||
</Button>
|
||||
:
|
||||
<Typography className={classes.text}>
|
||||
Your browser does not support downloading files using WebTorrent.
|
||||
<FormattedMessage
|
||||
id='label.fileSharingUnsupported'
|
||||
defaultMessage='File sharing not supported'
|
||||
/>
|
||||
</Typography>
|
||||
}
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
|
||||
{ file.timeout ?
|
||||
{ file.timeout &&
|
||||
<Typography className={classes.text}>
|
||||
If this process takes a long time, there might not be anyone seeding
|
||||
this torrent. Try asking someone to reupload the file that you want.
|
||||
<FormattedMessage
|
||||
id='filesharing.missingSeeds'
|
||||
defaultMessage={
|
||||
`If this process takes a long time, there might not
|
||||
be anyone seeding this torrent. Try asking someone to
|
||||
reupload the file that you want.`
|
||||
}
|
||||
/>
|
||||
</Typography>
|
||||
:null
|
||||
}
|
||||
|
||||
{ file.active ?
|
||||
{ file.active &&
|
||||
<progress value={file.progress} />
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { connect } from 'react-redux';
|
|||
import PropTypes from 'prop-types';
|
||||
import * as appPropTypes from '../../appPropTypes';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import File from './File';
|
||||
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
||||
|
||||
|
|
@ -45,8 +46,8 @@ class FileList extends React.PureComponent
|
|||
const {
|
||||
files,
|
||||
me,
|
||||
picture,
|
||||
peers,
|
||||
intl,
|
||||
classes
|
||||
} = this.props;
|
||||
|
||||
|
|
@ -60,8 +61,11 @@ class FileList extends React.PureComponent
|
|||
|
||||
if (me.id === file.peerId)
|
||||
{
|
||||
displayName = 'You';
|
||||
filePicture = picture;
|
||||
displayName = intl.formatMessage({
|
||||
id : 'room.me',
|
||||
defaultMessage : 'Me'
|
||||
});
|
||||
filePicture = me.picture;
|
||||
}
|
||||
else if (peers[file.peerId])
|
||||
{
|
||||
|
|
@ -70,7 +74,10 @@ class FileList extends React.PureComponent
|
|||
}
|
||||
else
|
||||
{
|
||||
displayName = 'Unknown';
|
||||
displayName = intl.formatMessage({
|
||||
id : 'label.unknown',
|
||||
defaultMessage : 'Unknown'
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -91,18 +98,17 @@ FileList.propTypes =
|
|||
{
|
||||
files : PropTypes.object.isRequired,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
picture : PropTypes.string,
|
||||
peers : PropTypes.object.isRequired,
|
||||
intl : PropTypes.object.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
files : state.files,
|
||||
me : state.me,
|
||||
picture : state.settings.picture,
|
||||
peers : state.peers
|
||||
files : state.files,
|
||||
me : state.me,
|
||||
peers : state.peers
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -116,9 +122,8 @@ export default connect(
|
|||
return (
|
||||
prev.files === next.files &&
|
||||
prev.me === next.me &&
|
||||
prev.settings.picture === next.settings.picture &&
|
||||
prev.peers === next.peers
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(FileList));
|
||||
)(withStyles(styles)(injectIntl(FileList)));
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { connect } from 'react-redux';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import { useIntl } from 'react-intl';
|
||||
import FileList from './FileList';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Button from '@material-ui/core/Button';
|
||||
|
|
@ -26,58 +27,57 @@ const styles = (theme) =>
|
|||
}
|
||||
});
|
||||
|
||||
class FileSharing extends React.PureComponent
|
||||
const FileSharing = (props) =>
|
||||
{
|
||||
constructor(props)
|
||||
{
|
||||
super(props);
|
||||
const intl = useIntl();
|
||||
|
||||
this._fileInput = React.createRef();
|
||||
}
|
||||
|
||||
handleFileChange = async (event) =>
|
||||
const handleFileChange = async (event) =>
|
||||
{
|
||||
if (event.target.files.length > 0)
|
||||
{
|
||||
this.props.roomClient.shareFiles(event.target.files);
|
||||
props.roomClient.shareFiles(event.target.files);
|
||||
}
|
||||
};
|
||||
|
||||
render()
|
||||
{
|
||||
const {
|
||||
canShareFiles,
|
||||
classes
|
||||
} = this.props;
|
||||
const {
|
||||
canShareFiles,
|
||||
classes
|
||||
} = props;
|
||||
|
||||
const buttonDescription = canShareFiles ?
|
||||
'Share file' : 'File sharing not supported';
|
||||
const buttonDescription = canShareFiles ?
|
||||
intl.formatMessage({
|
||||
id : 'label.shareFile',
|
||||
defaultMessage : 'Share file'
|
||||
})
|
||||
:
|
||||
intl.formatMessage({
|
||||
id : 'label.fileSharingUnsupported',
|
||||
defaultMessage : 'File sharing not supported'
|
||||
});
|
||||
|
||||
return (
|
||||
<Paper className={classes.root}>
|
||||
<input
|
||||
ref={this._fileInput}
|
||||
className={classes.input}
|
||||
type='file'
|
||||
onChange={this.handleFileChange}
|
||||
id='share-files-button'
|
||||
/>
|
||||
<label htmlFor='share-files-button'>
|
||||
<Button
|
||||
variant='contained'
|
||||
component='span'
|
||||
className={classes.button}
|
||||
disabled={!canShareFiles}
|
||||
>
|
||||
{buttonDescription}
|
||||
</Button>
|
||||
</label>
|
||||
return (
|
||||
<Paper className={classes.root}>
|
||||
<input
|
||||
className={classes.input}
|
||||
type='file'
|
||||
onChange={handleFileChange}
|
||||
id='share-files-button'
|
||||
/>
|
||||
<label htmlFor='share-files-button'>
|
||||
<Button
|
||||
variant='contained'
|
||||
component='span'
|
||||
className={classes.button}
|
||||
disabled={!canShareFiles}
|
||||
>
|
||||
{buttonDescription}
|
||||
</Button>
|
||||
</label>
|
||||
|
||||
<FileList />
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
}
|
||||
<FileList />
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
FileSharing.propTypes = {
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import * as stateActions from '../../actions/stateActions';
|
||||
import * as toolareaActions from '../../actions/toolareaActions';
|
||||
import { useIntl } from 'react-intl';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Tabs from '@material-ui/core/Tabs';
|
||||
import Tab from '@material-ui/core/Tab';
|
||||
|
|
@ -44,6 +45,8 @@ const styles = (theme) =>
|
|||
|
||||
const MeetingDrawer = (props) =>
|
||||
{
|
||||
const intl = useIntl();
|
||||
|
||||
const {
|
||||
currentToolTab,
|
||||
unreadMessages,
|
||||
|
|
@ -72,18 +75,29 @@ const MeetingDrawer = (props) =>
|
|||
<Tab
|
||||
label={
|
||||
<Badge color='secondary' badgeContent={unreadMessages}>
|
||||
Chat
|
||||
{intl.formatMessage({
|
||||
id : 'label.chat',
|
||||
defaultMessage : 'Chat'
|
||||
})}
|
||||
</Badge>
|
||||
}
|
||||
/>
|
||||
<Tab
|
||||
label={
|
||||
<Badge color='secondary' badgeContent={unreadFiles}>
|
||||
File sharing
|
||||
{intl.formatMessage({
|
||||
id : 'label.filesharing',
|
||||
defaultMessage : 'File sharing'
|
||||
})}
|
||||
</Badge>
|
||||
}
|
||||
/>
|
||||
<Tab label='Participants' />
|
||||
<Tab
|
||||
label={intl.formatMessage({
|
||||
id : 'label.participants',
|
||||
defaultMessage : 'Participants'
|
||||
})}
|
||||
/>
|
||||
</Tabs>
|
||||
<IconButton onClick={closeDrawer}>
|
||||
{theme.direction === 'ltr' ? <ChevronLeftIcon /> : <ChevronRightIcon />}
|
||||
|
|
@ -114,7 +128,7 @@ const mapStateToProps = (state) => ({
|
|||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setToolTab : stateActions.setToolTab
|
||||
setToolTab : toolareaActions.setToolTab
|
||||
};
|
||||
|
||||
export default connect(
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ const ListMe = (props) =>
|
|||
classes
|
||||
} = props;
|
||||
|
||||
const picture = settings.picture || EmptyAvatar;
|
||||
const picture = me.picture || EmptyAvatar;
|
||||
|
||||
return (
|
||||
<li className={classes.root}>
|
||||
|
|
@ -91,9 +91,8 @@ const ListMe = (props) =>
|
|||
</div>
|
||||
|
||||
<div className={classes.indicators}>
|
||||
{ me.raisedHand ?
|
||||
{ me.raisedHand &&
|
||||
<div className={classnames(classes.icon, 'raise-hand')} />
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ const ListPeer = (props) =>
|
|||
{peer.displayName}
|
||||
</div>
|
||||
<div className={classes.indicators}>
|
||||
{ peer.raiseHandState ?
|
||||
{ peer.raiseHandState &&
|
||||
<div className={
|
||||
classnames(
|
||||
classes.icon, 'raise-hand', {
|
||||
|
|
@ -169,12 +169,11 @@ const ListPeer = (props) =>
|
|||
)
|
||||
}
|
||||
/>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
{children}
|
||||
<div className={classes.controls}>
|
||||
{ screenConsumer ?
|
||||
{ screenConsumer &&
|
||||
<div
|
||||
className={classnames(classes.button, 'screen', {
|
||||
on : screenVisible,
|
||||
|
|
@ -195,7 +194,6 @@ const ListPeer = (props) =>
|
|||
<ScreenOffIcon />
|
||||
}
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
<div
|
||||
className={classnames(classes.button, 'mic', {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import classNames from 'classnames';
|
|||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import ListPeer from './ListPeer';
|
||||
import ListMe from './ListMe';
|
||||
import Volume from '../../Containers/Volume';
|
||||
|
|
@ -84,12 +85,21 @@ class ParticipantList extends React.PureComponent
|
|||
return (
|
||||
<div className={classes.root} ref={(node) => { this.node = node; }}>
|
||||
<ul className={classes.list}>
|
||||
<li className={classes.listheader}>Me:</li>
|
||||
<li className={classes.listheader}>
|
||||
<FormattedMessage
|
||||
id='room.me'
|
||||
defaultMessage='Me'
|
||||
/>
|
||||
</li>
|
||||
<ListMe />
|
||||
</ul>
|
||||
<br />
|
||||
<ul className={classes.list}>
|
||||
<li className={classes.listheader}>Participants in Spotlight:</li>
|
||||
<li className={classes.listheader}>
|
||||
<FormattedMessage
|
||||
id='room.spotlights'
|
||||
defaultMessage='Participants in Spotlight'
|
||||
/>
|
||||
</li>
|
||||
{ spotlightPeers.map((peer) => (
|
||||
<li
|
||||
key={peer.id}
|
||||
|
|
@ -104,9 +114,13 @@ class ParticipantList extends React.PureComponent
|
|||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<br />
|
||||
<ul className={classes.list}>
|
||||
<li className={classes.listheader}>Passive Participants:</li>
|
||||
<li className={classes.listheader}>
|
||||
<FormattedMessage
|
||||
id='room.passive'
|
||||
defaultMessage='Passive Participants'
|
||||
/>
|
||||
</li>
|
||||
{ passivePeers.map((peerId) => (
|
||||
<li
|
||||
key={peerId}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
videoBoxesSelector,
|
||||
spotlightsLengthSelector
|
||||
} from '../Selectors';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Peer from '../Containers/Peer';
|
||||
|
|
@ -14,7 +15,7 @@ import HiddenPeers from '../Containers/HiddenPeers';
|
|||
|
||||
const RATIO = 1.334;
|
||||
const PADDING_V = 50;
|
||||
const PADDING_H = 20;
|
||||
const PADDING_H = 0;
|
||||
|
||||
const styles = () =>
|
||||
({
|
||||
|
|
@ -27,11 +28,17 @@ const styles = () =>
|
|||
flexWrap : 'wrap',
|
||||
justifyContent : 'center',
|
||||
alignItems : 'center',
|
||||
alignContent : 'center',
|
||||
paddingTop : 40,
|
||||
paddingBottom : 10,
|
||||
paddingLeft : 10,
|
||||
paddingRight : 10
|
||||
alignContent : 'center'
|
||||
},
|
||||
hiddenToolBar :
|
||||
{
|
||||
paddingTop : 0,
|
||||
transition : 'padding .5s'
|
||||
},
|
||||
showingToolBar :
|
||||
{
|
||||
paddingTop : 60,
|
||||
transition : 'padding .5s'
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -63,7 +70,8 @@ class Democratic extends React.PureComponent
|
|||
}
|
||||
|
||||
const width = this.peersRef.current.clientWidth - PADDING_H;
|
||||
const height = this.peersRef.current.clientHeight - PADDING_V;
|
||||
const height = this.peersRef.current.clientHeight -
|
||||
(this.props.toolbarsVisible ? PADDING_V : PADDING_H);
|
||||
|
||||
let x, y, space;
|
||||
|
||||
|
|
@ -86,8 +94,8 @@ class Democratic extends React.PureComponent
|
|||
if (Math.ceil(this.state.peerWidth) !== Math.ceil(0.9 * x))
|
||||
{
|
||||
this.setState({
|
||||
peerWidth : 0.9 * x,
|
||||
peerHeight : 0.9 * y
|
||||
peerWidth : 0.95 * x,
|
||||
peerHeight : 0.95 * y
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -125,6 +133,7 @@ class Democratic extends React.PureComponent
|
|||
peersLength,
|
||||
spotlightsPeers,
|
||||
spotlightsLength,
|
||||
toolbarsVisible,
|
||||
classes
|
||||
} = this.props;
|
||||
|
||||
|
|
@ -135,7 +144,13 @@ class Democratic extends React.PureComponent
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={classes.root} ref={this.peersRef}>
|
||||
<div
|
||||
className={classnames(
|
||||
classes.root,
|
||||
toolbarsVisible ? classes.showingToolBar : classes.hiddenToolBar
|
||||
)}
|
||||
ref={this.peersRef}
|
||||
>
|
||||
<Me
|
||||
advancedMode={advancedMode}
|
||||
spacing={6}
|
||||
|
|
@ -153,11 +168,10 @@ class Democratic extends React.PureComponent
|
|||
/>
|
||||
);
|
||||
})}
|
||||
{ spotlightsLength < peersLength ?
|
||||
{ spotlightsLength < peersLength &&
|
||||
<HiddenPeers
|
||||
hiddenPeersCount={peersLength - spotlightsLength}
|
||||
/>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
|
@ -171,6 +185,7 @@ Democratic.propTypes =
|
|||
boxes : PropTypes.number,
|
||||
spotlightsLength : PropTypes.number,
|
||||
spotlightsPeers : PropTypes.array.isRequired,
|
||||
toolbarsVisible : PropTypes.bool.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
|
|
@ -180,7 +195,8 @@ const mapStateToProps = (state) =>
|
|||
peersLength : peersLengthSelector(state),
|
||||
boxes : videoBoxesSelector(state),
|
||||
spotlightsPeers : spotlightPeersSelector(state),
|
||||
spotlightsLength : spotlightsLengthSelector(state)
|
||||
spotlightsLength : spotlightsLengthSelector(state),
|
||||
toolbarsVisible : state.room.toolbarsVisible
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -195,7 +211,8 @@ export default connect(
|
|||
prev.peers === next.peers &&
|
||||
prev.producers === next.producers &&
|
||||
prev.consumers === next.consumers &&
|
||||
prev.room.spotlights === next.room.spotlights
|
||||
prev.room.spotlights === next.room.spotlights &&
|
||||
prev.room.toolbarsVisible === next.room.toolbarsVisible
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,13 +228,12 @@ class Filmstrip extends React.PureComponent
|
|||
return (
|
||||
<div className={classes.root}>
|
||||
<div className={classes.speaker} ref={this.activePeerContainer}>
|
||||
{ peers[activePeerId] ?
|
||||
{ peers[activePeerId] &&
|
||||
<SpeakerPeer
|
||||
advancedMode={advancedMode}
|
||||
id={activePeerId}
|
||||
style={speakerStyle}
|
||||
/>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
|
||||
|
|
@ -286,11 +285,10 @@ class Filmstrip extends React.PureComponent
|
|||
</Grid>
|
||||
</div>
|
||||
|
||||
{ spotlightsLength<Object.keys(peers).length ?
|
||||
{ spotlightsLength<Object.keys(peers).length &&
|
||||
<HiddenPeers
|
||||
hiddenPeersCount={Object.keys(peers).length-spotlightsLength}
|
||||
/>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Component } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withSnackbar } from 'notistack';
|
||||
import * as stateActions from '../../actions/stateActions';
|
||||
import * as notificationActions from '../../actions/notificationActions';
|
||||
|
||||
class Notifications extends Component
|
||||
{
|
||||
|
|
@ -77,7 +77,7 @@ const mapStateToProps = (state) =>
|
|||
const mapDispatchToProps = (dispatch) =>
|
||||
({
|
||||
removeNotification : (notificationId) =>
|
||||
dispatch(stateActions.removeNotification({ notificationId }))
|
||||
dispatch(notificationActions.removeNotification({ notificationId }))
|
||||
});
|
||||
|
||||
export default withSnackbar(
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ export default class PeerAudio extends React.PureComponent
|
|||
this._setTrack(audioTrack);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps)
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillReceiveProps(nextProps)
|
||||
{
|
||||
const { audioTrack } = nextProps;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react';
|
||||
|
||||
export const ReactLazyPreload = (importStatement) =>
|
||||
{
|
||||
const Component = React.lazy(importStatement);
|
||||
|
||||
Component.preload = importStatement;
|
||||
|
||||
return Component;
|
||||
};
|
||||
|
|
@ -2,24 +2,16 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as appPropTypes from './appPropTypes';
|
||||
import { withRoomContext } from '../RoomContext';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import * as stateActions from '../actions/stateActions';
|
||||
import * as roomActions from '../actions/roomActions';
|
||||
import * as toolareaActions from '../actions/toolareaActions';
|
||||
import { idle } from '../utils';
|
||||
import FullScreen from './FullScreen';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import CookieConsent from 'react-cookie-consent';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import SwipeableDrawer from '@material-ui/core/SwipeableDrawer';
|
||||
import Hidden from '@material-ui/core/Hidden';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Badge from '@material-ui/core/Badge';
|
||||
import AccountCircle from '@material-ui/icons/AccountCircle';
|
||||
import Notifications from './Notifications/Notifications';
|
||||
import MeetingDrawer from './MeetingDrawer/MeetingDrawer';
|
||||
import Democratic from './MeetingViews/Democratic';
|
||||
|
|
@ -27,16 +19,11 @@ import Filmstrip from './MeetingViews/Filmstrip';
|
|||
import AudioPeers from './PeerAudio/AudioPeers';
|
||||
import FullScreenView from './VideoContainers/FullScreenView';
|
||||
import VideoWindow from './VideoWindow/VideoWindow';
|
||||
import FullScreenIcon from '@material-ui/icons/Fullscreen';
|
||||
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
import LockIcon from '@material-ui/icons/Lock';
|
||||
import LockOpenIcon from '@material-ui/icons/LockOpen';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import LockDialog from './AccessControl/LockDialog/LockDialog';
|
||||
import Settings from './Settings/Settings';
|
||||
import JoinDialog from './JoinDialog';
|
||||
import TopBar from './Controls/TopBar';
|
||||
|
||||
const TIMEOUT = 10 * 1000;
|
||||
const TIMEOUT = 5 * 1000;
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
|
|
@ -52,44 +39,6 @@ const styles = (theme) =>
|
|||
backgroundSize : 'cover',
|
||||
backgroundRepeat : 'no-repeat'
|
||||
},
|
||||
message :
|
||||
{
|
||||
position : 'absolute',
|
||||
display : 'flex',
|
||||
top : '50%',
|
||||
left : '50%',
|
||||
transform : 'translateX(-50%) translateY(-50%)',
|
||||
width : '30vw',
|
||||
padding : theme.spacing(2),
|
||||
flexDirection : 'column',
|
||||
justifyContent : 'center',
|
||||
alignItems : 'center'
|
||||
},
|
||||
menuButton :
|
||||
{
|
||||
margin : 0,
|
||||
padding : 0
|
||||
},
|
||||
logo :
|
||||
{
|
||||
display : 'none',
|
||||
marginLeft : 20,
|
||||
[theme.breakpoints.up('sm')] :
|
||||
{
|
||||
display : 'block'
|
||||
}
|
||||
},
|
||||
show :
|
||||
{
|
||||
opacity : 1,
|
||||
transition : 'opacity .5s'
|
||||
},
|
||||
hide :
|
||||
{
|
||||
opacity : 0,
|
||||
transition : 'opacity .5s'
|
||||
},
|
||||
toolbar : theme.mixins.toolbar,
|
||||
drawerPaper :
|
||||
{
|
||||
width : '30vw',
|
||||
|
|
@ -109,44 +58,6 @@ const styles = (theme) =>
|
|||
{
|
||||
width : '90vw'
|
||||
}
|
||||
},
|
||||
grow :
|
||||
{
|
||||
flexGrow : 1
|
||||
},
|
||||
title :
|
||||
{
|
||||
display : 'none',
|
||||
marginLeft : 20,
|
||||
[theme.breakpoints.up('sm')] :
|
||||
{
|
||||
display : 'block'
|
||||
}
|
||||
},
|
||||
actionButtons :
|
||||
{
|
||||
display : 'flex'
|
||||
},
|
||||
actionButton :
|
||||
{
|
||||
margin : theme.spacing(1),
|
||||
padding : 0
|
||||
},
|
||||
meContainer :
|
||||
{
|
||||
position : 'fixed',
|
||||
zIndex : 110,
|
||||
overflow : 'hidden',
|
||||
boxShadow : 'var(--me-shadow)',
|
||||
transitionProperty : 'border-color',
|
||||
transitionDuration : '0.15s',
|
||||
top : '5em',
|
||||
left : '1em',
|
||||
border : 'var(--me-border)',
|
||||
'&.active-speaker' :
|
||||
{
|
||||
borderColor : 'var(--active-speaker-border-color)'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -225,16 +136,10 @@ class Room extends React.PureComponent
|
|||
render()
|
||||
{
|
||||
const {
|
||||
roomClient,
|
||||
room,
|
||||
advancedMode,
|
||||
myPicture,
|
||||
loggedIn,
|
||||
loginEnabled,
|
||||
setSettingsOpen,
|
||||
toolAreaOpen,
|
||||
toggleToolArea,
|
||||
unread,
|
||||
classes,
|
||||
theme
|
||||
} = this.props;
|
||||
|
|
@ -245,188 +150,65 @@ class Room extends React.PureComponent
|
|||
democratic : Democratic
|
||||
}[room.mode];
|
||||
|
||||
if (room.lockedOut)
|
||||
{
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Paper className={classes.message}>
|
||||
<Typography variant='h2'>This room is locked at the moment, try again later.</Typography>
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else if (!room.joined)
|
||||
{
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<JoinDialog />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<CookieConsent>
|
||||
This website uses cookies to enhance the user experience.
|
||||
</CookieConsent>
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<CookieConsent>
|
||||
<FormattedMessage
|
||||
id='room.cookieConsent'
|
||||
defaultMessage='This website uses cookies to enhance the user experience'
|
||||
/>
|
||||
</CookieConsent>
|
||||
|
||||
<FullScreenView advancedMode={advancedMode} />
|
||||
<FullScreenView advancedMode={advancedMode} />
|
||||
|
||||
<VideoWindow advancedMode={advancedMode} />
|
||||
<VideoWindow advancedMode={advancedMode} />
|
||||
|
||||
<AudioPeers />
|
||||
<AudioPeers />
|
||||
|
||||
<Notifications />
|
||||
<Notifications />
|
||||
|
||||
<CssBaseline />
|
||||
<CssBaseline />
|
||||
|
||||
<AppBar
|
||||
position='fixed'
|
||||
className={room.toolbarsVisible ? classes.show : classes.hide}
|
||||
>
|
||||
<Toolbar>
|
||||
<Badge
|
||||
color='secondary'
|
||||
badgeContent={unread}
|
||||
>
|
||||
<IconButton
|
||||
color='inherit'
|
||||
aria-label='Open drawer'
|
||||
onClick={() => toggleToolArea()}
|
||||
className={classes.menuButton}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</Badge>
|
||||
{ window.config.logo ?
|
||||
<img alt='Logo' className={classes.logo} src={window.config.logo} />
|
||||
:null
|
||||
}
|
||||
<Typography
|
||||
className={classes.title}
|
||||
variant='h6'
|
||||
color='inherit'
|
||||
noWrap
|
||||
>
|
||||
{ window.config.title }
|
||||
</Typography>
|
||||
<div className={classes.grow} />
|
||||
<div className={classes.actionButtons}>
|
||||
<IconButton
|
||||
aria-label='Lock room'
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={() =>
|
||||
{
|
||||
if (room.locked)
|
||||
{
|
||||
roomClient.unlockRoom();
|
||||
}
|
||||
else
|
||||
{
|
||||
roomClient.lockRoom();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{ room.locked ?
|
||||
<LockIcon />
|
||||
:
|
||||
<LockOpenIcon />
|
||||
}
|
||||
</IconButton>
|
||||
{ this.fullscreen.fullscreenEnabled ?
|
||||
<IconButton
|
||||
aria-label='Fullscreen'
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={this.handleToggleFullscreen}
|
||||
>
|
||||
{ this.state.fullscreen ?
|
||||
<FullScreenExitIcon />
|
||||
:
|
||||
<FullScreenIcon />
|
||||
}
|
||||
</IconButton>
|
||||
:null
|
||||
}
|
||||
<IconButton
|
||||
aria-label='Settings'
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={() => setSettingsOpen(!room.settingsOpen)}
|
||||
>
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
{ loginEnabled ?
|
||||
<IconButton
|
||||
aria-label='Account'
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={() =>
|
||||
{
|
||||
loggedIn ? roomClient.logout() : roomClient.login();
|
||||
}}
|
||||
>
|
||||
{ myPicture ?
|
||||
<Avatar src={myPicture} />
|
||||
:
|
||||
<AccountCircle />
|
||||
}
|
||||
</IconButton>
|
||||
:null
|
||||
}
|
||||
<Button
|
||||
aria-label='Leave meeting'
|
||||
className={classes.actionButton}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
onClick={() => roomClient.close()}
|
||||
>
|
||||
Leave
|
||||
</Button>
|
||||
</div>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<nav>
|
||||
<Hidden implementation='css'>
|
||||
<SwipeableDrawer
|
||||
variant='temporary'
|
||||
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
|
||||
open={toolAreaOpen}
|
||||
onClose={() => toggleToolArea()}
|
||||
onOpen={() => toggleToolArea()}
|
||||
classes={{
|
||||
paper : classes.drawerPaper
|
||||
}}
|
||||
>
|
||||
<MeetingDrawer closeDrawer={toggleToolArea} />
|
||||
</SwipeableDrawer>
|
||||
</Hidden>
|
||||
</nav>
|
||||
<TopBar
|
||||
fullscreenEnabled={this.fullscreen.fullscreenEnabled}
|
||||
fullscreen={this.state.fullscreen}
|
||||
onFullscreen={this.handleToggleFullscreen}
|
||||
/>
|
||||
|
||||
<View advancedMode={advancedMode} />
|
||||
<nav>
|
||||
<Hidden implementation='css'>
|
||||
<SwipeableDrawer
|
||||
variant='temporary'
|
||||
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
|
||||
open={toolAreaOpen}
|
||||
onClose={() => toggleToolArea()}
|
||||
onOpen={() => toggleToolArea()}
|
||||
classes={{
|
||||
paper : classes.drawerPaper
|
||||
}}
|
||||
>
|
||||
<MeetingDrawer closeDrawer={toggleToolArea} />
|
||||
</SwipeableDrawer>
|
||||
</Hidden>
|
||||
</nav>
|
||||
|
||||
<Settings />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<View advancedMode={advancedMode} />
|
||||
|
||||
<LockDialog />
|
||||
|
||||
<Settings />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Room.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.object.isRequired,
|
||||
room : appPropTypes.Room.isRequired,
|
||||
advancedMode : PropTypes.bool.isRequired,
|
||||
myPicture : PropTypes.string,
|
||||
loggedIn : PropTypes.bool.isRequired,
|
||||
loginEnabled : PropTypes.bool.isRequired,
|
||||
toolAreaOpen : PropTypes.bool.isRequired,
|
||||
setToolbarsVisible : PropTypes.func.isRequired,
|
||||
setSettingsOpen : PropTypes.func.isRequired,
|
||||
toggleToolArea : PropTypes.func.isRequired,
|
||||
unread : PropTypes.number.isRequired,
|
||||
classes : PropTypes.object.isRequired,
|
||||
theme : PropTypes.object.isRequired
|
||||
};
|
||||
|
|
@ -435,31 +217,22 @@ const mapStateToProps = (state) =>
|
|||
({
|
||||
room : state.room,
|
||||
advancedMode : state.settings.advancedMode,
|
||||
loggedIn : state.me.loggedIn,
|
||||
loginEnabled : state.me.loginEnabled,
|
||||
myPicture : state.settings.picture,
|
||||
toolAreaOpen : state.toolarea.toolAreaOpen,
|
||||
unread : state.toolarea.unreadMessages +
|
||||
state.toolarea.unreadFiles
|
||||
toolAreaOpen : state.toolarea.toolAreaOpen
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
({
|
||||
setToolbarsVisible : (visible) =>
|
||||
{
|
||||
dispatch(stateActions.setToolbarsVisible(visible));
|
||||
},
|
||||
setSettingsOpen : (settingsOpen) =>
|
||||
{
|
||||
dispatch(stateActions.setSettingsOpen({ settingsOpen }));
|
||||
dispatch(roomActions.setToolbarsVisible(visible));
|
||||
},
|
||||
toggleToolArea : () =>
|
||||
{
|
||||
dispatch(stateActions.toggleToolArea());
|
||||
dispatch(toolareaActions.toggleToolArea());
|
||||
}
|
||||
});
|
||||
|
||||
export default withRoomContext(connect(
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
|
|
@ -468,14 +241,9 @@ export default withRoomContext(connect(
|
|||
{
|
||||
return (
|
||||
prev.room === next.room &&
|
||||
prev.me.loggedIn === next.me.loggedIn &&
|
||||
prev.me.loginEnabled === next.me.loginEnabled &&
|
||||
prev.settings.picture === next.settings.picture &&
|
||||
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen &&
|
||||
prev.toolarea.unreadMessages === next.toolarea.unreadMessages &&
|
||||
prev.toolarea.unreadFiles === next.toolarea.unreadFiles &&
|
||||
prev.settings.advancedMode === next.settings.advancedMode
|
||||
prev.settings.advancedMode === next.settings.advancedMode &&
|
||||
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles, { withTheme: true })(Room)));
|
||||
)(withStyles(styles, { withTheme: true })(Room));
|
||||
|
|
@ -4,6 +4,7 @@ const producersSelect = (state) => state.producers;
|
|||
const consumersSelect = (state) => state.consumers;
|
||||
const spotlightsSelector = (state) => state.room.spotlights;
|
||||
const peersSelector = (state) => state.peers;
|
||||
const lobbyPeersSelector = (state) => state.lobbyPeers;
|
||||
const getPeerConsumers = (state, props) =>
|
||||
(state.peers[props.id] ? state.peers[props.id].consumers : null);
|
||||
const getAllConsumers = (state) => state.consumers;
|
||||
|
|
@ -12,6 +13,11 @@ const peersKeySelector = createSelector(
|
|||
(peers) => Object.keys(peers)
|
||||
);
|
||||
|
||||
export const lobbyPeersKeySelector = createSelector(
|
||||
lobbyPeersSelector,
|
||||
(lobbyPeers) => Object.keys(lobbyPeers)
|
||||
);
|
||||
|
||||
export const micProducersSelector = createSelector(
|
||||
producersSelect,
|
||||
(producers) => Object.values(producers).filter((producer) => producer.source === 'mic')
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ import { connect } from 'react-redux';
|
|||
import * as appPropTypes from '../appPropTypes';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../RoomContext';
|
||||
import * as stateActions from '../../actions/stateActions';
|
||||
import * as roomActions from '../../actions/roomActions';
|
||||
import * as settingsActions from '../../actions/settingsActions';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
|
|
@ -51,35 +53,6 @@ const styles = (theme) =>
|
|||
}
|
||||
});
|
||||
|
||||
const modes = [ {
|
||||
value : 'democratic',
|
||||
label : 'Democratic view'
|
||||
}, {
|
||||
value : 'filmstrip',
|
||||
label : 'Filmstrip view'
|
||||
} ];
|
||||
|
||||
const resolutions = [ {
|
||||
value : 'low',
|
||||
label : 'Low'
|
||||
},
|
||||
{
|
||||
value : 'medium',
|
||||
label : 'Medium'
|
||||
},
|
||||
{
|
||||
value : 'high',
|
||||
label : 'High (HD)'
|
||||
},
|
||||
{
|
||||
value : 'veryhigh',
|
||||
label : 'Very high (FHD)'
|
||||
},
|
||||
{
|
||||
value : 'ultra',
|
||||
label : 'Ultra (UHD)'
|
||||
} ];
|
||||
|
||||
const Settings = ({
|
||||
roomClient,
|
||||
room,
|
||||
|
|
@ -91,6 +64,58 @@ const Settings = ({
|
|||
classes
|
||||
}) =>
|
||||
{
|
||||
const intl = useIntl();
|
||||
|
||||
const modes = [ {
|
||||
value : 'democratic',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.democratic',
|
||||
defaultMessage : 'Democratic view'
|
||||
})
|
||||
}, {
|
||||
value : 'filmstrip',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.filmstrip',
|
||||
defaultMessage : 'Filmstrip view'
|
||||
})
|
||||
} ];
|
||||
|
||||
const resolutions = [ {
|
||||
value : 'low',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.low',
|
||||
defaultMessage : 'Low'
|
||||
})
|
||||
},
|
||||
{
|
||||
value : 'medium',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.medium',
|
||||
defaultMessage : 'Medium'
|
||||
})
|
||||
},
|
||||
{
|
||||
value : 'high',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.high',
|
||||
defaultMessage : 'High (HD)'
|
||||
})
|
||||
},
|
||||
{
|
||||
value : 'veryhigh',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.veryHigh',
|
||||
defaultMessage : 'Very high (FHD)'
|
||||
})
|
||||
},
|
||||
{
|
||||
value : 'ultra',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.ultra',
|
||||
defaultMessage : 'Ultra (UHD)'
|
||||
})
|
||||
} ];
|
||||
|
||||
let webcams;
|
||||
|
||||
if (me.webcamDevices)
|
||||
|
|
@ -114,7 +139,12 @@ const Settings = ({
|
|||
paper : classes.dialogPaper
|
||||
}}
|
||||
>
|
||||
<DialogTitle id='form-dialog-title'>Settings</DialogTitle>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
<FormattedMessage
|
||||
id='settings.settings'
|
||||
defaultMessage='Settings'
|
||||
/>
|
||||
</DialogTitle>
|
||||
<form className={classes.setting} autoComplete='off'>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
|
|
@ -125,7 +155,10 @@ const Settings = ({
|
|||
roomClient.changeWebcam(event.target.value);
|
||||
}}
|
||||
displayEmpty
|
||||
name='Camera'
|
||||
name={intl.formatMessage({
|
||||
id : 'settings.camera',
|
||||
defaultMessage : 'Camera'
|
||||
})}
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
disabled={webcams.length === 0 || me.webcamInProgress}
|
||||
|
|
@ -139,9 +172,15 @@ const Settings = ({
|
|||
</Select>
|
||||
<FormHelperText>
|
||||
{ webcams.length > 0 ?
|
||||
'Select video device'
|
||||
intl.formatMessage({
|
||||
id : 'settings.selectCamera',
|
||||
defaultMessage : 'Select video device'
|
||||
})
|
||||
:
|
||||
'Unable to select video device'
|
||||
intl.formatMessage({
|
||||
id : 'settings.cantSelectCamera',
|
||||
defaultMessage : 'Unable to select video device'
|
||||
})
|
||||
}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
|
@ -156,7 +195,10 @@ const Settings = ({
|
|||
roomClient.changeAudioDevice(event.target.value);
|
||||
}}
|
||||
displayEmpty
|
||||
name='Audio device'
|
||||
name={intl.formatMessage({
|
||||
id : 'settings.audio',
|
||||
defaultMessage : 'Audio device'
|
||||
})}
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
disabled={audioDevices.length === 0 || me.audioInProgress}
|
||||
|
|
@ -170,9 +212,15 @@ const Settings = ({
|
|||
</Select>
|
||||
<FormHelperText>
|
||||
{ audioDevices.length > 0 ?
|
||||
'Select audio device'
|
||||
intl.formatMessage({
|
||||
id : 'settings.selectAudio',
|
||||
defaultMessage : 'Select audio device'
|
||||
})
|
||||
:
|
||||
'Unable to select audio device'
|
||||
intl.formatMessage({
|
||||
id : 'settings.cantSelectAudio',
|
||||
defaultMessage : 'Unable to select audio device'
|
||||
})
|
||||
}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
|
@ -200,7 +248,10 @@ const Settings = ({
|
|||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
Select your video resolution
|
||||
<FormattedMessage
|
||||
id='settings.resolution'
|
||||
defaultMessage='Select your video resolution'
|
||||
/>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
|
|
@ -213,7 +264,10 @@ const Settings = ({
|
|||
if (event.target.value)
|
||||
handleChangeMode(event.target.value);
|
||||
}}
|
||||
name='Room layout'
|
||||
name={intl.formatMessage({
|
||||
id : 'settings.layout',
|
||||
defaultMessage : 'Room layout'
|
||||
})}
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
>
|
||||
|
|
@ -227,18 +281,27 @@ const Settings = ({
|
|||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
Select room layout
|
||||
<FormattedMessage
|
||||
id='settings.selectRoomLayout'
|
||||
defaultMessage='Select room layout'
|
||||
/>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
<FormControlLabel
|
||||
className={classes.setting}
|
||||
control={<Checkbox checked={settings.advancedMode} onChange={onToggleAdvancedMode} value='advancedMode' />}
|
||||
label='Advanced mode'
|
||||
label={intl.formatMessage({
|
||||
id : 'settings.advancedMode',
|
||||
defaultMessage : 'Advanced mode'
|
||||
})}
|
||||
/>
|
||||
<DialogActions>
|
||||
<Button onClick={() => handleCloseSettings({ settingsOpen: false })} color='primary'>
|
||||
Close
|
||||
<FormattedMessage
|
||||
id='label.close'
|
||||
defaultMessage='Close'
|
||||
/>
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
|
@ -267,9 +330,9 @@ const mapStateToProps = (state) =>
|
|||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
onToggleAdvancedMode : stateActions.toggleAdvancedMode,
|
||||
handleChangeMode : stateActions.setDisplayMode,
|
||||
handleCloseSettings : stateActions.setSettingsOpen,
|
||||
onToggleAdvancedMode : settingsActions.toggleAdvancedMode,
|
||||
handleChangeMode : roomActions.setDisplayMode,
|
||||
handleCloseSettings : roomActions.setSettingsOpen
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||
import classnames from 'classnames';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import * as stateActions from '../../actions/stateActions';
|
||||
import * as roomActions from '../../actions/roomActions';
|
||||
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
|
||||
import VideoView from './VideoView';
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ const mapDispatchToProps = (dispatch) =>
|
|||
toggleConsumerFullscreen : (consumer) =>
|
||||
{
|
||||
if (consumer)
|
||||
dispatch(stateActions.toggleConsumerFullscreen(consumer.id));
|
||||
dispatch(roomActions.toggleConsumerFullscreen(consumer.id));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import EditableInput from '../Controls/EditableInput';
|
||||
|
||||
const styles = (theme) =>
|
||||
|
|
@ -105,16 +104,6 @@ const styles = (theme) =>
|
|||
{
|
||||
backgroundColor : 'rgb(174, 255, 0, 0.25)'
|
||||
}
|
||||
},
|
||||
deviceInfo :
|
||||
{
|
||||
'& span' :
|
||||
{
|
||||
userSelect : 'none',
|
||||
pointerEvents : 'none',
|
||||
fontSize : 11,
|
||||
color : 'rgba(255, 255, 255, 0.55)'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -143,7 +132,6 @@ class VideoView extends React.PureComponent
|
|||
const {
|
||||
isMe,
|
||||
isScreen,
|
||||
peer,
|
||||
displayName,
|
||||
showPeerInfo,
|
||||
videoContain,
|
||||
|
|
@ -171,24 +159,17 @@ class VideoView extends React.PureComponent
|
|||
})}
|
||||
>
|
||||
<div className={classes.box}>
|
||||
{ audioCodec ?
|
||||
<p>{audioCodec}</p>
|
||||
:null
|
||||
}
|
||||
{ audioCodec && <p>{audioCodec}</p> }
|
||||
|
||||
{ videoCodec ?
|
||||
<p>{videoCodec} {videoProfile}</p>
|
||||
:null
|
||||
}
|
||||
{ videoCodec && <p>{videoCodec} {videoProfile}</p> }
|
||||
|
||||
{ (videoVisible && videoWidth !== null) ?
|
||||
{ (videoVisible && videoWidth !== null) &&
|
||||
<p>{videoWidth}x{videoHeight}</p>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ showPeerInfo ?
|
||||
{ showPeerInfo &&
|
||||
<div className={classes.peer}>
|
||||
<div className={classes.box}>
|
||||
{ isMe ?
|
||||
|
|
@ -211,18 +192,8 @@ class VideoView extends React.PureComponent
|
|||
{displayName}
|
||||
</span>
|
||||
}
|
||||
|
||||
{ advancedMode ?
|
||||
<div className={classes.deviceInfo}>
|
||||
<span>
|
||||
{peer.device.name} {Math.floor(peer.device.version) || null}
|
||||
</span>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
:null
|
||||
}
|
||||
</div>
|
||||
|
||||
|
|
@ -230,7 +201,7 @@ class VideoView extends React.PureComponent
|
|||
ref='video'
|
||||
className={classnames(classes.video, {
|
||||
hidden : !videoVisible,
|
||||
'isMe' : isMe && !isScreen,
|
||||
'isMe' : isMe && !isScreen,
|
||||
loading : videoProfile === 'none',
|
||||
contain : videoContain
|
||||
})}
|
||||
|
|
@ -256,7 +227,8 @@ class VideoView extends React.PureComponent
|
|||
clearInterval(this._videoResolutionTimer);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps)
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillReceiveProps(nextProps)
|
||||
{
|
||||
const { videoTrack } = nextProps;
|
||||
|
||||
|
|
@ -323,8 +295,6 @@ VideoView.propTypes =
|
|||
{
|
||||
isMe : PropTypes.bool,
|
||||
isScreen : PropTypes.bool,
|
||||
peer : PropTypes.oneOfType(
|
||||
[ appPropTypes.Me, appPropTypes.Peer ]),
|
||||
displayName : PropTypes.string,
|
||||
showPeerInfo : PropTypes.bool,
|
||||
videoContain : PropTypes.bool,
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ class NewWindow extends React.PureComponent
|
|||
return ReactDOM.createPortal([
|
||||
<div key='newwindow' className={classes.root}>
|
||||
<div className={classes.controls}>
|
||||
{this.fullscreen.fullscreenEnabled && (
|
||||
{ this.fullscreen.fullscreenEnabled &&
|
||||
<div
|
||||
className={classes.button}
|
||||
onClick={this.handleToggleFullscreen}
|
||||
|
|
@ -144,9 +144,9 @@ class NewWindow extends React.PureComponent
|
|||
<FullScreenIcon className={classes.icon} />
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
}
|
||||
</div>
|
||||
{this.props.children}
|
||||
{ this.props.children }
|
||||
</div>
|
||||
], this.container);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
|||
import NewWindow from './NewWindow';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import * as stateActions from '../../actions/stateActions';
|
||||
import * as roomActions from '../../actions/roomActions';
|
||||
import FullView from '../VideoContainers/FullView';
|
||||
|
||||
const VideoWindow = (props) =>
|
||||
|
|
@ -59,7 +59,7 @@ const mapDispatchToProps = (dispatch) =>
|
|||
return {
|
||||
toggleConsumerWindow : () =>
|
||||
{
|
||||
dispatch(stateActions.toggleConsumerWindow());
|
||||
dispatch(roomActions.toggleConsumerWindow());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,17 +8,9 @@ export const Room = PropTypes.shape(
|
|||
activeSpeakerId : PropTypes.string
|
||||
});
|
||||
|
||||
export const Device = PropTypes.shape(
|
||||
{
|
||||
flag : PropTypes.string.isRequired,
|
||||
name : PropTypes.string.isRequired,
|
||||
version : PropTypes.string
|
||||
});
|
||||
|
||||
export const Me = PropTypes.shape(
|
||||
{
|
||||
id : PropTypes.string.isRequired,
|
||||
device : Device.isRequired,
|
||||
canSendMic : PropTypes.bool.isRequired,
|
||||
canSendWebcam : PropTypes.bool.isRequired,
|
||||
webcamInProgress : PropTypes.bool.isRequired
|
||||
|
|
@ -39,7 +31,6 @@ export const Peer = PropTypes.shape(
|
|||
{
|
||||
id : PropTypes.string.isRequired,
|
||||
displayName : PropTypes.string,
|
||||
device : Device.isRequired,
|
||||
consumers : PropTypes.arrayOf(PropTypes.string).isRequired
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ html
|
|||
font-family: 'Roboto';
|
||||
font-weight: 300;
|
||||
margin : 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
|
|
|
|||
|
|
@ -2,14 +2,16 @@ import domready from 'domready';
|
|||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { createIntl, createIntlCache, RawIntlProvider } from 'react-intl';
|
||||
import randomString from 'random-string';
|
||||
import Logger from './Logger';
|
||||
import debug from 'debug';
|
||||
import RoomClient from './RoomClient';
|
||||
import RoomContext from './RoomContext';
|
||||
import deviceInfo from './deviceInfo';
|
||||
import * as stateActions from './actions/stateActions';
|
||||
import Room from './components/Room';
|
||||
import * as roomActions from './actions/roomActions';
|
||||
import * as meActions from './actions/meActions';
|
||||
import App from './components/App';
|
||||
import LoadingView from './components/LoadingView';
|
||||
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
|
||||
import { PersistGate } from 'redux-persist/lib/integration/react';
|
||||
|
|
@ -17,9 +19,27 @@ import { persistor, store } from './store';
|
|||
import { SnackbarProvider } from 'notistack';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
|
||||
import messagesEnglish from './translations/en';
|
||||
import messagesNorwegian from './translations/nb';
|
||||
|
||||
import './index.css';
|
||||
|
||||
if (process.env.NODE_ENV !== 'production')
|
||||
const cache = createIntlCache();
|
||||
|
||||
const messages =
|
||||
{
|
||||
'en' : messagesEnglish,
|
||||
'nb' : messagesNorwegian
|
||||
};
|
||||
|
||||
const locale = navigator.language.split(/[-_]/)[0]; // language without region code
|
||||
|
||||
const intl = createIntl({
|
||||
locale,
|
||||
messages : messages[locale]
|
||||
}, cache);
|
||||
|
||||
if (process.env.REACT_APP_DEBUG === '*' || process.env.NODE_ENV !== 'production')
|
||||
{
|
||||
debug.enable('* -engine* -socket* -RIE* *WARN* *ERROR*');
|
||||
}
|
||||
|
|
@ -28,7 +48,7 @@ const logger = new Logger();
|
|||
|
||||
let roomClient;
|
||||
|
||||
RoomClient.init({ store });
|
||||
RoomClient.init({ store, intl });
|
||||
|
||||
const theme = createMuiTheme(window.config.theme);
|
||||
|
||||
|
|
@ -62,8 +82,8 @@ function run()
|
|||
window.history.pushState('', '', urlParser.toString());
|
||||
}
|
||||
|
||||
const accessCode = parameters.get('code');
|
||||
const produce = parameters.get('produce') !== 'false';
|
||||
const consume = parameters.get('consume') !== 'false';
|
||||
const useSimulcast = parameters.get('simulcast') === 'true';
|
||||
const forceTcp = parameters.get('forceTcp') === 'true';
|
||||
|
||||
|
|
@ -73,31 +93,32 @@ function run()
|
|||
const device = deviceInfo();
|
||||
|
||||
store.dispatch(
|
||||
stateActions.setRoomUrl(roomUrl));
|
||||
roomActions.setRoomUrl(roomUrl));
|
||||
|
||||
store.dispatch(
|
||||
stateActions.setMe({
|
||||
meActions.setMe({
|
||||
peerId,
|
||||
device,
|
||||
loginEnabled : window.config.loginEnabled
|
||||
})
|
||||
);
|
||||
|
||||
roomClient = new RoomClient(
|
||||
{ roomId, peerId, device, useSimulcast, produce, consume, forceTcp });
|
||||
{ roomId, peerId, accessCode, device, useSimulcast, produce, forceTcp });
|
||||
|
||||
global.CLIENT = roomClient;
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<PersistGate loading={<LoadingView />} persistor={persistor}>
|
||||
<RoomContext.Provider value={roomClient}>
|
||||
<SnackbarProvider>
|
||||
<Room />
|
||||
</SnackbarProvider>
|
||||
</RoomContext.Provider>
|
||||
</PersistGate>
|
||||
<RawIntlProvider value={intl}>
|
||||
<PersistGate loading={<LoadingView />} persistor={persistor}>
|
||||
<RoomContext.Provider value={roomClient}>
|
||||
<SnackbarProvider>
|
||||
<App />
|
||||
</SnackbarProvider>
|
||||
</RoomContext.Provider>
|
||||
</PersistGate>
|
||||
</RawIntlProvider>
|
||||
</MuiThemeProvider>
|
||||
</Provider>,
|
||||
document.getElementById('multiparty-meeting')
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import
|
|||
createNewMessage
|
||||
} from './helper';
|
||||
|
||||
const chatmessages = (state = [], action) =>
|
||||
const chat = (state = [], action) =>
|
||||
{
|
||||
switch (action.type)
|
||||
{
|
||||
|
|
@ -30,14 +30,9 @@ const chatmessages = (state = [], action) =>
|
|||
return [ ...state, ...chatHistory ];
|
||||
}
|
||||
|
||||
case 'DROP_MESSAGES':
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default chatmessages;
|
||||
export default chat;
|
||||
|
|
@ -85,13 +85,6 @@ const files = (state = {}, action) =>
|
|||
return { ...state, [magnetUri]: newFile };
|
||||
}
|
||||
|
||||
case 'REMOVE_FILE':
|
||||
{
|
||||
const { magnetUri } = action.payload;
|
||||
|
||||
return state.filter((file) => file.magnetUri !== magnetUri);
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
const lobbyPeer = (state = {}, action) =>
|
||||
{
|
||||
switch (action.type)
|
||||
{
|
||||
case 'ADD_LOBBY_PEER':
|
||||
return { id: action.payload.peerId };
|
||||
|
||||
case 'SET_LOBBY_PEER_DISPLAY_NAME':
|
||||
return { ...state, displayName: action.payload.displayName };
|
||||
case 'SET_LOBBY_PEER_PICTURE':
|
||||
return { ...state, picture: action.payload.picture };
|
||||
case 'SET_LOBBY_PEER_PROMOTION_IN_PROGRESS':
|
||||
return { ...state, promotionInProgress: action.payload.flag };
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const lobbyPeers = (state = {}, action) =>
|
||||
{
|
||||
switch (action.type)
|
||||
{
|
||||
case 'ADD_LOBBY_PEER':
|
||||
{
|
||||
return { ...state, [action.payload.peerId]: lobbyPeer(undefined, action) };
|
||||
}
|
||||
|
||||
case 'REMOVE_LOBBY_PEER':
|
||||
{
|
||||
const { peerId } = action.payload;
|
||||
const newState = { ...state };
|
||||
|
||||
delete newState[peerId];
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
case 'SET_LOBBY_PEER_DISPLAY_NAME':
|
||||
case 'SET_LOBBY_PEER_PICTURE':
|
||||
case 'SET_LOBBY_PEER_PROMOTION_IN_PROGRESS':
|
||||
{
|
||||
const oldLobbyPeer = state[action.payload.peerId];
|
||||
|
||||
if (!oldLobbyPeer)
|
||||
{
|
||||
// Tried to update non-existant lobbyPeer. Has probably been promoted, or left.
|
||||
return state;
|
||||
}
|
||||
|
||||
return { ...state, [oldLobbyPeer.id]: lobbyPeer(oldLobbyPeer, action) };
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default lobbyPeers;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
const initialState =
|
||||
{
|
||||
id : null,
|
||||
device : null,
|
||||
picture : null,
|
||||
canSendMic : false,
|
||||
canSendWebcam : false,
|
||||
canShareScreen : false,
|
||||
|
|
@ -11,6 +11,7 @@ const initialState =
|
|||
webcamInProgress : false,
|
||||
audioInProgress : false,
|
||||
screenShareInProgress : false,
|
||||
displayNameInProgress : false,
|
||||
loginEnabled : false,
|
||||
raiseHand : false,
|
||||
raiseHandInProgress : false,
|
||||
|
|
@ -25,23 +26,25 @@ const me = (state = initialState, action) =>
|
|||
{
|
||||
const {
|
||||
peerId,
|
||||
device,
|
||||
loginEnabled
|
||||
} = action.payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
id : peerId,
|
||||
device,
|
||||
loginEnabled
|
||||
};
|
||||
}
|
||||
|
||||
case 'LOGGED_IN':
|
||||
return { ...state, loggedIn: true };
|
||||
{
|
||||
const { flag } = action.payload;
|
||||
|
||||
case 'USER_LOGOUT':
|
||||
return { ...state, loggedIn: false };
|
||||
return { ...state, loggedIn: flag };
|
||||
}
|
||||
|
||||
case 'SET_PICTURE':
|
||||
return { ...state, picture: action.payload.picture };
|
||||
|
||||
case 'SET_MEDIA_CAPABILITIES':
|
||||
{
|
||||
|
|
@ -110,6 +113,13 @@ const me = (state = initialState, action) =>
|
|||
return { ...state, raiseHandInProgress: flag };
|
||||
}
|
||||
|
||||
case 'SET_DISPLAY_NAME_IN_PROGRESS':
|
||||
{
|
||||
const { flag } = action.payload;
|
||||
|
||||
return { ...state, displayNameInProgress: flag };
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
const initialState =
|
||||
{
|
||||
url : null,
|
||||
name : '',
|
||||
state : 'new', // new/connecting/connected/disconnected/closed,
|
||||
locked : false,
|
||||
lockedOut : false,
|
||||
inLobby : false,
|
||||
signInRequired : false,
|
||||
accessCode : '', // access code to the room if locked and joinByAccessCode == true
|
||||
joinByAccessCode : true, // if true: accessCode is a possibility to open the room
|
||||
activeSpeakerId : null,
|
||||
torrentSupport : false,
|
||||
showSettings : false,
|
||||
|
|
@ -14,6 +18,7 @@ const initialState =
|
|||
selectedPeerId : null,
|
||||
spotlights : [],
|
||||
settingsOpen : false,
|
||||
lockDialogOpen : false,
|
||||
joined : false
|
||||
};
|
||||
|
||||
|
|
@ -28,6 +33,13 @@ const room = (state = initialState, action) =>
|
|||
return { ...state, url };
|
||||
}
|
||||
|
||||
case 'SET_ROOM_NAME':
|
||||
{
|
||||
const { name } = action.payload;
|
||||
|
||||
return { ...state, name };
|
||||
}
|
||||
|
||||
case 'SET_ROOM_STATE':
|
||||
{
|
||||
const roomState = action.payload.state;
|
||||
|
|
@ -48,11 +60,41 @@ const room = (state = initialState, action) =>
|
|||
return { ...state, locked: false };
|
||||
}
|
||||
|
||||
case 'SET_ROOM_LOCKED_OUT':
|
||||
case 'SET_IN_LOBBY':
|
||||
{
|
||||
return { ...state, lockedOut: true };
|
||||
const { inLobby } = action.payload;
|
||||
|
||||
return { ...state, inLobby };
|
||||
}
|
||||
|
||||
case 'SET_SIGN_IN_REQUIRED':
|
||||
{
|
||||
const { signInRequired } = action.payload;
|
||||
|
||||
return { ...state, signInRequired };
|
||||
}
|
||||
|
||||
case 'SET_ACCESS_CODE':
|
||||
{
|
||||
const { accessCode } = action.payload;
|
||||
|
||||
return { ...state, accessCode };
|
||||
}
|
||||
|
||||
case 'SET_JOIN_BY_ACCESS_CODE':
|
||||
{
|
||||
const { joinByAccessCode } = action.payload;
|
||||
|
||||
return { ...state, joinByAccessCode };
|
||||
}
|
||||
|
||||
case 'SET_LOCK_DIALOG_OPEN':
|
||||
{
|
||||
const { lockDialogOpen } = action.payload;
|
||||
|
||||
return { ...state, lockDialogOpen };
|
||||
}
|
||||
|
||||
case 'SET_SETTINGS_OPEN':
|
||||
{
|
||||
const { settingsOpen } = action.payload;
|
||||
|
|
@ -74,13 +116,6 @@ const room = (state = initialState, action) =>
|
|||
return { ...state, torrentSupport: supported };
|
||||
}
|
||||
|
||||
case 'TOGGLE_SETTINGS':
|
||||
{
|
||||
const showSettings = !state.showSettings;
|
||||
|
||||
return { ...state, showSettings };
|
||||
}
|
||||
|
||||
case 'TOGGLE_JOINED':
|
||||
{
|
||||
const joined = !state.joined;
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ import room from './room';
|
|||
import me from './me';
|
||||
import producers from './producers';
|
||||
import peers from './peers';
|
||||
import lobbyPeers from './lobbyPeers';
|
||||
import consumers from './consumers';
|
||||
import peerVolumes from './peerVolumes';
|
||||
import notifications from './notifications';
|
||||
import chatmessages from './chatmessages';
|
||||
import chat from './chat';
|
||||
import toolarea from './toolarea';
|
||||
import files from './files';
|
||||
import settings from './settings';
|
||||
|
|
@ -16,10 +17,11 @@ export default combineReducers({
|
|||
me,
|
||||
producers,
|
||||
peers,
|
||||
lobbyPeers,
|
||||
consumers,
|
||||
peerVolumes,
|
||||
notifications,
|
||||
chatmessages,
|
||||
chat,
|
||||
toolarea,
|
||||
files,
|
||||
settings
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
const initialState =
|
||||
{
|
||||
displayName : 'Guest',
|
||||
picture : null,
|
||||
selectedWebcam : null,
|
||||
selectedAudioDevice : null,
|
||||
advancedMode : false,
|
||||
|
|
@ -24,20 +23,11 @@ const settings = (state = initialState, action) =>
|
|||
|
||||
case 'SET_DISPLAY_NAME':
|
||||
{
|
||||
let { displayName } = action.payload;
|
||||
|
||||
// Be ready for undefined displayName (so keep previous one).
|
||||
if (!displayName)
|
||||
displayName = state.displayName;
|
||||
const { displayName } = action.payload;
|
||||
|
||||
return { ...state, displayName };
|
||||
}
|
||||
|
||||
case 'SET_PICTURE':
|
||||
{
|
||||
return { ...state, picture: action.payload.picture };
|
||||
}
|
||||
|
||||
case 'TOGGLE_ADVANCED_MODE':
|
||||
{
|
||||
const advancedMode = !state.advancedMode;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const reduxMiddlewares =
|
|||
thunk
|
||||
];
|
||||
|
||||
if (process.env.NODE_ENV !== 'production')
|
||||
if (process.env.REACT_APP_DEBUG === '*' || process.env.NODE_ENV !== 'production')
|
||||
{
|
||||
const reduxLogger = createLogger(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
{
|
||||
"socket.disconnected": "You are disconnected",
|
||||
"socket.reconnecting": "You are disconnected, attempting to reconnect",
|
||||
"socket.reconnected": "You are reconnected",
|
||||
"socket.requestError": "Error on server request",
|
||||
|
||||
"room.cookieConsent": "This website uses cookies to enhance the user experience",
|
||||
"room.joined": "You have joined the room",
|
||||
"room.cantJoin": "Unable to join the room",
|
||||
"room.youLocked": "You locked the room",
|
||||
"room.cantLock": "Unable to lock the room",
|
||||
"room.youUnLocked": "You unlocked the room",
|
||||
"room.cantUnLock": "Unable to unlock the room",
|
||||
"room.locked": "Room is now locked",
|
||||
"room.unlocked": "Room is now unlocked",
|
||||
"room.newLobbyPeer": "New participant entered the lobby",
|
||||
"room.lobbyPeerLeft": "Participant in lobby left",
|
||||
"room.lobbyPeerChangedDisplayName": "Participant in lobby changed name to {displayName}",
|
||||
"room.lobbyPeerChangedPicture": "Participant in lobby changed picture",
|
||||
"room.setAccessCode": "Access code for room updated",
|
||||
"room.accessCodeOn": "Access code for room is now activated",
|
||||
"room.accessCodeOff": "Access code for room is now deactivated",
|
||||
"room.peerChangedDisplayName": "{oldDisplayName} is now {displayName}",
|
||||
"room.newPeer": "{displayName} joined the room",
|
||||
"room.newFile": "New file available",
|
||||
"room.toggleAdvancedMode": "Toggled advanced mode",
|
||||
"room.setDemocraticView": "Changed layout to democratic view",
|
||||
"room.setFilmStripView": "Changed layout to filmstrip view",
|
||||
"room.loggedIn": "You are logged in",
|
||||
"room.loggedOut": "You are logged out",
|
||||
"room.changedDisplayName": "Your display name changed to {displayName}",
|
||||
"room.changeDisplayNameError": "An error occured while changing your display name",
|
||||
"room.chatError": "Unable to send chat message",
|
||||
"room.aboutToJoin": "You are about to join a meeting",
|
||||
"room.roomId": "Room ID: {roomName}",
|
||||
"room.setYourName": "Set your name for participation, and choose how you want to join:",
|
||||
"room.audioOnly": "Audio only",
|
||||
"room.audioVideo": "Audio and Video",
|
||||
"room.youAreReady": "Ok, you are ready",
|
||||
"room.emptyRequireLogin": "The room is empty! You can Log In to start the meeting or wait until the host joins",
|
||||
"room.locketWait": "The room is locked - hang on until somebody lets you in ...",
|
||||
"room.lobbyAdministration": "Lobby administration",
|
||||
"room.peersInLobby": "Participants in Lobby",
|
||||
"room.lobbyEmpty": "There are currently no one in the lobby",
|
||||
"room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}",
|
||||
"room.me": "Me",
|
||||
"room.spotlights": "Participants in Spotlight",
|
||||
"room.passive": "Passive Participants",
|
||||
"room.videoPaused": "This video is paused",
|
||||
|
||||
"tooltip.login": "Log in",
|
||||
"tooltip.logout": "Log out",
|
||||
"tooltip.admitFromLobby": "Admit from lobby",
|
||||
"tooltip.lockRoom": "Lock room",
|
||||
"tooltip.unLockRoom": "Unlock room",
|
||||
"tooltip.enterFullscreen": "Enter fullscreen",
|
||||
"tooltip.leaveFullscreen": "Leave fullscreen",
|
||||
"tooltip.lobby": "Show lobby",
|
||||
"tooltip.settings": "Show settings",
|
||||
|
||||
"label.yourName": "Your name",
|
||||
"label.newWindow": "New window",
|
||||
"label.fullscreen": "Fullscreen",
|
||||
"label.openDrawer": "Open drawer",
|
||||
"label.leave": "Leave",
|
||||
"label.chatInput": "Enter chat message...",
|
||||
"label.chat": "Chat",
|
||||
"label.filesharing": "File sharing",
|
||||
"label.participants": "Participants",
|
||||
"label.shareFile": "Share file",
|
||||
"label.fileSharingUnsupported": "File sharing not supported",
|
||||
"label.unknown": "Unknown",
|
||||
"label.democratic": "Democratic view",
|
||||
"label.filmstrip": "Filmstrip view",
|
||||
"label.low": "Low",
|
||||
"label.medium": "Medium",
|
||||
"label.high": "High (HD)",
|
||||
"label.veryHigh": "Very high (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Close",
|
||||
|
||||
"settings.settings": "Settings",
|
||||
"settings.camera": "Camera",
|
||||
"settings.selectCamera": "Select video device",
|
||||
"settings.cantSelectCamera": "Unable to select video device",
|
||||
"settings.audio": "Audio device",
|
||||
"settings.selectAudio": "Select audio device",
|
||||
"settings.cantSelectAudio": "Unable to select audio device",
|
||||
"settings.resolution": "Select your video resolution",
|
||||
"settings.layout": "Room layout",
|
||||
"settings.selectRoomLayout": "Select room layout",
|
||||
"settings.advancedMode": "Advanced mode",
|
||||
|
||||
"filesharing.saveFileError": "Unable to save file",
|
||||
"filesharing.startingFileShare": "Attempting to share file",
|
||||
"filesharing.successfulFileShare": "File successfully shared",
|
||||
"filesharing.unableToShare": "Unable to share file",
|
||||
"filesharing.error": "There was a filesharing error",
|
||||
"filesharing.finished": "File finished downloading",
|
||||
"filesharing.save": "Save",
|
||||
"filesharing.sharedFile": "{displayName} shared a file",
|
||||
"filesharing.download": "Download",
|
||||
"filesharing.missingSeeds": "If this process takes a long time, there might not be anyone seeding this torrent. Try asking someone to reupload the file that you want.",
|
||||
|
||||
"devices.devicesChanged": "Your devices changed, configure your devices in the settings dialog",
|
||||
|
||||
"device.audioUnsupported": "Audio unsupported",
|
||||
"device.activateAudio": "Activate audio",
|
||||
"device.muteAudio": "Mute audio",
|
||||
"device.unMuteAudio": "Unmute audio",
|
||||
|
||||
"device.videoUnsupported": "Video unsupported",
|
||||
"device.startVideo": "Start video",
|
||||
"device.stopVideo": "Stop video",
|
||||
|
||||
"device.screenSharingUnsupported": "Screen sharing not supported",
|
||||
"device.startScreenSharing": "Start screen sharing",
|
||||
"device.stopScreenSharing": "Stop screen sharing",
|
||||
|
||||
"devices.microphoneDisconnected": "Microphone disconnected",
|
||||
"devices.microphoneError": "An error occured while accessing your microphone",
|
||||
"devices.microPhoneMute": "Muted your microphone",
|
||||
"devices.micophoneUnMute": "Unmuted your microphone",
|
||||
"devices.microphoneEnable": "Enabled your microphone",
|
||||
"devices.microphoneMuteError": "Unable to mute your microphone",
|
||||
"devices.microphoneUnMuteError": "Unable to unmute your microphone",
|
||||
|
||||
"devices.screenSharingDisconnected" : "Screen sharing disconnected",
|
||||
"devices.screenSharingError": "An error occured while accessing your screen",
|
||||
|
||||
"devices.cameraDisconnected": "Camera disconnected",
|
||||
"devices.cameraError": "An error occured while accessing your camera"
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
{
|
||||
"socket.disconnected": "Du er frakoblet",
|
||||
"socket.reconnecting": "Du er frakoblet, forsøker å koble til på nytt",
|
||||
"socket.reconnected": "Du er koblet til igjen",
|
||||
"socket.requestError": "Feil på server melding",
|
||||
|
||||
"room.cookieConsent": "Denne siden bruker cookies for å forbedre brukeropplevelsen",
|
||||
"room.joined": "Du ble med i møtet",
|
||||
"room.cantJoin": "Kunne ikke bli med i møtet",
|
||||
"room.youLocked": "Du låste møtet",
|
||||
"room.cantLock": "Klarte ikke å låse møtet",
|
||||
"room.youUnLocked": "Du låste opp møtet",
|
||||
"room.cantUnLock": "Klarte ikke å låse opp møtet",
|
||||
"room.locked": "Møtet er låst",
|
||||
"room.unlocked": "Møtet er låst opp",
|
||||
"room.newLobbyPeer": "Ny deltaker i lobbyen",
|
||||
"room.lobbyPeerLeft": "Deltaker i lobbyen forsvant",
|
||||
"room.lobbyPeerChangedDisplayName": "Deltaker i lobbyen endret navn til {displayName}",
|
||||
"room.lobbyPeerChangedPicture": "Deltaker i lobbyen endret bilde",
|
||||
"room.setAccessCode": "Tilgangskode for møtet er oppdatert",
|
||||
"room.accessCodeOn": "Tilgangskode for møtet er aktivert",
|
||||
"room.accessCodeOff": "Tilgangskode for møtet er deaktivert",
|
||||
"room.peerChangedDisplayName": "{oldDisplayName} heter nå {displayName}",
|
||||
"room.newPeer": "{displayName} ble med i møtet",
|
||||
"room.newFile": "Ny fil tilgjengelig",
|
||||
"room.toggleAdvancedMode": "Aktiver avansert modus",
|
||||
"room.setDemocraticView": "Endret layout til demokratisk",
|
||||
"room.setFilmStripView": "Endret layout til filmstripe",
|
||||
"room.loggedIn": "Du er logget inn",
|
||||
"room.loggedOut": "Du er logget ut",
|
||||
"room.changedDisplayName": "Navnet ditt er nå {displayName}",
|
||||
"room.changeDisplayNameError": "Det skjedde en feil ved endring av navnet ditt",
|
||||
"room.chatError": "Klarte ikke sende melding",
|
||||
"room.aboutToJoin": "Du er i ferd med å bli med i et møte",
|
||||
"room.roomId": "Møte ID: {roomName}",
|
||||
"room.setYourName": "Skriv inn navnet ditt, og velg hvordan du vil bli med i møtet",
|
||||
"room.audioOnly": "Kun lyd",
|
||||
"room.audioVideo": "Lyd og bilde",
|
||||
"room.youAreReady": "Ok, du er klar",
|
||||
"room.emptyRequireLogin": "Møtet er tomt. Du kan logge inn for å starte møtet, eller vente til verten kommer",
|
||||
"room.locketWait": "Møtet er låst, vent til noen slipper deg inn",
|
||||
"room.lobbyAdministration": "Lobby administrasjon",
|
||||
"room.peersInLobby": "Deltakere i lobbyen",
|
||||
"room.lobbyEmpty": "Det er for øyeblikket ingen deltakere i lobbyen",
|
||||
"room.hiddenPeers": "{hiddenPeersCount, plural, one {deltaker} other {deltakere}}",
|
||||
"room.me": "Meg",
|
||||
"room.spotlights": "Deltakere i fokus",
|
||||
"room.passive": "Passive deltakere",
|
||||
"room.videoPaused": "Denne videoen er inaktiv",
|
||||
|
||||
"tooltip.login": "Logg in",
|
||||
"tooltip.logout": "Logg ut",
|
||||
"tooltip.admitFromLobby": "Slipp inn fra lobby",
|
||||
"tooltip.lockRoom": "Lås møtet",
|
||||
"tooltip.unLockRoom": "Lås opp møtet",
|
||||
"tooltip.enterFullscreen": "Gå til fullskjerm",
|
||||
"tooltip.leaveFullscreen": "Forlat fullskjerm",
|
||||
"tooltip.lobby": "Vis lobby",
|
||||
"tooltip.settings": "Vis innstillinger",
|
||||
|
||||
"label.yourName": "Ditt navn",
|
||||
"label.newWindow": "Flytt til separat vindu",
|
||||
"label.fullscreen": "Fullskjerm",
|
||||
"label.openDrawer": "Åpne meny",
|
||||
"label.leave": "Avslutt",
|
||||
"label.chatInput": "Skriv melding...",
|
||||
"label.chat": "Chat",
|
||||
"label.filesharing": "Fildeling",
|
||||
"label.participants": "Deltakere",
|
||||
"label.shareFile": "Del fil",
|
||||
"label.fileSharingUnsupported": "Fildeling ikke støttet",
|
||||
"label.unknown": "Ukjent",
|
||||
"label.democratic": "Demokratisk",
|
||||
"label.filmstrip": "Filmstripe",
|
||||
"label.low": "Lav",
|
||||
"label.medium": "Medium",
|
||||
"label.high": "Høy (HD)",
|
||||
"label.veryHigh": "Veldig høy (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Lukk",
|
||||
|
||||
"settings.settings": "Innstillinger",
|
||||
"settings.camera": "Kamera",
|
||||
"settings.selectCamera": "Velg videoenhet",
|
||||
"settings.cantSelectCamera": "Kan ikke velge videoenhet",
|
||||
"settings.audio": "Lydenhet",
|
||||
"settings.selectAudio": "Velg lydenhet",
|
||||
"settings.cantSelectAudio": "Kan ikke velge lydenhet",
|
||||
"settings.resolution": "Velg oppløsning",
|
||||
"settings.layout": "Møtelayout",
|
||||
"settings.selectRoomLayout": "Velg møtelayout",
|
||||
"settings.advancedMode": "Avansert modus",
|
||||
|
||||
"filesharing.saveFileError": "Klarte ikke å lagre fil",
|
||||
"filesharing.startingFileShare": "Starter fildeling",
|
||||
"filesharing.successfulFileShare": "Filen ble delt",
|
||||
"filesharing.unableToShare": "Klarte ikke å dele fil",
|
||||
"filesharing.error": "Det skjedde noe feil med fildeling",
|
||||
"filesharing.finished": "Fil ferdig lastet ned",
|
||||
"filesharing.save": "Lagre",
|
||||
"filesharing.sharedFile": "{displayName} delte en fil",
|
||||
"filesharing.download": "Last ned",
|
||||
"filesharing.missingSeeds": "Dersom dette tar lang til mangler det kanskje noen som kan dele denne filen. Prøv å spørre noen om å laste opp filen på nytt.",
|
||||
|
||||
"devices.devicesChanged": "Medieenhetene dine endret seg, du kan konfigurere enheter i innstillinger",
|
||||
|
||||
"device.audioUnsupported": "Lyd ikke støttet",
|
||||
"device.activateAudio": "Aktiver lyd",
|
||||
"device.muteAudio": "Demp lyd",
|
||||
"device.unMuteAudio": "Aktiver lyd",
|
||||
|
||||
"device.videoUnsupported": "Video ikke støttet",
|
||||
"device.startVideo": "Start video",
|
||||
"device.stopVideo": "Stopp video",
|
||||
|
||||
"device.screenSharingUnsupported": "Skjermdeling ikke støttet",
|
||||
"device.startScreenSharing": "Start skjermdeling",
|
||||
"device.stopScreenSharing": "Stopp skjermdeling",
|
||||
|
||||
"devices.microphoneDisconnected": "Mikrofon koblet fra",
|
||||
"devices.microphoneError": "Det skjedde noe feil med mikrofonen din",
|
||||
"devices.microPhoneMute": "Dempet mikrofonen",
|
||||
"devices.micophoneUnMute": "Aktiverte mikrofonen",
|
||||
"devices.microphoneEnable": "Aktiverte mikrofonen",
|
||||
"devices.microphoneMuteError": "Klarte ikke å dempe mikrofonen",
|
||||
"devices.microphoneUnMuteError": "Klarte ikke å aktivere mikrofonen",
|
||||
|
||||
"devices.screenSharingDisconnected" : "Skjermdelingen forsvant",
|
||||
"devices.screenSharingError": "Det skjedde noe feil med skjermdelingen din",
|
||||
|
||||
"devices.cameraDisconnected": "Kamera koblet fra",
|
||||
"devices.cameraError": "Det skjedde noe feil med kameraet ditt"
|
||||
}
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
module.exports =
|
||||
{
|
||||
env:
|
||||
{
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true
|
||||
},
|
||||
extends:
|
||||
[
|
||||
'eslint:recommended'
|
||||
],
|
||||
settings: {},
|
||||
parserOptions:
|
||||
{
|
||||
ecmaVersion: 6,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures:
|
||||
{
|
||||
impliedStrict: 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, 90,
|
||||
{
|
||||
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, 'never' ],
|
||||
'space-in-parens': [ 2, 'never' ],
|
||||
'spaced-comment': [ 2, 'always' ],
|
||||
'strict': 0,
|
||||
'valid-typeof': 2,
|
||||
'yoda': 2
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
{
|
||||
"env":
|
||||
{
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends":
|
||||
[
|
||||
"eslint:recommended"
|
||||
],
|
||||
"settings": {},
|
||||
"parserOptions":
|
||||
{
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures":
|
||||
{
|
||||
"impliedStrict": 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, 90,
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"comments": 90,
|
||||
"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": [ 1, { "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,
|
||||
"yoda": 2
|
||||
}
|
||||
}
|
||||
|
|
@ -3,26 +3,27 @@ const os = require('os');
|
|||
module.exports =
|
||||
{
|
||||
// oAuth2 conf
|
||||
auth :
|
||||
/* auth :
|
||||
{
|
||||
/*
|
||||
The issuer URL for OpenID Connect discovery
|
||||
The OpenID Provider Configuration Document
|
||||
could be discovered on:
|
||||
issuerURL + '/.well-known/openid-configuration'
|
||||
*/
|
||||
issuerURL : 'https://example.com',
|
||||
clientOptions :
|
||||
{
|
||||
|
||||
// The issuer URL for OpenID Connect discovery
|
||||
// The OpenID Provider Configuration Document
|
||||
// could be discovered on:
|
||||
// issuerURL + '/.well-known/openid-configuration'
|
||||
|
||||
// issuerURL : 'https://example.com',
|
||||
// clientOptions :
|
||||
// {
|
||||
client_id : '',
|
||||
client_secret : '',
|
||||
scope : 'openid email profile',
|
||||
// where client.example.com is your multiparty meeting server
|
||||
redirect_uri : 'https://client.example.com/auth/callback'
|
||||
}
|
||||
},
|
||||
},*/
|
||||
// session cookie secret
|
||||
cookieSecret : 'T0P-S3cR3t_cook!e',
|
||||
cookieName : 'multiparty-meeting.sid',
|
||||
tls :
|
||||
{
|
||||
cert : `${__dirname}/../certs/mediasoup-demo.localhost.cert.pem`,
|
||||
|
|
@ -33,6 +34,16 @@ module.exports =
|
|||
// Any http request is redirected to https.
|
||||
// Listening port for http server.
|
||||
listeningRedirectPort : 80,
|
||||
// If this is set to true, only signed-in users will be able
|
||||
// to join a room directly. Non-signed-in users (guests) will
|
||||
// always be put in the lobby regardless of room lock status.
|
||||
// If false, there is no difference between guests and signed-in
|
||||
// users when joining.
|
||||
requireSignInToAccess : true,
|
||||
// This flag has no effect when requireSignInToAccess is false
|
||||
// When truthy, the room will be open to all users when the first
|
||||
// authenticated user has already joined the room.
|
||||
activateOnHostJoin : true,
|
||||
// Mediasoup settings
|
||||
mediasoup :
|
||||
{
|
||||
|
|
@ -84,6 +95,7 @@ module.exports =
|
|||
{
|
||||
listenIps :
|
||||
[
|
||||
// change ip to your servers IP address!
|
||||
{ ip: '1.2.3.4', announcedIp: null }
|
||||
],
|
||||
maxIncomingBitrate : 1500000,
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
* Tasks:
|
||||
*
|
||||
* gulp lint
|
||||
* Checks source code
|
||||
*
|
||||
* gulp watch
|
||||
* Observes changes in the code
|
||||
*
|
||||
* gulp
|
||||
* Invokes both `lint` and `watch` tasks
|
||||
*/
|
||||
|
||||
const gulp = require('gulp');
|
||||
const plumber = require('gulp-plumber');
|
||||
const eslint = require('gulp-eslint');
|
||||
|
||||
const LINTING_FILES =
|
||||
[
|
||||
'gulpfile.js',
|
||||
'server.js',
|
||||
'config/config.example.js',
|
||||
'lib/**/*.js'
|
||||
];
|
||||
|
||||
gulp.task('lint', () =>
|
||||
{
|
||||
|
||||
return gulp.src(LINTING_FILES)
|
||||
.pipe(plumber())
|
||||
.pipe(eslint())
|
||||
.pipe(eslint.format());
|
||||
});
|
||||
|
||||
gulp.task('lint-fix', function()
|
||||
{
|
||||
return gulp.src(LINTING_FILES)
|
||||
.pipe(plumber())
|
||||
.pipe(eslint({ fix: true }))
|
||||
.pipe(eslint.format())
|
||||
.pipe(gulp.dest((file) => file.base));
|
||||
});
|
||||
|
||||
gulp.task('default', gulp.series('lint'));
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const headers = {
|
||||
'access-control-allow-origin': '*',
|
||||
'access-control-allow-methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'access-control-allow-headers': 'content-type, accept',
|
||||
'access-control-max-age': 10,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
exports.prepareResponse = (req, cb) =>
|
||||
{
|
||||
let data = '';
|
||||
|
||||
req.on('data', (chunk) =>
|
||||
{
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
req.on('end', () =>
|
||||
{
|
||||
cb(data);
|
||||
});
|
||||
};
|
||||
|
||||
exports.respond = (res, data, status) =>
|
||||
{
|
||||
status = status || 200;
|
||||
res.writeHead(status, headers);
|
||||
res.end(data);
|
||||
};
|
||||
|
||||
exports.send404 = (res) =>
|
||||
{
|
||||
exports.respond(res, 'Not Found', 404);
|
||||
};
|
||||
|
||||
exports.redirector = (res, loc, status) =>
|
||||
{
|
||||
status = status || 302;
|
||||
res.writeHead(status, { Location: loc });
|
||||
res.end();
|
||||
};
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
exports.loginHelper = function(data)
|
||||
{
|
||||
const html = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<title>Multiparty Meeting</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
let data = ${JSON.stringify(data)};
|
||||
|
||||
window.opener.CLIENT.receiveLoginChildWindow(data);
|
||||
|
||||
window.close();
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
exports.logoutHelper = function()
|
||||
{
|
||||
const html = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<title>Multiparty Meeting</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
window.opener.CLIENT.receiveLogoutChildWindow();
|
||||
|
||||
window.close();
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
return html;
|
||||
};
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
const EventEmitter = require('events').EventEmitter;
|
||||
const Logger = require('./Logger');
|
||||
|
||||
const logger = new Logger('Lobby');
|
||||
|
||||
class Lobby extends EventEmitter
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
logger.info('constructor()');
|
||||
|
||||
super();
|
||||
|
||||
// Closed flag.
|
||||
this._closed = false;
|
||||
|
||||
this._peers = new Map();
|
||||
}
|
||||
|
||||
close()
|
||||
{
|
||||
logger.info('close()');
|
||||
|
||||
this._closed = true;
|
||||
|
||||
this._peers.forEach((peer) =>
|
||||
{
|
||||
if (!peer.closed)
|
||||
peer.close();
|
||||
});
|
||||
|
||||
this._peers.clear();
|
||||
}
|
||||
|
||||
checkEmpty()
|
||||
{
|
||||
logger.info('checkEmpty()');
|
||||
|
||||
return this._peers.size === 0;
|
||||
}
|
||||
|
||||
peerList()
|
||||
{
|
||||
logger.info('peerList()');
|
||||
|
||||
return Array.from(this._peers.values()).map((peer) =>
|
||||
({
|
||||
peerId : peer.id,
|
||||
displayName : peer.displayName
|
||||
}));
|
||||
}
|
||||
|
||||
hasPeer(peerId)
|
||||
{
|
||||
return this._peers.has(peerId);
|
||||
}
|
||||
|
||||
promoteAllPeers()
|
||||
{
|
||||
logger.info('promoteAllPeers()');
|
||||
|
||||
this._peers.forEach((peer) =>
|
||||
{
|
||||
if (peer.socket)
|
||||
this.promotePeer(peer.id);
|
||||
});
|
||||
}
|
||||
|
||||
promotePeer(peerId)
|
||||
{
|
||||
logger.info('promotePeer() [peer:"%s"]', peerId);
|
||||
|
||||
const peer = this._peers.get(peerId);
|
||||
|
||||
if (peer)
|
||||
{
|
||||
peer.socket.removeListener('request', peer.socketRequestHandler);
|
||||
peer.removeListener('authenticationChanged', peer.authenticationHandler);
|
||||
peer.removeListener('close', peer.closeHandler);
|
||||
|
||||
peer.socketRequestHandler = null;
|
||||
peer.authenticationHandler = null;
|
||||
peer.closeHandler = null;
|
||||
|
||||
this.emit('promotePeer', peer);
|
||||
this._peers.delete(peerId);
|
||||
}
|
||||
}
|
||||
|
||||
parkPeer(peer)
|
||||
{
|
||||
logger.info('parkPeer() [peer:"%s"]', peer.id);
|
||||
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
peer.socketRequestHandler = (request, cb) =>
|
||||
{
|
||||
logger.debug(
|
||||
'Peer "request" event [method:"%s", peer:"%s"]',
|
||||
request.method, peer.id);
|
||||
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
this._handleSocketRequest(peer, request, cb)
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('request failed [error:"%o"]', error);
|
||||
|
||||
cb(error);
|
||||
});
|
||||
};
|
||||
|
||||
peer.authenticationHandler = () =>
|
||||
{
|
||||
logger.info('parkPeer() | authenticationChange [peer:"%s"]', peer.id);
|
||||
|
||||
if (peer.authenticated)
|
||||
{
|
||||
this.emit('changeDisplayName', peer);
|
||||
this.emit('changePicture', peer);
|
||||
this.emit('peerAuthenticated', peer);
|
||||
}
|
||||
};
|
||||
|
||||
peer.closeHandler = () =>
|
||||
{
|
||||
logger.debug('Peer "close" event [peer:"%s"]', peer.id);
|
||||
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
this.emit('peerClosed', peer);
|
||||
|
||||
this._peers.delete(peer.id);
|
||||
|
||||
if (this.checkEmpty())
|
||||
this.emit('lobbyEmpty');
|
||||
};
|
||||
|
||||
this._notification(peer.socket, 'enteredLobby');
|
||||
|
||||
this._peers.set(peer.id, peer);
|
||||
|
||||
peer.on('authenticationChanged', peer.authenticationHandler);
|
||||
|
||||
peer.socket.on('request', peer.socketRequestHandler);
|
||||
|
||||
peer.on('close', peer.closeHandler);
|
||||
}
|
||||
|
||||
async _handleSocketRequest(peer, request, cb)
|
||||
{
|
||||
logger.debug(
|
||||
'_handleSocketRequest [peer:"%s"], [request:"%s"]',
|
||||
peer.id,
|
||||
request.method
|
||||
);
|
||||
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
switch (request.method)
|
||||
{
|
||||
case 'changeDisplayName':
|
||||
{
|
||||
const { displayName } = request.data;
|
||||
|
||||
peer.displayName = displayName;
|
||||
|
||||
this.emit('changeDisplayName', peer);
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
case 'changePicture':
|
||||
{
|
||||
const { picture } = request.data;
|
||||
|
||||
peer.picture = picture;
|
||||
|
||||
this.emit('changePicture', peer);
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_notification(socket, method, data = {}, broadcast = false)
|
||||
{
|
||||
if (broadcast)
|
||||
{
|
||||
socket.broadcast.to(this._roomId).emit(
|
||||
'notification', { method, data }
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
socket.emit('notification', { method, data });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Lobby;
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
const debug = require('debug');
|
||||
|
||||
const APP_NAME = 'multiparty-meeting-server';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,329 @@
|
|||
const EventEmitter = require('events').EventEmitter;
|
||||
const Logger = require('./Logger');
|
||||
|
||||
const logger = new Logger('Peer');
|
||||
|
||||
class Peer extends EventEmitter
|
||||
{
|
||||
constructor({ id, socket })
|
||||
{
|
||||
logger.info('constructor() [id:"%s", socket:"%s"]', id, socket.id);
|
||||
super();
|
||||
|
||||
this._id = id;
|
||||
|
||||
this._authId = null;
|
||||
|
||||
this._socket = socket;
|
||||
|
||||
this._closed = false;
|
||||
|
||||
this._joined = false;
|
||||
|
||||
this._inLobby = false;
|
||||
|
||||
this._authenticated = false;
|
||||
|
||||
this._displayName = false;
|
||||
|
||||
this._picture = null;
|
||||
|
||||
this._email = null;
|
||||
|
||||
this._rtpCapabilities = null;
|
||||
|
||||
this._raisedHand = false;
|
||||
|
||||
this._transports = new Map();
|
||||
|
||||
this._producers = new Map();
|
||||
|
||||
this._consumers = new Map();
|
||||
|
||||
this._checkAuthentication();
|
||||
|
||||
this._handlePeer();
|
||||
}
|
||||
|
||||
close()
|
||||
{
|
||||
logger.info('close()');
|
||||
|
||||
this._closed = true;
|
||||
|
||||
// Iterate and close all mediasoup Transport associated to this Peer, so all
|
||||
// its Producers and Consumers will also be closed.
|
||||
this.transports.forEach((transport) =>
|
||||
{
|
||||
transport.close();
|
||||
});
|
||||
|
||||
if (this._socket)
|
||||
this._socket.disconnect(true);
|
||||
|
||||
this.emit('close');
|
||||
}
|
||||
|
||||
_handlePeer()
|
||||
{
|
||||
this.socket.use((packet, next) =>
|
||||
{
|
||||
this._checkAuthentication();
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
this.socket.on('disconnect', () =>
|
||||
{
|
||||
if (this.closed)
|
||||
return;
|
||||
|
||||
logger.debug('"disconnect" event [id:%s]', this.id);
|
||||
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
_checkAuthentication()
|
||||
{
|
||||
if (
|
||||
Boolean(this.socket.handshake.session.passport) &&
|
||||
Boolean(this.socket.handshake.session.passport.user)
|
||||
)
|
||||
{
|
||||
const {
|
||||
id,
|
||||
displayName,
|
||||
picture,
|
||||
email
|
||||
} = this.socket.handshake.session.passport.user;
|
||||
|
||||
id && (this.authId = id);
|
||||
displayName && (this.displayName = displayName);
|
||||
picture && (this.picture = picture);
|
||||
email && (this.email = email);
|
||||
|
||||
this.authenticated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.authenticated = false;
|
||||
}
|
||||
}
|
||||
|
||||
get id()
|
||||
{
|
||||
return this._id;
|
||||
}
|
||||
|
||||
set id(id)
|
||||
{
|
||||
this._id = id;
|
||||
}
|
||||
|
||||
get authId()
|
||||
{
|
||||
return this._authId;
|
||||
}
|
||||
|
||||
set authId(authId)
|
||||
{
|
||||
this._authId = authId;
|
||||
}
|
||||
|
||||
get socket()
|
||||
{
|
||||
return this._socket;
|
||||
}
|
||||
|
||||
set socket(socket)
|
||||
{
|
||||
this._socket = socket;
|
||||
}
|
||||
|
||||
get closed()
|
||||
{
|
||||
return this._closed;
|
||||
}
|
||||
|
||||
get joined()
|
||||
{
|
||||
return this._joined;
|
||||
}
|
||||
|
||||
set joined(joined)
|
||||
{
|
||||
this._joined = joined;
|
||||
}
|
||||
|
||||
get inLobby()
|
||||
{
|
||||
return this._inLobby;
|
||||
}
|
||||
|
||||
set inLobby(inLobby)
|
||||
{
|
||||
this._inLobby = inLobby;
|
||||
}
|
||||
|
||||
get authenticated()
|
||||
{
|
||||
return this._authenticated;
|
||||
}
|
||||
|
||||
set authenticated(authenticated)
|
||||
{
|
||||
if (authenticated !== this._authenticated)
|
||||
{
|
||||
const oldAuthenticated = this._authenticated;
|
||||
|
||||
this._authenticated = authenticated;
|
||||
|
||||
this.emit('authenticationChanged', { oldAuthenticated });
|
||||
}
|
||||
}
|
||||
|
||||
get displayName()
|
||||
{
|
||||
return this._displayName;
|
||||
}
|
||||
|
||||
set displayName(displayName)
|
||||
{
|
||||
if (displayName !== this._displayName)
|
||||
{
|
||||
const oldDisplayName = this._displayName;
|
||||
|
||||
this._displayName = displayName;
|
||||
|
||||
this.emit('displayNameChanged', { oldDisplayName });
|
||||
}
|
||||
}
|
||||
|
||||
get picture()
|
||||
{
|
||||
return this._picture;
|
||||
}
|
||||
|
||||
set picture(picture)
|
||||
{
|
||||
if (picture !== this._picture)
|
||||
{
|
||||
const oldPicture = this._picture;
|
||||
|
||||
this._picture = picture;
|
||||
|
||||
this.emit('pictureChanged', { oldPicture });
|
||||
}
|
||||
}
|
||||
|
||||
get email()
|
||||
{
|
||||
return this._email;
|
||||
}
|
||||
|
||||
set email(email)
|
||||
{
|
||||
this._email = email;
|
||||
}
|
||||
|
||||
get rtpCapabilities()
|
||||
{
|
||||
return this._rtpCapabilities;
|
||||
}
|
||||
|
||||
set rtpCapabilities(rtpCapabilities)
|
||||
{
|
||||
this._rtpCapabilities = rtpCapabilities;
|
||||
}
|
||||
|
||||
get raisedHand()
|
||||
{
|
||||
return this._raisedHand;
|
||||
}
|
||||
|
||||
set raisedHand(raisedHand)
|
||||
{
|
||||
this._raisedHand = raisedHand;
|
||||
}
|
||||
|
||||
get transports()
|
||||
{
|
||||
return this._transports;
|
||||
}
|
||||
|
||||
get producers()
|
||||
{
|
||||
return this._producers;
|
||||
}
|
||||
|
||||
get consumers()
|
||||
{
|
||||
return this._consumers;
|
||||
}
|
||||
|
||||
addTransport(id, transport)
|
||||
{
|
||||
this.transports.set(id, transport);
|
||||
}
|
||||
|
||||
getTransport(id)
|
||||
{
|
||||
return this.transports.get(id);
|
||||
}
|
||||
|
||||
getConsumerTransport()
|
||||
{
|
||||
return Array.from(this.transports.values())
|
||||
.find((t) => t.appData.consuming);
|
||||
}
|
||||
|
||||
removeTransport(id)
|
||||
{
|
||||
this.transports.delete(id);
|
||||
}
|
||||
|
||||
addProducer(id, producer)
|
||||
{
|
||||
this.producers.set(id, producer);
|
||||
}
|
||||
|
||||
getProducer(id)
|
||||
{
|
||||
return this.producers.get(id);
|
||||
}
|
||||
|
||||
removeProducer(id)
|
||||
{
|
||||
this.producers.delete(id);
|
||||
}
|
||||
|
||||
addConsumer(id, consumer)
|
||||
{
|
||||
this.consumers.set(id, consumer);
|
||||
}
|
||||
|
||||
getConsumer(id)
|
||||
{
|
||||
return this.consumers.get(id);
|
||||
}
|
||||
|
||||
removeConsumer(id)
|
||||
{
|
||||
this.consumers.delete(id);
|
||||
}
|
||||
|
||||
get peerInfo()
|
||||
{
|
||||
const peerInfo =
|
||||
{
|
||||
id : this.id,
|
||||
displayName : this.displayName,
|
||||
picture : this.picture
|
||||
};
|
||||
|
||||
return peerInfo;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Peer;
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,380 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const STATS_INTERVAL = 4000; // TODO
|
||||
|
||||
function homer(server)
|
||||
{
|
||||
if (!process.env.MEDIASOUP_HOMER_OUTPUT)
|
||||
throw new Error('MEDIASOUP_HOMER_OUTPUT env not set');
|
||||
|
||||
server.on('newroom', (room) =>
|
||||
{
|
||||
const fileName =
|
||||
path.join(process.env.MEDIASOUP_HOMER_OUTPUT);
|
||||
|
||||
const stream = fs.createWriteStream(fileName, { flags: 'a' });
|
||||
|
||||
emit(
|
||||
{
|
||||
event : 'server.newroom',
|
||||
roomId : room.id,
|
||||
rtpCapabilities : room.rtpCapabilities
|
||||
},
|
||||
stream);
|
||||
|
||||
handleRoom(room, stream);
|
||||
});
|
||||
}
|
||||
|
||||
function handleRoom(room, stream)
|
||||
{
|
||||
const baseEvent =
|
||||
{
|
||||
roomId : room.id
|
||||
};
|
||||
|
||||
room.on('close', () =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'room.close'
|
||||
}),
|
||||
stream);
|
||||
|
||||
stream.end();
|
||||
});
|
||||
|
||||
room.on('newpeer', (peer) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'room.newpeer',
|
||||
peerId : peer.id,
|
||||
rtpCapabilities : peer.rtpCapabilities
|
||||
}),
|
||||
stream);
|
||||
|
||||
handlePeer(peer, baseEvent, stream);
|
||||
});
|
||||
}
|
||||
|
||||
function handlePeer(peer, baseEvent, stream)
|
||||
{
|
||||
baseEvent = Object.assign({}, baseEvent,
|
||||
{
|
||||
peerId : peer.id
|
||||
});
|
||||
|
||||
peer.on('close', (originator) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'peer.close',
|
||||
originator : originator
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
|
||||
peer.on('newtransport', (transport) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'peer.newtransport',
|
||||
transportId : transport.id,
|
||||
direction : transport.direction,
|
||||
iceLocalCandidates : transport.iceLocalCandidates
|
||||
}),
|
||||
stream);
|
||||
|
||||
handleTransport(transport, baseEvent, stream);
|
||||
});
|
||||
|
||||
peer.on('newproducer', (producer) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'peer.newproducer',
|
||||
producerId : producer.id,
|
||||
kind : producer.kind,
|
||||
transportId : producer.transport.id,
|
||||
rtpParameters : producer.rtpParameters
|
||||
}),
|
||||
stream);
|
||||
|
||||
handleProducer(producer, baseEvent, stream);
|
||||
});
|
||||
|
||||
peer.on('newconsumer', (consumer) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'peer.newconsumer',
|
||||
consumerId : consumer.id,
|
||||
kind : consumer.kind,
|
||||
sourceId : consumer.source.id,
|
||||
rtpParameters : consumer.rtpParameters
|
||||
}),
|
||||
stream);
|
||||
|
||||
handleConsumer(consumer, baseEvent, stream);
|
||||
});
|
||||
|
||||
// Must also handle existing Consumers at the time the Peer was created.
|
||||
for (const consumer of peer.consumers)
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'peer.newconsumer',
|
||||
consumerId : consumer.id,
|
||||
kind : consumer.kind,
|
||||
sourceId : consumer.source.id,
|
||||
rtpParameters : consumer.rtpParameters
|
||||
}),
|
||||
stream);
|
||||
|
||||
handleConsumer(consumer, baseEvent, stream);
|
||||
}
|
||||
}
|
||||
|
||||
function handleTransport(transport, baseEvent, stream)
|
||||
{
|
||||
baseEvent = Object.assign({}, baseEvent,
|
||||
{
|
||||
transportId : transport.id
|
||||
});
|
||||
|
||||
const statsInterval = setInterval(() =>
|
||||
{
|
||||
if (typeof transport.getStats === 'function')
|
||||
{
|
||||
transport.getStats()
|
||||
.then((stats) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'transport.stats',
|
||||
stats : stats
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
}
|
||||
}, STATS_INTERVAL);
|
||||
|
||||
transport.on('close', (originator) =>
|
||||
{
|
||||
clearInterval(statsInterval);
|
||||
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'transport.close',
|
||||
originator : originator
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
|
||||
transport.on('iceselectedtuplechange', (iceSelectedTuple) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'transport.iceselectedtuplechange',
|
||||
iceSelectedTuple : iceSelectedTuple
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
|
||||
transport.on('icestatechange', (iceState) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'transport.icestatechange',
|
||||
iceState : iceState
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
|
||||
transport.on('dtlsstatechange', (dtlsState) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'transport.dtlsstatechange',
|
||||
dtlsState : dtlsState
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
}
|
||||
|
||||
function handleProducer(producer, baseEvent, stream)
|
||||
{
|
||||
baseEvent = Object.assign({}, baseEvent,
|
||||
{
|
||||
producerId : producer.id
|
||||
});
|
||||
|
||||
const statsInterval = setInterval(() =>
|
||||
{
|
||||
producer.getStats()
|
||||
.then((stats) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'producer.stats',
|
||||
stats : stats
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
}, STATS_INTERVAL);
|
||||
|
||||
producer.on('close', (originator) =>
|
||||
{
|
||||
clearInterval(statsInterval);
|
||||
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'producer.close',
|
||||
originator : originator
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
|
||||
producer.on('pause', (originator) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'producer.pause',
|
||||
originator : originator
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
|
||||
producer.on('resume', (originator) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'producer.resume',
|
||||
originator : originator
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
}
|
||||
|
||||
function handleConsumer(consumer, baseEvent, stream)
|
||||
{
|
||||
baseEvent = Object.assign({}, baseEvent,
|
||||
{
|
||||
consumerId : consumer.id
|
||||
});
|
||||
|
||||
const statsInterval = setInterval(() =>
|
||||
{
|
||||
consumer.getStats()
|
||||
.then((stats) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'consumer.stats',
|
||||
stats : stats
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
}, STATS_INTERVAL);
|
||||
|
||||
consumer.on('close', (originator) =>
|
||||
{
|
||||
clearInterval(statsInterval);
|
||||
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'consumer.close',
|
||||
originator : originator
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
|
||||
consumer.on('handled', () =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'consumer.handled',
|
||||
transportId : consumer.transport.id
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
|
||||
consumer.on('unhandled', () =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'consumer.handled'
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
|
||||
consumer.on('pause', (originator) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'consumer.pause',
|
||||
originator : originator
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
|
||||
consumer.on('resume', (originator) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'consumer.resume',
|
||||
originator : originator
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
|
||||
consumer.on('effectiveprofilechange', (profile) =>
|
||||
{
|
||||
emit(
|
||||
Object.assign({}, baseEvent,
|
||||
{
|
||||
event : 'consumer.effectiveprofilechange',
|
||||
profile : profile
|
||||
}),
|
||||
stream);
|
||||
});
|
||||
}
|
||||
|
||||
function emit(event, stream)
|
||||
{
|
||||
// Add timestamp.
|
||||
event.timestamp = Date.now();
|
||||
|
||||
const line = JSON.stringify(event);
|
||||
|
||||
stream.write(line);
|
||||
stream.write('\n');
|
||||
}
|
||||
|
||||
module.exports = homer;
|
||||
|
|
@ -1,321 +0,0 @@
|
|||
const mediasoup = require('mediasoup');
|
||||
const readline = require('readline');
|
||||
const colors = require('colors/safe');
|
||||
const repl = require('repl');
|
||||
const homer = require('./lib/homer');
|
||||
const config = require('./config/config');
|
||||
|
||||
// mediasoup server.
|
||||
const mediaServer = mediasoup.Server(
|
||||
{
|
||||
numWorkers : null,
|
||||
logLevel : config.mediasoup.logLevel,
|
||||
logTags : config.mediasoup.logTags,
|
||||
rtcIPv4 : config.mediasoup.rtcIPv4,
|
||||
rtcIPv6 : config.mediasoup.rtcIPv6,
|
||||
rtcAnnouncedIPv4 : config.mediasoup.rtcAnnouncedIPv4,
|
||||
rtcAnnouncedIPv6 : config.mediasoup.rtcAnnouncedIPv6,
|
||||
rtcMinPort : config.mediasoup.rtcMinPort,
|
||||
rtcMaxPort : config.mediasoup.rtcMaxPort
|
||||
});
|
||||
|
||||
// Do Homer stuff.
|
||||
if (process.env.MEDIASOUP_HOMER_OUTPUT)
|
||||
homer(mediaServer);
|
||||
|
||||
global.SERVER = mediaServer;
|
||||
|
||||
mediaServer.on('newroom', (room) =>
|
||||
{
|
||||
global.ROOM = room;
|
||||
|
||||
room.on('newpeer', (peer) =>
|
||||
{
|
||||
global.PEER = peer;
|
||||
|
||||
if (peer.consumers.length > 0)
|
||||
global.CONSUMER = peer.consumers[peer.consumers.length - 1];
|
||||
|
||||
peer.on('newtransport', (transport) =>
|
||||
{
|
||||
global.TRANSPORT = transport;
|
||||
});
|
||||
|
||||
peer.on('newproducer', (producer) =>
|
||||
{
|
||||
global.PRODUCER = producer;
|
||||
});
|
||||
|
||||
peer.on('newconsumer', (consumer) =>
|
||||
{
|
||||
global.CONSUMER = consumer;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Listen for keyboard input.
|
||||
|
||||
let cmd;
|
||||
let terminal;
|
||||
|
||||
openCommandConsole();
|
||||
|
||||
function openCommandConsole()
|
||||
{
|
||||
stdinLog('[opening Readline Command Console...]');
|
||||
|
||||
closeCommandConsole();
|
||||
closeTerminal();
|
||||
|
||||
cmd = readline.createInterface(
|
||||
{
|
||||
input : process.stdin,
|
||||
output : process.stdout
|
||||
});
|
||||
|
||||
cmd.on('SIGINT', () =>
|
||||
{
|
||||
process.exit();
|
||||
});
|
||||
|
||||
readStdin();
|
||||
|
||||
function readStdin()
|
||||
{
|
||||
cmd.question('cmd> ', (answer) =>
|
||||
{
|
||||
switch (answer)
|
||||
{
|
||||
case '':
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'h':
|
||||
case 'help':
|
||||
{
|
||||
stdinLog('');
|
||||
stdinLog('available commands:');
|
||||
stdinLog('- h, help : show this message');
|
||||
stdinLog('- sd, serverdump : execute server.dump()');
|
||||
stdinLog('- rd, roomdump : execute room.dump() for the latest created mediasoup Room');
|
||||
stdinLog('- pd, peerdump : execute peer.dump() for the latest created mediasoup Peer');
|
||||
stdinLog('- td, transportdump : execute transport.dump() for the latest created mediasoup Transport');
|
||||
stdinLog('- prd, producerdump : execute producer.dump() for the latest created mediasoup Producer');
|
||||
stdinLog('- cd, consumerdump : execute consumer.dump() for the latest created mediasoup Consumer');
|
||||
stdinLog('- t, terminal : open REPL Terminal');
|
||||
stdinLog('');
|
||||
readStdin();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'sd':
|
||||
case 'serverdump':
|
||||
{
|
||||
mediaServer.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`server.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`mediaServer.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'rd':
|
||||
case 'roomdump':
|
||||
{
|
||||
if (!global.ROOM)
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
global.ROOM.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`room.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`room.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'pd':
|
||||
case 'peerdump':
|
||||
{
|
||||
if (!global.PEER)
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
global.PEER.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`peer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`peer.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'td':
|
||||
case 'transportdump':
|
||||
{
|
||||
if (!global.TRANSPORT)
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
global.TRANSPORT.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`transport.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`transport.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'prd':
|
||||
case 'producerdump':
|
||||
{
|
||||
if (!global.PRODUCER)
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
global.PRODUCER.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`producer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`producer.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'cd':
|
||||
case 'consumerdump':
|
||||
{
|
||||
if (!global.CONSUMER)
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
global.CONSUMER.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`consumer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`consumer.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 't':
|
||||
case 'terminal':
|
||||
{
|
||||
openTerminal();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
stdinError(`unknown command: ${answer}`);
|
||||
stdinLog('press \'h\' or \'help\' to get the list of available commands');
|
||||
|
||||
readStdin();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function openTerminal()
|
||||
{
|
||||
stdinLog('[opening REPL Terminal...]');
|
||||
|
||||
closeCommandConsole();
|
||||
closeTerminal();
|
||||
|
||||
terminal = repl.start(
|
||||
{
|
||||
prompt : 'terminal> ',
|
||||
useColors : true,
|
||||
useGlobal : true,
|
||||
ignoreUndefined : false
|
||||
});
|
||||
|
||||
terminal.on('exit', () => openCommandConsole());
|
||||
}
|
||||
|
||||
function closeCommandConsole()
|
||||
{
|
||||
if (cmd)
|
||||
{
|
||||
cmd.close();
|
||||
cmd = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function closeTerminal()
|
||||
{
|
||||
if (terminal)
|
||||
{
|
||||
terminal.removeAllListeners('exit');
|
||||
terminal.close();
|
||||
terminal = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function stdinLog(msg)
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(colors.green(msg));
|
||||
}
|
||||
|
||||
function stdinError(msg)
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(colors.red.bold('ERROR: ') + colors.red(msg));
|
||||
}
|
||||
|
||||
module.exports = mediaServer;
|
||||
|
|
@ -1,28 +1,29 @@
|
|||
{
|
||||
"name": "multiparty-meeting-server",
|
||||
"version": "3.0.0",
|
||||
"private": true,
|
||||
"description": "multiparty meeting server",
|
||||
"author": "Håvar Aambø Fosstveit <h@fosstveit.net>",
|
||||
"license": "MIT",
|
||||
"main": "lib/index.js",
|
||||
"dependencies": {
|
||||
"awaitqueue": "^1.0.0",
|
||||
"base-64": "^0.1.0",
|
||||
"colors": "^1.1.2",
|
||||
"compression": "^1.7.3",
|
||||
"debug": "^4.1.0",
|
||||
"express": "^4.16.3",
|
||||
"express-session": "^1.16.1",
|
||||
"mediasoup": "^3.0.12",
|
||||
"openid-client": "^2.5.0",
|
||||
"passport": "^0.4.0",
|
||||
"socket.io": "^2.1.1",
|
||||
"spdy": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-eslint": "^5.0.0",
|
||||
"gulp-plumber": "^1.2.0"
|
||||
}
|
||||
"name": "multiparty-meeting-server",
|
||||
"version": "3.1.0",
|
||||
"private": true,
|
||||
"description": "multiparty meeting server",
|
||||
"author": "Håvar Aambø Fosstveit <h@fosstveit.net>",
|
||||
"license": "MIT",
|
||||
"main": "lib/index.js",
|
||||
"dependencies": {
|
||||
"awaitqueue": "^1.0.0",
|
||||
"base-64": "^0.1.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"colors": "^1.4.0",
|
||||
"compression": "^1.7.4",
|
||||
"connect-redis": "^4.0.3",
|
||||
"cookie-parser": "^1.4.4",
|
||||
"debug": "^4.1.1",
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.0",
|
||||
"express-socket.io-session": "^1.3.5",
|
||||
"helmet": "^3.21.2",
|
||||
"mediasoup": "^3.0.12",
|
||||
"openid-client": "^3.7.3",
|
||||
"passport": "^0.4.0",
|
||||
"redis": "^2.8.0",
|
||||
"socket.io": "^2.3.0",
|
||||
"spdy": "^4.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
201
server/server.js
201
server/server.js
|
|
@ -1,7 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
process.title = 'multiparty-meeting-server';
|
||||
|
||||
const config = require('./config/config');
|
||||
|
|
@ -9,17 +7,28 @@ const fs = require('fs');
|
|||
const http = require('http');
|
||||
const spdy = require('spdy');
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const compression = require('compression');
|
||||
const mediasoup = require('mediasoup');
|
||||
const AwaitQueue = require('awaitqueue');
|
||||
const Logger = require('./lib/Logger');
|
||||
const Room = require('./lib/Room');
|
||||
const utils = require('./util');
|
||||
const Peer = require('./lib/Peer');
|
||||
const base64 = require('base-64');
|
||||
const helmet = require('helmet');
|
||||
const {
|
||||
loginHelper,
|
||||
logoutHelper
|
||||
} = require('./httpHelper');
|
||||
// auth
|
||||
const passport = require('passport');
|
||||
const redis = require('redis');
|
||||
const client = redis.createClient();
|
||||
const { Issuer, Strategy } = require('openid-client');
|
||||
const session = require('express-session');
|
||||
const expressSession = require('express-session');
|
||||
const RedisStore = require('connect-redis')(expressSession);
|
||||
const sharedSession = require('express-socket.io-session');
|
||||
|
||||
/* eslint-disable no-console */
|
||||
console.log('- process.env.DEBUG:', process.env.DEBUG);
|
||||
|
|
@ -42,17 +51,51 @@ let nextMediasoupWorkerIdx = 0;
|
|||
// Map of Room instances indexed by roomId.
|
||||
const rooms = new Map();
|
||||
|
||||
// Map of Peer instances indexed by peerId.
|
||||
const peers = new Map();
|
||||
|
||||
// TLS server configuration.
|
||||
const tls =
|
||||
{
|
||||
cert : fs.readFileSync(config.tls.cert),
|
||||
key : fs.readFileSync(config.tls.key)
|
||||
cert : fs.readFileSync(config.tls.cert),
|
||||
key : fs.readFileSync(config.tls.key),
|
||||
secureOptions : 'tlsv12',
|
||||
ciphers :
|
||||
[
|
||||
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
||||
'ECDHE-RSA-AES128-GCM-SHA256',
|
||||
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
||||
'ECDHE-RSA-AES256-GCM-SHA384',
|
||||
'ECDHE-ECDSA-CHACHA20-POLY1305',
|
||||
'ECDHE-RSA-CHACHA20-POLY1305',
|
||||
'DHE-RSA-AES128-GCM-SHA256',
|
||||
'DHE-RSA-AES256-GCM-SHA384'
|
||||
].join(':'),
|
||||
honorCipherOrder : true
|
||||
};
|
||||
|
||||
const app = express();
|
||||
let httpsServer;
|
||||
let oidcClient;
|
||||
let oidcStrategy;
|
||||
|
||||
app.use(helmet.hsts());
|
||||
|
||||
app.use(cookieParser());
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
||||
const session = expressSession({
|
||||
secret : config.cookieSecret,
|
||||
name : config.cookieName,
|
||||
resave : true,
|
||||
saveUninitialized : true,
|
||||
store : new RedisStore({ client }),
|
||||
cookie : {
|
||||
secure : true,
|
||||
httpOnly : true,
|
||||
maxAge : 60 * 60 * 1000 // Expire after 1 hour since last request from user
|
||||
}
|
||||
});
|
||||
|
||||
app.use(session);
|
||||
|
||||
passport.serializeUser((user, done) =>
|
||||
{
|
||||
|
|
@ -64,17 +107,22 @@ passport.deserializeUser((user, done) =>
|
|||
done(null, user);
|
||||
});
|
||||
|
||||
let httpsServer;
|
||||
let io;
|
||||
let oidcClient;
|
||||
let oidcStrategy;
|
||||
|
||||
const auth = config.auth;
|
||||
|
||||
async function run()
|
||||
{
|
||||
if (
|
||||
if (
|
||||
typeof(auth) !== 'undefined' &&
|
||||
typeof(auth.issuerURL) !== 'undefined' &&
|
||||
typeof(auth.clientOptions) !== 'undefined'
|
||||
)
|
||||
{
|
||||
Issuer.discover(auth.issuerURL).then( async (oidcIssuer) =>
|
||||
Issuer.discover(auth.issuerURL).then(async (oidcIssuer) =>
|
||||
{
|
||||
// Setup authentication
|
||||
await setupAuth(oidcIssuer);
|
||||
|
|
@ -89,8 +137,8 @@ async function run()
|
|||
await runWebSocketServer();
|
||||
})
|
||||
.catch((err) =>
|
||||
{
|
||||
logger.error(err);
|
||||
{
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
else
|
||||
|
|
@ -115,6 +163,15 @@ async function run()
|
|||
room.logStatus();
|
||||
}
|
||||
}, 120000);
|
||||
|
||||
// check for deserted rooms
|
||||
setInterval(() =>
|
||||
{
|
||||
for (const room of rooms.values())
|
||||
{
|
||||
room.checkEmpty();
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
async function setupAuth(oidcIssuer)
|
||||
|
|
@ -130,16 +187,15 @@ async function setupAuth(oidcIssuer)
|
|||
|
||||
// optional, defaults to false, when true req is passed as a first
|
||||
// argument to verify fn
|
||||
const passReqToCallback = false;
|
||||
const passReqToCallback = false;
|
||||
|
||||
// optional, defaults to false, when true the code_challenge_method will be
|
||||
// resolved from the issuer configuration, instead of true you may provide
|
||||
// any of the supported values directly, i.e. "S256" (recommended) or "plain"
|
||||
const usePKCE = false;
|
||||
const client = oidcClient;
|
||||
|
||||
oidcStrategy = new Strategy(
|
||||
{ client, params, passReqToCallback, usePKCE },
|
||||
{ client: oidcClient, params, passReqToCallback, usePKCE },
|
||||
(tokenset, userinfo, done) =>
|
||||
{
|
||||
const user =
|
||||
|
|
@ -150,15 +206,15 @@ async function setupAuth(oidcIssuer)
|
|||
_claims : tokenset.claims
|
||||
};
|
||||
|
||||
if (typeof(userinfo.picture) !== 'undefined')
|
||||
if (userinfo.picture != null)
|
||||
{
|
||||
if (!userinfo.picture.match(/^http/g))
|
||||
{
|
||||
user.Photos = [ { value: `data:image/jpeg;base64, ${userinfo.picture}` } ];
|
||||
user.picture = `data:image/jpeg;base64, ${userinfo.picture}`;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.Photos = [ { value: userinfo.picture } ];
|
||||
user.picture = userinfo.picture;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,22 +230,22 @@ async function setupAuth(oidcIssuer)
|
|||
|
||||
if (userinfo.email != null)
|
||||
{
|
||||
user.emails = [ { value: userinfo.email } ];
|
||||
user.email = userinfo.email;
|
||||
}
|
||||
|
||||
if (userinfo.given_name != null)
|
||||
{
|
||||
user.name = { givenName: userinfo.given_name };
|
||||
user.name.givenName = userinfo.given_name;
|
||||
}
|
||||
|
||||
if (userinfo.family_name != null)
|
||||
{
|
||||
user.name = { familyName: userinfo.family_name };
|
||||
user.name.familyName = userinfo.family_name;
|
||||
}
|
||||
|
||||
if (userinfo.middle_name != null)
|
||||
{
|
||||
user.name = { middleName: userinfo.middle_name };
|
||||
user.name.middleName = userinfo.middle_name;
|
||||
}
|
||||
|
||||
return done(null, user);
|
||||
|
|
@ -198,24 +254,15 @@ async function setupAuth(oidcIssuer)
|
|||
|
||||
passport.use('oidc', oidcStrategy);
|
||||
|
||||
app.use(session({
|
||||
secret : config.cookieSecret,
|
||||
resave : true,
|
||||
saveUninitialized : true,
|
||||
cookie : { secure: true }
|
||||
}));
|
||||
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
// login
|
||||
// loginparams
|
||||
app.get('/auth/login', (req, res, next) =>
|
||||
{
|
||||
passport.authenticate('oidc', {
|
||||
state : base64.encode(JSON.stringify({
|
||||
roomId : req.query.roomId,
|
||||
peerId : req.query.peerId,
|
||||
code : utils.random(10)
|
||||
id : req.query.id
|
||||
}))
|
||||
})(req, res, next);
|
||||
});
|
||||
|
|
@ -224,7 +271,7 @@ async function setupAuth(oidcIssuer)
|
|||
app.get('/auth/logout', (req, res) =>
|
||||
{
|
||||
req.logout();
|
||||
res.redirect('/');
|
||||
res.send(logoutHelper());
|
||||
});
|
||||
|
||||
// callback
|
||||
|
|
@ -235,41 +282,32 @@ async function setupAuth(oidcIssuer)
|
|||
{
|
||||
const state = JSON.parse(base64.decode(req.query.state));
|
||||
|
||||
if (rooms.has(state.roomId))
|
||||
let displayName;
|
||||
let picture;
|
||||
|
||||
if (req.user != null)
|
||||
{
|
||||
let displayName;
|
||||
let photo;
|
||||
if (req.user.displayName != null)
|
||||
displayName = req.user.displayName;
|
||||
else
|
||||
displayName = '';
|
||||
|
||||
if (req.user != null)
|
||||
{
|
||||
if (req.user.displayName != null)
|
||||
displayName = req.user.displayName;
|
||||
else
|
||||
displayName = '';
|
||||
|
||||
if (
|
||||
req.user.Photos != null &&
|
||||
req.user.Photos[0] != null &&
|
||||
req.user.Photos[0].value != null
|
||||
)
|
||||
photo = req.user.Photos[0].value;
|
||||
else
|
||||
photo = '/static/media/buddy.403cb9f6.svg';
|
||||
}
|
||||
|
||||
const data =
|
||||
{
|
||||
peerId : state.peerId,
|
||||
displayName : displayName,
|
||||
picture : photo
|
||||
};
|
||||
|
||||
const room = rooms.get(state.roomId);
|
||||
|
||||
room.authCallback(data);
|
||||
if (req.user.picture != null)
|
||||
picture = req.user.picture;
|
||||
else
|
||||
picture = '/static/media/buddy.403cb9f6.svg';
|
||||
}
|
||||
|
||||
res.send('');
|
||||
const peer = peers.get(state.id);
|
||||
|
||||
peer && (peer.displayName = displayName);
|
||||
peer && (peer.picture = picture);
|
||||
peer && (peer.authenticated = true);
|
||||
|
||||
res.send(loginHelper({
|
||||
displayName,
|
||||
picture
|
||||
}));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -316,11 +354,17 @@ async function runHttpsServer()
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a protoo WebSocketServer to allow WebSocket connections from browsers.
|
||||
* Create a WebSocketServer to allow WebSocket connections from browsers.
|
||||
*/
|
||||
async function runWebSocketServer()
|
||||
{
|
||||
const io = require('socket.io')(httpsServer);
|
||||
io = require('socket.io')(httpsServer);
|
||||
|
||||
io.use(
|
||||
sharedSession(session, {
|
||||
autoSave : true
|
||||
})
|
||||
);
|
||||
|
||||
// Handle connections from clients.
|
||||
io.on('connection', (socket) =>
|
||||
|
|
@ -342,17 +386,22 @@ async function runWebSocketServer()
|
|||
queue.push(async () =>
|
||||
{
|
||||
const room = await getOrCreateRoom({ roomId });
|
||||
const peer = new Peer({ id: peerId, socket });
|
||||
|
||||
room.handleConnection({ peerId, socket });
|
||||
peers.set(peerId, peer);
|
||||
|
||||
peer.on('close', () => peers.delete(peerId));
|
||||
|
||||
room.handlePeer(peer);
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('room creation or room joining failed:%o', error);
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('room creation or room joining failed [error:"%o"]', error);
|
||||
|
||||
socket.disconnect(true);
|
||||
socket.disconnect(true);
|
||||
|
||||
return;
|
||||
});
|
||||
return;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -410,7 +459,7 @@ async function getOrCreateRoom({ roomId })
|
|||
// If the Room does not exist create a new one.
|
||||
if (!room)
|
||||
{
|
||||
logger.info('creating a new Room [roomId:%s]', roomId);
|
||||
logger.info('creating a new Room [roomId:"%s"]', roomId);
|
||||
|
||||
const mediasoupWorker = getMediasoupWorker();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var crypto = require('crypto');
|
||||
|
||||
exports.random = function (howMany, chars) {
|
||||
chars = chars
|
||||
|| "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789";
|
||||
var rnd = crypto.randomBytes(howMany)
|
||||
, value = new Array(howMany)
|
||||
, len = len = Math.min(256, chars.length)
|
||||
, d = 256 / len
|
||||
|
||||
for (var i = 0; i < howMany; i++) {
|
||||
value[i] = chars[Math.floor(rnd[i] / d)]
|
||||
};
|
||||
|
||||
return value.join('');
|
||||
}
|
||||
Loading…
Reference in New Issue