Merge branch 'develop'
commit
cae50e3f33
40
CHANGELOG.md
40
CHANGELOG.md
|
|
@ -1,6 +1,26 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
### 3.1
|
## 3.2
|
||||||
|
|
||||||
|
* Add munin plugin
|
||||||
|
* Add muted=true search param to disble audio by deffault
|
||||||
|
* Modify webtorrent tracker
|
||||||
|
* Add key shortcut `space` for audio mute
|
||||||
|
* Add key shortcut `v` for video mute
|
||||||
|
* Add user configurable LastN
|
||||||
|
* Add option to sticky top bar (sticky by default)
|
||||||
|
* update mediasoup server
|
||||||
|
* Add simulcast options to app config (disabled by default)
|
||||||
|
* Add stats option to get counts of rooms and peers
|
||||||
|
* Add httpOnly option for loadbalancer backend setups
|
||||||
|
* LTI integration for LMS systems like moodle
|
||||||
|
* Add muted=false search parameter
|
||||||
|
* Add translations (12+1 languages)
|
||||||
|
* Add support IPv6
|
||||||
|
* Many other fixes and refactorings
|
||||||
|
|
||||||
|
## 3.1
|
||||||
|
|
||||||
* Browser session storage
|
* Browser session storage
|
||||||
* Virtual lobby for rooms
|
* Virtual lobby for rooms
|
||||||
* Allow minimum TLSv1.2 and recommended ciphers
|
* Allow minimum TLSv1.2 and recommended ciphers
|
||||||
|
|
@ -9,7 +29,8 @@
|
||||||
* Internationalization support
|
* Internationalization support
|
||||||
* Can require sign in for access
|
* Can require sign in for access
|
||||||
|
|
||||||
### 3.0
|
## 3.0
|
||||||
|
|
||||||
* Updated to mediasoup v3
|
* Updated to mediasoup v3
|
||||||
* Replace lib "passport-datporten" with "openid-client" (a general OIDC certified client)
|
* Replace lib "passport-datporten" with "openid-client" (a general OIDC certified client)
|
||||||
- OpenID Connect discovery
|
- OpenID Connect discovery
|
||||||
|
|
@ -18,25 +39,30 @@
|
||||||
- Notice it does not supports node 11.x
|
- Notice it does not supports node 11.x
|
||||||
* Updated to Material UI v4
|
* Updated to Material UI v4
|
||||||
|
|
||||||
### 2.0
|
## 2.0
|
||||||
|
|
||||||
* Material UI
|
* Material UI
|
||||||
* Separate settings for lastN for desktop and mobile
|
* Separate settings for lastN for desktop and mobile
|
||||||
|
|
||||||
### 1.2
|
## 1.2
|
||||||
|
|
||||||
* Add Lock Room feature
|
* Add Lock Room feature
|
||||||
* Fix suspended Web Audio context / fixed delayed getUsermedia
|
* Fix suspended Web Audio context / fixed delayed getUsermedia
|
||||||
* Added support for the new getdisplaymedia API in Chrome 72
|
* Added support for the new getdisplaymedia API in Chrome 72
|
||||||
|
|
||||||
### 1.1
|
## 1.1
|
||||||
|
|
||||||
* Moved Filesharing code out from React code to RoomClient
|
* Moved Filesharing code out from React code to RoomClient
|
||||||
* Major cleanup of CSS. Variables for most colors and sizes exposed in :root
|
* Major cleanup of CSS. Variables for most colors and sizes exposed in :root
|
||||||
* Started using React Context instead of middleware
|
* Started using React Context instead of middleware
|
||||||
* Small fixes to buttons and layout
|
* Small fixes to buttons and layout
|
||||||
|
|
||||||
### 1.0
|
## 1.0
|
||||||
|
|
||||||
* Fixed toolarea button based on feedback from users
|
* Fixed toolarea button based on feedback from users
|
||||||
* Added possibility to move video to separate window
|
* Added possibility to move video to separate window
|
||||||
* Added SIP gateway
|
* Added SIP gateway
|
||||||
|
|
||||||
### RC1 1.0
|
## RC1 1.0
|
||||||
|
|
||||||
* First stable release?
|
* First stable release?
|
||||||
|
|
|
||||||
24
README.md
24
README.md
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
A WebRTC meeting service using [mediasoup](https://mediasoup.org).
|
A WebRTC meeting service using [mediasoup](https://mediasoup.org).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Try it online at https://letsmeet.no. You can add /roomname to the URL for specifying a room.
|
Try it online at https://letsmeet.no. You can add /roomname to the URL for specifying a room.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
@ -15,7 +17,19 @@ Try it online at https://letsmeet.no. You can add /roomname to the URL for speci
|
||||||
## Docker
|
## Docker
|
||||||
If you want the automatic approach, you can find a docker image [here](https://hub.docker.com/r/misi/mm/).
|
If you want the automatic approach, you can find a docker image [here](https://hub.docker.com/r/misi/mm/).
|
||||||
|
|
||||||
|
## Ansible
|
||||||
|
If you want the ansible approach, you can find ansible role [here](https://github.com/misi/mm-ansible/).
|
||||||
|
[](https://asciinema.org/a/311365)
|
||||||
|
|
||||||
|
|
||||||
## Manual installation
|
## Manual installation
|
||||||
|
* Prerequisites:
|
||||||
|
Currently multiparty-meeting will only run on nodejs v10.*
|
||||||
|
To install see here [here](https://github.com/nodesource/distributions/blob/master/README.md#debinstall).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo apt install npm build-essentials redis
|
||||||
|
```
|
||||||
|
|
||||||
* Clone the project:
|
* Clone the project:
|
||||||
|
|
||||||
|
|
@ -50,7 +64,6 @@ This will build the client application and copy everythink to `server/public` fr
|
||||||
* Set up the server:
|
* Set up the server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ sudo apt install redis
|
|
||||||
$ cd ..
|
$ cd ..
|
||||||
$ cd server
|
$ cd server
|
||||||
$ npm install
|
$ npm install
|
||||||
|
|
@ -64,7 +77,8 @@ $ npm install
|
||||||
$ cd server
|
$ cd server
|
||||||
$ npm start
|
$ npm start
|
||||||
```
|
```
|
||||||
* test your service in a webRTC enabled browser: `https://yourDomainOrIPAdress:3443/roomname`
|
* Note: Do not run the server as root. If you need to use port 80/443 make a iptables-mapping for that or use systemd configuration for that (see futher down this doc).
|
||||||
|
* Test your service in a webRTC enabled browser: `https://yourDomainOrIPAdress:3443/roomname`
|
||||||
|
|
||||||
## Deploy it in a server
|
## Deploy it in a server
|
||||||
|
|
||||||
|
|
@ -74,14 +88,14 @@ $ cp multiparty-meeting.service /etc/systemd/system/
|
||||||
$ edit /etc/systemd/system/multiparty-meeting.service
|
$ edit /etc/systemd/system/multiparty-meeting.service
|
||||||
```
|
```
|
||||||
|
|
||||||
* reload systemd configuration and start service:
|
* Reload systemd configuration and start service:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ systemctl daemon-reload
|
$ systemctl daemon-reload
|
||||||
$ systemctl start multiparty-meeting
|
$ systemctl start multiparty-meeting
|
||||||
```
|
```
|
||||||
|
|
||||||
* if you want to start multiparty-meeting at boot time:
|
* If you want to start multiparty-meeting at boot time:
|
||||||
```bash
|
```bash
|
||||||
$ systemctl enable multiparty-meeting
|
$ systemctl enable multiparty-meeting
|
||||||
```
|
```
|
||||||
|
|
@ -114,4 +128,4 @@ 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.
|
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.
|
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,2 @@
|
||||||
|
react: npm start
|
||||||
|
electron: node src/electron-wait-react
|
||||||
|
|
@ -5,16 +5,20 @@
|
||||||
"description": "multiparty meeting service",
|
"description": "multiparty meeting service",
|
||||||
"author": "Håvar Aambø Fosstveit <h@fosstveit.net>",
|
"author": "Håvar Aambø Fosstveit <h@fosstveit.net>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"homepage": "./",
|
||||||
|
"main": "src/electron-starter.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "^4.5.1",
|
"@material-ui/core": "^4.5.1",
|
||||||
"@material-ui/icons": "^4.5.1",
|
"@material-ui/icons": "^4.5.1",
|
||||||
"bowser": "^2.7.0",
|
"bowser": "^2.7.0",
|
||||||
"dompurify": "^2.0.7",
|
"dompurify": "^2.0.7",
|
||||||
"domready": "^1.0.8",
|
"domready": "^1.0.8",
|
||||||
|
"end-of-stream": "1.4.0",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"hark": "^1.2.3",
|
"hark": "^1.2.3",
|
||||||
"marked": "^0.7.0",
|
"is-electron": "^2.2.0",
|
||||||
"mediasoup-client": "^3.2.7",
|
"marked": "^0.8.0",
|
||||||
|
"mediasoup-client": "^3.5.4",
|
||||||
"notistack": "^0.9.5",
|
"notistack": "^0.9.5",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"random-string": "^0.2.0",
|
"random-string": "^0.2.0",
|
||||||
|
|
@ -23,7 +27,8 @@
|
||||||
"react-dom": "^16.10.2",
|
"react-dom": "^16.10.2",
|
||||||
"react-intl": "^3.4.0",
|
"react-intl": "^3.4.0",
|
||||||
"react-redux": "^7.1.1",
|
"react-redux": "^7.1.1",
|
||||||
"react-scripts": "3.2.0",
|
"react-router-dom": "^5.1.2",
|
||||||
|
"react-scripts": "^3.3.0",
|
||||||
"redux": "^4.0.4",
|
"redux": "^4.0.4",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
|
|
@ -35,12 +40,13 @@
|
||||||
"webtorrent": "^0.107.16"
|
"webtorrent": "^0.107.16"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze-main": "source-map-explorer build/static/js/main.*",
|
"analyze": "source-map-explorer build/static/js/*",
|
||||||
"analyze-chunk": "source-map-explorer build/static/js/2.*",
|
|
||||||
"start": "HTTPS=true PORT=4443 react-scripts start",
|
"start": "HTTPS=true PORT=4443 react-scripts start",
|
||||||
"build": "react-scripts build && mkdir -p ../server/public && rm -rf ../server/public/* && cp -r build/* ../server/public/",
|
"build": "react-scripts build && mkdir -p ../server/public && rm -rf ../server/public/* && cp -r build/* ../server/public/",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject",
|
||||||
|
"electron": "electron --no-sandbox .",
|
||||||
|
"dev": "nf start -p 3000"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
">0.2%",
|
">0.2%",
|
||||||
|
|
@ -49,8 +55,12 @@
|
||||||
"not op_mini all"
|
"not op_mini all"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"electron": "^7.1.1",
|
||||||
"eslint": "^6.5.1",
|
"eslint": "^6.5.1",
|
||||||
"eslint-plugin-import": "^2.18.2",
|
"eslint-plugin-import": "^2.18.2",
|
||||||
"eslint-plugin-react": "^7.16.0"
|
"eslint-plugin-react": "^7.16.0",
|
||||||
|
"foreman": "^3.0.1",
|
||||||
|
"jest": "^24.9.0",
|
||||||
|
"redux-mock-store": "^1.5.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset='utf-8'>
|
|
||||||
<title>Multiparty Meeting</title>
|
|
||||||
</head>
|
|
||||||
<style>
|
|
||||||
body
|
|
||||||
{
|
|
||||||
margin:auto;
|
|
||||||
padding:0.5vmin;
|
|
||||||
text-align:center;
|
|
||||||
position: fixed;
|
|
||||||
left: 50%;
|
|
||||||
top: 40%;
|
|
||||||
width: 90%;
|
|
||||||
transform: translate(-50%, 0%);
|
|
||||||
background-image: url('/images/background.jpg');
|
|
||||||
background-attachment: fixed;
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: repeat;
|
|
||||||
}
|
|
||||||
input:hover { opacity:0.9; }
|
|
||||||
input[type=text]
|
|
||||||
{
|
|
||||||
font-size: 1.5em;
|
|
||||||
padding: 1.5vmin;
|
|
||||||
background-color: rgba(0,0,0,0.3);
|
|
||||||
border: 0;
|
|
||||||
color: #fff;
|
|
||||||
margin: 0.8vmin;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover { background-color: #f5f5f5; }
|
|
||||||
button
|
|
||||||
{
|
|
||||||
font-size: 1.5em;
|
|
||||||
padding: 1.5vmin;
|
|
||||||
margin: 0.8vmin;
|
|
||||||
background-color: #fafafa;
|
|
||||||
border-radius: 1.8vmin;
|
|
||||||
color: #000;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
img
|
|
||||||
{
|
|
||||||
height: 15vmin;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<body>
|
|
||||||
<a>
|
|
||||||
<img src='/images/logo.svg'></img><br />
|
|
||||||
</a>
|
|
||||||
<input id='room' type='text' onkeypress='checkEnter(event)' value='' placeholder='your room name' />
|
|
||||||
<button onclick = 'start(location.href)'>Go to room</button>
|
|
||||||
</body>
|
|
||||||
<script>
|
|
||||||
let room = document.getElementById('room');
|
|
||||||
let stateObj = { foo: 'bar' };
|
|
||||||
|
|
||||||
room.addEventListener('input', (e) =>
|
|
||||||
{
|
|
||||||
console.log(e.charCode);
|
|
||||||
history.replaceState(stateObj, 'Multiparty Meeting', '/'+room.value);
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
room.focus();
|
|
||||||
|
|
||||||
function start(target)
|
|
||||||
{
|
|
||||||
location.href;history.replaceState(stateObj, 'Multiparty Meeting', '/');
|
|
||||||
window.location = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkEnter(event)
|
|
||||||
{
|
|
||||||
let x = event.charCode || event.keyCode;
|
|
||||||
if (x == 13 )
|
|
||||||
{
|
|
||||||
start(location.href);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
var config =
|
var config =
|
||||||
{
|
{
|
||||||
loginEnabled : false,
|
loginEnabled : false,
|
||||||
developmentPort : 3443,
|
developmentPort : 3443,
|
||||||
turnServers : [
|
productionPort : 443,
|
||||||
|
multipartyServer : 'letsmeet.no',
|
||||||
|
turnServers : [
|
||||||
{
|
{
|
||||||
urls : [
|
urls : [
|
||||||
'turn:turn.example.com:443?transport=tcp'
|
'turn:turn.example.com:443?transport=tcp'
|
||||||
|
|
@ -12,8 +14,29 @@ var config =
|
||||||
credential : 'example'
|
credential : 'example'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
requestTimeout : 10000,
|
/**
|
||||||
transportOptions :
|
* If defaultResolution is set, it will override user settings when joining:
|
||||||
|
* low ~ 320x240
|
||||||
|
* medium ~ 640x480
|
||||||
|
* high ~ 1280x720
|
||||||
|
* veryhigh ~ 1920x1080
|
||||||
|
* ultra ~ 3840x2560
|
||||||
|
**/
|
||||||
|
defaultResolution : 'medium',
|
||||||
|
// Enable or disable simulcast for webcam video
|
||||||
|
simulcast : true,
|
||||||
|
// Enable or disable simulcast for screen sharing video
|
||||||
|
simulcastSharing : false,
|
||||||
|
// Simulcast encoding layers and levels
|
||||||
|
simulcastEncodings :
|
||||||
|
[
|
||||||
|
{ scaleResolutionDownBy: 4 },
|
||||||
|
{ scaleResolutionDownBy: 2 },
|
||||||
|
{ scaleResolutionDownBy: 1 }
|
||||||
|
],
|
||||||
|
// Socket.io request timeout
|
||||||
|
requestTimeout : 10000,
|
||||||
|
transportOptions :
|
||||||
{
|
{
|
||||||
tcp : true
|
tcp : true
|
||||||
},
|
},
|
||||||
|
|
@ -51,6 +74,17 @@ var config =
|
||||||
backgroundColor : '#518029'
|
backgroundColor : '#518029'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
MuiBadge :
|
||||||
|
{
|
||||||
|
colorPrimary :
|
||||||
|
{
|
||||||
|
backgroundColor : '#5F9B2D',
|
||||||
|
'&:hover' :
|
||||||
|
{
|
||||||
|
backgroundColor : '#518029'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
typography :
|
typography :
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,24 @@ let ScreenShare;
|
||||||
|
|
||||||
let Spotlights;
|
let Spotlights;
|
||||||
|
|
||||||
const {
|
let turnServers,
|
||||||
turnServers,
|
|
||||||
requestTimeout,
|
requestTimeout,
|
||||||
transportOptions,
|
transportOptions,
|
||||||
lastN,
|
lastN,
|
||||||
mobileLastN
|
mobileLastN,
|
||||||
} = window.config;
|
defaultResolution;
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'test')
|
||||||
|
{
|
||||||
|
({
|
||||||
|
turnServers,
|
||||||
|
requestTimeout,
|
||||||
|
transportOptions,
|
||||||
|
lastN,
|
||||||
|
mobileLastN,
|
||||||
|
defaultResolution
|
||||||
|
} = window.config);
|
||||||
|
}
|
||||||
|
|
||||||
const logger = new Logger('RoomClient');
|
const logger = new Logger('RoomClient');
|
||||||
|
|
||||||
|
|
@ -72,11 +83,28 @@ const VIDEO_CONSTRAINS =
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const VIDEO_ENCODINGS =
|
const PC_PROPRIETARY_CONSTRAINTS =
|
||||||
|
{
|
||||||
|
optional : [ { googDscp: true } ]
|
||||||
|
};
|
||||||
|
|
||||||
|
const VIDEO_SIMULCAST_ENCODINGS =
|
||||||
[
|
[
|
||||||
{ maxBitrate: 180000, scaleResolutionDownBy: 4 },
|
{ scaleResolutionDownBy: 4 },
|
||||||
{ maxBitrate: 360000, scaleResolutionDownBy: 2 },
|
{ scaleResolutionDownBy: 2 },
|
||||||
{ maxBitrate: 1500000, scaleResolutionDownBy: 1 }
|
{ scaleResolutionDownBy: 1 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Used for VP9 webcam video.
|
||||||
|
const VIDEO_KSVC_ENCODINGS =
|
||||||
|
[
|
||||||
|
{ scalabilityMode: 'S3T3_KEY' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Used for VP9 desktop sharing.
|
||||||
|
const VIDEO_SVC_ENCODINGS =
|
||||||
|
[
|
||||||
|
{ scalabilityMode: 'S3T3', dtx: true }
|
||||||
];
|
];
|
||||||
|
|
||||||
let store;
|
let store;
|
||||||
|
|
@ -97,13 +125,18 @@ export default class RoomClient
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{ roomId, peerId, accessCode, device, useSimulcast, produce, forceTcp })
|
{ peerId, accessCode, device, useSimulcast, useSharingSimulcast, produce, forceTcp, displayName, muted } = {})
|
||||||
{
|
{
|
||||||
logger.debug(
|
if (!peerId)
|
||||||
'constructor() [roomId: "%s", peerId: "%s", device: "%s", useSimulcast: "%s", produce: "%s", forceTcp: "%s"]',
|
throw new Error('Missing peerId');
|
||||||
roomId, peerId, device.flag, useSimulcast, produce, forceTcp);
|
else if (!device)
|
||||||
|
throw new Error('Missing device');
|
||||||
|
|
||||||
this._signalingUrl = getSignalingUrl(peerId, roomId);
|
logger.debug(
|
||||||
|
'constructor() [peerId: "%s", device: "%s", useSimulcast: "%s", produce: "%s", forceTcp: "%s", displayName ""]',
|
||||||
|
peerId, device.flag, useSimulcast, produce, forceTcp, displayName);
|
||||||
|
|
||||||
|
this._signalingUrl = null;
|
||||||
|
|
||||||
// Closed flag.
|
// Closed flag.
|
||||||
this._closed = false;
|
this._closed = false;
|
||||||
|
|
@ -114,12 +147,27 @@ export default class RoomClient
|
||||||
// Wheter we force TCP
|
// Wheter we force TCP
|
||||||
this._forceTcp = forceTcp;
|
this._forceTcp = forceTcp;
|
||||||
|
|
||||||
|
// Use displayName
|
||||||
|
if (displayName)
|
||||||
|
store.dispatch(settingsActions.setDisplayName(displayName));
|
||||||
|
|
||||||
// Torrent support
|
// Torrent support
|
||||||
this._torrentSupport = null;
|
this._torrentSupport = null;
|
||||||
|
|
||||||
// Whether simulcast should be used.
|
// Whether simulcast should be used.
|
||||||
this._useSimulcast = useSimulcast;
|
this._useSimulcast = useSimulcast;
|
||||||
|
|
||||||
|
if ('simulcast' in window.config)
|
||||||
|
this._useSimulcast = window.config.simulcast;
|
||||||
|
|
||||||
|
// Whether simulcast should be used for sharing
|
||||||
|
this._useSharingSimulcast = useSharingSimulcast;
|
||||||
|
|
||||||
|
if ('simulcastSharing' in window.config)
|
||||||
|
this._useSharingSimulcast = window.config.simulcastSharing;
|
||||||
|
|
||||||
|
this._muted = muted;
|
||||||
|
|
||||||
// This device
|
// This device
|
||||||
this._device = device;
|
this._device = device;
|
||||||
|
|
||||||
|
|
@ -136,8 +184,7 @@ export default class RoomClient
|
||||||
this._signalingSocket = null;
|
this._signalingSocket = null;
|
||||||
|
|
||||||
// The room ID
|
// The room ID
|
||||||
this._roomId = roomId;
|
this._roomId = null;
|
||||||
store.dispatch(roomActions.setRoomName(roomId));
|
|
||||||
|
|
||||||
// mediasoup-client Device instance.
|
// mediasoup-client Device instance.
|
||||||
// @type {mediasoupClient.Device}
|
// @type {mediasoupClient.Device}
|
||||||
|
|
@ -146,11 +193,17 @@ export default class RoomClient
|
||||||
// Our WebTorrent client
|
// Our WebTorrent client
|
||||||
this._webTorrent = null;
|
this._webTorrent = null;
|
||||||
|
|
||||||
|
if (defaultResolution)
|
||||||
|
store.dispatch(settingsActions.setVideoResolution(defaultResolution));
|
||||||
|
|
||||||
// Max spotlights
|
// Max spotlights
|
||||||
if (device.bowser.ios || device.bowser.mobile || device.bowser.android)
|
if (device.bowser.getPlatformType() === 'desktop')
|
||||||
this._maxSpotlights = mobileLastN;
|
|
||||||
else
|
|
||||||
this._maxSpotlights = lastN;
|
this._maxSpotlights = lastN;
|
||||||
|
else
|
||||||
|
this._maxSpotlights = mobileLastN;
|
||||||
|
|
||||||
|
store.dispatch(
|
||||||
|
settingsActions.setLastN(this._maxSpotlights));
|
||||||
|
|
||||||
// Manager of spotlight
|
// Manager of spotlight
|
||||||
this._spotlights = null;
|
this._spotlights = null;
|
||||||
|
|
@ -268,6 +321,7 @@ export default class RoomClient
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ' ':
|
||||||
case 'm': // Toggle microphone
|
case 'm': // Toggle microphone
|
||||||
{
|
{
|
||||||
if (this._micProducer)
|
if (this._micProducer)
|
||||||
|
|
@ -313,6 +367,16 @@ export default class RoomClient
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'v': // Toggle video
|
||||||
|
{
|
||||||
|
if (this._webcamProducer)
|
||||||
|
this.disableWebcam();
|
||||||
|
else
|
||||||
|
this.enableWebcam();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
|
|
@ -480,7 +544,7 @@ export default class RoomClient
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
meActions.setDisplayNameInProgress(true));
|
meActions.setDisplayNameInProgress(true));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await this.sendRequest('changeDisplayName', { displayName });
|
await this.sendRequest('changeDisplayName', { displayName });
|
||||||
|
|
@ -640,30 +704,26 @@ export default class RoomClient
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._webTorrent.seed(files, (torrent) =>
|
this._webTorrent.seed(
|
||||||
{
|
files,
|
||||||
const existingTorrent = this._webTorrent.get(torrent);
|
{ announceList: [['wss://tracker.lab.vvc.niif.hu:443']] },
|
||||||
|
(torrent) =>
|
||||||
if (existingTorrent)
|
|
||||||
{
|
{
|
||||||
return this._sendFile(existingTorrent.magnetURI);
|
store.dispatch(requestActions.notify(
|
||||||
}
|
{
|
||||||
|
text : intl.formatMessage({
|
||||||
|
id : 'filesharing.successfulFileShare',
|
||||||
|
defaultMessage : 'File successfully shared'
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
store.dispatch(requestActions.notify(
|
store.dispatch(fileActions.addFile(
|
||||||
{
|
this._peerId,
|
||||||
text : intl.formatMessage({
|
torrent.magnetURI
|
||||||
id : 'filesharing.successfulFileShare',
|
));
|
||||||
defaultMessage : 'File successfully shared'
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
|
|
||||||
store.dispatch(fileActions.addFile(
|
this._sendFile(torrent.magnetURI);
|
||||||
this._peerId,
|
});
|
||||||
torrent.magnetURI
|
|
||||||
));
|
|
||||||
|
|
||||||
this._sendFile(torrent.magnetURI);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// { file, name, picture }
|
// { file, name, picture }
|
||||||
|
|
@ -798,7 +858,7 @@ export default class RoomClient
|
||||||
catch (error)
|
catch (error)
|
||||||
{
|
{
|
||||||
logger.error('unmuteMic() | failed: %o', error);
|
logger.error('unmuteMic() | failed: %o', error);
|
||||||
|
|
||||||
store.dispatch(requestActions.notify(
|
store.dispatch(requestActions.notify(
|
||||||
{
|
{
|
||||||
type : 'error',
|
type : 'error',
|
||||||
|
|
@ -811,6 +871,14 @@ export default class RoomClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeMaxSpotlights(maxSpotlights)
|
||||||
|
{
|
||||||
|
this._spotlights.maxSpotlights = maxSpotlights;
|
||||||
|
|
||||||
|
store.dispatch(
|
||||||
|
settingsActions.setLastN(maxSpotlights));
|
||||||
|
}
|
||||||
|
|
||||||
// Updated consumers based on spotlights
|
// Updated consumers based on spotlights
|
||||||
async updateSpotlights(spotlights)
|
async updateSpotlights(spotlights)
|
||||||
{
|
{
|
||||||
|
|
@ -872,8 +940,9 @@ export default class RoomClient
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'changeAudioDevice() | new selected webcam [device:%o]',
|
'changeAudioDevice() | new selected webcam [device:%o]',
|
||||||
device);
|
device);
|
||||||
|
|
||||||
this._micProducer.track.stop();
|
if (this._micProducer && this._micProducer.track)
|
||||||
|
this._micProducer.track.stop();
|
||||||
|
|
||||||
logger.debug('changeAudioDevice() | calling getUserMedia()');
|
logger.debug('changeAudioDevice() | calling getUserMedia()');
|
||||||
|
|
||||||
|
|
@ -887,9 +956,11 @@ export default class RoomClient
|
||||||
|
|
||||||
const track = stream.getAudioTracks()[0];
|
const track = stream.getAudioTracks()[0];
|
||||||
|
|
||||||
await this._micProducer.replaceTrack({ track });
|
if (this._micProducer)
|
||||||
|
await this._micProducer.replaceTrack({ track });
|
||||||
|
|
||||||
this._micProducer.volume = 0;
|
if (this._micProducer)
|
||||||
|
this._micProducer.volume = 0;
|
||||||
|
|
||||||
const harkStream = new MediaStream();
|
const harkStream = new MediaStream();
|
||||||
|
|
||||||
|
|
@ -925,9 +996,9 @@ export default class RoomClient
|
||||||
store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume));
|
store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (this._micProducer && this._micProducer.id)
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
producerActions.setProducerTrack(this._micProducer.id, track));
|
producerActions.setProducerTrack(this._micProducer.id, track));
|
||||||
|
|
||||||
store.dispatch(settingsActions.setSelectedAudioDevice(deviceId));
|
store.dispatch(settingsActions.setSelectedAudioDevice(deviceId));
|
||||||
|
|
||||||
|
|
@ -1195,6 +1266,75 @@ export default class RoomClient
|
||||||
meActions.setMyRaiseHandStateInProgress(false));
|
meActions.setMyRaiseHandStateInProgress(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setMaxSendingSpatialLayer(spatialLayer)
|
||||||
|
{
|
||||||
|
logger.debug('setMaxSendingSpatialLayer() [spatialLayer:%s]', spatialLayer);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (this._webcamProducer)
|
||||||
|
await this._webcamProducer.setMaxSpatialLayer(spatialLayer);
|
||||||
|
if (this._screenSharingProducer)
|
||||||
|
await this._screenSharingProducer.setMaxSpatialLayer(spatialLayer);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
logger.error('setMaxSendingSpatialLayer() | failed:"%o"', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async setConsumerPreferredLayers(consumerId, spatialLayer, temporalLayer)
|
||||||
|
{
|
||||||
|
logger.debug(
|
||||||
|
'setConsumerPreferredLayers() [consumerId:%s, spatialLayer:%s, temporalLayer:%s]',
|
||||||
|
consumerId, spatialLayer, temporalLayer);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await this.sendRequest(
|
||||||
|
'setConsumerPreferedLayers', { consumerId, spatialLayer, temporalLayer });
|
||||||
|
|
||||||
|
store.dispatch(consumerActions.setConsumerPreferredLayers(
|
||||||
|
consumerId, spatialLayer, temporalLayer));
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
logger.error('setConsumerPreferredLayers() | failed:"%o"', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async setConsumerPriority(consumerId, priority)
|
||||||
|
{
|
||||||
|
logger.debug(
|
||||||
|
'setConsumerPriority() [consumerId:%s, priority:%d]',
|
||||||
|
consumerId, priority);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await this.sendRequest('setConsumerPriority', { consumerId, priority });
|
||||||
|
|
||||||
|
store.dispatch(consumerActions.setConsumerPriority(consumerId, priority));
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
logger.error('setConsumerPriority() | failed:%o', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestConsumerKeyFrame(consumerId)
|
||||||
|
{
|
||||||
|
logger.debug('requestConsumerKeyFrame() [consumerId:%s]', consumerId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await this.sendRequest('requestConsumerKeyFrame', { consumerId });
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
logger.error('requestConsumerKeyFrame() | failed:%o', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async _loadDynamicImports()
|
async _loadDynamicImports()
|
||||||
{
|
{
|
||||||
({ default: WebTorrent } = await import(
|
({ default: WebTorrent } = await import(
|
||||||
|
|
@ -1240,10 +1380,16 @@ export default class RoomClient
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
async join({ joinVideo })
|
async join({ roomId, joinVideo })
|
||||||
{
|
{
|
||||||
await this._loadDynamicImports();
|
await this._loadDynamicImports();
|
||||||
|
|
||||||
|
this._roomId = roomId;
|
||||||
|
|
||||||
|
store.dispatch(roomActions.setRoomName(roomId));
|
||||||
|
|
||||||
|
this._signalingUrl = getSignalingUrl(this._peerId, roomId);
|
||||||
|
|
||||||
this._torrentSupport = WebTorrent.WEBRTC_SUPPORT;
|
this._torrentSupport = WebTorrent.WEBRTC_SUPPORT;
|
||||||
|
|
||||||
this._webTorrent = this._torrentSupport && new WebTorrent({
|
this._webTorrent = this._torrentSupport && new WebTorrent({
|
||||||
|
|
@ -1406,6 +1552,7 @@ export default class RoomClient
|
||||||
temporalLayers : temporalLayers,
|
temporalLayers : temporalLayers,
|
||||||
preferredSpatialLayer : spatialLayers - 1,
|
preferredSpatialLayer : spatialLayers - 1,
|
||||||
preferredTemporalLayer : temporalLayers - 1,
|
preferredTemporalLayer : temporalLayers - 1,
|
||||||
|
priority : 1,
|
||||||
codec : consumer.rtpParameters.codecs[0].mimeType.split('/')[1],
|
codec : consumer.rtpParameters.codecs[0].mimeType.split('/')[1],
|
||||||
track : consumer.track
|
track : consumer.track
|
||||||
},
|
},
|
||||||
|
|
@ -1477,7 +1624,7 @@ export default class RoomClient
|
||||||
case 'enteredLobby':
|
case 'enteredLobby':
|
||||||
{
|
{
|
||||||
store.dispatch(roomActions.setInLobby(true));
|
store.dispatch(roomActions.setInLobby(true));
|
||||||
|
|
||||||
const { displayName } = store.getState().settings;
|
const { displayName } = store.getState().settings;
|
||||||
const { picture } = store.getState().me;
|
const { picture } = store.getState().me;
|
||||||
|
|
||||||
|
|
@ -1646,7 +1793,7 @@ export default class RoomClient
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
roomActions.setJoinByAccessCode(joinByAccessCode));
|
roomActions.setJoinByAccessCode(joinByAccessCode));
|
||||||
|
|
||||||
if (joinByAccessCode)
|
if (joinByAccessCode)
|
||||||
{
|
{
|
||||||
store.dispatch(requestActions.notify(
|
store.dispatch(requestActions.notify(
|
||||||
{
|
{
|
||||||
|
|
@ -1686,10 +1833,10 @@ export default class RoomClient
|
||||||
case 'changeDisplayName':
|
case 'changeDisplayName':
|
||||||
{
|
{
|
||||||
const { peerId, displayName, oldDisplayName } = notification.data;
|
const { peerId, displayName, oldDisplayName } = notification.data;
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
peerActions.setPeerDisplayName(displayName, peerId));
|
peerActions.setPeerDisplayName(displayName, peerId));
|
||||||
|
|
||||||
store.dispatch(requestActions.notify(
|
store.dispatch(requestActions.notify(
|
||||||
{
|
{
|
||||||
text : intl.formatMessage({
|
text : intl.formatMessage({
|
||||||
|
|
@ -1700,26 +1847,26 @@ export default class RoomClient
|
||||||
displayName
|
displayName
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'changePicture':
|
case 'changePicture':
|
||||||
{
|
{
|
||||||
const { peerId, picture } = notification.data;
|
const { peerId, picture } = notification.data;
|
||||||
|
|
||||||
store.dispatch(peerActions.setPeerPicture(peerId, picture));
|
store.dispatch(peerActions.setPeerPicture(peerId, picture));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'chatMessage':
|
case 'chatMessage':
|
||||||
{
|
{
|
||||||
const { peerId, chatMessage } = notification.data;
|
const { peerId, chatMessage } = notification.data;
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
chatActions.addResponseMessage({ ...chatMessage, peerId }));
|
chatActions.addResponseMessage({ ...chatMessage, peerId }));
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!store.getState().toolarea.toolAreaOpen ||
|
!store.getState().toolarea.toolAreaOpen ||
|
||||||
(store.getState().toolarea.toolAreaOpen &&
|
(store.getState().toolarea.toolAreaOpen &&
|
||||||
|
|
@ -1730,16 +1877,16 @@ export default class RoomClient
|
||||||
roomActions.setToolbarsVisible(true));
|
roomActions.setToolbarsVisible(true));
|
||||||
this._soundNotification();
|
this._soundNotification();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'sendFile':
|
case 'sendFile':
|
||||||
{
|
{
|
||||||
const { peerId, magnetUri } = notification.data;
|
const { peerId, magnetUri } = notification.data;
|
||||||
|
|
||||||
store.dispatch(fileActions.addFile(peerId, magnetUri));
|
store.dispatch(fileActions.addFile(peerId, magnetUri));
|
||||||
|
|
||||||
store.dispatch(requestActions.notify(
|
store.dispatch(requestActions.notify(
|
||||||
{
|
{
|
||||||
text : intl.formatMessage({
|
text : intl.formatMessage({
|
||||||
|
|
@ -1747,7 +1894,7 @@ export default class RoomClient
|
||||||
defaultMessage : 'New file available'
|
defaultMessage : 'New file available'
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!store.getState().toolarea.toolAreaOpen ||
|
!store.getState().toolarea.toolAreaOpen ||
|
||||||
(store.getState().toolarea.toolAreaOpen &&
|
(store.getState().toolarea.toolAreaOpen &&
|
||||||
|
|
@ -1758,27 +1905,27 @@ export default class RoomClient
|
||||||
roomActions.setToolbarsVisible(true));
|
roomActions.setToolbarsVisible(true));
|
||||||
this._soundNotification();
|
this._soundNotification();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'producerScore':
|
case 'producerScore':
|
||||||
{
|
{
|
||||||
const { producerId, score } = notification.data;
|
const { producerId, score } = notification.data;
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
producerActions.setProducerScore(producerId, score));
|
producerActions.setProducerScore(producerId, score));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'newPeer':
|
case 'newPeer':
|
||||||
{
|
{
|
||||||
const { id, displayName, picture, device } = notification.data;
|
const { id, displayName, picture } = notification.data;
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
peerActions.addPeer({ id, displayName, picture, device, consumers: [] }));
|
peerActions.addPeer({ id, displayName, picture, consumers: [] }));
|
||||||
|
|
||||||
store.dispatch(requestActions.notify(
|
store.dispatch(requestActions.notify(
|
||||||
{
|
{
|
||||||
text : intl.formatMessage({
|
text : intl.formatMessage({
|
||||||
|
|
@ -1788,20 +1935,20 @@ export default class RoomClient
|
||||||
displayName
|
displayName
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'peerClosed':
|
case 'peerClosed':
|
||||||
{
|
{
|
||||||
const { peerId } = notification.data;
|
const { peerId } = notification.data;
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
peerActions.removePeer(peerId));
|
peerActions.removePeer(peerId));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'consumerClosed':
|
case 'consumerClosed':
|
||||||
{
|
{
|
||||||
const { consumerId } = notification.data;
|
const { consumerId } = notification.data;
|
||||||
|
|
@ -1835,7 +1982,7 @@ export default class RoomClient
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
consumerActions.setConsumerPaused(consumerId, 'remote'));
|
consumerActions.setConsumerPaused(consumerId, 'remote'));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1941,7 +2088,9 @@ export default class RoomClient
|
||||||
id,
|
id,
|
||||||
iceParameters,
|
iceParameters,
|
||||||
iceCandidates,
|
iceCandidates,
|
||||||
dtlsParameters
|
dtlsParameters,
|
||||||
|
iceServers : ROOM_OPTIONS.turnServers,
|
||||||
|
proprietaryConstraints : PC_PROPRIETARY_CONSTRAINTS
|
||||||
});
|
});
|
||||||
|
|
||||||
this._sendTransport.on(
|
this._sendTransport.on(
|
||||||
|
|
@ -1958,18 +2107,26 @@ export default class RoomClient
|
||||||
});
|
});
|
||||||
|
|
||||||
this._sendTransport.on(
|
this._sendTransport.on(
|
||||||
'produce', ({ kind, rtpParameters, appData }, callback, errback) =>
|
'produce', async ({ kind, rtpParameters, appData }, callback, errback) =>
|
||||||
{
|
{
|
||||||
this.sendRequest(
|
try
|
||||||
'produce',
|
{
|
||||||
{
|
// eslint-disable-next-line no-shadow
|
||||||
transportId : this._sendTransport.id,
|
const { id } = await this.sendRequest(
|
||||||
kind,
|
'produce',
|
||||||
rtpParameters,
|
{
|
||||||
appData
|
transportId : this._sendTransport.id,
|
||||||
})
|
kind,
|
||||||
.then(callback)
|
rtpParameters,
|
||||||
.catch(errback);
|
appData
|
||||||
|
});
|
||||||
|
|
||||||
|
callback({ id });
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
errback(error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1993,7 +2150,8 @@ export default class RoomClient
|
||||||
id,
|
id,
|
||||||
iceParameters,
|
iceParameters,
|
||||||
iceCandidates,
|
iceCandidates,
|
||||||
dtlsParameters
|
dtlsParameters,
|
||||||
|
iceServers : ROOM_OPTIONS.turnServers
|
||||||
});
|
});
|
||||||
|
|
||||||
this._recvTransport.on(
|
this._recvTransport.on(
|
||||||
|
|
@ -2047,7 +2205,8 @@ export default class RoomClient
|
||||||
if (this._produce)
|
if (this._produce)
|
||||||
{
|
{
|
||||||
if (this._mediasoupDevice.canProduce('audio'))
|
if (this._mediasoupDevice.canProduce('audio'))
|
||||||
this.enableMic();
|
if (!this._muted)
|
||||||
|
this.enableMic();
|
||||||
|
|
||||||
if (joinVideo && this._mediasoupDevice.canProduce('video'))
|
if (joinVideo && this._mediasoupDevice.canProduce('video'))
|
||||||
this.enableWebcam();
|
this.enableWebcam();
|
||||||
|
|
@ -2214,7 +2373,7 @@ export default class RoomClient
|
||||||
if (this._micProducer)
|
if (this._micProducer)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!this._mediasoupDevice.canProduce('audio'))
|
if (this._mediasoupDevice && !this._mediasoupDevice.canProduce('audio'))
|
||||||
{
|
{
|
||||||
logger.error('enableMic() | cannot produce audio');
|
logger.error('enableMic() | cannot produce audio');
|
||||||
|
|
||||||
|
|
@ -2411,19 +2570,45 @@ export default class RoomClient
|
||||||
logger.debug('enableScreenSharing() | calling getUserMedia()');
|
logger.debug('enableScreenSharing() | calling getUserMedia()');
|
||||||
|
|
||||||
const stream = await this._screenSharing.start({
|
const stream = await this._screenSharing.start({
|
||||||
width : 1280,
|
width : 1920,
|
||||||
height : 720,
|
height : 1080,
|
||||||
frameRate : 3
|
frameRate : 5
|
||||||
});
|
});
|
||||||
|
|
||||||
track = stream.getVideoTracks()[0];
|
track = stream.getVideoTracks()[0];
|
||||||
|
|
||||||
if (this._useSimulcast)
|
if (this._useSharingSimulcast)
|
||||||
{
|
{
|
||||||
|
// If VP9 is the only available video codec then use SVC.
|
||||||
|
const firstVideoCodec = this._mediasoupDevice
|
||||||
|
.rtpCapabilities
|
||||||
|
.codecs
|
||||||
|
.find((c) => c.kind === 'video');
|
||||||
|
|
||||||
|
let encodings;
|
||||||
|
|
||||||
|
if (firstVideoCodec.mimeType.toLowerCase() === 'video/vp9')
|
||||||
|
{
|
||||||
|
encodings = VIDEO_SVC_ENCODINGS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ('simulcastEncodings' in window.config)
|
||||||
|
{
|
||||||
|
encodings = window.config.simulcastEncodings
|
||||||
|
.map((encoding) => ({ ...encoding, dtx: true }));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
encodings = VIDEO_SIMULCAST_ENCODINGS
|
||||||
|
.map((encoding) => ({ ...encoding, dtx: true }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this._screenSharingProducer = await this._sendTransport.produce(
|
this._screenSharingProducer = await this._sendTransport.produce(
|
||||||
{
|
{
|
||||||
track,
|
track,
|
||||||
encodings : VIDEO_ENCODINGS,
|
encodings,
|
||||||
codecOptions :
|
codecOptions :
|
||||||
{
|
{
|
||||||
videoGoogleStartBitrate : 1000
|
videoGoogleStartBitrate : 1000
|
||||||
|
|
@ -2574,10 +2759,28 @@ export default class RoomClient
|
||||||
|
|
||||||
if (this._useSimulcast)
|
if (this._useSimulcast)
|
||||||
{
|
{
|
||||||
|
// If VP9 is the only available video codec then use SVC.
|
||||||
|
const firstVideoCodec = this._mediasoupDevice
|
||||||
|
.rtpCapabilities
|
||||||
|
.codecs
|
||||||
|
.find((c) => c.kind === 'video');
|
||||||
|
|
||||||
|
let encodings;
|
||||||
|
|
||||||
|
if (firstVideoCodec.mimeType.toLowerCase() === 'video/vp9')
|
||||||
|
encodings = VIDEO_KSVC_ENCODINGS;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ('simulcastEncodings' in window.config)
|
||||||
|
encodings = window.config.simulcastEncodings;
|
||||||
|
else
|
||||||
|
encodings = VIDEO_SIMULCAST_ENCODINGS;
|
||||||
|
}
|
||||||
|
|
||||||
this._webcamProducer = await this._sendTransport.produce(
|
this._webcamProducer = await this._sendTransport.produce(
|
||||||
{
|
{
|
||||||
track,
|
track,
|
||||||
encodings : VIDEO_ENCODINGS,
|
encodings,
|
||||||
codecOptions :
|
codecOptions :
|
||||||
{
|
{
|
||||||
videoGoogleStartBitrate : 1000
|
videoGoogleStartBitrate : 1000
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,70 @@
|
||||||
|
import isElectron from 'is-electron';
|
||||||
|
|
||||||
|
let electron = null;
|
||||||
|
|
||||||
|
if (isElectron())
|
||||||
|
electron = window.require('electron');
|
||||||
|
|
||||||
|
class ElectronScreenShare
|
||||||
|
{
|
||||||
|
constructor()
|
||||||
|
{
|
||||||
|
this._stream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
start()
|
||||||
|
{
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() =>
|
||||||
|
{
|
||||||
|
return electron.desktopCapturer.getSources({ types: [ 'window', 'screen' ] });
|
||||||
|
})
|
||||||
|
.then((sources) =>
|
||||||
|
{
|
||||||
|
for (const source of sources)
|
||||||
|
{
|
||||||
|
// Currently only getting whole screen
|
||||||
|
if (source.name === 'Entire Screen')
|
||||||
|
{
|
||||||
|
return navigator.mediaDevices.getUserMedia({
|
||||||
|
audio : false,
|
||||||
|
video :
|
||||||
|
{
|
||||||
|
mandatory :
|
||||||
|
{
|
||||||
|
chromeMediaSource : 'desktop',
|
||||||
|
chromeMediaSourceId : source.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((stream) =>
|
||||||
|
{
|
||||||
|
this._stream = stream;
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stop()
|
||||||
|
{
|
||||||
|
if (this._stream instanceof MediaStream === false)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._stream.getTracks().forEach((track) => track.stop());
|
||||||
|
this._stream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isScreenShareAvailable()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class DisplayMediaScreenShare
|
class DisplayMediaScreenShare
|
||||||
{
|
{
|
||||||
constructor()
|
constructor()
|
||||||
|
|
@ -34,12 +101,25 @@ class DisplayMediaScreenShare
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_toConstraints()
|
_toConstraints(options)
|
||||||
{
|
{
|
||||||
const constraints = {
|
const constraints = {
|
||||||
video : true
|
video : {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isFinite(options.width))
|
||||||
|
{
|
||||||
|
constraints.video.width = options.width;
|
||||||
|
}
|
||||||
|
if (isFinite(options.height))
|
||||||
|
{
|
||||||
|
constraints.video.height = options.height;
|
||||||
|
}
|
||||||
|
if (isFinite(options.frameRate))
|
||||||
|
{
|
||||||
|
constraints.video.frameRate = options.frameRate;
|
||||||
|
}
|
||||||
|
|
||||||
return constraints;
|
return constraints;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -131,26 +211,31 @@ export default class ScreenShare
|
||||||
{
|
{
|
||||||
static create(device)
|
static create(device)
|
||||||
{
|
{
|
||||||
switch (device.flag)
|
if (isElectron())
|
||||||
|
return new ElectronScreenShare();
|
||||||
|
else
|
||||||
{
|
{
|
||||||
case 'firefox':
|
switch (device.flag)
|
||||||
{
|
{
|
||||||
if (device.version < 66.0)
|
case 'firefox':
|
||||||
return new FirefoxScreenShare();
|
{
|
||||||
else
|
if (device.version < 66.0)
|
||||||
|
return new FirefoxScreenShare();
|
||||||
|
else
|
||||||
|
return new DisplayMediaScreenShare();
|
||||||
|
}
|
||||||
|
case 'chrome':
|
||||||
|
{
|
||||||
return new DisplayMediaScreenShare();
|
return new DisplayMediaScreenShare();
|
||||||
}
|
}
|
||||||
case 'chrome':
|
case 'msedge':
|
||||||
{
|
{
|
||||||
return new DisplayMediaScreenShare();
|
return new DisplayMediaScreenShare();
|
||||||
}
|
}
|
||||||
case 'msedge':
|
default:
|
||||||
{
|
{
|
||||||
return new DisplayMediaScreenShare();
|
return new DefaultScreenShare();
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
{
|
|
||||||
return new DefaultScreenShare();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -198,4 +198,19 @@ export default class Spotlights extends EventEmitter
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get maxSpotlights()
|
||||||
|
{
|
||||||
|
return this._maxSpotlights;
|
||||||
|
}
|
||||||
|
|
||||||
|
set maxSpotlights(maxSpotlights)
|
||||||
|
{
|
||||||
|
const oldMaxSpotlights = this._maxSpotlights;
|
||||||
|
|
||||||
|
this._maxSpotlights = maxSpotlights;
|
||||||
|
|
||||||
|
if (oldMaxSpotlights !== this._maxSpotlights)
|
||||||
|
this._spotlightsUpdated();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { Route, MemoryRouter } from 'react-router-dom';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { createIntl, createIntlCache, RawIntlProvider } from 'react-intl';
|
||||||
|
import App from '../components/App';
|
||||||
|
import ChooseRoom from '../components/ChooseRoom';
|
||||||
|
import RoomContext from '../RoomContext';
|
||||||
|
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
|
||||||
|
const mockStore = configureStore([]);
|
||||||
|
|
||||||
|
let container;
|
||||||
|
|
||||||
|
let store;
|
||||||
|
|
||||||
|
let intl;
|
||||||
|
|
||||||
|
const roomClient = {};
|
||||||
|
|
||||||
|
beforeEach(() =>
|
||||||
|
{
|
||||||
|
container = document.createElement('div');
|
||||||
|
|
||||||
|
store = mockStore({
|
||||||
|
me : {
|
||||||
|
displayNameInProgress : false,
|
||||||
|
id : 'jesttester',
|
||||||
|
loggedIn : false,
|
||||||
|
loginEnabled : true
|
||||||
|
},
|
||||||
|
room : {
|
||||||
|
},
|
||||||
|
settings : {
|
||||||
|
displayName : 'Jest Tester'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const cache = createIntlCache();
|
||||||
|
|
||||||
|
const locale = 'en';
|
||||||
|
|
||||||
|
intl = createIntl({
|
||||||
|
locale,
|
||||||
|
messages : {}
|
||||||
|
}, cache);
|
||||||
|
|
||||||
|
document.body.appendChild(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() =>
|
||||||
|
{
|
||||||
|
document.body.removeChild(container);
|
||||||
|
container = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('<ChooseRoom />', () =>
|
||||||
|
{
|
||||||
|
test('renders chooseroom', () =>
|
||||||
|
{
|
||||||
|
act(() =>
|
||||||
|
{
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<RawIntlProvider value={intl}>
|
||||||
|
<RoomContext.Provider value={roomClient}>
|
||||||
|
<MemoryRouter initialEntries={[ '/' ]}>
|
||||||
|
<Route path='/' component={ChooseRoom} />
|
||||||
|
</MemoryRouter>
|
||||||
|
</RoomContext.Provider>
|
||||||
|
</RawIntlProvider>
|
||||||
|
</Provider>,
|
||||||
|
container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('<App />', () =>
|
||||||
|
{
|
||||||
|
test('renders joindialog', () =>
|
||||||
|
{
|
||||||
|
act(() =>
|
||||||
|
{
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<RawIntlProvider value={intl}>
|
||||||
|
<RoomContext.Provider value={roomClient}>
|
||||||
|
<MemoryRouter initialEntries={[ '/test' ]}>
|
||||||
|
<Route path='/:id' component={App} />
|
||||||
|
</MemoryRouter>
|
||||||
|
</RoomContext.Provider>
|
||||||
|
</RawIntlProvider>
|
||||||
|
</Provider>,
|
||||||
|
container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { createIntl, createIntlCache, RawIntlProvider } from 'react-intl';
|
||||||
|
import Room from '../components/Room';
|
||||||
|
import { SnackbarProvider } from 'notistack';
|
||||||
|
import RoomContext from '../RoomContext';
|
||||||
|
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
|
||||||
|
const mockStore = configureStore([]);
|
||||||
|
|
||||||
|
let container;
|
||||||
|
|
||||||
|
let store;
|
||||||
|
|
||||||
|
let intl;
|
||||||
|
|
||||||
|
const roomClient = {};
|
||||||
|
|
||||||
|
beforeEach(() =>
|
||||||
|
{
|
||||||
|
container = document.createElement('div');
|
||||||
|
|
||||||
|
store = mockStore({
|
||||||
|
chat : [],
|
||||||
|
consumers : {},
|
||||||
|
files : {},
|
||||||
|
lobbyPeers : {},
|
||||||
|
me : {
|
||||||
|
audioDevices : null,
|
||||||
|
audioInProgress : false,
|
||||||
|
canSendMic : false,
|
||||||
|
canSendWebcam : false,
|
||||||
|
canShareFiles : false,
|
||||||
|
canShareScreen : false,
|
||||||
|
displayNameInProgress : false,
|
||||||
|
id : 'jesttester',
|
||||||
|
loggedIn : false,
|
||||||
|
loginEnabled : true,
|
||||||
|
picture : null,
|
||||||
|
raiseHand : false,
|
||||||
|
raiseHandInProgress : false,
|
||||||
|
screenShareInProgress : false,
|
||||||
|
webcamDevices : null,
|
||||||
|
webcamInProgress : false
|
||||||
|
},
|
||||||
|
notifications : [],
|
||||||
|
peerVolumes : {},
|
||||||
|
peers : {},
|
||||||
|
producers : {},
|
||||||
|
room : {
|
||||||
|
accessCode : '',
|
||||||
|
activeSpeakerId : null,
|
||||||
|
fullScreenConsumer : null,
|
||||||
|
inLobby : true,
|
||||||
|
joinByAccessCode : true,
|
||||||
|
joined : false,
|
||||||
|
lockDialogOpen : false,
|
||||||
|
locked : false,
|
||||||
|
mode : 'democratic',
|
||||||
|
name : 'test',
|
||||||
|
selectedPeerId : null,
|
||||||
|
settingsOpen : false,
|
||||||
|
showSettings : false,
|
||||||
|
signInRequired : false,
|
||||||
|
spotlights : [],
|
||||||
|
state : 'connecting',
|
||||||
|
toolbarsVisible : true,
|
||||||
|
torrentSupport : false,
|
||||||
|
windowConsumer : null
|
||||||
|
},
|
||||||
|
settings : {
|
||||||
|
advancedMode : true,
|
||||||
|
displayName : 'Jest Tester',
|
||||||
|
resolution : 'ultra',
|
||||||
|
selectedAudioDevice : 'default',
|
||||||
|
selectedWebcam : 'soifjsiajosjfoi'
|
||||||
|
},
|
||||||
|
toolarea : {
|
||||||
|
currentToolTab : 'chat',
|
||||||
|
toolAreaOpen : false,
|
||||||
|
unreadFiles : 0,
|
||||||
|
unreadMessages : 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const cache = createIntlCache();
|
||||||
|
|
||||||
|
const locale = 'en';
|
||||||
|
|
||||||
|
intl = createIntl({
|
||||||
|
locale,
|
||||||
|
messages : {}
|
||||||
|
}, cache);
|
||||||
|
|
||||||
|
document.body.appendChild(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() =>
|
||||||
|
{
|
||||||
|
document.body.removeChild(container);
|
||||||
|
container = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('<Room />', () =>
|
||||||
|
{
|
||||||
|
test('renders correctly', () =>
|
||||||
|
{
|
||||||
|
act(() =>
|
||||||
|
{
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<RawIntlProvider value={intl}>
|
||||||
|
<SnackbarProvider>
|
||||||
|
<RoomContext.Provider value={roomClient}>
|
||||||
|
<Room />
|
||||||
|
</RoomContext.Provider>
|
||||||
|
</SnackbarProvider>
|
||||||
|
</RawIntlProvider>
|
||||||
|
</Provider>,
|
||||||
|
container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import RoomClient from '../RoomClient';
|
||||||
|
|
||||||
|
describe('new RoomClient() without paramaters throws Error', () =>
|
||||||
|
{
|
||||||
|
test('Matches the snapshot', () =>
|
||||||
|
{
|
||||||
|
expect(() => new RoomClient()).toThrow(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -34,6 +34,14 @@ export const setConsumerPreferredLayers = (consumerId, spatialLayer, temporalLay
|
||||||
payload : { consumerId, spatialLayer, temporalLayer }
|
payload : { consumerId, spatialLayer, temporalLayer }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const setConsumerPriority = (consumerId, priority) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'SET_CONSUMER_PRIORITY',
|
||||||
|
payload : { consumerId, priority }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const setConsumerTrack = (consumerId, track) =>
|
export const setConsumerTrack = (consumerId, track) =>
|
||||||
({
|
({
|
||||||
type : 'SET_CONSUMER_TRACK',
|
type : 'SET_CONSUMER_TRACK',
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,3 @@
|
||||||
export const setRoomUrl = (url) =>
|
|
||||||
({
|
|
||||||
type : 'SET_ROOM_URL',
|
|
||||||
payload : { url }
|
|
||||||
});
|
|
||||||
|
|
||||||
export const setRoomName = (name) =>
|
export const setRoomName = (name) =>
|
||||||
({
|
({
|
||||||
type : 'SET_ROOM_NAME',
|
type : 'SET_ROOM_NAME',
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,15 @@ export const setDisplayName = (displayName) =>
|
||||||
export const toggleAdvancedMode = () =>
|
export const toggleAdvancedMode = () =>
|
||||||
({
|
({
|
||||||
type : 'TOGGLE_ADVANCED_MODE'
|
type : 'TOGGLE_ADVANCED_MODE'
|
||||||
|
});
|
||||||
|
|
||||||
|
export const togglePermanentTopBar = () =>
|
||||||
|
({
|
||||||
|
type : 'TOGGLE_PERMANENT_TOPBAR'
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setLastN = (lastN) =>
|
||||||
|
({
|
||||||
|
type : 'SET_LAST_N',
|
||||||
|
payload : { lastN }
|
||||||
});
|
});
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useEffect, Suspense } from 'react';
|
import React, { useEffect, Suspense } from 'react';
|
||||||
|
import { useParams } from 'react-router';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import JoinDialog from './JoinDialog';
|
import JoinDialog from './JoinDialog';
|
||||||
|
|
@ -13,6 +14,8 @@ const App = (props) =>
|
||||||
room
|
room
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
Room.preload();
|
Room.preload();
|
||||||
|
|
@ -23,7 +26,7 @@ const App = (props) =>
|
||||||
if (!room.joined)
|
if (!room.joined)
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<JoinDialog />
|
<JoinDialog roomId={id} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,291 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
|
import { withRoomContext } from '../RoomContext';
|
||||||
|
import isElectron from 'is-electron';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useIntl, FormattedMessage } from 'react-intl';
|
||||||
|
import randomString from 'random-string';
|
||||||
|
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 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 ? window.config.background : null})`,
|
||||||
|
backgroundAttachment : 'fixed',
|
||||||
|
backgroundPosition : 'center',
|
||||||
|
backgroundSize : 'cover',
|
||||||
|
backgroundRepeat : 'no-repeat'
|
||||||
|
},
|
||||||
|
dialogTitle :
|
||||||
|
{
|
||||||
|
},
|
||||||
|
dialogPaper :
|
||||||
|
{
|
||||||
|
width : '30vw',
|
||||||
|
padding : theme.spacing(2),
|
||||||
|
[theme.breakpoints.down('lg')] :
|
||||||
|
{
|
||||||
|
width : '40vw'
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('md')] :
|
||||||
|
{
|
||||||
|
width : '50vw'
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('sm')] :
|
||||||
|
{
|
||||||
|
width : '70vw'
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('xs')] :
|
||||||
|
{
|
||||||
|
width : '90vw'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
logo :
|
||||||
|
{
|
||||||
|
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 && window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
|
||||||
|
<Typography variant='h5'>{children}</Typography>
|
||||||
|
{ window.config && 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 ChooseRoom = ({
|
||||||
|
roomClient,
|
||||||
|
loggedIn,
|
||||||
|
myPicture,
|
||||||
|
classes
|
||||||
|
}) =>
|
||||||
|
{
|
||||||
|
const [ roomId, setRoomId ] =
|
||||||
|
useState(randomString({ length: 8 }).toLowerCase());
|
||||||
|
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<Dialog
|
||||||
|
open
|
||||||
|
classes={{
|
||||||
|
paper : classes.dialogPaper
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle
|
||||||
|
myPicture={myPicture}
|
||||||
|
onLogin={() =>
|
||||||
|
{
|
||||||
|
loggedIn ? roomClient.logout() : roomClient.login();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ window.config && window.config.title ? window.config.title : 'Multiparty meeting' }
|
||||||
|
<hr />
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText gutterBottom>
|
||||||
|
<FormattedMessage
|
||||||
|
id='room.chooseRoom'
|
||||||
|
defaultMessage='Choose the name of the room you would like to join'
|
||||||
|
/>
|
||||||
|
</DialogContentText>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
id='roomId'
|
||||||
|
label={intl.formatMessage({
|
||||||
|
id : 'label.roomName',
|
||||||
|
defaultMessage : 'Room name'
|
||||||
|
})}
|
||||||
|
value={roomId}
|
||||||
|
variant='outlined'
|
||||||
|
margin='normal'
|
||||||
|
onChange={(event) =>
|
||||||
|
{
|
||||||
|
const { value } = event.target;
|
||||||
|
|
||||||
|
setRoomId(value.toLowerCase());
|
||||||
|
}}
|
||||||
|
onBlur={() =>
|
||||||
|
{
|
||||||
|
if (roomId === '')
|
||||||
|
setRoomId(randomString({ length: 8 }).toLowerCase());
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
to={roomId}
|
||||||
|
variant='contained'
|
||||||
|
color='secondary'
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='label.chooseRoomButton'
|
||||||
|
defaultMessage='Continue'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
|
||||||
|
{ !isElectron() &&
|
||||||
|
<CookieConsent buttonText={intl.formatMessage({
|
||||||
|
id : 'room.consentUnderstand',
|
||||||
|
defaultMessage : 'I understand'
|
||||||
|
})}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='room.cookieConsent'
|
||||||
|
defaultMessage='This website uses cookies to enhance the user experience'
|
||||||
|
/>
|
||||||
|
</CookieConsent>
|
||||||
|
}
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ChooseRoom.propTypes =
|
||||||
|
{
|
||||||
|
roomClient : PropTypes.any.isRequired,
|
||||||
|
loginEnabled : PropTypes.bool.isRequired,
|
||||||
|
loggedIn : PropTypes.bool.isRequired,
|
||||||
|
myPicture : PropTypes.string,
|
||||||
|
classes : PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
loginEnabled : state.me.loginEnabled,
|
||||||
|
loggedIn : state.me.loggedIn,
|
||||||
|
myPicture : state.me.picture
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withRoomContext(connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
areStatesEqual : (next, prev) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
prev.me.loginEnabled === next.me.loginEnabled &&
|
||||||
|
prev.me.loggedIn === next.me.loggedIn &&
|
||||||
|
prev.me.picture === next.me.picture
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)(withStyles(styles)(ChooseRoom)));
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import * as toolareaActions from '../../actions/toolareaActions';
|
|
||||||
import BuddyImage from '../../images/buddy.svg';
|
|
||||||
|
|
||||||
const styles = () =>
|
|
||||||
({
|
|
||||||
root :
|
|
||||||
{
|
|
||||||
width : '12vmin',
|
|
||||||
height : '9vmin',
|
|
||||||
position : 'absolute',
|
|
||||||
bottom : '3%',
|
|
||||||
right : '3%',
|
|
||||||
color : 'rgba(170, 170, 170, 1)',
|
|
||||||
cursor : 'pointer',
|
|
||||||
backgroundImage : `url(${BuddyImage})`,
|
|
||||||
backgroundColor : 'rgba(42, 75, 88, 1)',
|
|
||||||
backgroundPosition : 'bottom',
|
|
||||||
backgroundSize : 'auto 85%',
|
|
||||||
backgroundRepeat : 'no-repeat',
|
|
||||||
border : 'var(--peer-border)',
|
|
||||||
boxShadow : 'var(--peer-shadow)',
|
|
||||||
textAlign : 'center',
|
|
||||||
verticalAlign : 'middle',
|
|
||||||
lineHeight : '1.8vmin',
|
|
||||||
fontSize : '1.7vmin',
|
|
||||||
fontWeight : 'bolder',
|
|
||||||
animation : 'none',
|
|
||||||
'&.pulse' :
|
|
||||||
{
|
|
||||||
animation : 'pulse 0.5s'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'@keyframes pulse' :
|
|
||||||
{
|
|
||||||
'0%' :
|
|
||||||
{
|
|
||||||
transform : 'scale3d(1, 1, 1)'
|
|
||||||
},
|
|
||||||
'50%' :
|
|
||||||
{
|
|
||||||
transform : 'scale3d(1.2, 1.2, 1.2)'
|
|
||||||
},
|
|
||||||
'100%' :
|
|
||||||
{
|
|
||||||
transform : 'scale3d(1, 1, 1)'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
class HiddenPeers extends React.PureComponent
|
|
||||||
{
|
|
||||||
constructor(props)
|
|
||||||
{
|
|
||||||
super(props);
|
|
||||||
this.state = { className: '' };
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps)
|
|
||||||
{
|
|
||||||
const { hiddenPeersCount } = this.props;
|
|
||||||
|
|
||||||
if (hiddenPeersCount !== prevProps.hiddenPeersCount)
|
|
||||||
{
|
|
||||||
// eslint-disable-next-line react/no-did-update-set-state
|
|
||||||
this.setState({ className: 'pulse' }, () =>
|
|
||||||
{
|
|
||||||
if (this.timeout)
|
|
||||||
{
|
|
||||||
clearTimeout(this.timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.timeout = setTimeout(() =>
|
|
||||||
{
|
|
||||||
this.setState({ className: '' });
|
|
||||||
}, 500);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render()
|
|
||||||
{
|
|
||||||
const {
|
|
||||||
hiddenPeersCount,
|
|
||||||
openUsersTab,
|
|
||||||
classes
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classnames(classes.root, this.state.className)}
|
|
||||||
onClick={() => openUsersTab()}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
+{hiddenPeersCount} <br />
|
|
||||||
<FormattedMessage
|
|
||||||
id='room.hiddenPeers'
|
|
||||||
defaultMessage={
|
|
||||||
`{hiddenPeersCount, plural,
|
|
||||||
one {participant}
|
|
||||||
other {participants}}`
|
|
||||||
}
|
|
||||||
values={{
|
|
||||||
hiddenPeersCount
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HiddenPeers.propTypes =
|
|
||||||
{
|
|
||||||
hiddenPeersCount : PropTypes.number,
|
|
||||||
openUsersTab : PropTypes.func.isRequired,
|
|
||||||
classes : PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
openUsersTab : () =>
|
|
||||||
{
|
|
||||||
dispatch(toolareaActions.openToolArea());
|
|
||||||
dispatch(toolareaActions.setToolTab('users'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
null,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(withStyles(styles)(HiddenPeers));
|
|
||||||
|
|
@ -31,21 +31,28 @@ const styles = (theme) =>
|
||||||
backgroundPosition : 'bottom',
|
backgroundPosition : 'bottom',
|
||||||
backgroundSize : 'auto 85%',
|
backgroundSize : 'auto 85%',
|
||||||
backgroundRepeat : 'no-repeat',
|
backgroundRepeat : 'no-repeat',
|
||||||
'&.webcam' :
|
'&.hover' :
|
||||||
|
{
|
||||||
|
boxShadow : '0px 1px 3px rgba(0, 0, 0, 0.05) inset, 0px 0px 8px rgba(82, 168, 236, 0.9)'
|
||||||
|
},
|
||||||
|
'&.active-speaker' :
|
||||||
|
{
|
||||||
|
// transition : 'filter .2s',
|
||||||
|
// filter : 'grayscale(0)',
|
||||||
|
borderColor : 'var(--active-speaker-border-color)'
|
||||||
|
},
|
||||||
|
'&:not(.active-speaker):not(.screen)' :
|
||||||
|
{
|
||||||
|
// transition : 'filter 10s',
|
||||||
|
// filter : 'grayscale(0.75)'
|
||||||
|
},
|
||||||
|
'&.webcam' :
|
||||||
{
|
{
|
||||||
order : 1
|
order : 1
|
||||||
},
|
},
|
||||||
'&.screen' :
|
'&.screen' :
|
||||||
{
|
{
|
||||||
order : 2
|
order : 2
|
||||||
},
|
|
||||||
'&.hover' :
|
|
||||||
{
|
|
||||||
boxShadow : '0px 1px 3px rgba(0, 0, 0, 0.05) inset, 0px 0px 8px rgba(82, 168, 236, 0.9)'
|
|
||||||
},
|
|
||||||
'&.active-speaker' :
|
|
||||||
{
|
|
||||||
borderColor : 'var(--active-speaker-border-color)'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fab :
|
fab :
|
||||||
|
|
|
||||||
|
|
@ -31,21 +31,28 @@ const styles = (theme) =>
|
||||||
backgroundPosition : 'bottom',
|
backgroundPosition : 'bottom',
|
||||||
backgroundSize : 'auto 85%',
|
backgroundSize : 'auto 85%',
|
||||||
backgroundRepeat : 'no-repeat',
|
backgroundRepeat : 'no-repeat',
|
||||||
'&.webcam' :
|
'&.hover' :
|
||||||
|
{
|
||||||
|
boxShadow : '0px 1px 3px rgba(0, 0, 0, 0.05) inset, 0px 0px 8px rgba(82, 168, 236, 0.9)'
|
||||||
|
},
|
||||||
|
'&.active-speaker' :
|
||||||
|
{
|
||||||
|
// transition : 'filter .2s',
|
||||||
|
// filter : 'grayscale(0)',
|
||||||
|
borderColor : 'var(--active-speaker-border-color)'
|
||||||
|
},
|
||||||
|
'&:not(.active-speaker):not(.screen)' :
|
||||||
|
{
|
||||||
|
// transition : 'filter 10s',
|
||||||
|
// filter : 'grayscale(0.75)'
|
||||||
|
},
|
||||||
|
'&.webcam' :
|
||||||
{
|
{
|
||||||
order : 4
|
order : 4
|
||||||
},
|
},
|
||||||
'&.screen' :
|
'&.screen' :
|
||||||
{
|
{
|
||||||
order : 3
|
order : 3
|
||||||
},
|
|
||||||
'&.hover' :
|
|
||||||
{
|
|
||||||
boxShadow : '0px 1px 3px rgba(0, 0, 0, 0.05) inset, 0px 0px 8px rgba(82, 168, 236, 0.9)'
|
|
||||||
},
|
|
||||||
'&.active-speaker' :
|
|
||||||
{
|
|
||||||
borderColor : 'var(--active-speaker-border-color)'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fab :
|
fab :
|
||||||
|
|
@ -145,16 +152,6 @@ const Peer = (props) =>
|
||||||
!screenConsumer.remotelyPaused
|
!screenConsumer.remotelyPaused
|
||||||
);
|
);
|
||||||
|
|
||||||
let videoProfile;
|
|
||||||
|
|
||||||
if (webcamConsumer)
|
|
||||||
videoProfile = webcamConsumer.profile;
|
|
||||||
|
|
||||||
let screenProfile;
|
|
||||||
|
|
||||||
if (screenConsumer)
|
|
||||||
screenProfile = screenConsumer.profile;
|
|
||||||
|
|
||||||
const smallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
const smallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
const rootStyle =
|
const rootStyle =
|
||||||
|
|
@ -325,11 +322,27 @@ const Peer = (props) =>
|
||||||
peer={peer}
|
peer={peer}
|
||||||
displayName={peer.displayName}
|
displayName={peer.displayName}
|
||||||
showPeerInfo
|
showPeerInfo
|
||||||
|
consumerSpatialLayers={webcamConsumer ? webcamConsumer.spatialLayers : null}
|
||||||
|
consumerTemporalLayers={webcamConsumer ? webcamConsumer.temporalLayers : null}
|
||||||
|
consumerCurrentSpatialLayer={
|
||||||
|
webcamConsumer ? webcamConsumer.currentSpatialLayer : null
|
||||||
|
}
|
||||||
|
consumerCurrentTemporalLayer={
|
||||||
|
webcamConsumer ? webcamConsumer.currentTemporalLayer : null
|
||||||
|
}
|
||||||
|
consumerPreferredSpatialLayer={
|
||||||
|
webcamConsumer ? webcamConsumer.preferredSpatialLayer : null
|
||||||
|
}
|
||||||
|
consumerPreferredTemporalLayer={
|
||||||
|
webcamConsumer ? webcamConsumer.preferredTemporalLayer : null
|
||||||
|
}
|
||||||
|
videoMultiLayer={webcamConsumer && webcamConsumer.type !== 'simple'}
|
||||||
videoTrack={webcamConsumer && webcamConsumer.track}
|
videoTrack={webcamConsumer && webcamConsumer.track}
|
||||||
videoVisible={videoVisible}
|
videoVisible={videoVisible}
|
||||||
videoProfile={videoProfile}
|
|
||||||
audioCodec={micConsumer && micConsumer.codec}
|
audioCodec={micConsumer && micConsumer.codec}
|
||||||
videoCodec={webcamConsumer && webcamConsumer.codec}
|
videoCodec={webcamConsumer && webcamConsumer.codec}
|
||||||
|
audioScore={micConsumer ? micConsumer.score : null}
|
||||||
|
videoScore={webcamConsumer ? webcamConsumer.score : null}
|
||||||
>
|
>
|
||||||
<Volume id={peer.id} />
|
<Volume id={peer.id} />
|
||||||
</VideoView>
|
</VideoView>
|
||||||
|
|
@ -360,109 +373,124 @@ const Peer = (props) =>
|
||||||
}}
|
}}
|
||||||
style={rootStyle}
|
style={rootStyle}
|
||||||
>
|
>
|
||||||
{ !screenVisible &&
|
<div className={classnames(classes.viewContainer)}>
|
||||||
<div className={classes.videoInfo}>
|
{ !screenVisible &&
|
||||||
<p>
|
<div className={classes.videoInfo}>
|
||||||
<FormattedMessage
|
<p>
|
||||||
id='room.videoPaused'
|
<FormattedMessage
|
||||||
defaultMessage='This video is paused'
|
id='room.videoPaused'
|
||||||
/>
|
defaultMessage='This video is paused'
|
||||||
</p>
|
/>
|
||||||
</div>
|
</p>
|
||||||
}
|
</div>
|
||||||
|
}
|
||||||
|
<div
|
||||||
|
className={classnames(classes.controls, hover && 'hover')}
|
||||||
|
onMouseOver={() => setHover(true)}
|
||||||
|
onMouseOut={() => setHover(false)}
|
||||||
|
onTouchStart={() =>
|
||||||
|
{
|
||||||
|
if (touchTimeout)
|
||||||
|
clearTimeout(touchTimeout);
|
||||||
|
|
||||||
|
setHover(true);
|
||||||
|
}}
|
||||||
|
onTouchEnd={() =>
|
||||||
|
{
|
||||||
|
|
||||||
{ screenVisible &&
|
if (touchTimeout)
|
||||||
<div className={classnames(classes.viewContainer)}>
|
clearTimeout(touchTimeout);
|
||||||
<div
|
|
||||||
className={classnames(classes.controls, hover && 'hover')}
|
touchTimeout = setTimeout(() =>
|
||||||
onMouseOver={() => setHover(true)}
|
|
||||||
onMouseOut={() => setHover(false)}
|
|
||||||
onTouchStart={() =>
|
|
||||||
{
|
{
|
||||||
if (touchTimeout)
|
setHover(false);
|
||||||
clearTimeout(touchTimeout);
|
}, 2000);
|
||||||
|
}}
|
||||||
setHover(true);
|
>
|
||||||
}}
|
{ !smallScreen &&
|
||||||
onTouchEnd={() =>
|
|
||||||
{
|
|
||||||
|
|
||||||
if (touchTimeout)
|
|
||||||
clearTimeout(touchTimeout);
|
|
||||||
|
|
||||||
touchTimeout = setTimeout(() =>
|
|
||||||
{
|
|
||||||
setHover(false);
|
|
||||||
}, 2000);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{ !smallScreen &&
|
|
||||||
<Tooltip
|
|
||||||
title={intl.formatMessage({
|
|
||||||
id : 'label.newWindow',
|
|
||||||
defaultMessage : 'New window'
|
|
||||||
})}
|
|
||||||
placement={smallScreen ? 'top' : 'left'}
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
}
|
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={intl.formatMessage({
|
title={intl.formatMessage({
|
||||||
id : 'label.fullscreen',
|
id : 'label.newWindow',
|
||||||
defaultMessage : 'Fullscreen'
|
defaultMessage : 'New window'
|
||||||
})}
|
})}
|
||||||
placement={smallScreen ? 'top' : 'left'}
|
placement={smallScreen ? 'top' : 'left'}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Fab
|
<Fab
|
||||||
aria-label={intl.formatMessage({
|
aria-label={intl.formatMessage({
|
||||||
id : 'label.fullscreen',
|
id : 'label.newWindow',
|
||||||
defaultMessage : 'Fullscreen'
|
defaultMessage : 'New window'
|
||||||
})}
|
})}
|
||||||
className={classes.fab}
|
className={classes.fab}
|
||||||
disabled={!screenVisible}
|
disabled={
|
||||||
|
!screenVisible ||
|
||||||
|
(windowConsumer === screenConsumer.id)
|
||||||
|
}
|
||||||
size={smallButtons ? 'small' : 'large'}
|
size={smallButtons ? 'small' : 'large'}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
{
|
{
|
||||||
toggleConsumerFullscreen(screenConsumer);
|
toggleConsumerWindow(screenConsumer);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FullScreenIcon />
|
<NewWindowIcon />
|
||||||
</Fab>
|
</Fab>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
}
|
||||||
<VideoView
|
|
||||||
advancedMode={advancedMode}
|
<Tooltip
|
||||||
videoContain
|
title={intl.formatMessage({
|
||||||
videoTrack={screenConsumer && screenConsumer.track}
|
id : 'label.fullscreen',
|
||||||
videoVisible={screenVisible}
|
defaultMessage : 'Fullscreen'
|
||||||
videoProfile={screenProfile}
|
})}
|
||||||
videoCodec={screenConsumer && screenConsumer.codec}
|
placement={smallScreen ? 'top' : 'left'}
|
||||||
/>
|
>
|
||||||
|
<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>
|
</div>
|
||||||
}
|
<VideoView
|
||||||
|
advancedMode={advancedMode}
|
||||||
|
videoContain
|
||||||
|
consumerSpatialLayers={
|
||||||
|
screenConsumer ? screenConsumer.spatialLayers : null
|
||||||
|
}
|
||||||
|
consumerTemporalLayers={
|
||||||
|
screenConsumer ? screenConsumer.temporalLayers : null
|
||||||
|
}
|
||||||
|
consumerCurrentSpatialLayer={
|
||||||
|
screenConsumer ? screenConsumer.currentSpatialLayer : null
|
||||||
|
}
|
||||||
|
consumerCurrentTemporalLayer={
|
||||||
|
screenConsumer ? screenConsumer.currentTemporalLayer : null
|
||||||
|
}
|
||||||
|
consumerPreferredSpatialLayer={
|
||||||
|
screenConsumer ? screenConsumer.preferredSpatialLayer : null
|
||||||
|
}
|
||||||
|
consumerPreferredTemporalLayer={
|
||||||
|
screenConsumer ? screenConsumer.preferredTemporalLayer : null
|
||||||
|
}
|
||||||
|
videoMultiLayer={screenConsumer && screenConsumer.type !== 'simple'}
|
||||||
|
videoTrack={screenConsumer && screenConsumer.track}
|
||||||
|
videoVisible={screenVisible}
|
||||||
|
videoCodec={screenConsumer && screenConsumer.codec}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
@ -488,17 +516,17 @@ Peer.propTypes =
|
||||||
theme : PropTypes.object.isRequired
|
theme : PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeMapStateToProps = (initialState, props) =>
|
const makeMapStateToProps = (initialState, { id }) =>
|
||||||
{
|
{
|
||||||
const getPeerConsumers = makePeerConsumerSelector();
|
const getPeerConsumers = makePeerConsumerSelector();
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
peer : state.peers[props.id],
|
peer : state.peers[id],
|
||||||
...getPeerConsumers(state, props),
|
...getPeerConsumers(state, id),
|
||||||
windowConsumer : state.room.windowConsumer,
|
windowConsumer : state.room.windowConsumer,
|
||||||
activeSpeaker : props.id === state.room.activeSpeakerId
|
activeSpeaker : id === state.room.activeSpeakerId
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -190,13 +190,13 @@ SpeakerPeer.propTypes =
|
||||||
classes : PropTypes.object.isRequired
|
classes : PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, props) =>
|
const mapStateToProps = (state, { id }) =>
|
||||||
{
|
{
|
||||||
const getPeerConsumers = makePeerConsumerSelector();
|
const getPeerConsumers = makePeerConsumerSelector();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
peer : state.peers[props.id],
|
peer : state.peers[id],
|
||||||
...getPeerConsumers(state, props)
|
...getPeerConsumers(state, id)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
lobbyPeersKeySelector
|
lobbyPeersKeySelector,
|
||||||
|
peersLengthSelector
|
||||||
} from '../Selectors';
|
} from '../Selectors';
|
||||||
import * as appPropTypes from '../appPropTypes';
|
import * as appPropTypes from '../appPropTypes';
|
||||||
import { withRoomContext } from '../../RoomContext';
|
import { withRoomContext } from '../../RoomContext';
|
||||||
|
|
@ -22,6 +23,7 @@ import FullScreenIcon from '@material-ui/icons/Fullscreen';
|
||||||
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
|
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
|
||||||
import SettingsIcon from '@material-ui/icons/Settings';
|
import SettingsIcon from '@material-ui/icons/Settings';
|
||||||
import SecurityIcon from '@material-ui/icons/Security';
|
import SecurityIcon from '@material-ui/icons/Security';
|
||||||
|
import PeopleIcon from '@material-ui/icons/People';
|
||||||
import LockIcon from '@material-ui/icons/Lock';
|
import LockIcon from '@material-ui/icons/Lock';
|
||||||
import LockOpenIcon from '@material-ui/icons/LockOpen';
|
import LockOpenIcon from '@material-ui/icons/LockOpen';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
|
|
@ -43,6 +45,10 @@ const styles = (theme) =>
|
||||||
display : 'block'
|
display : 'block'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
divider :
|
||||||
|
{
|
||||||
|
marginLeft : theme.spacing(3),
|
||||||
|
},
|
||||||
show :
|
show :
|
||||||
{
|
{
|
||||||
opacity : 1,
|
opacity : 1,
|
||||||
|
|
@ -115,7 +121,9 @@ const TopBar = (props) =>
|
||||||
const {
|
const {
|
||||||
roomClient,
|
roomClient,
|
||||||
room,
|
room,
|
||||||
|
peersLength,
|
||||||
lobbyPeers,
|
lobbyPeers,
|
||||||
|
permanentTopBar,
|
||||||
myPicture,
|
myPicture,
|
||||||
loggedIn,
|
loggedIn,
|
||||||
loginEnabled,
|
loginEnabled,
|
||||||
|
|
@ -125,6 +133,7 @@ const TopBar = (props) =>
|
||||||
setSettingsOpen,
|
setSettingsOpen,
|
||||||
setLockDialogOpen,
|
setLockDialogOpen,
|
||||||
toggleToolArea,
|
toggleToolArea,
|
||||||
|
openUsersTab,
|
||||||
unread,
|
unread,
|
||||||
classes
|
classes
|
||||||
} = props;
|
} = props;
|
||||||
|
|
@ -165,12 +174,13 @@ const TopBar = (props) =>
|
||||||
return (
|
return (
|
||||||
<AppBar
|
<AppBar
|
||||||
position='fixed'
|
position='fixed'
|
||||||
className={room.toolbarsVisible ? classes.show : classes.hide}
|
className={room.toolbarsVisible || permanentTopBar ? classes.show : classes.hide}
|
||||||
>
|
>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<PulsingBadge
|
<PulsingBadge
|
||||||
color='secondary'
|
color='secondary'
|
||||||
badgeContent={unread}
|
badgeContent={unread}
|
||||||
|
onClick={() => toggleToolArea()}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
color='inherit'
|
color='inherit'
|
||||||
|
|
@ -178,23 +188,81 @@ const TopBar = (props) =>
|
||||||
id : 'label.openDrawer',
|
id : 'label.openDrawer',
|
||||||
defaultMessage : 'Open drawer'
|
defaultMessage : 'Open drawer'
|
||||||
})}
|
})}
|
||||||
onClick={() => toggleToolArea()}
|
|
||||||
className={classes.menuButton}
|
className={classes.menuButton}
|
||||||
>
|
>
|
||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</PulsingBadge>
|
</PulsingBadge>
|
||||||
{ window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
|
{ window.config && window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
|
||||||
<Typography
|
<Typography
|
||||||
className={classes.title}
|
className={classes.title}
|
||||||
variant='h6'
|
variant='h6'
|
||||||
color='inherit'
|
color='inherit'
|
||||||
noWrap
|
noWrap
|
||||||
>
|
>
|
||||||
{ window.config.title }
|
{ window.config && window.config.title ? window.config.title : 'Multiparty meeting' }
|
||||||
</Typography>
|
</Typography>
|
||||||
<div className={classes.grow} />
|
<div className={classes.grow} />
|
||||||
<div className={classes.actionButtons}>
|
<div className={classes.actionButtons}>
|
||||||
|
{ 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.participants',
|
||||||
|
defaultMessage : 'Show participants'
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
aria-label={intl.formatMessage({
|
||||||
|
id : 'tooltip.participants',
|
||||||
|
defaultMessage : 'Show participants'
|
||||||
|
})}
|
||||||
|
color='inherit'
|
||||||
|
onClick={() => openUsersTab()}
|
||||||
|
>
|
||||||
|
<Badge
|
||||||
|
color='primary'
|
||||||
|
badgeContent={peersLength + 1}
|
||||||
|
>
|
||||||
|
<PeopleIcon />
|
||||||
|
</Badge>
|
||||||
|
</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>
|
||||||
<Tooltip title={lockTooltip}>
|
<Tooltip title={lockTooltip}>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={intl.formatMessage({
|
aria-label={intl.formatMessage({
|
||||||
|
|
@ -246,43 +314,6 @@ const TopBar = (props) =>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</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 &&
|
{ loginEnabled &&
|
||||||
<Tooltip title={loginTooltip}>
|
<Tooltip title={loginTooltip}>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
@ -305,6 +336,7 @@ const TopBar = (props) =>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
|
<div className={classes.divider} />
|
||||||
<Button
|
<Button
|
||||||
aria-label={intl.formatMessage({
|
aria-label={intl.formatMessage({
|
||||||
id : 'label.leave',
|
id : 'label.leave',
|
||||||
|
|
@ -330,7 +362,9 @@ TopBar.propTypes =
|
||||||
{
|
{
|
||||||
roomClient : PropTypes.object.isRequired,
|
roomClient : PropTypes.object.isRequired,
|
||||||
room : appPropTypes.Room.isRequired,
|
room : appPropTypes.Room.isRequired,
|
||||||
|
peersLength : PropTypes.number,
|
||||||
lobbyPeers : PropTypes.array,
|
lobbyPeers : PropTypes.array,
|
||||||
|
permanentTopBar : PropTypes.bool,
|
||||||
myPicture : PropTypes.string,
|
myPicture : PropTypes.string,
|
||||||
loggedIn : PropTypes.bool.isRequired,
|
loggedIn : PropTypes.bool.isRequired,
|
||||||
loginEnabled : PropTypes.bool.isRequired,
|
loginEnabled : PropTypes.bool.isRequired,
|
||||||
|
|
@ -341,6 +375,7 @@ TopBar.propTypes =
|
||||||
setSettingsOpen : PropTypes.func.isRequired,
|
setSettingsOpen : PropTypes.func.isRequired,
|
||||||
setLockDialogOpen : PropTypes.func.isRequired,
|
setLockDialogOpen : PropTypes.func.isRequired,
|
||||||
toggleToolArea : PropTypes.func.isRequired,
|
toggleToolArea : PropTypes.func.isRequired,
|
||||||
|
openUsersTab : PropTypes.func.isRequired,
|
||||||
unread : PropTypes.number.isRequired,
|
unread : PropTypes.number.isRequired,
|
||||||
classes : PropTypes.object.isRequired,
|
classes : PropTypes.object.isRequired,
|
||||||
theme : PropTypes.object.isRequired
|
theme : PropTypes.object.isRequired
|
||||||
|
|
@ -349,8 +384,9 @@ TopBar.propTypes =
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
({
|
({
|
||||||
room : state.room,
|
room : state.room,
|
||||||
|
peersLength : peersLengthSelector(state),
|
||||||
lobbyPeers : lobbyPeersKeySelector(state),
|
lobbyPeers : lobbyPeersKeySelector(state),
|
||||||
advancedMode : state.settings.advancedMode,
|
permanentTopBar : state.settings.permanentTopBar,
|
||||||
loggedIn : state.me.loggedIn,
|
loggedIn : state.me.loggedIn,
|
||||||
loginEnabled : state.me.loginEnabled,
|
loginEnabled : state.me.loginEnabled,
|
||||||
myPicture : state.me.picture,
|
myPicture : state.me.picture,
|
||||||
|
|
@ -375,6 +411,11 @@ const mapDispatchToProps = (dispatch) =>
|
||||||
toggleToolArea : () =>
|
toggleToolArea : () =>
|
||||||
{
|
{
|
||||||
dispatch(toolareaActions.toggleToolArea());
|
dispatch(toolareaActions.toggleToolArea());
|
||||||
|
},
|
||||||
|
openUsersTab : () =>
|
||||||
|
{
|
||||||
|
dispatch(toolareaActions.openToolArea());
|
||||||
|
dispatch(toolareaActions.setToolTab('users'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -387,7 +428,9 @@ export default withRoomContext(connect(
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
prev.room === next.room &&
|
prev.room === next.room &&
|
||||||
|
prev.peers === next.peers &&
|
||||||
prev.lobbyPeers === next.lobbyPeers &&
|
prev.lobbyPeers === next.lobbyPeers &&
|
||||||
|
prev.settings.permanentTopBar === next.settings.permanentTopBar &&
|
||||||
prev.me.loggedIn === next.me.loggedIn &&
|
prev.me.loggedIn === next.me.loggedIn &&
|
||||||
prev.me.loginEnabled === next.me.loginEnabled &&
|
prev.me.loginEnabled === next.me.loginEnabled &&
|
||||||
prev.me.picture === next.me.picture &&
|
prev.me.picture === next.me.picture &&
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import { withRoomContext } from '../RoomContext';
|
import { withRoomContext } from '../RoomContext';
|
||||||
|
import isElectron from 'is-electron';
|
||||||
import * as settingsActions from '../actions/settingsActions';
|
import * as settingsActions from '../actions/settingsActions';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl, FormattedMessage } from 'react-intl';
|
import { useIntl, FormattedMessage } from 'react-intl';
|
||||||
|
|
@ -27,7 +28,7 @@ const styles = (theme) =>
|
||||||
width : '100%',
|
width : '100%',
|
||||||
height : '100%',
|
height : '100%',
|
||||||
backgroundColor : 'var(--background-color)',
|
backgroundColor : 'var(--background-color)',
|
||||||
backgroundImage : `url(${window.config.background})`,
|
backgroundImage : `url(${window.config ? window.config.background : null})`,
|
||||||
backgroundAttachment : 'fixed',
|
backgroundAttachment : 'fixed',
|
||||||
backgroundPosition : 'center',
|
backgroundPosition : 'center',
|
||||||
backgroundSize : 'cover',
|
backgroundSize : 'cover',
|
||||||
|
|
@ -116,9 +117,9 @@ const DialogTitle = withStyles(styles)((props) =>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MuiDialogTitle disableTypography className={classes.dialogTitle} {...other}>
|
<MuiDialogTitle disableTypography className={classes.dialogTitle} {...other}>
|
||||||
{ window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
|
{ window.config && window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
|
||||||
<Typography variant='h5'>{children}</Typography>
|
<Typography variant='h5'>{children}</Typography>
|
||||||
{ window.config.loginEnabled &&
|
{ window.config && window.config.loginEnabled &&
|
||||||
<Tooltip
|
<Tooltip
|
||||||
onClose={handleTooltipClose}
|
onClose={handleTooltipClose}
|
||||||
onOpen={handleTooltipOpen}
|
onOpen={handleTooltipOpen}
|
||||||
|
|
@ -165,6 +166,7 @@ const DialogActions = withStyles((theme) => ({
|
||||||
const JoinDialog = ({
|
const JoinDialog = ({
|
||||||
roomClient,
|
roomClient,
|
||||||
room,
|
room,
|
||||||
|
roomId,
|
||||||
displayName,
|
displayName,
|
||||||
displayNameInProgress,
|
displayNameInProgress,
|
||||||
loggedIn,
|
loggedIn,
|
||||||
|
|
@ -210,7 +212,7 @@ const JoinDialog = ({
|
||||||
loggedIn ? roomClient.logout() : roomClient.login();
|
loggedIn ? roomClient.logout() : roomClient.login();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ window.config.title }
|
{ window.config && window.config.title ? window.config.title : 'Multiparty meeting' }
|
||||||
<hr />
|
<hr />
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|
@ -226,7 +228,7 @@ const JoinDialog = ({
|
||||||
id='room.roomId'
|
id='room.roomId'
|
||||||
defaultMessage='Room ID: {roomName}'
|
defaultMessage='Room ID: {roomName}'
|
||||||
values={{
|
values={{
|
||||||
roomName : room.name
|
roomName : roomId
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
|
|
@ -275,7 +277,7 @@ const JoinDialog = ({
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
{
|
{
|
||||||
roomClient.join({ joinVideo: false });
|
roomClient.join({ roomId, joinVideo: false });
|
||||||
}}
|
}}
|
||||||
variant='contained'
|
variant='contained'
|
||||||
color='secondary'
|
color='secondary'
|
||||||
|
|
@ -288,7 +290,7 @@ const JoinDialog = ({
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
{
|
{
|
||||||
roomClient.join({ joinVideo: true });
|
roomClient.join({ roomId, joinVideo: true });
|
||||||
}}
|
}}
|
||||||
variant='contained'
|
variant='contained'
|
||||||
color='secondary'
|
color='secondary'
|
||||||
|
|
@ -333,12 +335,17 @@ const JoinDialog = ({
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
}
|
}
|
||||||
|
|
||||||
<CookieConsent>
|
{ !isElectron() &&
|
||||||
<FormattedMessage
|
<CookieConsent buttonText={intl.formatMessage({
|
||||||
id='room.cookieConsent'
|
id : 'room.consentUnderstand',
|
||||||
defaultMessage='This website uses cookies to enhance the user experience'
|
defaultMessage : 'I understand'
|
||||||
/>
|
})}>
|
||||||
</CookieConsent>
|
<FormattedMessage
|
||||||
|
id='room.cookieConsent'
|
||||||
|
defaultMessage='This website uses cookies to enhance the user experience'
|
||||||
|
/>
|
||||||
|
</CookieConsent>
|
||||||
|
}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -348,6 +355,7 @@ JoinDialog.propTypes =
|
||||||
{
|
{
|
||||||
roomClient : PropTypes.any.isRequired,
|
roomClient : PropTypes.any.isRequired,
|
||||||
room : PropTypes.object.isRequired,
|
room : PropTypes.object.isRequired,
|
||||||
|
roomId : PropTypes.string.isRequired,
|
||||||
displayName : PropTypes.string.isRequired,
|
displayName : PropTypes.string.isRequired,
|
||||||
displayNameInProgress : PropTypes.bool.isRequired,
|
displayNameInProgress : PropTypes.bool.isRequired,
|
||||||
loginEnabled : PropTypes.bool.isRequired,
|
loginEnabled : PropTypes.bool.isRequired,
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ const ChatInput = (props) =>
|
||||||
{
|
{
|
||||||
if (message && message !== '')
|
if (message && message !== '')
|
||||||
{
|
{
|
||||||
const sendMessage = this.createNewMessage(message, 'response', displayName, picture);
|
const sendMessage = createNewMessage(message, 'response', displayName, picture);
|
||||||
|
|
||||||
roomClient.sendChatMessage(sendMessage);
|
roomClient.sendChatMessage(sendMessage);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ linkRenderer.link = (href, title, text) =>
|
||||||
title = title ? title : href;
|
title = title ? title : href;
|
||||||
text = text ? text : href;
|
text = text ? text : href;
|
||||||
|
|
||||||
return (`<a target='_blank' href='${ href }' title='${ title }'>${ text }</a>`);
|
return `<a target='_blank' href='${ href }' title='${ title }'>${ text }</a>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = (theme) =>
|
const styles = (theme) =>
|
||||||
|
|
@ -81,7 +81,11 @@ const Message = (props) =>
|
||||||
marked.parse(
|
marked.parse(
|
||||||
text,
|
text,
|
||||||
{ renderer: linkRenderer }
|
{ renderer: linkRenderer }
|
||||||
)
|
),
|
||||||
|
{
|
||||||
|
ALLOWED_TAGS : [ 'a' ],
|
||||||
|
ALLOWED_ATTR : [ 'href', 'target', 'title' ]
|
||||||
|
}
|
||||||
) }}
|
) }}
|
||||||
/>
|
/>
|
||||||
<Typography variant='caption'>{self ? 'Me' : name} - {time}</Typography>
|
<Typography variant='caption'>{self ? 'Me' : name} - {time}</Typography>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import * as appPropTypes from '../../appPropTypes';
|
import * as appPropTypes from '../../appPropTypes';
|
||||||
import { withRoomContext } from '../../../RoomContext';
|
import { withRoomContext } from '../../../RoomContext';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
import MicIcon from '@material-ui/icons/Mic';
|
import MicIcon from '@material-ui/icons/Mic';
|
||||||
import MicOffIcon from '@material-ui/icons/MicOff';
|
import MicOffIcon from '@material-ui/icons/MicOff';
|
||||||
import ScreenIcon from '@material-ui/icons/ScreenShare';
|
import ScreenIcon from '@material-ui/icons/ScreenShare';
|
||||||
|
|
@ -128,6 +130,8 @@ const styles = (theme) =>
|
||||||
|
|
||||||
const ListPeer = (props) =>
|
const ListPeer = (props) =>
|
||||||
{
|
{
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
roomClient,
|
roomClient,
|
||||||
peer,
|
peer,
|
||||||
|
|
@ -174,47 +178,49 @@ const ListPeer = (props) =>
|
||||||
{children}
|
{children}
|
||||||
<div className={classes.controls}>
|
<div className={classes.controls}>
|
||||||
{ screenConsumer &&
|
{ screenConsumer &&
|
||||||
<div
|
<IconButton
|
||||||
className={classnames(classes.button, 'screen', {
|
aria-label={intl.formatMessage({
|
||||||
on : screenVisible,
|
id : 'tooltip.muteScreenSharing',
|
||||||
off : !screenVisible,
|
defaultMessage : 'Mute participant share'
|
||||||
disabled : peer.peerScreenInProgress
|
|
||||||
})}
|
})}
|
||||||
|
color={ screenVisible ? 'primary' : 'secondary'}
|
||||||
|
disabled={ peer.peerScreenInProgress }
|
||||||
onClick={(e) =>
|
onClick={(e) =>
|
||||||
{
|
{
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
screenVisible ?
|
screenVisible ?
|
||||||
roomClient.modifyPeerConsumer(peer.id, 'screen', true) :
|
roomClient.modifyPeerConsumer(peer.id, 'screen', true) :
|
||||||
roomClient.modifyPeerConsumer(peer.id, 'screen', false);
|
roomClient.modifyPeerConsumer(peer.id, 'screen', false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ screenVisible ?
|
{ screenVisible ?
|
||||||
<ScreenIcon />
|
<ScreenIcon />
|
||||||
:
|
:
|
||||||
<ScreenOffIcon />
|
<ScreenOffIcon />
|
||||||
}
|
}
|
||||||
</div>
|
</IconButton>
|
||||||
}
|
}
|
||||||
<div
|
<IconButton
|
||||||
className={classnames(classes.button, 'mic', {
|
aria-label={intl.formatMessage({
|
||||||
on : micEnabled,
|
id : 'tooltip.muteParticipant',
|
||||||
off : !micEnabled,
|
defaultMessage : 'Mute participant'
|
||||||
disabled : peer.peerAudioInProgress
|
|
||||||
})}
|
})}
|
||||||
|
color={ micEnabled ? 'primary' : 'secondary'}
|
||||||
|
disabled={ peer.peerAudioInProgress }
|
||||||
onClick={(e) =>
|
onClick={(e) =>
|
||||||
{
|
{
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
micEnabled ?
|
micEnabled ?
|
||||||
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
|
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
|
||||||
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
|
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ micEnabled ?
|
{ micEnabled ?
|
||||||
<MicIcon />
|
<MicIcon />
|
||||||
:
|
:
|
||||||
<MicOffIcon />
|
<MicOffIcon />
|
||||||
}
|
}
|
||||||
</div>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -232,15 +238,15 @@ ListPeer.propTypes =
|
||||||
classes : PropTypes.object.isRequired
|
classes : PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeMapStateToProps = (initialState, props) =>
|
const makeMapStateToProps = (initialState, { id }) =>
|
||||||
{
|
{
|
||||||
const getPeerConsumers = makePeerConsumerSelector();
|
const getPeerConsumers = makePeerConsumerSelector();
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
peer : state.peers[props.id],
|
peer : state.peers[id],
|
||||||
...getPeerConsumers(state, props)
|
...getPeerConsumers(state, id)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,16 +100,16 @@ class ParticipantList extends React.PureComponent
|
||||||
defaultMessage='Participants in Spotlight'
|
defaultMessage='Participants in Spotlight'
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
{ spotlightPeers.map((peer) => (
|
{ spotlightPeers.map((peerId) => (
|
||||||
<li
|
<li
|
||||||
key={peer.id}
|
key={peerId}
|
||||||
className={classNames(classes.listItem, {
|
className={classNames(classes.listItem, {
|
||||||
selected : peer.id === selectedPeerId
|
selected : peerId === selectedPeerId
|
||||||
})}
|
})}
|
||||||
onClick={() => roomClient.setSelectedPeer(peer.id)}
|
onClick={() => roomClient.setSelectedPeer(peerId)}
|
||||||
>
|
>
|
||||||
<ListPeer id={peer.id} advancedMode={advancedMode}>
|
<ListPeer id={peerId} advancedMode={advancedMode}>
|
||||||
<Volume small id={peer.id} />
|
<Volume small id={peerId} />
|
||||||
</ListPeer>
|
</ListPeer>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,13 @@ import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
spotlightPeersSelector,
|
spotlightPeersSelector,
|
||||||
peersLengthSelector,
|
videoBoxesSelector
|
||||||
videoBoxesSelector,
|
|
||||||
spotlightsLengthSelector
|
|
||||||
} from '../Selectors';
|
} from '../Selectors';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import Peer from '../Containers/Peer';
|
import Peer from '../Containers/Peer';
|
||||||
import Me from '../Containers/Me';
|
import Me from '../Containers/Me';
|
||||||
import HiddenPeers from '../Containers/HiddenPeers';
|
|
||||||
|
|
||||||
const RATIO = 1.334;
|
const RATIO = 1.334;
|
||||||
const PADDING_V = 50;
|
const PADDING_V = 50;
|
||||||
|
|
@ -71,7 +68,7 @@ class Democratic extends React.PureComponent
|
||||||
|
|
||||||
const width = this.peersRef.current.clientWidth - PADDING_H;
|
const width = this.peersRef.current.clientWidth - PADDING_H;
|
||||||
const height = this.peersRef.current.clientHeight -
|
const height = this.peersRef.current.clientHeight -
|
||||||
(this.props.toolbarsVisible ? PADDING_V : PADDING_H);
|
(this.props.toolbarsVisible || this.props.permanentTopBar ? PADDING_V : PADDING_H);
|
||||||
|
|
||||||
let x, y, space;
|
let x, y, space;
|
||||||
|
|
||||||
|
|
@ -91,11 +88,11 @@ class Democratic extends React.PureComponent
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Math.ceil(this.state.peerWidth) !== Math.ceil(0.9 * x))
|
if (Math.ceil(this.state.peerWidth) !== Math.ceil(0.94 * x))
|
||||||
{
|
{
|
||||||
this.setState({
|
this.setState({
|
||||||
peerWidth : 0.95 * x,
|
peerWidth : 0.94 * x,
|
||||||
peerHeight : 0.95 * y
|
peerHeight : 0.94 * y
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -130,10 +127,9 @@ class Democratic extends React.PureComponent
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
advancedMode,
|
advancedMode,
|
||||||
peersLength,
|
|
||||||
spotlightsPeers,
|
spotlightsPeers,
|
||||||
spotlightsLength,
|
|
||||||
toolbarsVisible,
|
toolbarsVisible,
|
||||||
|
permanentTopBar,
|
||||||
classes
|
classes
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
|
@ -147,7 +143,7 @@ class Democratic extends React.PureComponent
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
classes.root,
|
classes.root,
|
||||||
toolbarsVisible ? classes.showingToolBar : classes.hiddenToolBar
|
toolbarsVisible || permanentTopBar ? classes.showingToolBar : classes.hiddenToolBar
|
||||||
)}
|
)}
|
||||||
ref={this.peersRef}
|
ref={this.peersRef}
|
||||||
>
|
>
|
||||||
|
|
@ -160,19 +156,14 @@ class Democratic extends React.PureComponent
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<Peer
|
<Peer
|
||||||
key={peer.id}
|
key={peer}
|
||||||
advancedMode={advancedMode}
|
advancedMode={advancedMode}
|
||||||
id={peer.id}
|
id={peer}
|
||||||
spacing={6}
|
spacing={6}
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{ spotlightsLength < peersLength &&
|
|
||||||
<HiddenPeers
|
|
||||||
hiddenPeersCount={peersLength - spotlightsLength}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -181,22 +172,20 @@ class Democratic extends React.PureComponent
|
||||||
Democratic.propTypes =
|
Democratic.propTypes =
|
||||||
{
|
{
|
||||||
advancedMode : PropTypes.bool,
|
advancedMode : PropTypes.bool,
|
||||||
peersLength : PropTypes.number,
|
|
||||||
boxes : PropTypes.number,
|
boxes : PropTypes.number,
|
||||||
spotlightsLength : PropTypes.number,
|
|
||||||
spotlightsPeers : PropTypes.array.isRequired,
|
spotlightsPeers : PropTypes.array.isRequired,
|
||||||
toolbarsVisible : PropTypes.bool.isRequired,
|
toolbarsVisible : PropTypes.bool.isRequired,
|
||||||
|
permanentTopBar : PropTypes.bool,
|
||||||
classes : PropTypes.object.isRequired
|
classes : PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
peersLength : peersLengthSelector(state),
|
boxes : videoBoxesSelector(state),
|
||||||
boxes : videoBoxesSelector(state),
|
spotlightsPeers : spotlightPeersSelector(state),
|
||||||
spotlightsPeers : spotlightPeersSelector(state),
|
toolbarsVisible : state.room.toolbarsVisible,
|
||||||
spotlightsLength : spotlightsLengthSelector(state),
|
permanentTopBar : state.settings.permanentTopBar
|
||||||
toolbarsVisible : state.room.toolbarsVisible
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -212,7 +201,8 @@ export default connect(
|
||||||
prev.producers === next.producers &&
|
prev.producers === next.producers &&
|
||||||
prev.consumers === next.consumers &&
|
prev.consumers === next.consumers &&
|
||||||
prev.room.spotlights === next.room.spotlights &&
|
prev.room.spotlights === next.room.spotlights &&
|
||||||
prev.room.toolbarsVisible === next.room.toolbarsVisible
|
prev.room.toolbarsVisible === next.room.toolbarsVisible &&
|
||||||
|
prev.settings.permanentTopBar === next.settings.permanentTopBar
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,12 @@ import { connect } from 'react-redux';
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import {
|
import {
|
||||||
spotlightsLengthSelector,
|
|
||||||
videoBoxesSelector
|
videoBoxesSelector
|
||||||
} from '../Selectors';
|
} from '../Selectors';
|
||||||
import { withRoomContext } from '../../RoomContext';
|
import { withRoomContext } from '../../RoomContext';
|
||||||
import Me from '../Containers/Me';
|
import Me from '../Containers/Me';
|
||||||
import Peer from '../Containers/Peer';
|
import Peer from '../Containers/Peer';
|
||||||
import SpeakerPeer from '../Containers/SpeakerPeer';
|
import SpeakerPeer from '../Containers/SpeakerPeer';
|
||||||
import HiddenPeers from '../Containers/HiddenPeers';
|
|
||||||
import Grid from '@material-ui/core/Grid';
|
import Grid from '@material-ui/core/Grid';
|
||||||
|
|
||||||
const styles = () =>
|
const styles = () =>
|
||||||
|
|
@ -207,7 +205,6 @@ class Filmstrip extends React.PureComponent
|
||||||
myId,
|
myId,
|
||||||
advancedMode,
|
advancedMode,
|
||||||
spotlights,
|
spotlights,
|
||||||
spotlightsLength,
|
|
||||||
classes
|
classes
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
|
@ -284,12 +281,6 @@ class Filmstrip extends React.PureComponent
|
||||||
})}
|
})}
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ spotlightsLength<Object.keys(peers).length &&
|
|
||||||
<HiddenPeers
|
|
||||||
hiddenPeersCount={Object.keys(peers).length-spotlightsLength}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -303,7 +294,6 @@ Filmstrip.propTypes = {
|
||||||
consumers : PropTypes.object.isRequired,
|
consumers : PropTypes.object.isRequired,
|
||||||
myId : PropTypes.string.isRequired,
|
myId : PropTypes.string.isRequired,
|
||||||
selectedPeerId : PropTypes.string,
|
selectedPeerId : PropTypes.string,
|
||||||
spotlightsLength : PropTypes.number,
|
|
||||||
spotlights : PropTypes.array.isRequired,
|
spotlights : PropTypes.array.isRequired,
|
||||||
boxes : PropTypes.number,
|
boxes : PropTypes.number,
|
||||||
classes : PropTypes.object.isRequired
|
classes : PropTypes.object.isRequired
|
||||||
|
|
@ -318,8 +308,7 @@ const mapStateToProps = (state) =>
|
||||||
consumers : state.consumers,
|
consumers : state.consumers,
|
||||||
myId : state.me.id,
|
myId : state.me.id,
|
||||||
spotlights : state.room.spotlights,
|
spotlights : state.room.spotlights,
|
||||||
spotlightsLength : spotlightsLengthSelector(state),
|
boxes : videoBoxesSelector(state)
|
||||||
boxes : videoBoxesSelector(state),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as appPropTypes from './appPropTypes';
|
import * as appPropTypes from './appPropTypes';
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
|
import isElectron from 'is-electron';
|
||||||
import * as roomActions from '../actions/roomActions';
|
import * as roomActions from '../actions/roomActions';
|
||||||
import * as toolareaActions from '../actions/toolareaActions';
|
import * as toolareaActions from '../actions/toolareaActions';
|
||||||
import { idle } from '../utils';
|
import { idle } from '../utils';
|
||||||
|
|
@ -33,7 +34,7 @@ const styles = (theme) =>
|
||||||
width : '100%',
|
width : '100%',
|
||||||
height : '100%',
|
height : '100%',
|
||||||
backgroundColor : 'var(--background-color)',
|
backgroundColor : 'var(--background-color)',
|
||||||
backgroundImage : `url(${window.config.background})`,
|
backgroundImage : `url(${window.config ? window.config.background : null})`,
|
||||||
backgroundAttachment : 'fixed',
|
backgroundAttachment : 'fixed',
|
||||||
backgroundPosition : 'center',
|
backgroundPosition : 'center',
|
||||||
backgroundSize : 'cover',
|
backgroundSize : 'cover',
|
||||||
|
|
@ -152,12 +153,21 @@ class Room extends React.PureComponent
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<CookieConsent>
|
{ !isElectron() &&
|
||||||
<FormattedMessage
|
<CookieConsent
|
||||||
id='room.cookieConsent'
|
buttonText={
|
||||||
defaultMessage='This website uses cookies to enhance the user experience'
|
<FormattedMessage
|
||||||
/>
|
id = 'room.consentUnderstand'
|
||||||
</CookieConsent>
|
defaultMessage = 'I understand'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id = 'room.cookieConsent'
|
||||||
|
defaultMessage='This website uses cookies to enhance the user experience'
|
||||||
|
/>
|
||||||
|
</CookieConsent>
|
||||||
|
}
|
||||||
|
|
||||||
<FullScreenView advancedMode={advancedMode} />
|
<FullScreenView advancedMode={advancedMode} />
|
||||||
|
|
||||||
|
|
@ -194,9 +204,13 @@ class Room extends React.PureComponent
|
||||||
|
|
||||||
<View advancedMode={advancedMode} />
|
<View advancedMode={advancedMode} />
|
||||||
|
|
||||||
<LockDialog />
|
{ room.lockDialogOpen &&
|
||||||
|
<LockDialog />
|
||||||
|
}
|
||||||
|
|
||||||
<Settings />
|
{ room.settingsOpen &&
|
||||||
|
<Settings />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ const consumersSelect = (state) => state.consumers;
|
||||||
const spotlightsSelector = (state) => state.room.spotlights;
|
const spotlightsSelector = (state) => state.room.spotlights;
|
||||||
const peersSelector = (state) => state.peers;
|
const peersSelector = (state) => state.peers;
|
||||||
const lobbyPeersSelector = (state) => state.lobbyPeers;
|
const lobbyPeersSelector = (state) => state.lobbyPeers;
|
||||||
const getPeerConsumers = (state, props) =>
|
const getPeerConsumers = (state, id) =>
|
||||||
(state.peers[props.id] ? state.peers[props.id].consumers : null);
|
(state.peers[id] ? state.peers[id].consumers : null);
|
||||||
const getAllConsumers = (state) => state.consumers;
|
const getAllConsumers = (state) => state.consumers;
|
||||||
const peersKeySelector = createSelector(
|
const peersKeySelector = createSelector(
|
||||||
peersSelector,
|
peersSelector,
|
||||||
|
|
@ -70,15 +70,8 @@ export const spotlightsLengthSelector = createSelector(
|
||||||
|
|
||||||
export const spotlightPeersSelector = createSelector(
|
export const spotlightPeersSelector = createSelector(
|
||||||
spotlightsSelector,
|
spotlightsSelector,
|
||||||
peersSelector,
|
peersKeySelector,
|
||||||
(spotlights, peers) =>
|
(spotlights, peers) => peers.filter((peerId) => spotlights.includes(peerId))
|
||||||
spotlights.reduce((result, peerId) =>
|
|
||||||
{
|
|
||||||
if (peers[peerId])
|
|
||||||
result.push(peers[peerId]);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}, [])
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const peersLengthSelector = createSelector(
|
export const peersLengthSelector = createSelector(
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ const Settings = ({
|
||||||
me,
|
me,
|
||||||
settings,
|
settings,
|
||||||
onToggleAdvancedMode,
|
onToggleAdvancedMode,
|
||||||
|
onTogglePermanentTopBar,
|
||||||
handleCloseSettings,
|
handleCloseSettings,
|
||||||
handleChangeMode,
|
handleChangeMode,
|
||||||
classes
|
classes
|
||||||
|
|
@ -296,6 +297,48 @@ const Settings = ({
|
||||||
defaultMessage : 'Advanced mode'
|
defaultMessage : 'Advanced mode'
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
{ settings.advancedMode &&
|
||||||
|
<React.Fragment>
|
||||||
|
<form className={classes.setting} autoComplete='off'>
|
||||||
|
<FormControl className={classes.formControl}>
|
||||||
|
<Select
|
||||||
|
value={settings.lastN || ''}
|
||||||
|
onChange={(event) =>
|
||||||
|
{
|
||||||
|
if (event.target.value)
|
||||||
|
roomClient.changeMaxSpotlights(event.target.value);
|
||||||
|
}}
|
||||||
|
name='Last N'
|
||||||
|
autoWidth
|
||||||
|
className={classes.selectEmpty}
|
||||||
|
>
|
||||||
|
{ [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ].map((lastN) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<MenuItem key={lastN} value={lastN}>
|
||||||
|
{lastN}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
<FormHelperText>
|
||||||
|
<FormattedMessage
|
||||||
|
id='settings.lastn'
|
||||||
|
defaultMessage='Number of visible videos'
|
||||||
|
/>
|
||||||
|
</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
</form>
|
||||||
|
<FormControlLabel
|
||||||
|
className={classes.setting}
|
||||||
|
control={<Checkbox checked={settings.permanentTopBar} onChange={onTogglePermanentTopBar} value='permanentTopBar' />}
|
||||||
|
label={intl.formatMessage({
|
||||||
|
id : 'settings.permanentTopBar',
|
||||||
|
defaultMessage : 'Permanent top bar'
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => handleCloseSettings({ settingsOpen: false })} color='primary'>
|
<Button onClick={() => handleCloseSettings({ settingsOpen: false })} color='primary'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
|
@ -315,6 +358,7 @@ Settings.propTypes =
|
||||||
room : appPropTypes.Room.isRequired,
|
room : appPropTypes.Room.isRequired,
|
||||||
settings : PropTypes.object.isRequired,
|
settings : PropTypes.object.isRequired,
|
||||||
onToggleAdvancedMode : PropTypes.func.isRequired,
|
onToggleAdvancedMode : PropTypes.func.isRequired,
|
||||||
|
onTogglePermanentTopBar : PropTypes.func.isRequired,
|
||||||
handleChangeMode : PropTypes.func.isRequired,
|
handleChangeMode : PropTypes.func.isRequired,
|
||||||
handleCloseSettings : PropTypes.func.isRequired,
|
handleCloseSettings : PropTypes.func.isRequired,
|
||||||
classes : PropTypes.object.isRequired
|
classes : PropTypes.object.isRequired
|
||||||
|
|
@ -331,6 +375,7 @@ const mapStateToProps = (state) =>
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
onToggleAdvancedMode : settingsActions.toggleAdvancedMode,
|
onToggleAdvancedMode : settingsActions.toggleAdvancedMode,
|
||||||
|
onTogglePermanentTopBar : settingsActions.togglePermanentTopBar,
|
||||||
handleChangeMode : roomActions.setDisplayMode,
|
handleChangeMode : roomActions.setDisplayMode,
|
||||||
handleCloseSettings : roomActions.setSettingsOpen
|
handleCloseSettings : roomActions.setSettingsOpen
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ const FullScreenView = (props) =>
|
||||||
consumer,
|
consumer,
|
||||||
toggleConsumerFullscreen,
|
toggleConsumerFullscreen,
|
||||||
toolbarsVisible,
|
toolbarsVisible,
|
||||||
|
permanentTopBar,
|
||||||
classes
|
classes
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
|
@ -105,7 +106,7 @@ const FullScreenView = (props) =>
|
||||||
<div className={classes.controls}>
|
<div className={classes.controls}>
|
||||||
<div
|
<div
|
||||||
className={classnames(classes.button, {
|
className={classnames(classes.button, {
|
||||||
visible : toolbarsVisible
|
visible : toolbarsVisible || permanentTopBar
|
||||||
})}
|
})}
|
||||||
onClick={(e) =>
|
onClick={(e) =>
|
||||||
{
|
{
|
||||||
|
|
@ -134,13 +135,15 @@ FullScreenView.propTypes =
|
||||||
consumer : appPropTypes.Consumer,
|
consumer : appPropTypes.Consumer,
|
||||||
toggleConsumerFullscreen : PropTypes.func.isRequired,
|
toggleConsumerFullscreen : PropTypes.func.isRequired,
|
||||||
toolbarsVisible : PropTypes.bool,
|
toolbarsVisible : PropTypes.bool,
|
||||||
|
permanentTopBar : PropTypes.bool,
|
||||||
classes : PropTypes.object.isRequired
|
classes : PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
({
|
({
|
||||||
consumer : state.consumers[state.room.fullScreenConsumer],
|
consumer : state.consumers[state.room.fullScreenConsumer],
|
||||||
toolbarsVisible : state.room.toolbarsVisible
|
toolbarsVisible : state.room.toolbarsVisible,
|
||||||
|
permanentTopBar : state.settings.permanentTopBar
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) =>
|
const mapDispatchToProps = (dispatch) =>
|
||||||
|
|
@ -162,7 +165,8 @@ export default connect(
|
||||||
return (
|
return (
|
||||||
prev.consumers[prev.room.fullScreenConsumer] ===
|
prev.consumers[prev.room.fullScreenConsumer] ===
|
||||||
next.consumers[next.room.fullScreenConsumer] &&
|
next.consumers[next.room.fullScreenConsumer] &&
|
||||||
prev.room.toolbarsVisible === next.room.toolbarsVisible
|
prev.room.toolbarsVisible === next.room.toolbarsVisible &&
|
||||||
|
prev.settings.permanentTopBar === next.settings.permanentTopBar
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,15 @@ class VideoView extends React.PureComponent
|
||||||
videoContain,
|
videoContain,
|
||||||
advancedMode,
|
advancedMode,
|
||||||
videoVisible,
|
videoVisible,
|
||||||
videoProfile,
|
videoMultiLayer,
|
||||||
|
// audioScore,
|
||||||
|
// videoScore,
|
||||||
|
// consumerSpatialLayers,
|
||||||
|
// consumerTemporalLayers,
|
||||||
|
consumerCurrentSpatialLayer,
|
||||||
|
consumerCurrentTemporalLayer,
|
||||||
|
consumerPreferredSpatialLayer,
|
||||||
|
consumerPreferredTemporalLayer,
|
||||||
audioCodec,
|
audioCodec,
|
||||||
videoCodec,
|
videoCodec,
|
||||||
onChangeDisplayName,
|
onChangeDisplayName,
|
||||||
|
|
@ -161,7 +169,19 @@ class VideoView extends React.PureComponent
|
||||||
<div className={classes.box}>
|
<div className={classes.box}>
|
||||||
{ audioCodec && <p>{audioCodec}</p> }
|
{ audioCodec && <p>{audioCodec}</p> }
|
||||||
|
|
||||||
{ videoCodec && <p>{videoCodec} {videoProfile}</p> }
|
{ videoCodec &&
|
||||||
|
<p>
|
||||||
|
{videoCodec}
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
{ videoMultiLayer &&
|
||||||
|
<p>
|
||||||
|
{`current spatial-temporal layers: ${consumerCurrentSpatialLayer} ${consumerCurrentTemporalLayer}`}
|
||||||
|
<br />
|
||||||
|
{`preferred spatial-temporal layers: ${consumerPreferredSpatialLayer} ${consumerPreferredTemporalLayer}`}
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
{ (videoVisible && videoWidth !== null) &&
|
{ (videoVisible && videoWidth !== null) &&
|
||||||
<p>{videoWidth}x{videoHeight}</p>
|
<p>{videoWidth}x{videoHeight}</p>
|
||||||
|
|
@ -202,12 +222,10 @@ class VideoView extends React.PureComponent
|
||||||
className={classnames(classes.video, {
|
className={classnames(classes.video, {
|
||||||
hidden : !videoVisible,
|
hidden : !videoVisible,
|
||||||
'isMe' : isMe && !isScreen,
|
'isMe' : isMe && !isScreen,
|
||||||
loading : videoProfile === 'none',
|
|
||||||
contain : videoContain
|
contain : videoContain
|
||||||
})}
|
})}
|
||||||
autoPlay
|
autoPlay
|
||||||
playsInline
|
playsInline
|
||||||
muted={isMe}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
|
|
@ -293,20 +311,28 @@ class VideoView extends React.PureComponent
|
||||||
|
|
||||||
VideoView.propTypes =
|
VideoView.propTypes =
|
||||||
{
|
{
|
||||||
isMe : PropTypes.bool,
|
isMe : PropTypes.bool,
|
||||||
isScreen : PropTypes.bool,
|
isScreen : PropTypes.bool,
|
||||||
displayName : PropTypes.string,
|
displayName : PropTypes.string,
|
||||||
showPeerInfo : PropTypes.bool,
|
showPeerInfo : PropTypes.bool,
|
||||||
videoContain : PropTypes.bool,
|
videoContain : PropTypes.bool,
|
||||||
advancedMode : PropTypes.bool,
|
advancedMode : PropTypes.bool,
|
||||||
videoTrack : PropTypes.any,
|
videoTrack : PropTypes.any,
|
||||||
videoVisible : PropTypes.bool.isRequired,
|
videoVisible : PropTypes.bool.isRequired,
|
||||||
videoProfile : PropTypes.string,
|
consumerSpatialLayers : PropTypes.number,
|
||||||
audioCodec : PropTypes.string,
|
consumerTemporalLayers : PropTypes.number,
|
||||||
videoCodec : PropTypes.string,
|
consumerCurrentSpatialLayer : PropTypes.number,
|
||||||
onChangeDisplayName : PropTypes.func,
|
consumerCurrentTemporalLayer : PropTypes.number,
|
||||||
children : PropTypes.object,
|
consumerPreferredSpatialLayer : PropTypes.number,
|
||||||
classes : PropTypes.object.isRequired
|
consumerPreferredTemporalLayer : PropTypes.number,
|
||||||
|
videoMultiLayer : PropTypes.bool,
|
||||||
|
audioScore : PropTypes.any,
|
||||||
|
videoScore : PropTypes.any,
|
||||||
|
audioCodec : PropTypes.string,
|
||||||
|
videoCodec : PropTypes.string,
|
||||||
|
onChangeDisplayName : PropTypes.func,
|
||||||
|
children : PropTypes.object,
|
||||||
|
classes : PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withStyles(styles)(VideoView);
|
export default withStyles(styles)(VideoView);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
|
||||||
|
|
||||||
export const Room = PropTypes.shape(
|
export const Room = PropTypes.shape(
|
||||||
{
|
{
|
||||||
url : PropTypes.string.isRequired,
|
|
||||||
state : PropTypes.oneOf(
|
state : PropTypes.oneOf(
|
||||||
[ 'new', 'connecting', 'connected', 'closed' ]).isRequired,
|
[ 'new', 'connecting', 'connected', 'closed' ]).isRequired,
|
||||||
activeSpeakerId : PropTypes.string
|
activeSpeakerId : PropTypes.string
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
const electron = require('electron');
|
||||||
|
|
||||||
|
const app = electron.app;
|
||||||
|
|
||||||
|
const BrowserWindow = electron.BrowserWindow;
|
||||||
|
const Menu = electron.Menu;
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const url = require('url');
|
||||||
|
|
||||||
|
let mainWindow;
|
||||||
|
|
||||||
|
function createWindow()
|
||||||
|
{
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
width : 1280,
|
||||||
|
height : 720,
|
||||||
|
webPreferences : { nodeIntegration: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
Menu.setApplicationMenu(null);
|
||||||
|
|
||||||
|
const startUrl = process.env.ELECTRON_START_URL || url.format({
|
||||||
|
pathname : path.join(__dirname, '/../build/index.html'),
|
||||||
|
protocol : 'file:',
|
||||||
|
slashes : true
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.loadURL(startUrl);
|
||||||
|
|
||||||
|
mainWindow.on('closed', () =>
|
||||||
|
{
|
||||||
|
mainWindow = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
app.on('ready', createWindow);
|
||||||
|
|
||||||
|
app.on('window-all-closed', () =>
|
||||||
|
{
|
||||||
|
if (process.platform !== 'darwin')
|
||||||
|
{
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('activate', () =>
|
||||||
|
{
|
||||||
|
if (mainWindow === null)
|
||||||
|
{
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
const net = require('net');
|
||||||
|
const port = process.env.PORT ? (process.env.PORT - 100) : 3000;
|
||||||
|
|
||||||
|
process.env.ELECTRON_START_URL = `http://localhost:${port}`;
|
||||||
|
|
||||||
|
const client = new net.Socket();
|
||||||
|
|
||||||
|
let startedElectron = false;
|
||||||
|
|
||||||
|
const tryConnection = () =>
|
||||||
|
client.connect({ port: port }, () =>
|
||||||
|
{
|
||||||
|
client.end();
|
||||||
|
|
||||||
|
if (!startedElectron)
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('starting electron');
|
||||||
|
|
||||||
|
startedElectron = true;
|
||||||
|
|
||||||
|
const exec = require('child_process').exec;
|
||||||
|
|
||||||
|
const electron = exec('npm run electron');
|
||||||
|
|
||||||
|
electron.stdout.on('data', (data) =>
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`stdout: ${data.toString()}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tryConnection();
|
||||||
|
|
||||||
|
client.on('error', () =>
|
||||||
|
{
|
||||||
|
setTimeout(tryConnection, 1000);
|
||||||
|
});
|
||||||
|
|
@ -1,35 +1,61 @@
|
||||||
import domready from 'domready';
|
import domready from 'domready';
|
||||||
import React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
import isElectron from 'is-electron';
|
||||||
import { createIntl, createIntlCache, RawIntlProvider } from 'react-intl';
|
import { createIntl, createIntlCache, RawIntlProvider } from 'react-intl';
|
||||||
|
import { Route, HashRouter, BrowserRouter } from 'react-router-dom';
|
||||||
import randomString from 'random-string';
|
import randomString from 'random-string';
|
||||||
import Logger from './Logger';
|
import Logger from './Logger';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import RoomClient from './RoomClient';
|
import RoomClient from './RoomClient';
|
||||||
import RoomContext from './RoomContext';
|
import RoomContext from './RoomContext';
|
||||||
import deviceInfo from './deviceInfo';
|
import deviceInfo from './deviceInfo';
|
||||||
import * as roomActions from './actions/roomActions';
|
|
||||||
import * as meActions from './actions/meActions';
|
import * as meActions from './actions/meActions';
|
||||||
import App from './components/App';
|
import ChooseRoom from './components/ChooseRoom';
|
||||||
import LoadingView from './components/LoadingView';
|
import LoadingView from './components/LoadingView';
|
||||||
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
|
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
|
||||||
import { PersistGate } from 'redux-persist/lib/integration/react';
|
import { PersistGate } from 'redux-persist/lib/integration/react';
|
||||||
import { persistor, store } from './store';
|
import { persistor, store } from './store';
|
||||||
import { SnackbarProvider } from 'notistack';
|
import { SnackbarProvider } from 'notistack';
|
||||||
import * as serviceWorker from './serviceWorker';
|
import * as serviceWorker from './serviceWorker';
|
||||||
|
import { ReactLazyPreload } from './components/ReactLazyPreload';
|
||||||
|
|
||||||
import messagesEnglish from './translations/en';
|
// import messagesEnglish from './translations/en';
|
||||||
import messagesNorwegian from './translations/nb';
|
import messagesNorwegian from './translations/nb';
|
||||||
|
import messagesGerman from './translations/de';
|
||||||
|
import messagesHungarian from './translations/hu';
|
||||||
|
import messagesPolish from './translations/pl';
|
||||||
|
import messagesDanish from './translations/dk';
|
||||||
|
import messagesFrench from './translations/fr';
|
||||||
|
import messagesGreek from './translations/el';
|
||||||
|
import messagesRomanian from './translations/ro';
|
||||||
|
import messagesPortuguese from './translations/pt';
|
||||||
|
import messagesChinese from './translations/cn';
|
||||||
|
import messagesSpanish from './translations/es';
|
||||||
|
import messagesCroatian from './translations/hr';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
|
const App = ReactLazyPreload(() => import(/* webpackChunkName: "app" */ './components/App'));
|
||||||
|
|
||||||
const cache = createIntlCache();
|
const cache = createIntlCache();
|
||||||
|
|
||||||
const messages =
|
const messages =
|
||||||
{
|
{
|
||||||
'en' : messagesEnglish,
|
// 'en' : messagesEnglish,
|
||||||
'nb' : messagesNorwegian
|
'nb' : messagesNorwegian,
|
||||||
|
'de' : messagesGerman,
|
||||||
|
'hu' : messagesHungarian,
|
||||||
|
'pl' : messagesPolish,
|
||||||
|
'dk' : messagesDanish,
|
||||||
|
'fr' : messagesFrench,
|
||||||
|
'el' : messagesGreek,
|
||||||
|
'ro' : messagesRomanian,
|
||||||
|
'pt' : messagesPortuguese,
|
||||||
|
'zh' : messagesChinese,
|
||||||
|
'es' : messagesSpanish,
|
||||||
|
'hr' : messagesCroatian
|
||||||
};
|
};
|
||||||
|
|
||||||
const locale = navigator.language.split(/[-_]/)[0]; // language without region code
|
const locale = navigator.language.split(/[-_]/)[0]; // language without region code
|
||||||
|
|
@ -52,6 +78,13 @@ RoomClient.init({ store, intl });
|
||||||
|
|
||||||
const theme = createMuiTheme(window.config.theme);
|
const theme = createMuiTheme(window.config.theme);
|
||||||
|
|
||||||
|
let Router;
|
||||||
|
|
||||||
|
if (isElectron())
|
||||||
|
Router = HashRouter;
|
||||||
|
else
|
||||||
|
Router = BrowserRouter;
|
||||||
|
|
||||||
domready(() =>
|
domready(() =>
|
||||||
{
|
{
|
||||||
logger.debug('DOM ready');
|
logger.debug('DOM ready');
|
||||||
|
|
@ -67,34 +100,17 @@ function run()
|
||||||
const urlParser = new URL(window.location);
|
const urlParser = new URL(window.location);
|
||||||
const parameters = urlParser.searchParams;
|
const parameters = urlParser.searchParams;
|
||||||
|
|
||||||
let roomId = (urlParser.pathname).substr(1);
|
|
||||||
|
|
||||||
if (!roomId)
|
|
||||||
roomId = parameters.get('roomId');
|
|
||||||
|
|
||||||
if (roomId)
|
|
||||||
roomId = roomId.toLowerCase();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
roomId = randomString({ length: 8 }).toLowerCase();
|
|
||||||
|
|
||||||
parameters.set('roomId', roomId);
|
|
||||||
window.history.pushState('', '', urlParser.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
const accessCode = parameters.get('code');
|
const accessCode = parameters.get('code');
|
||||||
const produce = parameters.get('produce') !== 'false';
|
const produce = parameters.get('produce') !== 'false';
|
||||||
const useSimulcast = parameters.get('simulcast') === 'true';
|
const useSimulcast = parameters.get('simulcast') === 'true';
|
||||||
|
const useSharingSimulcast = parameters.get('sharingSimulcast') === 'true';
|
||||||
const forceTcp = parameters.get('forceTcp') === 'true';
|
const forceTcp = parameters.get('forceTcp') === 'true';
|
||||||
|
const displayName = parameters.get('displayName');
|
||||||
const roomUrl = window.location.href.split('?')[0];
|
const muted = parameters.get('muted') === 'true';
|
||||||
|
|
||||||
// Get current device.
|
// Get current device.
|
||||||
const device = deviceInfo();
|
const device = deviceInfo();
|
||||||
|
|
||||||
store.dispatch(
|
|
||||||
roomActions.setRoomUrl(roomUrl));
|
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
meActions.setMe({
|
meActions.setMe({
|
||||||
peerId,
|
peerId,
|
||||||
|
|
@ -103,7 +119,17 @@ function run()
|
||||||
);
|
);
|
||||||
|
|
||||||
roomClient = new RoomClient(
|
roomClient = new RoomClient(
|
||||||
{ roomId, peerId, accessCode, device, useSimulcast, produce, forceTcp });
|
{
|
||||||
|
peerId,
|
||||||
|
accessCode,
|
||||||
|
device,
|
||||||
|
useSimulcast,
|
||||||
|
useSharingSimulcast,
|
||||||
|
produce,
|
||||||
|
forceTcp,
|
||||||
|
displayName,
|
||||||
|
muted
|
||||||
|
});
|
||||||
|
|
||||||
global.CLIENT = roomClient;
|
global.CLIENT = roomClient;
|
||||||
|
|
||||||
|
|
@ -114,7 +140,14 @@ function run()
|
||||||
<PersistGate loading={<LoadingView />} persistor={persistor}>
|
<PersistGate loading={<LoadingView />} persistor={persistor}>
|
||||||
<RoomContext.Provider value={roomClient}>
|
<RoomContext.Provider value={roomClient}>
|
||||||
<SnackbarProvider>
|
<SnackbarProvider>
|
||||||
<App />
|
<Router>
|
||||||
|
<Suspense fallback={<LoadingView />}>
|
||||||
|
<React.Fragment>
|
||||||
|
<Route exact path='/' component={ChooseRoom} />
|
||||||
|
<Route path='/:id' component={App} />
|
||||||
|
</React.Fragment>
|
||||||
|
</Suspense>
|
||||||
|
</Router>
|
||||||
</SnackbarProvider>
|
</SnackbarProvider>
|
||||||
</RoomContext.Provider>
|
</RoomContext.Provider>
|
||||||
</PersistGate>
|
</PersistGate>
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,15 @@ const consumers = (state = initialState, action) =>
|
||||||
return { ...state, [consumerId]: newConsumer };
|
return { ...state, [consumerId]: newConsumer };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'SET_CONSUMER_PRIORITY':
|
||||||
|
{
|
||||||
|
const { consumerId, priority } = action.payload;
|
||||||
|
const consumer = state[consumerId];
|
||||||
|
const newConsumer = { ...consumer, priority };
|
||||||
|
|
||||||
|
return { ...state, [consumerId]: newConsumer };
|
||||||
|
}
|
||||||
|
|
||||||
case 'SET_CONSUMER_TRACK':
|
case 'SET_CONSUMER_TRACK':
|
||||||
{
|
{
|
||||||
const { consumerId, track } = action.payload;
|
const { consumerId, track } = action.payload;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
const initialState =
|
const initialState =
|
||||||
{
|
{
|
||||||
url : null,
|
|
||||||
name : '',
|
name : '',
|
||||||
state : 'new', // new/connecting/connected/disconnected/closed,
|
state : 'new', // new/connecting/connected/disconnected/closed,
|
||||||
locked : false,
|
locked : false,
|
||||||
|
|
@ -26,13 +25,6 @@ const room = (state = initialState, action) =>
|
||||||
{
|
{
|
||||||
switch (action.type)
|
switch (action.type)
|
||||||
{
|
{
|
||||||
case 'SET_ROOM_URL':
|
|
||||||
{
|
|
||||||
const { url } = action.payload;
|
|
||||||
|
|
||||||
return { ...state, url };
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'SET_ROOM_NAME':
|
case 'SET_ROOM_NAME':
|
||||||
{
|
{
|
||||||
const { name } = action.payload;
|
const { name } = action.payload;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ const initialState =
|
||||||
selectedWebcam : null,
|
selectedWebcam : null,
|
||||||
selectedAudioDevice : null,
|
selectedAudioDevice : null,
|
||||||
advancedMode : false,
|
advancedMode : false,
|
||||||
resolution : 'high' // low, medium, high, veryhigh, ultra
|
resolution : 'medium', // low, medium, high, veryhigh, ultra
|
||||||
|
lastN : 4
|
||||||
};
|
};
|
||||||
|
|
||||||
const settings = (state = initialState, action) =>
|
const settings = (state = initialState, action) =>
|
||||||
|
|
@ -35,6 +36,20 @@ const settings = (state = initialState, action) =>
|
||||||
return { ...state, advancedMode };
|
return { ...state, advancedMode };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'SET_LAST_N':
|
||||||
|
{
|
||||||
|
const { lastN } = action.payload;
|
||||||
|
|
||||||
|
return { ...state, lastN };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'TOGGLE_PERMANENT_TOPBAR':
|
||||||
|
{
|
||||||
|
const permanentTopBar = !state.permanentTopBar;
|
||||||
|
|
||||||
|
return { ...state, permanentTopBar };
|
||||||
|
}
|
||||||
|
|
||||||
case 'SET_VIDEO_RESOLUTION':
|
case 'SET_VIDEO_RESOLUTION':
|
||||||
{
|
{
|
||||||
const { resolution } = action.payload;
|
const { resolution } = action.payload;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
{
|
||||||
|
"socket.disconnected": "您已断开连接",
|
||||||
|
"socket.reconnecting": "尝试重新连接",
|
||||||
|
"socket.reconnected": "您已重新连接",
|
||||||
|
"socket.requestError": "服务器请求错误",
|
||||||
|
|
||||||
|
"room.chooseRoom": "选择您要加入的房间的名称",
|
||||||
|
"room.cookieConsent": "这个网站使用cookies来增强用户体验",
|
||||||
|
"room.consentUnderstand": "I understand",
|
||||||
|
"room.joined": "您已加入房间",
|
||||||
|
"room.cantJoin": "无法加入房间",
|
||||||
|
"room.youLocked": "您已锁定房间",
|
||||||
|
"room.cantLock": "无法锁定房间",
|
||||||
|
"room.youUnLocked": "您解锁了房间",
|
||||||
|
"room.cantUnLock": "无法解锁房间",
|
||||||
|
"room.locked": "房间已锁定",
|
||||||
|
"room.unlocked": "房间现已解锁",
|
||||||
|
"room.newLobbyPeer": "新参与者进入大厅",
|
||||||
|
"room.lobbyPeerLeft": "新参与者离开大厅",
|
||||||
|
"room.lobbyPeerChangedDisplayName": "大厅的参与者将名称更改为{displayName}",
|
||||||
|
"room.lobbyPeerChangedPicture": "大厅的参与者已更改图片",
|
||||||
|
"room.setAccessCode": "设置房间的访问密码",
|
||||||
|
"room.accessCodeOn": "房间的访问密码现已激活",
|
||||||
|
"room.accessCodeOff": "房间的访问密码已停用",
|
||||||
|
"room.peerChangedDisplayName": "{oldDisplayName}现在为{displayName}",
|
||||||
|
"room.newPeer": "{displayName}加入了会议室",
|
||||||
|
"room.newFile": "新文件可用",
|
||||||
|
"room.toggleAdvancedMode": "切换高级模式",
|
||||||
|
"room.setDemocraticView": "将布局更改为民主视图",
|
||||||
|
"room.setFilmStripView": "将布局更改为幻灯片视图",
|
||||||
|
"room.loggedIn": "您已登录",
|
||||||
|
"room.loggedOut": "您已登出",
|
||||||
|
"room.changedDisplayName": "您的显示名称更改为{displayName}",
|
||||||
|
"room.changeDisplayNameError": "更改显示名称时发生错误",
|
||||||
|
"room.chatError": "无法发送聊天消息",
|
||||||
|
"room.aboutToJoin": "您即将参加会议",
|
||||||
|
"room.roomId": "房间ID: {roomName}",
|
||||||
|
"room.setYourName": "设置您的参与名,并选择您想加入的方式:",
|
||||||
|
"room.audioOnly": "仅音频",
|
||||||
|
"room.audioVideo": "音频和视频",
|
||||||
|
"room.youAreReady": "好,您准备好了",
|
||||||
|
"room.emptyRequireLogin": "房间是空的! 您可以登录以开始会议或等待主持人加入",
|
||||||
|
"room.locketWait": "房间已锁定-挂起,直到有人允许您进入...",
|
||||||
|
"room.lobbyAdministration": "大厅管理",
|
||||||
|
"room.peersInLobby": "大厅的参与者",
|
||||||
|
"room.lobbyEmpty": "大厅目前没有人",
|
||||||
|
"room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}",
|
||||||
|
"room.me": "我",
|
||||||
|
"room.spotlights": "Spotlight中的参与者",
|
||||||
|
"room.passive": "被动参与者",
|
||||||
|
"room.videoPaused": "该视频已暂停",
|
||||||
|
|
||||||
|
"tooltip.login": "登录",
|
||||||
|
"tooltip.logout": "注销",
|
||||||
|
"tooltip.admitFromLobby": "从大厅允许",
|
||||||
|
"tooltip.lockRoom": "锁房",
|
||||||
|
"tooltip.unLockRoom": "解锁房间",
|
||||||
|
"tooltip.enterFullscreen": "进入全屏",
|
||||||
|
"tooltip.leaveFullscreen": "退出全屏",
|
||||||
|
"tooltip.lobby": "显示大厅",
|
||||||
|
"tooltip.settings": "显示设置",
|
||||||
|
"tooltip.participants": "显示参加者",
|
||||||
|
|
||||||
|
"label.roomName": "房间名称",
|
||||||
|
"label.chooseRoomButton": "继续",
|
||||||
|
"label.yourName": "你的名字",
|
||||||
|
"label.newWindow": "新窗口",
|
||||||
|
"label.fullscreen": "全屏",
|
||||||
|
"label.openDrawer": "打开抽屉",
|
||||||
|
"label.leave": "离开",
|
||||||
|
"label.chatInput": "输入聊天消息",
|
||||||
|
"label.chat": "聊天",
|
||||||
|
"label.filesharing": "文件共享",
|
||||||
|
"label.participants": "参与者",
|
||||||
|
"label.shareFile": "共享文件",
|
||||||
|
"label.fileSharingUnsupported": "不支持文件共享",
|
||||||
|
"label.unknown": "未知",
|
||||||
|
"label.democratic": "民主视图",
|
||||||
|
"label.filmstrip": "幻灯片视图",
|
||||||
|
"label.low": "低",
|
||||||
|
"label.medium": "中等",
|
||||||
|
"label.high": "高 (HD)",
|
||||||
|
"label.veryHigh": "非常高 (FHD)",
|
||||||
|
"label.ultra": "超高 (UHD)",
|
||||||
|
"label.close": "关闭",
|
||||||
|
|
||||||
|
"settings.settings": "设置",
|
||||||
|
"settings.camera": "视频设备",
|
||||||
|
"settings.selectCamera": "选择视频设备",
|
||||||
|
"settings.cantSelectCamera": "无法选择视频设备",
|
||||||
|
"settings.audio": "音频设备",
|
||||||
|
"settings.selectAudio": "选择音频设备",
|
||||||
|
"settings.cantSelectAudio": "无法选择音频设备",
|
||||||
|
"settings.resolution": "选择视频分辨率",
|
||||||
|
"settings.layout": "房间布局",
|
||||||
|
"settings.selectRoomLayout": "选择房间布局",
|
||||||
|
"settings.advancedMode": "高级模式",
|
||||||
|
"settings.permanentTopBar": "永久顶吧",
|
||||||
|
"settings.lastn": "可见视频数量",
|
||||||
|
|
||||||
|
"filesharing.saveFileError": "无法保存文件",
|
||||||
|
"filesharing.startingFileShare": "正在尝试共享文件",
|
||||||
|
"filesharing.successfulFileShare": "文件已成功共享",
|
||||||
|
"filesharing.unableToShare": "无法共享文件",
|
||||||
|
"filesharing.error": "文件共享发生错误",
|
||||||
|
"filesharing.finished": "文件共享完成",
|
||||||
|
"filesharing.save": "保存共享文件",
|
||||||
|
"filesharing.sharedFile": "{displayName}共享了一个文件",
|
||||||
|
"filesharing.download": "下载共享文件",
|
||||||
|
"filesharing.missingSeeds": "如果此过程需要很长时间,则可能没有人播下该种子。请尝试让某人重新上传您想要的文件。",
|
||||||
|
|
||||||
|
"devices.devicesChanged": "您的设备已更改,请在设置对话框中配置设备",
|
||||||
|
|
||||||
|
"device.audioUnsupported": "音频不受支持",
|
||||||
|
"device.activateAudio": "激活音频",
|
||||||
|
"device.muteAudio": "静音",
|
||||||
|
"device.unMuteAudio": "取消静音",
|
||||||
|
|
||||||
|
"device.videoUnsupported": "视频不受支持",
|
||||||
|
"device.startVideo": "开始视频",
|
||||||
|
"device.stopVideo": "停止视频",
|
||||||
|
|
||||||
|
"device.screenSharingUnsupported": "不支持屏幕共享",
|
||||||
|
"device.startScreenSharing": "开始屏幕共享",
|
||||||
|
"device.stopScreenSharing": "停止屏幕共享",
|
||||||
|
|
||||||
|
"devices.microphoneDisconnected": "麦克风已断开",
|
||||||
|
"devices.microphoneError": "麦克风发生错误",
|
||||||
|
"devices.microPhoneMute": "麦克风静音",
|
||||||
|
"devices.micophoneUnMute": "取消麦克风静音",
|
||||||
|
"devices.microphoneEnable": "启用了麦克风",
|
||||||
|
"devices.microphoneMuteError": "无法使麦克风静音",
|
||||||
|
"devices.microphoneUnMuteError": "无法取消麦克风静音",
|
||||||
|
|
||||||
|
"devices.screenSharingDisconnected" : "屏幕共享已断开",
|
||||||
|
"devices.screenSharingError": "访问屏幕时发生错误",
|
||||||
|
|
||||||
|
"devices.cameraDisconnected": "相机已断开连接",
|
||||||
|
"devices.cameraError": "访问相机时发生错误"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
{
|
||||||
|
"socket.disconnected": "Verbindung unterbrochen",
|
||||||
|
"socket.reconnecting": "Verbindung unterbrochen, versuche neu zu verbinden",
|
||||||
|
"socket.reconnected": "Verbindung wieder herges|tellt",
|
||||||
|
"socket.requestError": "Fehler bei Serveranfrage",
|
||||||
|
|
||||||
|
"room.chooseRoom": "Choose the name of the room you would like to join",
|
||||||
|
"room.cookieConsent": "Diese Seite verwendet Cookies, um die Benutzerfreundlichkeit zu erhöhen",
|
||||||
|
"room.consentUnderstand": "I understand",
|
||||||
|
"room.joined": "Konferenzraum betreten",
|
||||||
|
"room.cantJoin": "Betreten des Raumes nicht möglich",
|
||||||
|
"room.youLocked": "Raum wurde abgeschlossen",
|
||||||
|
"room.cantLock": "Abschließen des Raumes nicht möglich",
|
||||||
|
"room.youUnLocked": "Raum geöffnet",
|
||||||
|
"room.cantUnLock": "Öffnen des Raumes nicht möglich",
|
||||||
|
"room.locked": "Raum wurde abgeschlossen",
|
||||||
|
"room.unlocked": "Raum wurde geöffnet",
|
||||||
|
"room.newLobbyPeer": "Neuer Teilnehmer im Empfangsraum",
|
||||||
|
"room.lobbyPeerLeft": "Teilnehmer hat Empfangsraum verlassen",
|
||||||
|
"room.lobbyPeerChangedDisplayName": "Teilnehmer im Empfangsraum hat seinen Namen geändert: {displayName}",
|
||||||
|
"room.lobbyPeerChangedPicture": "Teilnehmer in Empfangsraum hat sein Avatar geändert",
|
||||||
|
"room.setAccessCode": "Zugangskode für den Raum geändert",
|
||||||
|
"room.accessCodeOn": "Zugangskode aktiviert",
|
||||||
|
"room.accessCodeOff": "Zugangskode deaktiviert",
|
||||||
|
"room.peerChangedDisplayName": "{oldDisplayName} heißt jetzt {displayName}",
|
||||||
|
"room.newPeer": "{displayName} hat den Raum betreten",
|
||||||
|
"room.newFile": "Neue Datei verfügbar",
|
||||||
|
"room.toggleAdvancedMode": "Erweiterter Modus aktiv",
|
||||||
|
"room.setDemocraticView": "Raumlayout demokratisch",
|
||||||
|
"room.setFilmStripView": "Raumlayout Filmstreifen",
|
||||||
|
"room.loggedIn": "Angemeldet",
|
||||||
|
"room.loggedOut": "Abgemeldet",
|
||||||
|
"room.changedDisplayName": "Dein Name ist jetzt {displayName}",
|
||||||
|
"room.changeDisplayNameError": "Konnte Name nicht ändern",
|
||||||
|
"room.chatError": "Konnte Meldung nicht senden",
|
||||||
|
"room.aboutToJoin": "Du bist dabei den Raum zu betreten",
|
||||||
|
"room.roomId": "Raum ID: {roomName}",
|
||||||
|
"room.setYourName": "Gib deinen Namen an und wähle wie den Raum betreten willst",
|
||||||
|
"room.audioOnly": "Nur Audio",
|
||||||
|
"room.audioVideo": "Audio und Video",
|
||||||
|
"room.youAreReady": "Ok, Du bist bereit",
|
||||||
|
"room.emptyRequireLogin": "Der Raum ist leer. Melde dich an um den Raum zu aktivieren, oder warte bis der Raum aktiviert wird",
|
||||||
|
"room.locketWait": "Der Raum ist abgeschlossen, warte bis Dir jemand öffnet",
|
||||||
|
"room.lobbyAdministration": "Warteraum",
|
||||||
|
"room.peersInLobby": "Teilnehmer im Warteraum",
|
||||||
|
"room.lobbyEmpty": "Der Warteraum ist leer",
|
||||||
|
"room.hiddenPeers": "{hiddenPeersCount, plural, one {Teilnehmer} other {Teilnehmer}}",
|
||||||
|
"room.me": "Ich",
|
||||||
|
"room.spotlights": "Aktive Teinehmer",
|
||||||
|
"room.passive": "Passive Teilnehmer",
|
||||||
|
"room.videoPaused": "Video gestoppt",
|
||||||
|
|
||||||
|
"tooltip.login": "Anmelden",
|
||||||
|
"tooltip.logout": "Abmelden",
|
||||||
|
"tooltip.admitFromLobby": "Teilnehmer aktivieren",
|
||||||
|
"tooltip.lockRoom": "Raum abschließen",
|
||||||
|
"tooltip.unLockRoom": "Raum öffnen",
|
||||||
|
"tooltip.enterFullscreen": "Vollbild",
|
||||||
|
"tooltip.leaveFullscreen": "Vollbild verlassen",
|
||||||
|
"tooltip.lobby": "Warteraum",
|
||||||
|
"tooltip.settings": "Einstellungen",
|
||||||
|
"tooltip.participants": "Teilnehmer",
|
||||||
|
|
||||||
|
"label.roomName": "Room name",
|
||||||
|
"label.chooseRoomButton": "Continue",
|
||||||
|
"label.yourName": "Dein Name",
|
||||||
|
"label.newWindow": "In separatem Fenster öffnen",
|
||||||
|
"label.fullscreen": "Vollbild",
|
||||||
|
"label.openDrawer": "Menü",
|
||||||
|
"label.leave": "Ausgang",
|
||||||
|
"label.chatInput": "Schreibe Chat...",
|
||||||
|
"label.chat": "Chat",
|
||||||
|
"label.filesharing": "Dateien",
|
||||||
|
"label.participants": "Teilnehmer",
|
||||||
|
"label.shareFile": "Teile Datai",
|
||||||
|
"label.fileSharingUnsupported": "Dateifreigabe nicht unterstützt",
|
||||||
|
"label.unknown": "Unbekannt",
|
||||||
|
"label.democratic": "Demokratisch",
|
||||||
|
"label.filmstrip": "Filmstreifen",
|
||||||
|
"label.low": "Niedrig",
|
||||||
|
"label.medium": "Medium",
|
||||||
|
"label.high": "Hoch (HD)",
|
||||||
|
"label.veryHigh": "Sehr hoch (FHD)",
|
||||||
|
"label.ultra": "Ultra (UHD)",
|
||||||
|
"label.close": "Schließen",
|
||||||
|
|
||||||
|
"settings.settings": "Einstellungen",
|
||||||
|
"settings.camera": "Kamera",
|
||||||
|
"settings.selectCamera": "Wähle Videogerät",
|
||||||
|
"settings.cantSelectCamera": "Kann Videogerät nicht aktivieren",
|
||||||
|
"settings.audio": "Audiogerät",
|
||||||
|
"settings.selectAudio": "Wähle Audiogerät",
|
||||||
|
"settings.cantSelectAudio": "Kann Audiogerät nicht aktivieren",
|
||||||
|
"settings.resolution": "Wähle Auflösung",
|
||||||
|
"settings.layout": "Raumlayout",
|
||||||
|
"settings.selectRoomLayout": "Wähle Raumlayout",
|
||||||
|
"settings.advancedMode": "Erweiterter Modus",
|
||||||
|
"settings.permanentTopBar": "Permanente obere Leiste",
|
||||||
|
"settings.lastn": "Anzahl der sichtbaren Videos",
|
||||||
|
|
||||||
|
"filesharing.saveFileError": "Fehler beim Speichern der Datei",
|
||||||
|
"filesharing.startingFileShare": "Starte Teilen der Datei",
|
||||||
|
"filesharing.successfulFileShare": "Datei wurde geteilt",
|
||||||
|
"filesharing.unableToShare": "Kann Datei nicht teilen",
|
||||||
|
"filesharing.error": "Fehler beim Teilen der Datei",
|
||||||
|
"filesharing.finished": "Datei heruntergeladen",
|
||||||
|
"filesharing.save": "Speichern",
|
||||||
|
"filesharing.sharedFile": "{displayName} hat eine Datei geteilt",
|
||||||
|
"filesharing.download": "Herunterladen",
|
||||||
|
"filesharing.missingSeeds": "Wenn das Herunterladen nicht pausiert ist wahrscheinlich niemeand mehr im Raum der die Datei teilen kann. Datei muss erneut geteilt werden.",
|
||||||
|
|
||||||
|
"devices.devicesChanged": "Mediengeräte wurden aktualisiert und sind in Einstellungen verfügbar",
|
||||||
|
|
||||||
|
"device.audioUnsupported": "Audio nicht unterstützt",
|
||||||
|
"device.activateAudio": "Aktiviere Audio",
|
||||||
|
"device.muteAudio": "stummschalten",
|
||||||
|
"device.unMuteAudio": "Aktiviere Audio",
|
||||||
|
|
||||||
|
"device.videoUnsupported": "Video nicht unterstützt",
|
||||||
|
"device.startVideo": "Starte Video",
|
||||||
|
"device.stopVideo": "Stoppe Video",
|
||||||
|
|
||||||
|
"device.screenSharingUnsupported": "Bildschirmteilen nicht unterstützt",
|
||||||
|
"device.startScreenSharing": "Bildschirmteilen",
|
||||||
|
"device.stopScreenSharing": "Beende Bildschirmteilen",
|
||||||
|
|
||||||
|
"devices.microphoneDisconnected": "Mikrophon nicht verbunden",
|
||||||
|
"devices.microphoneError": "Fehler mit Mikrophon",
|
||||||
|
"devices.microPhoneMute": "Mikrophon stumm geschaltet",
|
||||||
|
"devices.micophoneUnMute": "Mikrophon aktiviert",
|
||||||
|
"devices.microphoneEnable": "Mikrofonen aktiviert",
|
||||||
|
"devices.microphoneMuteError": "Kann Mikrophon nicht stummschalten",
|
||||||
|
"devices.microphoneUnMuteError": "Kann Mikrophon nicht aktivieren",
|
||||||
|
|
||||||
|
"devices.screenSharingDisconnected" : "Bildschirmteilen unterbrochen",
|
||||||
|
"devices.screenSharingError": "Fehler beim Bildschirmteilen",
|
||||||
|
|
||||||
|
"devices.cameraDisconnected": "Video unterbrochen",
|
||||||
|
"devices.cameraError": "Fehler mit Videogerät"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
{
|
||||||
|
"socket.disconnected": "Du er frakoblet",
|
||||||
|
"socket.reconnecting": "Du er frakoblet, forsøger at oprette forbindelse igen",
|
||||||
|
"socket.reconnected": "Du er tilsluttet igen",
|
||||||
|
"socket.requestError": "Fejl ved serveranmodning",
|
||||||
|
|
||||||
|
"room.chooseRoom": "Vælg navnet på det rum, du vil være med på",
|
||||||
|
"room.cookieConsent": "Dette websted bruger cookies til at forbedre brugeroplevelsen",
|
||||||
|
"room.consentUnderstand": "I understand",
|
||||||
|
"room.joined": "Du er tilsluttet mødet",
|
||||||
|
"room.cantJoin": "Kan ikke deltage i mødet",
|
||||||
|
"room.youLocked": "Du låste mødet",
|
||||||
|
"room.cantLock": "Kan ikke låse mødet",
|
||||||
|
"room.youUnLocked": "Du låste lokalet op",
|
||||||
|
"room.cantUnLock": "Kan ikke låse mødet op",
|
||||||
|
"room.locked": "Mødet er nu låst",
|
||||||
|
"room.unlocked": "Mødet er nu ulåst",
|
||||||
|
"room.newLobbyPeer": "Ny deltager kom ind i lobbyen",
|
||||||
|
"room.lobbyPeerLeft": "Deltager i lobbyen tilbage",
|
||||||
|
"room.lobbyPeerChangedDisplayName": "Deltager i lobbyen ændrede navn til {displayName}",
|
||||||
|
"room.lobbyPeerChangedPicture": "Deltager i lobbyen ændrede billede",
|
||||||
|
"room.setAccessCode": "Adgangskode til værelse opdateret",
|
||||||
|
"room.accessCodeOn": "Adgangskode til værelse er nu aktiveret",
|
||||||
|
"room.accessCodeOff": "Adgangskode til værelse er nu deaktiveret",
|
||||||
|
"room.peerChangedDisplayName": "{oldDisplayName} er nu {displayName}",
|
||||||
|
"room.newPeer": "{displayName} kom med i mødet",
|
||||||
|
"room.newFile": "Ny fil tilgængelig",
|
||||||
|
"room.toggleAdvancedMode": "Skiftet avanceret tilstand",
|
||||||
|
"room.setDemocracyView": "Ændret layout til galleri visning",
|
||||||
|
"room.setFilmStripView": "Ændret layout til filmstrimmel visning",
|
||||||
|
"room.loggedIn": "Du er logget ind",
|
||||||
|
"room.loggedOut": "Du er logget ud",
|
||||||
|
"room.changDisplayName": "Dit visningsnavn blev ændret til {displayName}",
|
||||||
|
"room.changeDisplayNameError": "Der opstod en fejl under ændring af dit visningsnavn",
|
||||||
|
"room.chatError": "Kan ikke sende chatbesked",
|
||||||
|
"room.aboutToJoin": "Du er ved at deltage i et møde",
|
||||||
|
"room.roomId": "Værelse-ID: {roomName}",
|
||||||
|
"room.setYourName": "Indstil dit navn til deltagelse, og vælg, hvordan du vil være med:",
|
||||||
|
"room.audioOnly": "Kun lyd",
|
||||||
|
"room.audioVideo": "Audio og video",
|
||||||
|
"room.youAreReady": "Ok, du er klar",
|
||||||
|
"room.emptyRequireLogin": "Værelset er tomt! Du kan logge ind for at starte mødet eller vente til værten starter mødet",
|
||||||
|
"room.locketWait": "Værelset er låst - vent venligst, indtil nogen slipper dig ind ...",
|
||||||
|
"room.lobbyAdministration": "Lobbyadministration",
|
||||||
|
"room.peersInLobby": "Deltagere i lobbyen",
|
||||||
|
"room.lobbyEmpty": "Der er i øjeblikket ingen i lobbyen",
|
||||||
|
"room.hiddenPeers": "{HiddenPeersCount, plural, en {deltager} andre {deltagere}}",
|
||||||
|
"room.me": "Mig",
|
||||||
|
"room.spotlights": "Deltagere i fokus",
|
||||||
|
"room.passive": "Passive deltagere",
|
||||||
|
"room.videoPaused": "Denne video er sat på pause",
|
||||||
|
|
||||||
|
"tooltip.login": "Log ind",
|
||||||
|
"tooltip.logout": "Log ud",
|
||||||
|
"tooltip.admitFromLobby": "Giv adgang fra lobbyen",
|
||||||
|
"tooltip.lockRoom": "Låseværelse",
|
||||||
|
"tooltip.unLockRoom": "Lås op plads",
|
||||||
|
"tooltip.enterFullscreen": "Indtast fuldskærm",
|
||||||
|
"tooltip.leaveFullscreen": "Efterlad fuldskærm",
|
||||||
|
"tooltip.lobby": "Vis lobby",
|
||||||
|
"tooltip.settings": "Vis indstillinger",
|
||||||
|
"tooltip.participants": "Vis deltagere",
|
||||||
|
|
||||||
|
"label.roomName": "Værelsesnavn",
|
||||||
|
"label.chooseRoomButton": "Fortsæt",
|
||||||
|
"label.yourName": "Dit navn",
|
||||||
|
"label.newWindow": "Nyt vindue",
|
||||||
|
"label.fullscreen": "Fuldskærm",
|
||||||
|
"label.openDrawer": "Åben skuffe",
|
||||||
|
"label.leave": "Forlad",
|
||||||
|
"label.chatInput": "Indtast chatbesked ...",
|
||||||
|
"label.chat": "Chat",
|
||||||
|
"label.filesharing": "Fildeling",
|
||||||
|
"label.participants": "Deltagere",
|
||||||
|
"label.shareFile": "Del fil",
|
||||||
|
"label.fileSharingUnsupported": "Fildeling er ikke understøttet",
|
||||||
|
"label.unknown": "Ukendt",
|
||||||
|
"label.democracy": "Galleri visning",
|
||||||
|
"label.filmstrip": "Filmstrimmel visning",
|
||||||
|
"label.low": "Lav",
|
||||||
|
"label.medium": "Medium",
|
||||||
|
"label.high": "Høj (HD)",
|
||||||
|
"label.veryHigh": "Meget høj (FHD)",
|
||||||
|
"label.ultra": "Ultra (UHD)",
|
||||||
|
"label.close": "Luk",
|
||||||
|
|
||||||
|
"settings.settings": "Indstillinger",
|
||||||
|
"settings.camera": "Kamera",
|
||||||
|
"settings.selectCamera": "Vælg kamera",
|
||||||
|
"settings.cantSelectCamera": "Kan ikke vælge kamera",
|
||||||
|
"settings.audio": "Lydenhed",
|
||||||
|
"settings.selectAudio": "Vælg lydenhed",
|
||||||
|
"settings.cantSelectAudio": "Kan ikke vælge lydenhed",
|
||||||
|
"settings.resolution": "Vælg din videoopløsning",
|
||||||
|
"settings.layout": "Møde visning",
|
||||||
|
"settings.selectRoomLayout": "Vælg møde visning",
|
||||||
|
"settings.advancedMode": "Avanceret tilstand",
|
||||||
|
"settings.permanentTopBar": "Permanent øverste linje",
|
||||||
|
"settings.lastn": "Antal synlige videoer",
|
||||||
|
|
||||||
|
"filesharing.saveFileError": "Kan ikke gemme fil",
|
||||||
|
"filesharing.startingFileShare": "Forsøger at dele filen",
|
||||||
|
"filesharing.successfulFileShare": "Filen blev delet med succes",
|
||||||
|
"filesharing.unableToShare": "Kan ikke dele fil",
|
||||||
|
"filesharing.error": "Der var en fildelings fejl",
|
||||||
|
"filesharing.finished": "Filen er færdig med at downloade",
|
||||||
|
"filesharing.save": "Gem",
|
||||||
|
"filesharing.sharedFile": "{displayName} delte en fil",
|
||||||
|
"filesharing.download": "Download",
|
||||||
|
"filesharing.missingSeeds": "Hvis denne proces tager lang tid, er der muligvis ikke nogen, der seedede denne torrent. Prøv at bede nogen om at uploade den fil, du ønsker at hente.",
|
||||||
|
|
||||||
|
"device.devicesChanged": "Detekteret ndringer i dine enheder, konfigurer dine enheder i indstillingsdialogen",
|
||||||
|
|
||||||
|
"device.audioUnsupported": "Lyd ikke understøttet",
|
||||||
|
"device.activateAudio": "Aktivér lyd",
|
||||||
|
"device.muteAudio": "Slå lyd til",
|
||||||
|
"device.unMuteAudio": "Slå lyd til",
|
||||||
|
|
||||||
|
"device.videoUnsupported": "Video ikke understøttet",
|
||||||
|
"device.startVideo": "Start video",
|
||||||
|
"device.stopVideo": "Stop video",
|
||||||
|
|
||||||
|
"device.screenSharingUnsupported": "Skærmdeling understøttes ikke",
|
||||||
|
"device.startScreenSharing": "Start skærmdeling",
|
||||||
|
"device.stopScreenSharing": "Stop skærmdeling",
|
||||||
|
|
||||||
|
"device.microphoneDisconnected": "Mikrofon frakoblet",
|
||||||
|
"device.microphoneError": "Der opstod en fejl under adgang til din mikrofon",
|
||||||
|
"device.microPhoneMute": "Dæmp din mikrofon",
|
||||||
|
"device.micophoneUnMute": "Slå ikke lyden fra din mikrofon",
|
||||||
|
"device.microphoneEnable": "Aktiveret din mikrofon",
|
||||||
|
"device.microphoneMuteError": "Kan ikke slå din mikrofon fra",
|
||||||
|
"device.microphoneUnMuteError": "Kan ikke slå lyden til på din mikrofon",
|
||||||
|
|
||||||
|
"device.screenSharingDisconnected": "Skærmdelingen er frakoblet",
|
||||||
|
"devices.screenSharingError": "Der opstod en fejl ved adgang til skærmdeling",
|
||||||
|
|
||||||
|
"device.cameraDisconnected": "Kamera frakoblet",
|
||||||
|
"device.cameraError": "Der opstod en fejl ved tilkobling af dit kamera"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
{
|
||||||
|
"socket.disconnected": "Είστε αποσυνδεδεμένος",
|
||||||
|
"socket.reconnecting": "Είστε αποσυνδεδεμένος, προσπάθεια επανασύνδεσης",
|
||||||
|
"socket.reconnected": "Επανασυνδεθήκατε",
|
||||||
|
"socket.requestError": "Σφάλμα στο αίτημα του διακομιστή",
|
||||||
|
|
||||||
|
"room.chooseRoom": "Επιλέξτε το όνομα του δωματίου που θέλετε να συμμετάσχετε",
|
||||||
|
"room.cookieConsent": "Αυτός ο ιστότοπος χρησιμοποιεί cookies για να βελτιώσει την εμπειρία χρήσης",
|
||||||
|
"room.consentUnderstand": "I understand",
|
||||||
|
"room.joined": "Έχετε εισέλθει στο δωμάτιο",
|
||||||
|
"room.cantJoin": "Αδυναμία εισόδου στο δωμάτιο",
|
||||||
|
"room.youLocked": "Κλειδώσατε το δωμάτιο",
|
||||||
|
"room.cantLock": "Δεν είναι δυνατό να κλειδώσετε το δωμάτιο",
|
||||||
|
"room.youUnLocked": "Ξεκλειδώσατε το δωμάτιο",
|
||||||
|
"room.cantUnLock": "Δεν είναι δυνατό το ξεκλείδωμα του δωματίου",
|
||||||
|
"room.locked": "Το δωμάτιο είναι πλέον κλειδωμένο",
|
||||||
|
"room.unlocked": "Το δωμάτιο είναι τώρα ξεκλειδωμένο",
|
||||||
|
"room.newLobbyPeer": "Ένας νέος συμμετέχων μπήκε στο λόμπι",
|
||||||
|
"room.lobbyPeerLeft": "Ο συμμετέχων στο λόμπι έφυγε",
|
||||||
|
"room.lobbyPeerChangedDisplayName": "Ο συμμετέχων στο λόμπι άλλαξε το όνομά του σε {displayName}",
|
||||||
|
"room.lobbyPeerChangedPicture": "Ο συμμετέχων στο λόμπι άλλαξε εικόνα",
|
||||||
|
"room.setAccessCode": "Ο κωδικός πρόσβασης για το δωμάτιο ενημερώθηκε",
|
||||||
|
"room.accessCodeOn": "Ο κωδικός πρόσβασης για το δωμάτιο είναι τώρα ενεργοποιημένος",
|
||||||
|
"room.accessCodeOff": "Ο κωδικός πρόσβασης για το δωμάτιο είναι τώρα απενεργοποιημένος",
|
||||||
|
"room.peerChangedDisplayName": "{oldDisplayName} είναι τώρα {displayName}",
|
||||||
|
"room.newPeer": "{displayName} μπήκε στο δωμάτιο",
|
||||||
|
"room.newFile": "Νέο διαθέσιμο αρχείο",
|
||||||
|
"room.toggleAdvancedMode": "Επεξεργασία προηγμένης λειτουργίας",
|
||||||
|
"room.setDemocraticView": "Αλλαγή εμφάνισης σε democratic view",
|
||||||
|
"room.setFilmStripView": "Αλλαγή εμφάνισης σε filmstrip view",
|
||||||
|
"room.loggedIn": "Είστε συνδεδεμένοι",
|
||||||
|
"room.loggedOut": "Έχετε αποσυνδεθεί",
|
||||||
|
"room.changedDisplayName": "Το εμφανιζόμενο όνομα σας άλλαξε σε {displayName}",
|
||||||
|
"room.changeDisplayNameError": "Παρουσιάστηκε σφάλμα κατά την αλλαγή του ονόματος εμφάνισης",
|
||||||
|
"room.chatError": "Δεν είναι δυνατή η αποστολή μηνυμάτων συνομιλίας",
|
||||||
|
"room.aboutToJoin": "Είστε έτοιμοι να συμμετάσχετε σε μια συνάντηση",
|
||||||
|
"room.roomId": "Αναγνωριστικό δωματίου: {roomName}",
|
||||||
|
"room.setYourName": "Ορίστε το όνομά σας για συμμετοχή και επιλέξτε τον τρόπο συμμετοχής:",
|
||||||
|
"room.audioOnly": "Μόνο ήχος",
|
||||||
|
"room.audioVideo": "Ήχος και video",
|
||||||
|
"room.youAreReady": "Είστε έτοιμος",
|
||||||
|
"room.emptyRequireLogin": "Το δωμάτιο είναι άδειο! Μπορείτε να συνδεθείτε για να ξεκινήσετε τη σύσκεψη ή να περιμένετε έως ότου ο οικοδεσπότης συνδεθεί",
|
||||||
|
"room.locketWait": "Το δωμάτιο είναι κλειδωμένο - περιμένετε μέχρι να σας αφήσει κάποιος ...",
|
||||||
|
"room.lobbyAdministration": "Διαχείριση Δωματίου",
|
||||||
|
"room.peersInLobby": "Συμμετέχοντες στο δωμάτιο",
|
||||||
|
"room.lobbyEmpty": "Δεν υπάρχει κανένας συμμετέχοντας",
|
||||||
|
"room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}",
|
||||||
|
"room.me": "Εγώ",
|
||||||
|
"room.spotlights": "Συμμετέχοντες στο Spotlight",
|
||||||
|
"room.passive": "Παθητικοί συμμετέχοντες",
|
||||||
|
"room.videoPaused": "Το βίντεο έχει σταματήσει",
|
||||||
|
|
||||||
|
"tooltip.login": "Σύνδεση",
|
||||||
|
"tooltip.logout": "Αποσύνδεση",
|
||||||
|
"tooltip.admitFromLobby": "Admit from lobby",
|
||||||
|
"tooltip.lockRoom": "Κλείδωμα δωματίου",
|
||||||
|
"tooltip.unLockRoom": "Ξεκλείδωμα δωματίου",
|
||||||
|
"tooltip.enterFullscreen": "Πλήρης οθόνη",
|
||||||
|
"tooltip.leaveFullscreen": "Έξοδος από την πλήρη οθόνη",
|
||||||
|
"tooltip.lobby": "Εμφάνιση λόμπι",
|
||||||
|
"tooltip.settings": "Εμφάνιση ρυθμίσεων",
|
||||||
|
"tooltip.participants": "Εμφάνιση συμμετεχόντων",
|
||||||
|
|
||||||
|
"label.roomName": "Όνομα δωματίου",
|
||||||
|
"label.chooseRoomButton": "Συνέχεια",
|
||||||
|
"label.yourName": "Το όνομά σας",
|
||||||
|
"label.newWindow": "Νέο παράθυρο",
|
||||||
|
"label.fullscreen": "Πλήρης οθόνη",
|
||||||
|
"label.openDrawer": "Άνοιγμα drawer",
|
||||||
|
"label.leave": "Αποχώρηση",
|
||||||
|
"label.chatInput": "Γράψτε το μήνυμά σας...",
|
||||||
|
"label.chat": "Συνομολία",
|
||||||
|
"label.filesharing": "Διαμοιρασμοός αρχείου",
|
||||||
|
"label.participants": "Συμμετέχοντες",
|
||||||
|
"label.shareFile": "Διαμοιραστείτε ένα αρχείο",
|
||||||
|
"label.fileSharingUnsupported": "Ο διαμοιρασμός αρχείων δεν υποστηρίζεται",
|
||||||
|
"label.unknown": "Άγνωστο",
|
||||||
|
"label.democratic": "Democratic view",
|
||||||
|
"label.filmstrip": "Filmstrip view",
|
||||||
|
"label.low": "Χαμηλή",
|
||||||
|
"label.medium": "Μέτρια",
|
||||||
|
"label.high": "Υψηλή (HD)",
|
||||||
|
"label.veryHigh": "Πολύ υψηλή (FHD)",
|
||||||
|
"label.ultra": "Ultra (UHD)",
|
||||||
|
"label.close": "Κλείσιμο",
|
||||||
|
|
||||||
|
"settings.settings": "Ρυθμίσεις",
|
||||||
|
"settings.camera": "Κάμερα",
|
||||||
|
"settings.selectCamera": "Επιλέξτε συσκευή video",
|
||||||
|
"settings.cantSelectCamera": "Αδυναμία επιλογής συσκευής video",
|
||||||
|
"settings.audio": "Συσκευή ήχου",
|
||||||
|
"settings.selectAudio": "Επιλογή συσκευής ήχου",
|
||||||
|
"settings.cantSelectAudio": "Αδυναμία επιλογής συσκευής ήχου",
|
||||||
|
"settings.resolution": "Επιλέξτε την ανάλυση του video",
|
||||||
|
"settings.layout": "Περιβάλλον δωματίου",
|
||||||
|
"settings.selectRoomLayout": "Επιλογή περιβάλλοντος δωματίου",
|
||||||
|
"settings.advancedMode": "Προηγμένη λειτουργία",
|
||||||
|
"settings.permanentTopBar": "Μόνιμη μπάρα κορυφής",
|
||||||
|
"settings.lastn": "Αριθμός ορατών βίντεο",
|
||||||
|
|
||||||
|
"filesharing.saveFileError": "Αδυναμία αποθήκευσης του αρχείου",
|
||||||
|
"filesharing.startingFileShare": "Προσπάθεια διαμοιρασμού αρχείου",
|
||||||
|
"filesharing.successfulFileShare": "Το αρχείο έχει διαμοιραστεί επιτυχώς",
|
||||||
|
"filesharing.unableToShare": "Το αρχείο δεν μπορεί να διαμοιραστεί",
|
||||||
|
"filesharing.error": "Υπήρξε σφάλμα κατά τον διαμοιρασμό του αρχείου",
|
||||||
|
"filesharing.finished": "Το αρχείο έχει κατέβει",
|
||||||
|
"filesharing.save": "Αποθήκευση",
|
||||||
|
"filesharing.sharedFile": "{displayName} μοιράστηκε ένα αρχείο",
|
||||||
|
"filesharing.download": "Κατέβασμα",
|
||||||
|
"filesharing.missingSeeds": "Αν αυτή η διαδικασία διαρκεί πολύ, ίσως να μην υπάρχει κάποιος να διαμοιραστεί το αρχείο. Δοκιμάστε να ζητήσετε από κάποιον να φορτώσει εκ νέου το αρχείο που θέλετε.",
|
||||||
|
|
||||||
|
"devices.devicesChanged": "Οι συσκευές σας άλλαξαν, ρυθμίστε τις συσκευές σας στο παράθυρο ρυθμίσεων",
|
||||||
|
|
||||||
|
"device.audioUnsupported": "Δεν υποστηρίζεται ήχος",
|
||||||
|
"device.activateAudio": "Ενεργοποίηση ήχου",
|
||||||
|
"device.muteAudio": "Σίγαση ήχου",
|
||||||
|
"device.unMuteAudio": "Άνοιγμα ήχου",
|
||||||
|
|
||||||
|
"device.videoUnsupported": "Το βίντεο δεν υποστηρίζεται",
|
||||||
|
"device.startVideo": "Έναρξη βίντεο",
|
||||||
|
"device.stopVideo": "Διακοπή βίντεο",
|
||||||
|
|
||||||
|
"device.screenSharingUnsupported": "Ο διαμοιρασμός της οθόνης δεν υποστηρίζεται",
|
||||||
|
"device.startScreenSharing": "Έναρξη της κοινής χρήσης της οθόνης",
|
||||||
|
"device.stopScreenSharing": "Διακοπή της κοινής χρήσης της οθόνης",
|
||||||
|
|
||||||
|
"devices.microphoneDisconnected": "Το μικρόφωνο αποσυνδέθηκε",
|
||||||
|
"devices.microphoneError": "Παρουσιάστηκε σφάλμα κατά την πρόσβαση στο μικρόφωνό σας",
|
||||||
|
"devices.microPhoneMute": "Το μικρόφωνό σας είναι σε σίγαση",
|
||||||
|
"devices.micophoneUnMute": "Ανοίξτε το μικρόφωνό σας",
|
||||||
|
"devices.microphoneEnable": "Ενεργοποίησε το μικρόφωνό σας",
|
||||||
|
"devices.microphoneMuteError": "Δεν είναι δυνατή η σίγαση του μικροφώνου σας",
|
||||||
|
"devices.microphoneUnMuteError": "Δεν είναι δυνατό το άνοιγμα του μικροφώνου σας",
|
||||||
|
|
||||||
|
"devices.screenSharingDisconnected" : "Ο διαμοιρασμός οθόνης αποσυνδέθηκε",
|
||||||
|
"devices.screenSharingError": "Παρουσιάστηκε σφάλμα κατά την πρόσβαση στην οθόνη σας",
|
||||||
|
|
||||||
|
"devices.cameraDisconnected": "Η κάμερα αποσυνδέθηκε",
|
||||||
|
"devices.cameraError": "Παρουσιάστηκε σφάλμα κατά την πρόσβαση στην κάμερά σας"
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,9 @@
|
||||||
"socket.reconnected": "You are reconnected",
|
"socket.reconnected": "You are reconnected",
|
||||||
"socket.requestError": "Error on server request",
|
"socket.requestError": "Error on server request",
|
||||||
|
|
||||||
|
"room.chooseRoom": "Choose the name of the room you would like to join",
|
||||||
"room.cookieConsent": "This website uses cookies to enhance the user experience",
|
"room.cookieConsent": "This website uses cookies to enhance the user experience",
|
||||||
|
"room.consentUnderstand": "I understand",
|
||||||
"room.joined": "You have joined the room",
|
"room.joined": "You have joined the room",
|
||||||
"room.cantJoin": "Unable to join the room",
|
"room.cantJoin": "Unable to join the room",
|
||||||
"room.youLocked": "You locked the room",
|
"room.youLocked": "You locked the room",
|
||||||
|
|
@ -57,7 +59,10 @@
|
||||||
"tooltip.leaveFullscreen": "Leave fullscreen",
|
"tooltip.leaveFullscreen": "Leave fullscreen",
|
||||||
"tooltip.lobby": "Show lobby",
|
"tooltip.lobby": "Show lobby",
|
||||||
"tooltip.settings": "Show settings",
|
"tooltip.settings": "Show settings",
|
||||||
|
"tooltip.participants": "Show participants",
|
||||||
|
|
||||||
|
"label.roomName": "Room name",
|
||||||
|
"label.chooseRoomButton": "Continue",
|
||||||
"label.yourName": "Your name",
|
"label.yourName": "Your name",
|
||||||
"label.newWindow": "New window",
|
"label.newWindow": "New window",
|
||||||
"label.fullscreen": "Fullscreen",
|
"label.fullscreen": "Fullscreen",
|
||||||
|
|
@ -90,6 +95,8 @@
|
||||||
"settings.layout": "Room layout",
|
"settings.layout": "Room layout",
|
||||||
"settings.selectRoomLayout": "Select room layout",
|
"settings.selectRoomLayout": "Select room layout",
|
||||||
"settings.advancedMode": "Advanced mode",
|
"settings.advancedMode": "Advanced mode",
|
||||||
|
"settings.permanentTopBar": "Permanent top bar",
|
||||||
|
"settings.lastn": "Number of visible videos",
|
||||||
|
|
||||||
"filesharing.saveFileError": "Unable to save file",
|
"filesharing.saveFileError": "Unable to save file",
|
||||||
"filesharing.startingFileShare": "Attempting to share file",
|
"filesharing.startingFileShare": "Attempting to share file",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
{
|
||||||
|
"socket.disconnected": "Desconectado",
|
||||||
|
"socket.reconnecting": "Desconectado, intentando reconectar",
|
||||||
|
"socket.reconnected": "Reconectado",
|
||||||
|
"socket.requestError": "Error en la petición al servidor",
|
||||||
|
|
||||||
|
"room.chooseRoom": "Indique el nombre de la sala a la que le gustaría unirse",
|
||||||
|
"room.cookieConsent": "Esta web utiliza cookies para mejorar la experiencia de usuario",
|
||||||
|
"room.consentUnderstand": "I understand",
|
||||||
|
"room.joined": "Se ha unido a la sala",
|
||||||
|
"room.cantJoin": "No ha sido posible unirse a la sala",
|
||||||
|
"room.youLocked": "Ha cerrado la sala",
|
||||||
|
"room.cantLock": "No ha sido posible cerrar la sala",
|
||||||
|
"room.youUnLocked": "Ha abierto la sala",
|
||||||
|
"room.cantUnLock": "No ha sido posible abrir la sala",
|
||||||
|
"room.locked": "La sala ahora es privada",
|
||||||
|
"room.unlocked": "La sala ahora es pública",
|
||||||
|
"room.newLobbyPeer": "Nuevo participante en la sala de espera",
|
||||||
|
"room.lobbyPeerLeft": "Un participante en espera ha salido",
|
||||||
|
"room.lobbyPeerChangedDisplayName": "Participante en espera cambió su nombre a {displayName}",
|
||||||
|
"room.lobbyPeerChangedPicture": "Participante en espera cambió su foto",
|
||||||
|
"room.setAccessCode": "Código de acceso de la sala actualizado",
|
||||||
|
"room.accessCodeOn": "Código de acceso de la sala activado",
|
||||||
|
"room.accessCodeOff": "Código de acceso de la sala desactivado",
|
||||||
|
"room.peerChangedDisplayName": "{oldDisplayName} es ahora {displayName}",
|
||||||
|
"room.newPeer": "{displayName} se unió a la sala",
|
||||||
|
"room.newFile": "Nuevo fichero disponible",
|
||||||
|
"room.toggleAdvancedMode": "Cambiado a modo avanzado",
|
||||||
|
"room.setDemocraticView": "Cambiado a modo democrático",
|
||||||
|
"room.setFilmStripView": "Cambiado a modo viñeta",
|
||||||
|
"room.loggedIn": "Ha iniciado sesión",
|
||||||
|
"room.loggedOut": "Ha cerrado su sesión",
|
||||||
|
"room.changedDisplayName": "Ha cambiado su nombre a {displayName}",
|
||||||
|
"room.changeDisplayNameError": "Hubo un error al intentar cambiar su nombre",
|
||||||
|
"room.chatError": "No ha sido posible enviar su mensaje",
|
||||||
|
"room.aboutToJoin": "Está a punto de unirse a una reunión",
|
||||||
|
"room.roomId": "ID de la sala: {roomName}",
|
||||||
|
"room.setYourName": "Indique el nombre con el que quiere participar y cómo quiere unirse:",
|
||||||
|
"room.audioOnly": "Solo sonido",
|
||||||
|
"room.audioVideo": "Sonido y vídeo",
|
||||||
|
"room.youAreReady": "Ok, está preparado",
|
||||||
|
"room.emptyRequireLogin": "¡La sala está vacía! Puede iniciar sesión para comenzar la reunión o esperar hasta que el anfitrión se una",
|
||||||
|
"room.locketWait": "La sala es privada - espere hasta que alguien le invite ...",
|
||||||
|
"room.lobbyAdministration": "Administración de la sala de espera",
|
||||||
|
"room.peersInLobby": "Participantes en la sala de espera",
|
||||||
|
"room.lobbyEmpty": "La sala de espera está vacía",
|
||||||
|
"room.hiddenPeers": "{hiddenPeersCount, plural, one {participante} other {participantes}}",
|
||||||
|
"room.me": "Yo",
|
||||||
|
"room.spotlights": "Participantes destacados",
|
||||||
|
"room.passive": "Participantes pasivos",
|
||||||
|
"room.videoPaused": "El vídeo está pausado",
|
||||||
|
|
||||||
|
"tooltip.login": "Entrar",
|
||||||
|
"tooltip.logout": "Salir",
|
||||||
|
"tooltip.admitFromLobby": "Admitir desde la sala de espera",
|
||||||
|
"tooltip.lockRoom": "Configurar sala como privada",
|
||||||
|
"tooltip.unLockRoom": "Configurar sala como pública",
|
||||||
|
"tooltip.enterFullscreen": "Presentar en pantalla completa",
|
||||||
|
"tooltip.leaveFullscreen": "Salir de la pantalla completa",
|
||||||
|
"tooltip.lobby": "Mostrar sala de espera",
|
||||||
|
"tooltip.settings": "Mostrar ajustes",
|
||||||
|
"tooltip.participants": "Mostrar participantes",
|
||||||
|
|
||||||
|
"label.roomName": "Nombre de la sala",
|
||||||
|
"label.chooseRoomButton": "Continuar",
|
||||||
|
"label.yourName": "Su nombre",
|
||||||
|
"label.newWindow": "Nueva ventana",
|
||||||
|
"label.fullscreen": "Pantalla completa",
|
||||||
|
"label.openDrawer": "Abrir panel",
|
||||||
|
"label.leave": "Salir",
|
||||||
|
"label.chatInput": "Escriba su mensaje...",
|
||||||
|
"label.chat": "Chat",
|
||||||
|
"label.filesharing": "Compartir ficheros",
|
||||||
|
"label.participants": "Participantes",
|
||||||
|
"label.shareFile": "Compartir fichero",
|
||||||
|
"label.fileSharingUnsupported": "Compartir ficheros no está disponible",
|
||||||
|
"label.unknown": "Desconocido",
|
||||||
|
"label.democratic": "Vista democrática",
|
||||||
|
"label.filmstrip": "Vista en viñeta",
|
||||||
|
"label.low": "Baja",
|
||||||
|
"label.medium": "Media",
|
||||||
|
"label.high": "Alta (HD)",
|
||||||
|
"label.veryHigh": "Muy alta (FHD)",
|
||||||
|
"label.ultra": "Ultra (UHD)",
|
||||||
|
"label.close": "Cerrar",
|
||||||
|
|
||||||
|
"settings.settings": "Ajustes",
|
||||||
|
"settings.camera": "Cámara",
|
||||||
|
"settings.selectCamera": "Seleccionar dispositivo de vídeo",
|
||||||
|
"settings.cantSelectCamera": "No ha sido posible seleccionar el dispositivo de vídeo",
|
||||||
|
"settings.audio": "Dispositivo de sonido",
|
||||||
|
"settings.selectAudio": "Seleccione dispositivo de sonido",
|
||||||
|
"settings.cantSelectAudio": "No ha sido posible seleccionar el dispositivo de sonido",
|
||||||
|
"settings.resolution": "Seleccione su resolución de imagen",
|
||||||
|
"settings.layout": "Disposición de la sala",
|
||||||
|
"settings.selectRoomLayout": "Seleccione la disposición de la sala",
|
||||||
|
"settings.advancedMode": "Modo avanzado",
|
||||||
|
"settings.permanentTopBar": "Barra superior permanente",
|
||||||
|
"settings.lastn": "Cantidad de videos visibles",
|
||||||
|
|
||||||
|
"filesharing.saveFileError": "No ha sido posible guardar el fichero",
|
||||||
|
"filesharing.startingFileShare": "Intentando compartir el fichero",
|
||||||
|
"filesharing.successfulFileShare": "El fichero se compartió con éxito",
|
||||||
|
"filesharing.unableToShare": "No ha sido posible compartir el fichero",
|
||||||
|
"filesharing.error": "Hubo un error al compartir el fichero",
|
||||||
|
"filesharing.finished": "Descarga del fichero finalizada",
|
||||||
|
"filesharing.save": "Guardar",
|
||||||
|
"filesharing.sharedFile": "{displayName} compartió un fichero",
|
||||||
|
"filesharing.download": "Descargar",
|
||||||
|
"filesharing.missingSeeds": "Si este proceso demora en exceso, puede ocurrir que no haya nadie compartiendo el fichero. Pruebe a pedirle a alguien que vuelva a subir el fichero que busca.",
|
||||||
|
|
||||||
|
"devices.devicesChanged": "Sus dispositivos han cambiado, vuelva a configurarlos en la ventana de ajustes",
|
||||||
|
|
||||||
|
"device.audioUnsupported": "Sonido no disponible",
|
||||||
|
"device.activateAudio": "Activar sonido",
|
||||||
|
"device.muteAudio": "Silenciar sonido",
|
||||||
|
"device.unMuteAudio": "Reactivar sonido",
|
||||||
|
|
||||||
|
"device.videoUnsupported": "Vídeo no disponible",
|
||||||
|
"device.startVideo": "Iniciar vídeo",
|
||||||
|
"device.stopVideo": "Detener vídeo",
|
||||||
|
|
||||||
|
"device.screenSharingUnsupported": "Compartir pantalla no disponible",
|
||||||
|
"device.startScreenSharing": "Iniciar compartir pantalla",
|
||||||
|
"device.stopScreenSharing": "Detener compartir pantalla",
|
||||||
|
|
||||||
|
"devices.microphoneDisconnected": "Micrófono desconectado",
|
||||||
|
"devices.microphoneError": "Hubo un error al acceder a su micrófono",
|
||||||
|
"devices.microPhoneMute": "Desactivar micrófono",
|
||||||
|
"devices.micophoneUnMute": "Activar micrófono",
|
||||||
|
"devices.microphoneEnable": "Micrófono activado",
|
||||||
|
"devices.microphoneMuteError": "No ha sido posible desactivar su micrófono",
|
||||||
|
"devices.microphoneUnMuteError": "No ha sido posible activar su micrófono",
|
||||||
|
|
||||||
|
"devices.screenSharingDisconnected": "Pantalla compartida desconectada",
|
||||||
|
"devices.screenSharingError": "Hubo un error al acceder a su pantalla",
|
||||||
|
|
||||||
|
"devices.cameraDisconnected": "Cámara desconectada",
|
||||||
|
"devices.cameraError": "Hubo un error al acceder a su cámara"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
{
|
||||||
|
"socket.disconnected" : " Vous avez été déconnecté",
|
||||||
|
"socket.reconnecting" : " Vous avez été déconnecté, reconnexion en cours",
|
||||||
|
"socket.reconnected" : " Vous êtes reconnecté",
|
||||||
|
"socket.requestError" : " Erreur sur une requête serveur",
|
||||||
|
|
||||||
|
"room.chooseRoom" : " Choisissez le nom de la réunion que vous souhaitez rejoindre",
|
||||||
|
"room.cookieConsent" : " Ce site utilise les cookies pour améliorer votre expérience utilisateur",
|
||||||
|
"room.consentUnderstand": "I understand",
|
||||||
|
"room.joined" : " Vous avez rejoint la salle",
|
||||||
|
"room.cantJoin" : " Impossible de rejoindre la salle",
|
||||||
|
"room.youLocked" : " Vous avez privatisé la salle",
|
||||||
|
"room.cantLock" : " Impossible de privatiser la salle",
|
||||||
|
"room.youUnLocked" : " Vous avez dé-privatiser la salle",
|
||||||
|
"room.cantUnLock" : " Impossible de dé-privatiser la réunion",
|
||||||
|
"room.locked" : " La réunion est privée",
|
||||||
|
"room.unlocked" : " La réunion est publique",
|
||||||
|
"room.newLobbyPeer" : " Un nouveau participant est dans la salle d’attente",
|
||||||
|
"room.lobbyPeerLeft" : " Un participant de la salle d’attente vient de partir",
|
||||||
|
"room.lobbyPeerChangedDisplayName" : " Un participant dans la salle d’attente a changé de nom pour {displayName}",
|
||||||
|
"room.lobbyPeerChangedPicture" : " Un participant dans le hall à changer de photo",
|
||||||
|
"room.setAccessCode" : " Code d’accès à la réunion mis à jour",
|
||||||
|
"room.accessCodeOn" : " Code d’accès à la réunion activée",
|
||||||
|
"room.accessCodeOff" : " Code d’accès à la réunion désactivée",
|
||||||
|
"room.peerChangedDisplayName" : " {oldDisplayName} est maintenant {displayName}",
|
||||||
|
"room.newPeer" : " {displayName} a rejoint la salle",
|
||||||
|
"room.newFile" : " Nouveau fichier disponible",
|
||||||
|
"room.toggleAdvancedMode" : " Basculer en mode avancé",
|
||||||
|
"room.setDemocraticView" : " Passer en vue démocratique",
|
||||||
|
"room.setFilmStripView" : " Passer en vue vignette",
|
||||||
|
"room.loggedIn" : " Vous êtes connecté",
|
||||||
|
"room.loggedOut" : " Vous êtes déconnecté",
|
||||||
|
"room.changedDisplayName" : " Votre nom à changer pour {displayname}",
|
||||||
|
"room.changeDisplayNameError" : " Une erreur s’est produite pour votre changement de nom",
|
||||||
|
"room.chatError" : " Impossible d’envoyer un message",
|
||||||
|
"room.aboutToJoin" : " Vous allez rejoindre une réunion",
|
||||||
|
"room.roomId" : " Salle ID: {roomName}",
|
||||||
|
"room.setYourName" : " Choisissez votre nom de participant puis comment vous connecter :",
|
||||||
|
"room.audioOnly" : " Audio uniquement",
|
||||||
|
"room.audioVideo" : " Audio et Vidéo",
|
||||||
|
"room.youAreReady" : " Ok, vous êtes prêt",
|
||||||
|
"room.emptyRequireLogin" : " La réunion est vide ! Vous pouvez vous connecter pour commencer la réunion ou attendre qu'un hôte se connecte",
|
||||||
|
"room.locketWait" : " La réunion est privatisée - attendez que quelqu’un vous laisse entrer",
|
||||||
|
"room.lobbyAdministration" : " Administration de la salle d’attente",
|
||||||
|
"room.peersInLobby" : " Participants dans la salle d’attente",
|
||||||
|
"room.lobbyEmpty" : " Il n'y a actuellement aucun participant dans la salle d'attente",
|
||||||
|
"room.hiddenPeers" : " {hiddenPeersCount, plural, one {participant} other {participants}}",
|
||||||
|
"room.me" : " Moi",
|
||||||
|
"room.spotlights" : " Participants actifs",
|
||||||
|
"room.passive" : " Participants passifs",
|
||||||
|
"room.videoPaused" : " La vidéo est en pause",
|
||||||
|
|
||||||
|
"tooltip.login" : " Connexion",
|
||||||
|
"tooltip.logout" : " Déconnexion",
|
||||||
|
"tooltip.admitFromLobby" : " Autorisé depuis la salle d'attente",
|
||||||
|
"tooltip.lockRoom" : " Privatisation de la salle",
|
||||||
|
"tooltip.unLockRoom" : " Dé-privatisation de la salle",
|
||||||
|
"tooltip.enterFullscreen" : " Afficher en plein écran",
|
||||||
|
"tooltip.leaveFullscreen" : " Quitter le plein écran",
|
||||||
|
"tooltip.lobby" : " Afficher la salle d'attente",
|
||||||
|
"tooltip.settings" : " Afficher les paramètres",
|
||||||
|
"tooltip.participants": "Afficher les participants",
|
||||||
|
|
||||||
|
"label.roomName" : " Nom de la salle",
|
||||||
|
"label.chooseRoomButton" : " Continuer",
|
||||||
|
"label.yourName" : " Votre nom",
|
||||||
|
"label.newWindow" : " Nouvelle fenêtre",
|
||||||
|
"label.fullscreen" : " Plein écran",
|
||||||
|
"label.openDrawer" : " Ouvrir Drawer",
|
||||||
|
"label.leave" : " Quiter",
|
||||||
|
"label.chatInput" : " Entrer un message",
|
||||||
|
"label.chat" : " Chat",
|
||||||
|
"label.filesharing" : " Partage de fichier",
|
||||||
|
"label.participants" : " Participants",
|
||||||
|
"label.shareFile" : " Partager un fichier",
|
||||||
|
"label.fileSharingUnsupported" : " Partage de fichier non supporté",
|
||||||
|
"label.unknown" : " Inconnu",
|
||||||
|
"label.democratic" : " Vue démocratique",
|
||||||
|
"label.filmstrip" : " Vue avec miniature",
|
||||||
|
"label.low" : " Basse définition",
|
||||||
|
"label.medium" : " Définition normale",
|
||||||
|
"label.high" : " Haute Définition (HD)",
|
||||||
|
"label.veryHigh" : " Très Haute Définition (FHD)",
|
||||||
|
"label.ultra" : " Ultra Haute Définition",
|
||||||
|
"label.close" : " Fermer",
|
||||||
|
|
||||||
|
"settings.settings" : " Paramètres",
|
||||||
|
"settings.camera" : " Caméra",
|
||||||
|
"settings.selectCamera" : " Sélectionner votre caméra",
|
||||||
|
"settings.cantSelectCamera" : " Impossible de sélectionner votre caméra",
|
||||||
|
"settings.audio" : " Microphone",
|
||||||
|
"settings.selectAudio" : " Sélectionner votre microphone",
|
||||||
|
"settings.cantSelectAudio" : " Impossible de sélectionner votre la caméra",
|
||||||
|
"settings.resolution" : " Sélection votre résolution",
|
||||||
|
"settings.layout" : " Mode d'affichage de la salle",
|
||||||
|
"settings.selectRoomLayout" : " Sélectionner l'affiche de la salle",
|
||||||
|
"settings.advancedMode" : " Mode avancé",
|
||||||
|
"settings.permanentTopBar": "Barre supérieure permanente",
|
||||||
|
"settings.lastn": "Nombre de vidéos visibles",
|
||||||
|
|
||||||
|
"filesharing.saveFileError" : " Impossible d'enregistrer le fichier",
|
||||||
|
"filesharing.startingFileShare" : " Début du transfert de fichier",
|
||||||
|
"filesharing.successfulFileShare" : " Fichier transféré",
|
||||||
|
"filesharing.unableToShare" : " Impossible de transférer le fichier",
|
||||||
|
"filesharing.error" : " Erreur lors du transfert de fichier",
|
||||||
|
"filesharing.finished" : " Fin du transfert de fichier",
|
||||||
|
"filesharing.save" : " Sauver",
|
||||||
|
"filesharing.sharedFile" : " {displayName} a partagé un fichier",
|
||||||
|
"filesharing.download" : " Télécharger",
|
||||||
|
"filesharing.missingSeeds" : " Si le téléchargement prend trop de temps c’est qu’il n’y a peut-être plus personne qui partage ce torrent. Demander à quelqu’un de repartager le document.",
|
||||||
|
"devices.devicesChanged" : " Vos périphériques ont changé, reconfigurer vos périphériques avec le menu paramètre",
|
||||||
|
|
||||||
|
"device.audioUnsupported" : " Microphone non supporté",
|
||||||
|
"device.activateAudio" : " Activer l'audio",
|
||||||
|
"device.muteAudio" : " Désactiver l'audio",
|
||||||
|
"device.unMuteAudio" : " Réactiver l'audio",
|
||||||
|
|
||||||
|
"device.videoUnsupported" : " Vidéo non supporté",
|
||||||
|
"device.startVideo" : " Démarrer la vidéo",
|
||||||
|
"device.stopVideo" : " Arrêter la vidéo",
|
||||||
|
|
||||||
|
"device.screenSharingUnsupported" : " Partage d'écran non supporté",
|
||||||
|
"device.startScreenSharing" : " Démarrer le partage d 'écran'",
|
||||||
|
"device.stopScreenSharing" : " Arrêter le partage d'écran",
|
||||||
|
|
||||||
|
"devices.microphoneDisconnected" : " Microphone déconnecté",
|
||||||
|
"devices.microphoneError" : " Une erreur est apparue lors de l'accès à votre microphone",
|
||||||
|
"devices.microPhoneMute" : " Désactiver le microphone",
|
||||||
|
"devices.micophoneUnMute" : " Réactiver le microphone",
|
||||||
|
"devices.microphoneEnable" : " Activer le microphone",
|
||||||
|
"devices.microphoneMuteError" : " Impossible de désactiver le microphone",
|
||||||
|
"devices.microphoneUnMuteError" : " Impossible de réactiver le microphone",
|
||||||
|
|
||||||
|
"devices.screenSharingDisconnected" : " Partage d'écran déconnecté",
|
||||||
|
"devices.screenSharingError" : " Une erreur est apparue lors de l'accès à votre partage d'écran",
|
||||||
|
|
||||||
|
"devices.cameraDisconnected" : " Caméra déconnectée",
|
||||||
|
"devices.cameraError" : " Une erreur est apparue lors de l'accès à votre caméra"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
{
|
||||||
|
"socket.disconnected": "Odspojeni ste",
|
||||||
|
"socket.reconnecting": "Odspojeni ste, pokušavamo vas ponovno spojiti",
|
||||||
|
"socket.reconnected": "Ponovno ste spojeni",
|
||||||
|
"socket.requestError": "Greška na poslužitelju",
|
||||||
|
|
||||||
|
"room.chooseRoom": "Izaberite ime sobe u koju se želite prijaviti",
|
||||||
|
"room.cookieConsent": "Ova stranica koristi kolačiće radi poboljšanja korisničkog iskustva",
|
||||||
|
"room.consentUnderstand": "I understand",
|
||||||
|
"room.joined": "Prijavljeni ste u sobu",
|
||||||
|
"room.cantJoin": "Prijava u sobu nije moguća",
|
||||||
|
"room.youLocked": "Zaključali ste sobu",
|
||||||
|
"room.cantLock": "Zaključavanje sobe nije moguće",
|
||||||
|
"room.youUnLocked": "Otključali ste sobu",
|
||||||
|
"room.cantUnLock": "Otključavanje sobe nije moguće",
|
||||||
|
"room.locked": "Soba je sada zaključana",
|
||||||
|
"room.unlocked": "Soba je sada otključana",
|
||||||
|
"room.newLobbyPeer": "U predvorju je novi učesnik",
|
||||||
|
"room.lobbyPeerLeft": "Učesnik je napustio predvorje",
|
||||||
|
"room.lobbyPeerChangedDisplayName": "Učesnik u predvorju je promijenio ime u {displayName}",
|
||||||
|
"room.lobbyPeerChangedPicture": "Učesnik u predvorju je promijenio sliku",
|
||||||
|
"room.setAccessCode": "Obnovljena pristupna šifra za sobu",
|
||||||
|
"room.accessCodeOn": "Pristupna šifra sobe je aktivna",
|
||||||
|
"room.accessCodeOff":"Pristupna šifra sobe je neaktivna",
|
||||||
|
"room.peerChangedDisplayName": "{oldDisplayName} je sada {displayName}",
|
||||||
|
"room.newPeer": "{displayName} je ušao u sobu",
|
||||||
|
"room.newFile": "Dostupna nova datoteka",
|
||||||
|
"room.toggleAdvancedMode": "Uključen napredni način",
|
||||||
|
"room.setDemocraticView": "Prikaz promijenjen u način: demokratski",
|
||||||
|
"room.setFilmStripView": "Prikaz promijenjen u način: filmska traka",
|
||||||
|
"room.loggedIn": "Prijavljeni ste",
|
||||||
|
"room.loggedOut": "Odjavljeni ste",
|
||||||
|
"room.changedDisplayName": "Ime promijenjeno u {displayName}",
|
||||||
|
"room.changeDisplayNameError": "Dogodila se greška prilikom promjene imena",
|
||||||
|
"room.chatError": "Poruku nije moguće poslati",
|
||||||
|
"room.aboutToJoin": "Upravo ćete se priključiti sastanku",
|
||||||
|
"room.roomId": "Oznaka sobe: {roomName}",
|
||||||
|
"room.setYourName": "Postavite ime za sudjelovanje, i odaberite način prijave",
|
||||||
|
"room.audioOnly": "Samo zvuk",
|
||||||
|
"room.audioVideo": "Zvuk i slika",
|
||||||
|
"room.youAreReady": "Spremni ste",
|
||||||
|
"room.emptyRequireLogin": "Soba je trenutno prazna! Prijavite se za pokretanje sastanka, ili sačekajte organizatora" ,
|
||||||
|
"room.locketWait": "Soba je zaključana - pričekajte odobrenje ...",
|
||||||
|
"room.lobbyAdministration":"Upravljanje predvorjem",
|
||||||
|
"room.peersInLobby":"Učesnici u predvorju",
|
||||||
|
"room.lobbyEmpty": "Trenutno nema nikoga u predvorju",
|
||||||
|
"room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}",
|
||||||
|
"room.me": "Ja",
|
||||||
|
"room.spotlights": "Učesnici u fokusu",
|
||||||
|
"room.passive": "Pasivni učesnici",
|
||||||
|
"room.videoPaused": "Video pauziran",
|
||||||
|
|
||||||
|
"tooltip.login": "Prijava",
|
||||||
|
"tooltip.logout": "Odjava",
|
||||||
|
"tooltip.admitFromLobby": "Pusti iz predvorja",
|
||||||
|
"tooltip.lockRoom": "Zaključaj sobu",
|
||||||
|
"tooltip.unLockRoom": "Otključaj sobu",
|
||||||
|
"tooltip.enterFullscreen": "Postavi puni ekran",
|
||||||
|
"tooltip.leaveFullscreen": "Izađi iz punog ekrana",
|
||||||
|
"tooltip.lobby": "Prikaži predvorje",
|
||||||
|
"tooltip.settings": "Prikaži postavke",
|
||||||
|
"tooltip.participants": "Pokažite sudionike",
|
||||||
|
|
||||||
|
"label.roomName": "Naziv sobe",
|
||||||
|
"label.chooseRoomButton": "Nastavi",
|
||||||
|
"label.yourName": "Vaše ime",
|
||||||
|
"label.newWindow": "Novi ekran",
|
||||||
|
"label.fullscreen": "Puni ekran",
|
||||||
|
"label.openDrawer": "Otvori ladicu",
|
||||||
|
"label.leave": "Napusti",
|
||||||
|
"label.chatInput":"Uđi u razgovor porukama",
|
||||||
|
"label.chat": "Razgovor",
|
||||||
|
"label.filesharing": "Dijeljenje datoteka",
|
||||||
|
"label.participants": "Učesnici",
|
||||||
|
"label.shareFile": "Dijeli datoteku",
|
||||||
|
"label.fileSharingUnsupported": "Dijeljenje datoteka nije podržano",
|
||||||
|
"label.unknown": "Nepoznato",
|
||||||
|
"label.democratic":"Demokratski prikaz",
|
||||||
|
"label.filmstrip": "Prikaz filmska traka",
|
||||||
|
"label.low": "Niska",
|
||||||
|
"label.medium": "Srednja",
|
||||||
|
"label.high": "Visoka (HD)",
|
||||||
|
"label.veryHigh": "Vrlo visoka (FHD)",
|
||||||
|
"label.ultra": "Ultra visoka (UHD)",
|
||||||
|
"label.close": "Zatvori",
|
||||||
|
|
||||||
|
"settings.settings": "Postavke",
|
||||||
|
"settings.camera": "Kamera",
|
||||||
|
"settings.selectCamera": "Odaberi video uređaj",
|
||||||
|
"settings.cantSelectCamera": "Nije moguće odabrati video uređaj",
|
||||||
|
"settings.audio": "Uređaj za zvuk",
|
||||||
|
"settings.selectAudio": "Odaberi uređaj za zvuk",
|
||||||
|
"settings.cantSelectAudio": "Nije moguće odabrati uređaj za zvuk",
|
||||||
|
"settings.resolution": "Odaberi video rezoluciju",
|
||||||
|
"settings.layout": "Način prikaza",
|
||||||
|
"settings.selectRoomLayout": "Odaberi način prikaza",
|
||||||
|
"settings.advancedMode": "Napredne mogućnosti",
|
||||||
|
"settings.permanentTopBar": "Stalna gornja šipka",
|
||||||
|
"settings.lastn": "Broj vidljivih videozapisa",
|
||||||
|
|
||||||
|
"filesharing.saveFileError": "Nije moguće spremiti datoteku",
|
||||||
|
"filesharing.startingFileShare": "Pokušaj dijeljenja datoteke",
|
||||||
|
"filesharing.successfulFileShare": "Datoteka uspješno podijeljena",
|
||||||
|
"filesharing.unableToShare": "Nije moguće podijeliti datoteku",
|
||||||
|
"filesharing.error": "Greška prilikom dijeljenja datoteke",
|
||||||
|
"filesharing.finished": "Završeno učitavanje datoteke",
|
||||||
|
"filesharing.save": "Spremi",
|
||||||
|
"filesharing.sharedFile": "{displayName} je podijelio datoteku",
|
||||||
|
"filesharing.download": "Učitaj",
|
||||||
|
"filesharing.missingSeeds": "Ako ovaj proces traje dugo, možda više nitko ne dijeli datoteku. Pokušajte zamoliti nekoga da ponovo učita željenu datoteku." ,
|
||||||
|
|
||||||
|
"devices.devicesChanged": "Uređaji su se promijenili, podesite ponovno uređaje u postavkama",
|
||||||
|
|
||||||
|
"device.audioUnsupported": "Zvuk nije podržan",
|
||||||
|
"device.activateAudio": "Omogući zvuk",
|
||||||
|
"device.muteAudio": "Utišaj zvuk",
|
||||||
|
"device.unMuteAudio": "Vrati zvuk",
|
||||||
|
|
||||||
|
"device.videoUnsupported": "Slika nije podržana",
|
||||||
|
"device.startVideo": "Pokreni sliku",
|
||||||
|
"device.stopVideo": "Zaustavi sliku",
|
||||||
|
|
||||||
|
"device.screenSharingUnsupported": "Dijeljenje ekrana nije podržano",
|
||||||
|
"device.startScreenSharing": "Pokreni dijeljenje ekrana",
|
||||||
|
"device.stopScreenSharing": "Zaustavi dijeljenje ekrana",
|
||||||
|
|
||||||
|
"devices.microphoneDisconnected": "Mikrofon odspojen",
|
||||||
|
"devices.microphoneError": "Greška prilikom pristupa mikrofonu",
|
||||||
|
"devices.microPhoneMute": "Mikrofon utišan",
|
||||||
|
"devices.micophoneUnMute": "Mikrofon pojačan",
|
||||||
|
"devices.microphoneEnable": "Mikrofon omogućen",
|
||||||
|
"devices.microphoneMuteError": "Nije moguće utišati mikrofon",
|
||||||
|
"devices.microphoneUnMuteError": "Nije moguće pojačati mikrofon",
|
||||||
|
|
||||||
|
"devices.screenSharingDisconnected" : "Prekinuto dijeljenje ekrana",
|
||||||
|
"devices.screenSharingError": "Greška prilikom pristupa ekranu",
|
||||||
|
|
||||||
|
"devices.cameraDisconnected": "Kamera odspojena",
|
||||||
|
"devices.cameraError": "Greška prilikom pristupa kameri"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
{
|
||||||
|
"socket.disconnected": "A kapcsolat lebomlott",
|
||||||
|
"socket.reconnecting": "A kapcsolat lebomlott, újrapróbálkozás",
|
||||||
|
"socket.reconnected": "Sikeres újarkapcsolódás",
|
||||||
|
"socket.requestError": "Sikertelen szerver lekérés",
|
||||||
|
|
||||||
|
"room.chooseRoom": "Choose the name of the room you would like to join",
|
||||||
|
"room.cookieConsent": "Ez a weblap a felhasználói élmény fokozása miatt sütiket használ",
|
||||||
|
"room.consentUnderstand": "I understand",
|
||||||
|
"room.joined": "Csatlakozátál a konferenciához",
|
||||||
|
"room.cantJoin": "Sikertelen csatlakozás a konferenciához",
|
||||||
|
"room.youLocked": "A konferenciába való belépés letiltva",
|
||||||
|
"room.cantLock": "Sikertelen a konferenciaba való belépés letiltása",
|
||||||
|
"room.youUnLocked": "A konferenciába való belépés engedélyezve",
|
||||||
|
"room.cantUnLock": "Sikertelen a konferenciába való belépés engedélyezése",
|
||||||
|
"room.locked": "A konferenciába való belépés letiltva",
|
||||||
|
"room.unlocked": "A konferenciába való belépés engedélyezve",
|
||||||
|
"room.newLobbyPeer": "Új részvevő lépett be a konferencia előszobájába",
|
||||||
|
"room.lobbyPeerLeft": "A konferencia előszobájából a részvevő távozott",
|
||||||
|
"room.lobbyPeerChangedDisplayName": "Az előszobai résztvevő meváltoztatta a nevét: {displayName}",
|
||||||
|
"room.lobbyPeerChangedPicture": "Az előszobai résztvevő meváltoztatta a képét",
|
||||||
|
"room.setAccessCode": "A konferencia hozzáférési kódja megváltozott",
|
||||||
|
"room.accessCodeOn": "A konferencia hozzáférési kódja aktiválva",
|
||||||
|
"room.accessCodeOff": "A konferencia hozzáférési kódka deaktiválva",
|
||||||
|
"room.peerChangedDisplayName": "{oldDisplayName} megváltozott erre {displayName}",
|
||||||
|
"room.newPeer": "{displayName} kapcsolódott a konferenciához",
|
||||||
|
"room.newFile": "Új fájl érhető el",
|
||||||
|
"room.toggleAdvancedMode": "Részletes információk megjelenítése",
|
||||||
|
"room.setDemocraticView": "Egyenlő képméretű képkiosztás",
|
||||||
|
"room.setFilmStripView": "Egy nagy + sok kis filmkocka képkiosztás",
|
||||||
|
"room.loggedIn": "Sikeres belépés",
|
||||||
|
"room.loggedOut": "Sikeres kijelentkezés",
|
||||||
|
"room.changedDisplayName": "A neved sikeresen megváltozott: {displayName}",
|
||||||
|
"room.changeDisplayNameError": "Hiba történt a név megváltoztatásakor",
|
||||||
|
"room.chatError": "Hiba lépett fel a chat üzenetet elküldése során",
|
||||||
|
"room.aboutToJoin": "Hamarosan csatlakozol a konferenciához",
|
||||||
|
"room.roomId": "Konferenciaazonosító: {roomName}",
|
||||||
|
"room.setYourName": "Állítsd be a neved, és válaszd ki hogyan szeretnél csatlakozni:",
|
||||||
|
"room.audioOnly": "csak Hang",
|
||||||
|
"room.audioVideo": "Hang és Videó",
|
||||||
|
"room.youAreReady": "Ok, kész vagy",
|
||||||
|
"room.emptyRequireLogin": "A konferencia üres! Be kell lépned a konferecnia elkezdéséhez, vagy várnod kell amíg a házigazda becsatlakozik.",
|
||||||
|
"room.locketWait": "A konferencia szobába a a belépés tilos - Várj amíg valaki be nem enged ...",
|
||||||
|
"room.lobbyAdministration": "Előszoba adminisztráció",
|
||||||
|
"room.peersInLobby": "Résztvevők az előszobában",
|
||||||
|
"room.lobbyEmpty": "Épp senki sincs a konferencia előszobájában",
|
||||||
|
"room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}",
|
||||||
|
"room.me": "Én",
|
||||||
|
"room.spotlights": "Látható résztvevők",
|
||||||
|
"room.passive": "Passzív résztvevők",
|
||||||
|
"room.videoPaused": "Ez a videóstream szünetel",
|
||||||
|
|
||||||
|
"tooltip.login": "Belépés",
|
||||||
|
"tooltip.logout": "Kilépés",
|
||||||
|
"tooltip.admitFromLobby": "Beenegdem az előszobából",
|
||||||
|
"tooltip.lockRoom": "A konferenciába való belépés letiltása",
|
||||||
|
"tooltip.unLockRoom": "konferenciába való belépés engedélyezése",
|
||||||
|
"tooltip.enterFullscreen": "Teljes képernyős mód",
|
||||||
|
"tooltip.leaveFullscreen": "Kilépés teljes képernyős módból",
|
||||||
|
"tooltip.lobby": "Az előszobában várakozók listája",
|
||||||
|
"tooltip.settings": "Beállítások",
|
||||||
|
"tooltip.participants": "Résztvevők",
|
||||||
|
|
||||||
|
"label.roomName": "Konferencia",
|
||||||
|
"label.chooseRoomButton": "Tovább",
|
||||||
|
"label.yourName": "A neved",
|
||||||
|
"label.newWindow": "Új ablak",
|
||||||
|
"label.fullscreen": "Teljes képernyős mód",
|
||||||
|
"label.openDrawer": "Oldalsáv megnyitása",
|
||||||
|
"label.leave": "Kilépés",
|
||||||
|
"label.chatInput": "Chat üzenet ...",
|
||||||
|
"label.chat": "Chat",
|
||||||
|
"label.filesharing": "Fájl megosztás",
|
||||||
|
"label.participants": "Résztvevők",
|
||||||
|
"label.shareFile": "Fájl megosztása",
|
||||||
|
"label.fileSharingUnsupported": "Fájl megosztás nem támogatott",
|
||||||
|
"label.unknown": "Ismeretlen",
|
||||||
|
"label.democratic": "Egyforma képméretű képkiosztás",
|
||||||
|
"label.filmstrip": "Egy nagy és kis filmkockák képkiosztás",
|
||||||
|
"label.low": "Alacsony felbontás",
|
||||||
|
"label.medium": "Közepes felbontás",
|
||||||
|
"label.high": "Magas (HD) felbontás",
|
||||||
|
"label.veryHigh": "Nagyon magas (FHD)",
|
||||||
|
"label.ultra": "Ultra magas (UHD)",
|
||||||
|
"label.close": "Bezár",
|
||||||
|
|
||||||
|
"settings.settings": "Beállítások",
|
||||||
|
"settings.camera": "Kamera",
|
||||||
|
"settings.selectCamera": "Válasz videóeszközt",
|
||||||
|
"settings.cantSelectCamera": "Nem lehet a videó eszközt kiválasztani",
|
||||||
|
"settings.audio": "Hang eszköz",
|
||||||
|
"settings.selectAudio": "Válasz hangeszközt",
|
||||||
|
"settings.cantSelectAudio": "Nem lehet a hang eszközt kiválasztani",
|
||||||
|
"settings.resolution": "Válaszd ki a videóeszközöd felbontását",
|
||||||
|
"settings.layout": "A konferencia képkiosztása",
|
||||||
|
"settings.selectRoomLayout": "Válaszd ki a konferencia képkiosztását",
|
||||||
|
"settings.advancedMode": "Részletes információk",
|
||||||
|
"settings.permanentTopBar": "Állandó felső sáv",
|
||||||
|
"settings.lastn": "A látható videók száma",
|
||||||
|
|
||||||
|
"filesharing.saveFileError": "A file-t nem sikerült elmenteni",
|
||||||
|
"filesharing.startingFileShare": "Fájl megosztása",
|
||||||
|
"filesharing.successfulFileShare": "A fájl sikeresen megosztva",
|
||||||
|
"filesharing.unableToShare": "Sikereteln fájl megosztás",
|
||||||
|
"filesharing.error": "Hiba a fájlmegosztás során",
|
||||||
|
"filesharing.finished": "A fájl letöltés befejeződött",
|
||||||
|
"filesharing.save": "Mentés",
|
||||||
|
"filesharing.sharedFile": "{displayName} megosztott egy fájlt",
|
||||||
|
"filesharing.download": "Letöltés",
|
||||||
|
"filesharing.missingSeeds": "Ha a folyamat túl sok ideig tart, akkor lehet hogy senki sem seed-eli ez a torrent-et. Próbálj meg megkérni valakit hogy töltse fel újra ezt a fájlt.",
|
||||||
|
|
||||||
|
"devices.devicesChanged": "Az eszközei megváltoztak, konfiguráld őket be a beállítások menüben",
|
||||||
|
|
||||||
|
"device.audioUnsupported": "A hnag nem támogatott",
|
||||||
|
"device.activateAudio": "Hang aktiválása",
|
||||||
|
"device.muteAudio": "Hang némítása",
|
||||||
|
"device.unMuteAudio": "Hang némítás kikapcsolása",
|
||||||
|
|
||||||
|
"device.videoUnsupported": "A videó nem támogatott",
|
||||||
|
"device.startVideo": "Videó indítása",
|
||||||
|
"device.stopVideo": "Videó leállítása",
|
||||||
|
|
||||||
|
"device.screenSharingUnsupported": "A képernyő megosztás nem támogatott",
|
||||||
|
"device.startScreenSharing": "Képernyőmegosztás indítása",
|
||||||
|
"device.stopScreenSharing": "Képernyőmegosztás leáłłítása",
|
||||||
|
|
||||||
|
"devices.microphoneDisconnected": "Microphone kapcsolat bontva",
|
||||||
|
"devices.microphoneError": "Hiba történt a mikrofon hangeszköz elérése közben",
|
||||||
|
"devices.microPhoneMute": "A mikrofon némítva lett",
|
||||||
|
"devices.micophoneUnMute": "A mikrofon némítása ki lett kapocsolva",
|
||||||
|
"devices.microphoneEnable": "A mikrofon engedéylezve",
|
||||||
|
"devices.microphoneMuteError": "Nem sikerült a mikrofonod némítása",
|
||||||
|
"devices.microphoneUnMuteError": "Nem sikerült a mikrofonod némításának kikapcsolása",
|
||||||
|
|
||||||
|
"devices.screenSharingDisconnected" : "Képernyőmegosztás kapcsolat bontva",
|
||||||
|
"devices.screenSharingError": "Hiba történt a képernyőd megosztása során",
|
||||||
|
|
||||||
|
"devices.cameraDisconnected": "A kamera kapcsolata lebomlott",
|
||||||
|
"devices.cameraError": "Hiba történt a kamera elérése során"
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,9 @@
|
||||||
"socket.reconnected": "Du er koblet til igjen",
|
"socket.reconnected": "Du er koblet til igjen",
|
||||||
"socket.requestError": "Feil på server melding",
|
"socket.requestError": "Feil på server melding",
|
||||||
|
|
||||||
|
"room.chooseRoom": "Velg navn på møtet du vil bli med i eller starte",
|
||||||
"room.cookieConsent": "Denne siden bruker cookies for å forbedre brukeropplevelsen",
|
"room.cookieConsent": "Denne siden bruker cookies for å forbedre brukeropplevelsen",
|
||||||
|
"room.consentUnderstand": "Jeg forstår",
|
||||||
"room.joined": "Du ble med i møtet",
|
"room.joined": "Du ble med i møtet",
|
||||||
"room.cantJoin": "Kunne ikke bli med i møtet",
|
"room.cantJoin": "Kunne ikke bli med i møtet",
|
||||||
"room.youLocked": "Du låste møtet",
|
"room.youLocked": "Du låste møtet",
|
||||||
|
|
@ -57,7 +59,10 @@
|
||||||
"tooltip.leaveFullscreen": "Forlat fullskjerm",
|
"tooltip.leaveFullscreen": "Forlat fullskjerm",
|
||||||
"tooltip.lobby": "Vis lobby",
|
"tooltip.lobby": "Vis lobby",
|
||||||
"tooltip.settings": "Vis innstillinger",
|
"tooltip.settings": "Vis innstillinger",
|
||||||
|
"tooltip.participants": "Vis deltakere",
|
||||||
|
|
||||||
|
"label.roomName": "Møtenavn",
|
||||||
|
"label.chooseRoomButton": "Fortsett",
|
||||||
"label.yourName": "Ditt navn",
|
"label.yourName": "Ditt navn",
|
||||||
"label.newWindow": "Flytt til separat vindu",
|
"label.newWindow": "Flytt til separat vindu",
|
||||||
"label.fullscreen": "Fullskjerm",
|
"label.fullscreen": "Fullskjerm",
|
||||||
|
|
@ -90,6 +95,8 @@
|
||||||
"settings.layout": "Møtelayout",
|
"settings.layout": "Møtelayout",
|
||||||
"settings.selectRoomLayout": "Velg møtelayout",
|
"settings.selectRoomLayout": "Velg møtelayout",
|
||||||
"settings.advancedMode": "Avansert modus",
|
"settings.advancedMode": "Avansert modus",
|
||||||
|
"settings.permanentTopBar": "Permanent topplinje",
|
||||||
|
"settings.lastn": "Antall videoer synlig",
|
||||||
|
|
||||||
"filesharing.saveFileError": "Klarte ikke å lagre fil",
|
"filesharing.saveFileError": "Klarte ikke å lagre fil",
|
||||||
"filesharing.startingFileShare": "Starter fildeling",
|
"filesharing.startingFileShare": "Starter fildeling",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
{
|
||||||
|
"socket.disconnected": "Rozłączono",
|
||||||
|
"socket.reconnecting": "Próba ponownego połączenia",
|
||||||
|
"socket.reconnected": "Połączono",
|
||||||
|
"socket.requestError": "Błąd żądania serwera",
|
||||||
|
|
||||||
|
"room.chooseRoom": "Wybór konferencji",
|
||||||
|
"room.cookieConsent": "Ta strona internetowa wykorzystuje pliki cookie w celu zwiększenia wygody użytkowania.",
|
||||||
|
"room.consentUnderstand": "I understand",
|
||||||
|
"room.joined": "Podłączono do konferencji",
|
||||||
|
"room.cantJoin": "Brak możliwości dołączenia do pokoju",
|
||||||
|
"room.youLocked": "Zakluczono pokój",
|
||||||
|
"room.cantLock": "Nie można zakluczyć pokoju",
|
||||||
|
"room.youUnLocked": "Odkluczono pokój",
|
||||||
|
"room.cantUnLock": "Nie można odkluczyć pokoju",
|
||||||
|
"room.locked": "Pokój jest zakluczony",
|
||||||
|
"room.unlocked": "Pokój jest odkluczony",
|
||||||
|
"room.newLobbyPeer": "Nowy uczestnik w poczekalni",
|
||||||
|
"room.lobbyPeerLeft": "Uczestnik opuścił poczekalnię",
|
||||||
|
"room.lobbyPeerChangedDisplayName": "Uczestnik w poczekalni zmienił nazwę na {displayName}",
|
||||||
|
"room.lobbyPeerChangedPicture": "Uczestnik w poczekalni zmienił swój obraz profilowy",
|
||||||
|
"room.setAccessCode": "Kod dostępu do konferencji został zaktualizowany",
|
||||||
|
"room.accessCodeOn": "Kod dostępu do konferencji został włączony",
|
||||||
|
"room.accessCodeOff": "Kod dostępu do konferencji został wyłączony",
|
||||||
|
"room.peerChangedDisplayName": "Zmieniono nazwę użytkownika {oldDisplayName} na {displayName}",
|
||||||
|
"room.newPeer": "Nowy uczestnik {displayName} dołączył do konferencji",
|
||||||
|
"room.newFile": "Dostępny jest nowy plik",
|
||||||
|
"room.toggleAdvancedMode": "Przełączono tryb zaawansowany",
|
||||||
|
"room.setDemocraticView": "Widok zmieniony na: układ demokratyczny",
|
||||||
|
"room.setFilmStripView": "Widok zmieniony na: układ filmowy",
|
||||||
|
"room.loggedIn": "Zalogowano",
|
||||||
|
"room.loggedOut": "Wylogowano",
|
||||||
|
"room.changedDisplayName": "Nazwa użytkownika zmieniona na {displayName}",
|
||||||
|
"room.changeDisplayNameError": "Wystąpił błąd podczas zmiany nazwy użytkownika",
|
||||||
|
"room.chatError": "Nie można wysłać wiadomości",
|
||||||
|
"room.aboutToJoin": "Wkrótce nastąpi podłączenie do konferencji",
|
||||||
|
"room.roomId": "Identyfikator pokoju: {roomName}",
|
||||||
|
"room.setYourName": "Proszę podać swoją nazwę użytkownika i wybrać sposób połączenia:",
|
||||||
|
"room.audioOnly": "Tylko audio",
|
||||||
|
"room.audioVideo": "Audio i wideo",
|
||||||
|
"room.youAreReady": "Wszystko gotowe",
|
||||||
|
"room.emptyRequireLogin": "Pokój jest pusty. Proszę się zalogować lub poczekać na gospodarza pokoju.",
|
||||||
|
"room.locketWait": "Pokój jest zakluczony - proszę poczekać na otwarcie...",
|
||||||
|
"room.lobbyAdministration": "Administracja poczekalnią",
|
||||||
|
"room.peersInLobby": "Uczestnicy w poczekalni",
|
||||||
|
"room.lobbyEmpty": "Aktualnie nie ma nikogo w poczekalni",
|
||||||
|
"room.hiddenPeers": "{hiddenPeersCount, plural, one {uczestnik} other {uczestników}}",
|
||||||
|
"room.me": "Ja",
|
||||||
|
"room.spotlights": "Aktywni uczestnicy",
|
||||||
|
"room.passive": "Pasywni uczestnicy",
|
||||||
|
"room.videoPaused": "To wideo jest wstrzymane.",
|
||||||
|
|
||||||
|
"tooltip.login": "Zaloguj",
|
||||||
|
"tooltip.logout": "Wyloguj",
|
||||||
|
"tooltip.admitFromLobby": "Przejście z poczekalni",
|
||||||
|
"tooltip.lockRoom": "Zaklucz pokój",
|
||||||
|
"tooltip.unLockRoom": "Odklucz pokój",
|
||||||
|
"tooltip.enterFullscreen": "Włącz tryb pełnoekranowy",
|
||||||
|
"tooltip.leaveFullscreen": "Wyłącz tryb pełnoekranowy",
|
||||||
|
"tooltip.lobby": "Pokaż poczekalnię",
|
||||||
|
"tooltip.settings": "Pokaż ustawienia",
|
||||||
|
"tooltip.participants": "Pokaż uczestników",
|
||||||
|
|
||||||
|
"label.roomName": "Nazwa konferencji",
|
||||||
|
"label.chooseRoomButton": "Kontynuuj",
|
||||||
|
"label.yourName": "Moja nazwa użytkownika",
|
||||||
|
"label.newWindow": "Nowe okno",
|
||||||
|
"label.fullscreen": "Tryb pełnoekranowy",
|
||||||
|
"label.openDrawer": "Menu",
|
||||||
|
"label.leave": "Zakończ",
|
||||||
|
"label.chatInput": "Treść wiadomości...",
|
||||||
|
"label.chat": "Czat",
|
||||||
|
"label.filesharing": "Udostępnianie plików",
|
||||||
|
"label.participants": "Uczestnicy",
|
||||||
|
"label.shareFile": "Udostępnij plik",
|
||||||
|
"label.fileSharingUnsupported": "Udostępnianie plików nie jest obsługiwane",
|
||||||
|
"label.unknown": "Nieznane",
|
||||||
|
"label.democratic": "Układ demokratyczny",
|
||||||
|
"label.filmstrip": "Układ filmowy",
|
||||||
|
"label.low": "Niska",
|
||||||
|
"label.medium": "Średnia",
|
||||||
|
"label.high": "Wysoka (HD)",
|
||||||
|
"label.veryHigh": "Bardzo wysoka (FHD)",
|
||||||
|
"label.ultra": "Ultra (UHD)",
|
||||||
|
"label.close": "Zamknij",
|
||||||
|
|
||||||
|
"settings.settings": "Ustawienia",
|
||||||
|
"settings.camera": "Kamera",
|
||||||
|
"settings.selectCamera": "Wybór urządzenia wideo",
|
||||||
|
"settings.cantSelectCamera": "Nie można wybrać urządzenia wideo",
|
||||||
|
"settings.audio": "Urządzenie audio",
|
||||||
|
"settings.selectAudio": "Wybór urządzenia audio",
|
||||||
|
"settings.cantSelectAudio": "Nie można wybrać urządzenia audio",
|
||||||
|
"settings.resolution": "Wybór rozdzielczości wideo",
|
||||||
|
"settings.layout": "Układ konferencji",
|
||||||
|
"settings.selectRoomLayout": "Ustawienia układu konferencji",
|
||||||
|
"settings.advancedMode": "Tryb zaawansowany",
|
||||||
|
"settings.permanentTopBar": "Stały górny pasek",
|
||||||
|
"settings.lastn": "Liczba widocznych filmów",
|
||||||
|
|
||||||
|
"filesharing.saveFileError": "Nie można zapisać pliku",
|
||||||
|
"filesharing.startingFileShare": "Próba udostępnienia pliku",
|
||||||
|
"filesharing.successfulFileShare": "Plik został udostępniony",
|
||||||
|
"filesharing.unableToShare": "Nie można udostępnić pliku",
|
||||||
|
"filesharing.error": "Błąd udostępniania pliku",
|
||||||
|
"filesharing.finished": "Ukończono pobieranie pliku",
|
||||||
|
"filesharing.save": "Zapisz",
|
||||||
|
"filesharing.sharedFile": "{displayName} współdzieli plik",
|
||||||
|
"filesharing.download": "Pobierz",
|
||||||
|
"filesharing.missingSeeds": "Udostępnianie plików przez Torrent - jeśli ten proces trwa długo sugerujemy poprosić o ponowne załadowanie żądanego pliku",
|
||||||
|
|
||||||
|
"devices.devicesChanged": "Urządzenia wejściowe się zmieniły, konfiguracja dostępna w oknie dialogowym ustawień",
|
||||||
|
|
||||||
|
"device.audioUnsupported": "Dźwięk nieobsługiwany",
|
||||||
|
"device.activateAudio": "Aktywuj mikrofon",
|
||||||
|
"device.muteAudio": "Wyłącz mikrofon",
|
||||||
|
"device.unMuteAudio": "Włącz mikrofon",
|
||||||
|
|
||||||
|
"device.videoUnsupported": "Wideo nie jest obsługiwane",
|
||||||
|
"device.startVideo": "Włącz kamerę",
|
||||||
|
"device.stopVideo": "Wyłącz kamerę",
|
||||||
|
|
||||||
|
"device.screenSharingUnsupported": "Udostępnianie ekranu nie jest obsługiwane",
|
||||||
|
"device.startScreenSharing": "Rozpocznij udostępnianie ekranu",
|
||||||
|
"device.stopScreenSharing": "Zatrzymaj udostępnianie ekranu",
|
||||||
|
|
||||||
|
"devices.microphoneDisconnected": "Odłączono mikrofon",
|
||||||
|
"devices.microphoneError": "Błąd dostępu do mikrofonu",
|
||||||
|
"devices.microPhoneMute": "Wyciszenie mikrofonu włączone",
|
||||||
|
"devices.micophoneUnMute": "Wyciszenie mikrofonu wyłączone",
|
||||||
|
"devices.microphoneEnable": "Włączono mikrofon",
|
||||||
|
"devices.microphoneMuteError": "Nie można wyciszyć mikrofonu",
|
||||||
|
"devices.microphoneUnMuteError": "Nie można wyłączyć wyciszenia mikrofonu.",
|
||||||
|
|
||||||
|
"devices.screenSharingDisconnected" : "Udostępnianie ekranu zakończone",
|
||||||
|
"devices.screenSharingError": "Wystąpił błąd podczas uzyskiwania dostępu do ekranu",
|
||||||
|
|
||||||
|
"devices.cameraDisconnected": "Kamera odłączona",
|
||||||
|
"devices.cameraError": "Wystąpił błąd podczas uzyskiwania dostępu do kamery"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
{
|
||||||
|
"socket.disconnected": "Está desligado",
|
||||||
|
"socket.reconnecting": "Está desligado, tentando ligar novamente.",
|
||||||
|
"socket.reconnected": "Está novamente em sessão",
|
||||||
|
"socket.requestError": "Ocorreu um erro no pedido ao servidor",
|
||||||
|
|
||||||
|
"room.chooseRoom": "Indique o nome da sala em que pretende entrar",
|
||||||
|
"room.cookieConsent": "Este sitio web utiliza cookie para melhorar a sua experiência de utilização",
|
||||||
|
"room.consentUnderstand": "I understand",
|
||||||
|
"room.joined": "Entrou na sala",
|
||||||
|
"room.cantJoin": "Não foi possivel entrar na sala",
|
||||||
|
"room.youLocked": "Bloqueou o acesso à sala",
|
||||||
|
"room.cantLock": "Impossível bloquear acesso à sala",
|
||||||
|
"room.youUnLocked": "Desbloqueou o acesso à sala",
|
||||||
|
"room.cantUnLock": "Impossível desbloquear o acesso à sala",
|
||||||
|
"room.locked": "A sala está bloqueada",
|
||||||
|
"room.unlocked": "A sala está desbloqueada",
|
||||||
|
"room.newLobbyPeer": "Novo participante no em espera",
|
||||||
|
"room.lobbyPeerLeft": "Participantes em espera",
|
||||||
|
"room.lobbyPeerChangedDisplayName": "Participante em espera alterou o nome para {displayName}",
|
||||||
|
"room.lobbyPeerChangedPicture": "Participante em espera alterou a fotografia",
|
||||||
|
"room.setAccessCode": "Código de acesso para a sala atualizado",
|
||||||
|
"room.accessCodeOn": "Código de acesso à sala está ativado",
|
||||||
|
"room.accessCodeOff": "Códio de acesso à sala está desativado",
|
||||||
|
"room.peerChangedDisplayName": "{oldDisplayName} é agora {displayName}",
|
||||||
|
"room.newPeer": "{displayName} entrou na sala",
|
||||||
|
"room.newFile": "Novo ficheiro disponível",
|
||||||
|
"room.toggleAdvancedMode": "Alterado para modo avançado",
|
||||||
|
"room.setDemocraticView": "Alterar para vista democrática",
|
||||||
|
"room.setFilmStripView": "Alterar para vista de fita de cinema",
|
||||||
|
"room.loggedIn": "Realizou o acesso",
|
||||||
|
"room.loggedOut": "Terminou o acesso",
|
||||||
|
"room.changedDisplayName": "O seu nome foi alterado para {displayName}",
|
||||||
|
"room.changeDisplayNameError": "Um erro ocorreu enquanto estava a alterar o seu nome",
|
||||||
|
"room.chatError": "Foi impossível enviar a sua mensagem",
|
||||||
|
"room.aboutToJoin": "Está prestes a entrar numa reunião",
|
||||||
|
"room.roomId": "ID da Sala: {roomName}",
|
||||||
|
"room.setYourName": "Indique o seu nome de participante e escolha como pretende juntar-se à reunião:",
|
||||||
|
"room.audioOnly": "Apenas áudio",
|
||||||
|
"room.audioVideo": "Áudio e Vídeo",
|
||||||
|
"room.youAreReady": "Ok, está pronto",
|
||||||
|
"room.emptyRequireLogin": "A sala está vazia! Pode entrar e começar a reunião ou esperar até que o organizador da reunião entre",
|
||||||
|
"room.locketWait": "A sala está bloqueada - aguarde até que alguem o deixe entrar...",
|
||||||
|
"room.lobbyAdministration": "Administração da sala de espera",
|
||||||
|
"room.peersInLobby": "Participantes na sala de espera",
|
||||||
|
"room.lobbyEmpty": "É neste momento o único participante em espera",
|
||||||
|
"room.hiddenPeers": "{hiddenPeersCount, plural, one {participante} other {participantes}}",
|
||||||
|
"room.me": "Eu",
|
||||||
|
"room.spotlights": "Participantes em foco",
|
||||||
|
"room.passive": "Participantes passivos",
|
||||||
|
"room.videoPaused": "Este vídeo está em pausa",
|
||||||
|
|
||||||
|
"tooltip.login": "Entrar",
|
||||||
|
"tooltip.logout": "Sair",
|
||||||
|
"tooltip.admitFromLobby": "Admitir da sala de espera",
|
||||||
|
"tooltip.lockRoom": "Bloquear sala",
|
||||||
|
"tooltip.unLockRoom": "Desbloquear sala",
|
||||||
|
"tooltip.enterFullscreen": "Apresentar em ecrã completo",
|
||||||
|
"tooltip.leaveFullscreen": "Sair de ecrã completo",
|
||||||
|
"tooltip.lobby": "Apresentar sala de espera",
|
||||||
|
"tooltip.settings": "Apresentar definições",
|
||||||
|
"tooltip.participants": "Apresentar participantes",
|
||||||
|
|
||||||
|
"label.roomName": "Nome da sala",
|
||||||
|
"label.chooseRoomButton": "Continuar",
|
||||||
|
"label.yourName": "O seu nome",
|
||||||
|
"label.newWindow": "Nova janela",
|
||||||
|
"label.fullscreen": "Ecrã completo",
|
||||||
|
"label.openDrawer": "Abrir painel",
|
||||||
|
"label.leave": "Sair",
|
||||||
|
"label.chatInput": "Indique mensagem...",
|
||||||
|
"label.chat": "Chat",
|
||||||
|
"label.filesharing": "Partilha de ficheiro",
|
||||||
|
"label.participants": "Participantes",
|
||||||
|
"label.shareFile": "Partilhar ficheiro",
|
||||||
|
"label.fileSharingUnsupported": "Partilha de ficheiro não disponível",
|
||||||
|
"label.unknown": "Desconhecido",
|
||||||
|
"label.democratic": "Vista democrática",
|
||||||
|
"label.filmstrip": "Vista filme de cinema",
|
||||||
|
"label.low": "Biaixo",
|
||||||
|
"label.medium": "Medio",
|
||||||
|
"label.high": "Alta (HD)",
|
||||||
|
"label.veryHigh": "Muito alta (FHD)",
|
||||||
|
"label.ultra": "Ultra (UHD)",
|
||||||
|
"label.close": "Fechar",
|
||||||
|
|
||||||
|
"settings.settings": "Definições",
|
||||||
|
"settings.camera": "Camera",
|
||||||
|
"settings.selectCamera": "Selecione o seu dispositivo de fonte de vídeo",
|
||||||
|
"settings.cantSelectCamera": "Impossível selecionar dispositivo de vídeo",
|
||||||
|
"settings.audio": "Dispositivo Áudio",
|
||||||
|
"settings.selectAudio": "Selecione o seu dispositivo de áudio",
|
||||||
|
"settings.cantSelectAudio": "Impossível selecionar o seu dispositivo de áudio",
|
||||||
|
"settings.resolution": "Selecione a sua resolução de vídeo",
|
||||||
|
"settings.layout": "Disposição da sala",
|
||||||
|
"settings.selectRoomLayout": "Seleccione a disposição da sala",
|
||||||
|
"settings.advancedMode": "Modo avançado",
|
||||||
|
"settings.permanentTopBar": "Barra superior permanente",
|
||||||
|
"settings.lastn": "Número de vídeos visíveis",
|
||||||
|
|
||||||
|
"filesharing.saveFileError": "Impossível de gravar o ficheiro",
|
||||||
|
"filesharing.startingFileShare": "Tentando partilha de ficheiro",
|
||||||
|
"filesharing.successfulFileShare": "Ficheiro partilhado com sucesso",
|
||||||
|
"filesharing.unableToShare": "Foi impossível partilhar o ficheiro",
|
||||||
|
"filesharing.error": "Aconteceu um erro na partilha do ficheiro",
|
||||||
|
"filesharing.finished": "Ficheiro acabou de ser descarregado",
|
||||||
|
"filesharing.save": "Gravar",
|
||||||
|
"filesharing.sharedFile": "{displayName} partilhou um ficheiro",
|
||||||
|
"filesharing.download": "Descarregar",
|
||||||
|
"filesharing.missingSeeds": "Se este processo demorar muito tempo, pode ser quem ninguém esteja a partilhar este ficheiro. Solicite a alguém para partilhar o ficheiro que pretende.",
|
||||||
|
|
||||||
|
"devices.devicesChanged": "Os seus dispositivos foram alterados, configure os dispositivos na opção de definições",
|
||||||
|
|
||||||
|
"device.audioUnsupported": "Áudio não disponível",
|
||||||
|
"device.activateAudio": "Ativar áudio",
|
||||||
|
"device.muteAudio": "Desativar o microfone",
|
||||||
|
"device.unMuteAudio": "Ativar o microfone",
|
||||||
|
|
||||||
|
"device.videoUnsupported": "Video não disponível",
|
||||||
|
"device.startVideo": "Iniciar vídeo",
|
||||||
|
"device.stopVideo": "Parar vídeo",
|
||||||
|
|
||||||
|
"device.screenSharingUnsupported": "Partilha de ecrã não disponível",
|
||||||
|
"device.startScreenSharing": "Iniciar partilha de ecrã",
|
||||||
|
"device.stopScreenSharing": "Parar partilha de ecrã",
|
||||||
|
|
||||||
|
"devices.microphoneDisconnected": "Microfone desiligado",
|
||||||
|
"devices.microphoneError": "Ocorreu um erro no acesso ao microfone",
|
||||||
|
"devices.microPhoneMute": "Som microfone desativado",
|
||||||
|
"devices.micophoneUnMute": "Som mmicrofone ativado",
|
||||||
|
"devices.microphoneEnable": "Microfone ativado",
|
||||||
|
"devices.microphoneMuteError": "Não foi possível cortar o som do microfone",
|
||||||
|
"devices.microphoneUnMuteError": "Não foi possível ativar o som do microfone",
|
||||||
|
|
||||||
|
"devices.screenSharingDisconnected" : "Partilha de ecrã desligada",
|
||||||
|
"devices.screenSharingError": "Ocorreu um erro no acesso ao seu ecrã",
|
||||||
|
|
||||||
|
"devices.cameraDisconnected": "Câmara desconectada",
|
||||||
|
"devices.cameraError": "Ocorreu um erro no acesso à sua câmara"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
{
|
||||||
|
"socket.disconnected": "Ești disconectat",
|
||||||
|
"socket.reconnecting": "Ești disconectat, reconectare",
|
||||||
|
"socket.reconnected": "Ești reconectat",
|
||||||
|
"socket.requestError": "Eroare la solicitarea serverului",
|
||||||
|
|
||||||
|
"room.chooseRoom": "Alege numele camerei ai cărui vrei să te alături",
|
||||||
|
"room.cookieConsent": "Acest website utilizează cookie-uri pentru a îmbunătăți experiența utilizatorului",
|
||||||
|
"room.consentUnderstand": "I understand",
|
||||||
|
"room.joined": "Te-ai alăturat camerei",
|
||||||
|
"room.cantJoin": "Nu se poate alătura camerei",
|
||||||
|
"room.youLocked": "Ai blocat camera",
|
||||||
|
"room.cantLock": "Încercarea de a bloca camera a eșuat",
|
||||||
|
"room.youUnLocked": "Ai deblocat camera",
|
||||||
|
"room.cantUnLock": "Încercarea de a debloca camera a eșuat",
|
||||||
|
"room.locked": "Camera e blocată",
|
||||||
|
"room.unlocked": "Camera e deblocată",
|
||||||
|
"room.newLobbyPeer": "Un nou participant a intrat în hol",
|
||||||
|
"room.lobbyPeerLeft": "Participantul din hol a plecat",
|
||||||
|
"room.lobbyPeerChangedDisplayName": "Participantul din hol și-a schimbat numele în {displayName}",
|
||||||
|
"room.lobbyPeerChangedPicture": "Participantul din hol și-a schimbat imaginea",
|
||||||
|
"room.setAccessCode": "Codul de accesare pentru cameră e actualizat",
|
||||||
|
"room.accessCodeOn": "Codul de accesare pentru cameră e activat",
|
||||||
|
"room.accessCodeOff": "Codul de accesare pentru cameră e deactivat",
|
||||||
|
"room.peerChangedDisplayName": "{oldDisplayName} e acum {displayName}",
|
||||||
|
"room.newPeer": "{displayName} s-a alăturat camerei",
|
||||||
|
"room.newFile": "Un fișier nou e disponibil",
|
||||||
|
"room.toggleAdvancedMode": "Modul avansat e activat",
|
||||||
|
"room.setDemocraticView": "Aspectul e schimat la distribuție egală a dimensiunii imaginii",
|
||||||
|
"room.setFilmStripView": "Aspectul e schimat la aspect filmstrip",
|
||||||
|
"room.loggedIn": "Ești autentificat",
|
||||||
|
"room.loggedOut": "Ești deconectat",
|
||||||
|
"room.changedDisplayName": "Numele afișat e schimbat în {displayName}",
|
||||||
|
"room.changeDisplayNameError": "A apărut o eroare la schimbarea numelui afișat",
|
||||||
|
"room.chatError": "Încercarea de a trimite mesajul de chat a eșuat",
|
||||||
|
"room.aboutToJoin": "Vei alătura conferinței în curând",
|
||||||
|
"room.roomId": "ID cameră: {roomName}",
|
||||||
|
"room.setYourName": "Stabilește numele pentru participare și alege cum dorești să te alături:",
|
||||||
|
"room.audioOnly": "Doar audio",
|
||||||
|
"room.audioVideo": "Audio și video",
|
||||||
|
"room.youAreReady": "Ok, ești gata",
|
||||||
|
"room.emptyRequireLogin": "Camera e goală! Poți să te conectezi pentru a începe întâlnirea sau să aștepți până gazda se alătură",
|
||||||
|
"room.locketWait": "Camera este închisă - așteaptă până când cineva te lasă să intri ...",
|
||||||
|
"room.lobbyAdministration": "Administrarea holului",
|
||||||
|
"room.peersInLobby": "Participanți în hol",
|
||||||
|
"room.lobbyEmpty": "În prezent nu e nimeni în hol",
|
||||||
|
"room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}",
|
||||||
|
"room.me": "Eu",
|
||||||
|
"room.spotlights": "Participanți în Spotlight",
|
||||||
|
"room.passive": "Participanți pasivi",
|
||||||
|
"room.videoPaused": "Acest video este pus pe pauză",
|
||||||
|
|
||||||
|
"tooltip.login": "Intră în cont",
|
||||||
|
"tooltip.logout": "Deconectare",
|
||||||
|
"tooltip.admitFromLobby": "Admite din hol",
|
||||||
|
"tooltip.lockRoom": "Blocarea camerei",
|
||||||
|
"tooltip.unLockRoom": "Deblocarea camerei",
|
||||||
|
"tooltip.enterFullscreen": "Modul ecran complet",
|
||||||
|
"tooltip.leaveFullscreen": "Ieșire din modul ecran complet",
|
||||||
|
"tooltip.lobby": "Arată holul",
|
||||||
|
"tooltip.settings": "Arată participanții",
|
||||||
|
|
||||||
|
"label.roomName": "Numele camerei",
|
||||||
|
"label.chooseRoomButton": "Continuare",
|
||||||
|
"label.yourName": "Numele tău",
|
||||||
|
"label.newWindow": "Fereastră nouă",
|
||||||
|
"label.fullscreen": "Mod ecran complet",
|
||||||
|
"label.openDrawer": "Deschiderea panoului lateral",
|
||||||
|
"label.leave": "Deconectare",
|
||||||
|
"label.chatInput": "Introduce mesajul de chat...",
|
||||||
|
"label.chat": "Chat",
|
||||||
|
"label.filesharing": "Partajarea fișierelor",
|
||||||
|
"label.participants": "Participanți",
|
||||||
|
"label.shareFile": "Partajează fișierul",
|
||||||
|
"label.fileSharingUnsupported": "Partajarea fișierelor nu este acceptată",
|
||||||
|
"label.unknown": "Necunoscut",
|
||||||
|
"label.democratic": "Distribuție egală a dimensiunii imaginii",
|
||||||
|
"label.filmstrip": "Aspect filmstrip",
|
||||||
|
"label.low": "Rezoluție scăzută",
|
||||||
|
"label.medium": "Rezoluție mediu",
|
||||||
|
"label.high": "Rezoluție înaltă (HD)",
|
||||||
|
"label.veryHigh": "Rezoluție foarte înaltă (FHD)",
|
||||||
|
"label.ultra": "Rezoluție ultra înaltă (UHD)",
|
||||||
|
"label.close": "Închide",
|
||||||
|
|
||||||
|
"settings.settings": "Setări",
|
||||||
|
"settings.camera": "Cameră video",
|
||||||
|
"settings.selectCamera": "Selectarea dispozitivul video",
|
||||||
|
"settings.cantSelectCamera": "Încercarea de a selecta dispozitivul video a eșuat",
|
||||||
|
"settings.audio": "Dispozitivul audio",
|
||||||
|
"settings.selectAudio": "Selectarea dispozitivul audio",
|
||||||
|
"settings.cantSelectAudio": "Încercarea de a selecta dispozitivul audio a eșuat",
|
||||||
|
"settings.resolution": "Selectează rezoluția video",
|
||||||
|
"settings.layout": "Aspectul camerei video",
|
||||||
|
"settings.selectRoomLayout": "Selectează spectul camerei video",
|
||||||
|
"settings.advancedMode": "Mod avansat",
|
||||||
|
"settings.permanentTopBar": "Bara de sus permanentă",
|
||||||
|
"settings.lastn": "Numărul de videoclipuri vizibile",
|
||||||
|
|
||||||
|
"filesharing.saveFileError": "Încercarea de a salva fișierul a eșuat",
|
||||||
|
"filesharing.startingFileShare": "Partajarea fișierului",
|
||||||
|
"filesharing.successfulFileShare": "Fișierul a fost partajat cu succes",
|
||||||
|
"filesharing.unableToShare": "Încercarea de a partaja fișierul a eșuat",
|
||||||
|
"filesharing.error": "Eroare la partajarea fișierului",
|
||||||
|
"filesharing.finished": "Fișier descărcat",
|
||||||
|
"filesharing.save": "Salvare",
|
||||||
|
"filesharing.sharedFile": "{displayName} a partajat un fișier",
|
||||||
|
"filesharing.download": "Descărcare",
|
||||||
|
"filesharing.missingSeeds": "Dacă acest proces durează mult timp, s-ar putea să nu fie nimeni care seed-ează acest torrent. Încearcă să ceri cuiva să reîncarce fișierul.",
|
||||||
|
|
||||||
|
"devices.devicesChanged": "Dispozitivele tale s-au schimbat, configurează-ți dispozitivele în dialogul de setări",
|
||||||
|
|
||||||
|
"device.audioUnsupported": "Audioul nu e suportat",
|
||||||
|
"device.activateAudio": "Activează audioul",
|
||||||
|
"device.muteAudio": "Dezactivează audioul",
|
||||||
|
"device.unMuteAudio": "Retragerea dezactivării audioului",
|
||||||
|
|
||||||
|
"device.videoUnsupported": "Videoul nu e suportat",
|
||||||
|
"device.startVideo": "Pornirea videoului",
|
||||||
|
"device.stopVideo": "Oprirea videoului",
|
||||||
|
|
||||||
|
"device.screenSharingUnsupported": "Partajarea ecranulului nu e suportat",
|
||||||
|
"device.startScreenSharing": "Pornește partajarea ecranului",
|
||||||
|
"device.stopScreenSharing": "Oprește partajarea ecranului",
|
||||||
|
|
||||||
|
"devices.microphoneDisconnected": "Microfonul e deconectat",
|
||||||
|
"devices.microphoneError": "A apărut o eroare la accesarea microfonului",
|
||||||
|
"devices.microPhoneMute": "Microfonul e dezactivat",
|
||||||
|
"devices.micophoneUnMute": "Retragerea dezactivării microfonului",
|
||||||
|
"devices.microphoneEnable": "Microfonul e activat",
|
||||||
|
"devices.microphoneMuteError": "Încercarea de a dezactiva microfonului a eșuat",
|
||||||
|
"devices.microphoneUnMuteError": "Încercarea de a retrage dezactivarea microfonului a eșuat",
|
||||||
|
|
||||||
|
"devices.screenSharingDisconnected": "Partajarea ecranului este deconectată",
|
||||||
|
"devices.screenSharingError": "A apărut o eroare la accesarea ecranului",
|
||||||
|
|
||||||
|
"devices.cameraDisconnected": "Camera video e disconectată",
|
||||||
|
"devices.cameraError": "A apărut o eroare la accesarea camerei video"
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
export function getSignalingUrl(peerId, roomId)
|
export function getSignalingUrl(peerId, roomId)
|
||||||
{
|
{
|
||||||
const hostname = window.location.hostname;
|
const hostname = window.config.multipartyServer;
|
||||||
|
|
||||||
const port = process.env.NODE_ENV !== 'production' ? window.config.developmentPort : window.location.port;
|
const port =
|
||||||
|
process.env.NODE_ENV !== 'production' ?
|
||||||
|
window.config.developmentPort
|
||||||
|
:
|
||||||
|
window.config.productionPort;
|
||||||
|
|
||||||
const url = `wss://${hostname}:${port}/?peerId=${peerId}&roomId=${roomId}`;
|
const url = `wss://${hostname}:${port}/?peerId=${peerId}&roomId=${roomId}`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# -*- sh -*-
|
||||||
|
|
||||||
|
: << =cut
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
turn - Plugin to monitor the turn server test probe.
|
||||||
|
|
||||||
|
=head1 CONFIGURATION
|
||||||
|
|
||||||
|
No configuration
|
||||||
|
|
||||||
|
=head1 AUTHOR
|
||||||
|
|
||||||
|
Unknown author
|
||||||
|
|
||||||
|
=head1 LICENSE
|
||||||
|
|
||||||
|
GPLv2
|
||||||
|
|
||||||
|
=head1 MAGIC MARKERS
|
||||||
|
|
||||||
|
#%# family=auto
|
||||||
|
#%# capabilities=autoconf
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
. "$MUNIN_LIBDIR/plugins/plugin.sh"
|
||||||
|
|
||||||
|
if [ "$1" = "autoconf" ]; then
|
||||||
|
if [ -r /proc/sys/kernel/random/entropy_avail ]; then
|
||||||
|
echo yes
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo no
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" = "config" ]; then
|
||||||
|
echo 'graph_title MM stats'
|
||||||
|
#echo 'graph_args --base 1000 -l 0'
|
||||||
|
echo 'graph_vlabel Actual Seesion Count'
|
||||||
|
echo 'graph_category other'
|
||||||
|
echo 'graph_info This graph shows the mm stats.'
|
||||||
|
echo 'rooms.label rooms'
|
||||||
|
echo 'rooms.info The count of rooms.'
|
||||||
|
echo 'peers.label peers'
|
||||||
|
echo 'peers.info The count of peers.'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
ROOMS=`docker exec -t mm_mm_1 /opt/multiparty-meeting/server/connect.js --stats | grep 'rooms' | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g" | sed -E 's/rooms:([0-9]+)/\1/g'`
|
||||||
|
PEERS=`docker exec -t mm_mm_1 /opt/multiparty-meeting/server/connect.js --stats | grep 'peers' | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g" | sed -E 's/peers:([0-9]+)/\1/g'`
|
||||||
|
|
||||||
|
echo "rooms.value ${ROOMS}"
|
||||||
|
echo "peers.value ${PEERS}"
|
||||||
|
|
||||||
|
:
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
[mm]
|
||||||
|
user root
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Install a munin plugin, as a very basic monitoring
|
||||||
|
|
||||||
|
## munin-node
|
||||||
|
|
||||||
|
* install on your docker host munin-node on mm.example.com
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apt install munin-node
|
||||||
|
```
|
||||||
|
|
||||||
|
* Copy mm-plugin from this directory to plugins dir as mm
|
||||||
|
|
||||||
|
cp mm-plugin /usr/share/munin/plugins/mm
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ln -s /usr/share/munin/plugins/mm /etc/munin/plugins/mm
|
||||||
|
```
|
||||||
|
|
||||||
|
* Copy mm-plugin-conf from this directory to munin plugins conf dir as mm
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp mm-plugin-conf /etc/munin/plugin-conf.d/mm
|
||||||
|
```
|
||||||
|
|
||||||
|
* Restart munin
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl restart munin-node
|
||||||
|
```
|
||||||
|
|
||||||
|
## munin master
|
||||||
|
|
||||||
|
* Install a munin master on different host if you don't have munin already.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apt install munin
|
||||||
|
```
|
||||||
|
|
||||||
|
* On your munin master configure the new node
|
||||||
|
edit and add to /etc/munin.conf
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[mm]
|
||||||
|
mm.example.com
|
||||||
|
```
|
||||||
|
|
@ -2,25 +2,37 @@ const os = require('os');
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
{
|
{
|
||||||
// oAuth2 conf
|
|
||||||
/* auth :
|
// Auth conf
|
||||||
|
/*
|
||||||
|
auth :
|
||||||
{
|
{
|
||||||
|
lti :
|
||||||
// The issuer URL for OpenID Connect discovery
|
{
|
||||||
// The OpenID Provider Configuration Document
|
consumerKey : 'key',
|
||||||
// could be discovered on:
|
consumerSecret : 'secret'
|
||||||
// issuerURL + '/.well-known/openid-configuration'
|
},
|
||||||
|
oidc:
|
||||||
// issuerURL : 'https://example.com',
|
{
|
||||||
// clientOptions :
|
// The issuer URL for OpenID Connect discovery
|
||||||
// {
|
// The OpenID Provider Configuration Document
|
||||||
client_id : '',
|
// could be discovered on:
|
||||||
client_secret : '',
|
// issuerURL + '/.well-known/openid-configuration'
|
||||||
scope : 'openid email profile',
|
|
||||||
// where client.example.com is your multiparty meeting server
|
issuerURL : 'https://example.com',
|
||||||
redirect_uri : 'https://client.example.com/auth/callback'
|
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'
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
},*/
|
},
|
||||||
|
*/
|
||||||
|
redisOptions : {},
|
||||||
// session cookie secret
|
// session cookie secret
|
||||||
cookieSecret : 'T0P-S3cR3t_cook!e',
|
cookieSecret : 'T0P-S3cR3t_cook!e',
|
||||||
cookieName : 'multiparty-meeting.sid',
|
cookieName : 'multiparty-meeting.sid',
|
||||||
|
|
@ -34,6 +46,10 @@ module.exports =
|
||||||
// Any http request is redirected to https.
|
// Any http request is redirected to https.
|
||||||
// Listening port for http server.
|
// Listening port for http server.
|
||||||
listeningRedirectPort : 80,
|
listeningRedirectPort : 80,
|
||||||
|
// Listens only on http, only on listeningPort
|
||||||
|
// listeningRedirectPort disabled
|
||||||
|
// use case: loadbalancer backend
|
||||||
|
httpOnly : false,
|
||||||
// If this is set to true, only signed-in users will be able
|
// If this is set to true, only signed-in users will be able
|
||||||
// to join a room directly. Non-signed-in users (guests) will
|
// to join a room directly. Non-signed-in users (guests) will
|
||||||
// always be put in the lobby regardless of room lock status.
|
// always be put in the lobby regardless of room lock status.
|
||||||
|
|
@ -76,6 +92,37 @@ module.exports =
|
||||||
clockRate : 48000,
|
clockRate : 48000,
|
||||||
channels : 2
|
channels : 2
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
kind : 'video',
|
||||||
|
mimeType : 'video/VP8',
|
||||||
|
clockRate : 90000,
|
||||||
|
parameters :
|
||||||
|
{
|
||||||
|
'x-google-start-bitrate' : 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind : 'video',
|
||||||
|
mimeType : 'video/VP9',
|
||||||
|
clockRate : 90000,
|
||||||
|
parameters :
|
||||||
|
{
|
||||||
|
'profile-id' : 2,
|
||||||
|
'x-google-start-bitrate' : 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind : 'video',
|
||||||
|
mimeType : 'video/h264',
|
||||||
|
clockRate : 90000,
|
||||||
|
parameters :
|
||||||
|
{
|
||||||
|
'packetization-mode' : 1,
|
||||||
|
'profile-level-id' : '4d0032',
|
||||||
|
'level-asymmetry-allowed' : 1,
|
||||||
|
'x-google-start-bitrate' : 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
kind : 'video',
|
kind : 'video',
|
||||||
mimeType : 'video/h264',
|
mimeType : 'video/h264',
|
||||||
|
|
@ -96,10 +143,15 @@ module.exports =
|
||||||
listenIps :
|
listenIps :
|
||||||
[
|
[
|
||||||
// change ip to your servers IP address!
|
// change ip to your servers IP address!
|
||||||
{ ip: '1.2.3.4', announcedIp: null }
|
{ ip: '0.0.0.0', announcedIp: null }
|
||||||
|
|
||||||
|
// Can have multiple listening interfaces
|
||||||
|
// { ip: '::/0', announcedIp: null }
|
||||||
],
|
],
|
||||||
maxIncomingBitrate : 1500000,
|
initialAvailableOutgoingBitrate : 1000000,
|
||||||
initialAvailableOutgoingBitrate : 1000000
|
minimumAvailableOutgoingBitrate : 600000,
|
||||||
|
// Additional options that are not part of WebRtcTransportOptions.
|
||||||
|
maxIncomingBitrate : 1500000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const interactiveClient = require('./lib/interactiveClient');
|
||||||
|
|
||||||
|
interactiveClient();
|
||||||
|
|
@ -68,7 +68,7 @@ class Room extends EventEmitter
|
||||||
|
|
||||||
this._lastN = [];
|
this._lastN = [];
|
||||||
|
|
||||||
this._peers = new Map();
|
this._peers = {};
|
||||||
|
|
||||||
// mediasoup Router instance.
|
// mediasoup Router instance.
|
||||||
this._mediasoupRouter = mediasoupRouter;
|
this._mediasoupRouter = mediasoupRouter;
|
||||||
|
|
@ -96,13 +96,17 @@ class Room extends EventEmitter
|
||||||
|
|
||||||
this._lobby.close();
|
this._lobby.close();
|
||||||
|
|
||||||
this._peers.forEach((peer) =>
|
// Close the peers.
|
||||||
|
for (const peer in this._peers)
|
||||||
{
|
{
|
||||||
if (!peer.closed)
|
if (Object.prototype.hasOwnProperty.call(this._peers, peer))
|
||||||
peer.close();
|
{
|
||||||
});
|
if (!peer.closed)
|
||||||
|
peer.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this._peers.clear();
|
this._peers = null;
|
||||||
|
|
||||||
// Close the mediasoup Router.
|
// Close the mediasoup Router.
|
||||||
this._mediasoupRouter.close();
|
this._mediasoupRouter.close();
|
||||||
|
|
@ -116,7 +120,7 @@ class Room extends EventEmitter
|
||||||
logger.info('handlePeer() [peer:"%s"]', peer.id);
|
logger.info('handlePeer() [peer:"%s"]', peer.id);
|
||||||
|
|
||||||
// This will allow reconnects to join despite lock
|
// This will allow reconnects to join despite lock
|
||||||
if (this._peers.has(peer.id))
|
if (this._peers[peer.id])
|
||||||
{
|
{
|
||||||
logger.warn(
|
logger.warn(
|
||||||
'handleConnection() | there is already a peer with same peerId [peer:"%s"]',
|
'handleConnection() | there is already a peer with same peerId [peer:"%s"]',
|
||||||
|
|
@ -168,10 +172,10 @@ class Room extends EventEmitter
|
||||||
|
|
||||||
this._peerJoining(promotedPeer);
|
this._peerJoining(promotedPeer);
|
||||||
|
|
||||||
this._peers.forEach((peer) =>
|
for (const peer of this._getJoinedPeers())
|
||||||
{
|
{
|
||||||
this._notification(peer.socket, 'lobby:promotedPeer', { peerId: id });
|
this._notification(peer.socket, 'lobby:promotedPeer', { peerId: id });
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this._lobby.on('peerAuthenticated', (peer) =>
|
this._lobby.on('peerAuthenticated', (peer) =>
|
||||||
|
|
@ -183,20 +187,20 @@ class Room extends EventEmitter
|
||||||
{
|
{
|
||||||
const { id, displayName } = changedPeer;
|
const { id, displayName } = changedPeer;
|
||||||
|
|
||||||
this._peers.forEach((peer) =>
|
for (const peer of this._getJoinedPeers())
|
||||||
{
|
{
|
||||||
this._notification(peer.socket, 'lobby:changeDisplayName', { peerId: id, displayName });
|
this._notification(peer.socket, 'lobby:changeDisplayName', { peerId: id, displayName });
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this._lobby.on('changePicture', (changedPeer) =>
|
this._lobby.on('changePicture', (changedPeer) =>
|
||||||
{
|
{
|
||||||
const { id, picture } = changedPeer;
|
const { id, picture } = changedPeer;
|
||||||
|
|
||||||
this._peers.forEach((peer) =>
|
for (const peer of this._getJoinedPeers())
|
||||||
{
|
{
|
||||||
this._notification(peer.socket, 'lobby:changePicture', { peerId: id, picture });
|
this._notification(peer.socket, 'lobby:changePicture', { peerId: id, picture });
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this._lobby.on('peerClosed', (closedPeer) =>
|
this._lobby.on('peerClosed', (closedPeer) =>
|
||||||
|
|
@ -205,10 +209,10 @@ class Room extends EventEmitter
|
||||||
|
|
||||||
const { id } = closedPeer;
|
const { id } = closedPeer;
|
||||||
|
|
||||||
this._peers.forEach((peer) =>
|
for (const peer of this._getJoinedPeers())
|
||||||
{
|
{
|
||||||
this._notification(peer.socket, 'lobby:peerClosed', { peerId: id });
|
this._notification(peer.socket, 'lobby:peerClosed', { peerId: id });
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// If nobody left in lobby we should check if room is empty too and initiating
|
// If nobody left in lobby we should check if room is empty too and initiating
|
||||||
|
|
@ -230,22 +234,29 @@ class Room extends EventEmitter
|
||||||
const { producer, volume } = volumes[0];
|
const { producer, volume } = volumes[0];
|
||||||
|
|
||||||
// Notify all Peers.
|
// Notify all Peers.
|
||||||
this._peers.forEach((peer) =>
|
for (const peer of this._getJoinedPeers())
|
||||||
{
|
{
|
||||||
this._notification(peer.socket, 'activeSpeaker', {
|
this._notification(
|
||||||
peerId : producer.appData.peerId,
|
peer.socket,
|
||||||
volume : volume
|
'activeSpeaker',
|
||||||
});
|
{
|
||||||
});
|
peerId : producer.appData.peerId,
|
||||||
|
volume : volume
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this._audioLevelObserver.on('silence', () =>
|
this._audioLevelObserver.on('silence', () =>
|
||||||
{
|
{
|
||||||
// Notify all Peers.
|
// Notify all Peers.
|
||||||
this._peers.forEach((peer) =>
|
for (const peer of this._getJoinedPeers())
|
||||||
{
|
{
|
||||||
this._notification(peer.socket, 'activeSpeaker', { peerId: null });
|
this._notification(
|
||||||
});
|
peer.socket,
|
||||||
|
'activeSpeaker',
|
||||||
|
{ peerId: null }
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,10 +265,18 @@ class Room extends EventEmitter
|
||||||
logger.info(
|
logger.info(
|
||||||
'logStatus() [room id:"%s", peers:"%s"]',
|
'logStatus() [room id:"%s", peers:"%s"]',
|
||||||
this._roomId,
|
this._roomId,
|
||||||
this._peers.size
|
Object.keys(this._peers).length
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async dump()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
roomId : this._roomId,
|
||||||
|
peers : Object.keys(this._peers).length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
get id()
|
get id()
|
||||||
{
|
{
|
||||||
return this._roomId;
|
return this._roomId;
|
||||||
|
|
@ -287,17 +306,17 @@ class Room extends EventEmitter
|
||||||
// checks both room and lobby
|
// checks both room and lobby
|
||||||
checkEmpty()
|
checkEmpty()
|
||||||
{
|
{
|
||||||
return this._peers.size === 0;
|
return Object.keys(this._peers).length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_parkPeer(parkPeer)
|
_parkPeer(parkPeer)
|
||||||
{
|
{
|
||||||
this._lobby.parkPeer(parkPeer);
|
this._lobby.parkPeer(parkPeer);
|
||||||
|
|
||||||
this._peers.forEach((peer) =>
|
for (const peer of this._getJoinedPeers())
|
||||||
{
|
{
|
||||||
this._notification(peer.socket, 'parkedPeer', { peerId: parkPeer.id });
|
this._notification(peer.socket, 'parkedPeer', { peerId: parkPeer.id });
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_peerJoining(peer)
|
_peerJoining(peer)
|
||||||
|
|
@ -311,7 +330,7 @@ class Room extends EventEmitter
|
||||||
this._lastN.push(peer.id);
|
this._lastN.push(peer.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._peers.set(peer.id, peer);
|
this._peers[peer.id] = peer;
|
||||||
|
|
||||||
this._handlePeer(peer);
|
this._handlePeer(peer);
|
||||||
this._notification(peer.socket, 'roomReady');
|
this._notification(peer.socket, 'roomReady');
|
||||||
|
|
@ -354,7 +373,7 @@ class Room extends EventEmitter
|
||||||
this._lastN.splice(index, 1);
|
this._lastN.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._peers.delete(peer.id);
|
delete this._peers[peer.id];
|
||||||
|
|
||||||
// If this is the last Peer in the room and
|
// If this is the last Peer in the room and
|
||||||
// lobby is empty, close the room after a while.
|
// lobby is empty, close the room after a while.
|
||||||
|
|
@ -405,6 +424,27 @@ class Room extends EventEmitter
|
||||||
|
|
||||||
case 'join':
|
case 'join':
|
||||||
{
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (peer.socket.handshake.session.passport.user.displayName)
|
||||||
|
{
|
||||||
|
this._notification(
|
||||||
|
peer.socket,
|
||||||
|
'changeDisplayname',
|
||||||
|
{
|
||||||
|
peerId : peer.id,
|
||||||
|
displayName : peer.socket.handshake.session.passport.user.displayName,
|
||||||
|
oldDisplayName : ''
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
logger.error(error);
|
||||||
|
}
|
||||||
// Ensure the Peer is not already joined.
|
// Ensure the Peer is not already joined.
|
||||||
if (peer.joined)
|
if (peer.joined)
|
||||||
throw new Error('Peer already joined');
|
throw new Error('Peer already joined');
|
||||||
|
|
@ -423,41 +463,47 @@ class Room extends EventEmitter
|
||||||
// Tell the new Peer about already joined Peers.
|
// Tell the new Peer about already joined Peers.
|
||||||
// And also create Consumers for existing Producers.
|
// And also create Consumers for existing Producers.
|
||||||
|
|
||||||
const peerInfos = [];
|
const joinedPeers =
|
||||||
|
[
|
||||||
|
...this._getJoinedPeers()
|
||||||
|
];
|
||||||
|
|
||||||
this._peers.forEach((joinedPeer) =>
|
const peerInfos = joinedPeers
|
||||||
{
|
.filter((joinedPeer) => joinedPeer.id !== peer.id)
|
||||||
if (joinedPeer.joined)
|
.map((joinedPeer) => (joinedPeer.peerInfo));
|
||||||
{
|
|
||||||
peerInfos.push(joinedPeer.peerInfo);
|
|
||||||
|
|
||||||
joinedPeer.producers.forEach((producer) =>
|
|
||||||
{
|
|
||||||
this._createConsumer(
|
|
||||||
{
|
|
||||||
consumerPeer : peer,
|
|
||||||
producerPeer : joinedPeer,
|
|
||||||
producer
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cb(null, { peers: peerInfos });
|
cb(null, { peers: peerInfos });
|
||||||
|
|
||||||
// Mark the new Peer as joined.
|
// Mark the new Peer as joined.
|
||||||
peer.joined = true;
|
peer.joined = true;
|
||||||
|
|
||||||
this._notification(
|
for (const joinedPeer of joinedPeers)
|
||||||
peer.socket,
|
{
|
||||||
'newPeer',
|
// Create Consumers for existing Producers.
|
||||||
|
for (const producer of joinedPeer.producers.values())
|
||||||
{
|
{
|
||||||
id : peer.id,
|
this._createConsumer(
|
||||||
displayName : displayName,
|
{
|
||||||
picture : picture
|
consumerPeer : peer,
|
||||||
},
|
producerPeer : joinedPeer,
|
||||||
true
|
producer
|
||||||
);
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify the new Peer to all other Peers.
|
||||||
|
for (const otherPeer of this._getJoinedPeers({ excludePeer: peer }))
|
||||||
|
{
|
||||||
|
this._notification(
|
||||||
|
otherPeer.socket,
|
||||||
|
'newPeer',
|
||||||
|
{
|
||||||
|
id : peer.id,
|
||||||
|
displayName : displayName,
|
||||||
|
picture : picture
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'peer joined [peer: "%s", displayName: "%s", picture: "%s"]',
|
'peer joined [peer: "%s", displayName: "%s", picture: "%s"]',
|
||||||
|
|
@ -472,20 +518,28 @@ class Room extends EventEmitter
|
||||||
// initiate mediasoup Transports and be ready when he later joins.
|
// initiate mediasoup Transports and be ready when he later joins.
|
||||||
|
|
||||||
const { forceTcp, producing, consuming } = request.data;
|
const { forceTcp, producing, consuming } = request.data;
|
||||||
const {
|
|
||||||
maxIncomingBitrate,
|
const webRtcTransportOptions =
|
||||||
initialAvailableOutgoingBitrate
|
{
|
||||||
} = config.mediasoup.webRtcTransport;
|
...config.mediasoup.webRtcTransport,
|
||||||
|
appData : { producing, consuming }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (forceTcp)
|
||||||
|
{
|
||||||
|
webRtcTransportOptions.enableUdp = false;
|
||||||
|
webRtcTransportOptions.enableTcp = true;
|
||||||
|
}
|
||||||
|
|
||||||
const transport = await this._mediasoupRouter.createWebRtcTransport(
|
const transport = await this._mediasoupRouter.createWebRtcTransport(
|
||||||
{
|
webRtcTransportOptions
|
||||||
listenIps : config.mediasoup.webRtcTransport.listenIps,
|
);
|
||||||
enableUdp : !forceTcp,
|
|
||||||
enableTcp : true,
|
transport.on('dtlsstatechange', (dtlsState) =>
|
||||||
preferUdp : true,
|
{
|
||||||
initialAvailableOutgoingBitrate,
|
if (dtlsState === 'failed' || dtlsState === 'closed')
|
||||||
appData : { producing, consuming }
|
logger.warn('WebRtcTransport "dtlsstatechange" event [dtlsState:%s]', dtlsState);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Store the WebRtcTransport into the Peer data Object.
|
// Store the WebRtcTransport into the Peer data Object.
|
||||||
peer.addTransport(transport.id, transport);
|
peer.addTransport(transport.id, transport);
|
||||||
|
|
@ -499,6 +553,8 @@ class Room extends EventEmitter
|
||||||
dtlsParameters : transport.dtlsParameters
|
dtlsParameters : transport.dtlsParameters
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { maxIncomingBitrate } = config.mediasoup.webRtcTransport;
|
||||||
|
|
||||||
// If set, apply max incoming bitrate limit.
|
// If set, apply max incoming bitrate limit.
|
||||||
if (maxIncomingBitrate)
|
if (maxIncomingBitrate)
|
||||||
{
|
{
|
||||||
|
|
@ -577,18 +633,16 @@ class Room extends EventEmitter
|
||||||
|
|
||||||
cb(null, { id: producer.id });
|
cb(null, { id: producer.id });
|
||||||
|
|
||||||
this._peers.forEach((otherPeer) =>
|
// Optimization: Create a server-side Consumer for each Peer.
|
||||||
|
for (const otherPeer of this._getJoinedPeers({ excludePeer: peer }))
|
||||||
{
|
{
|
||||||
if (otherPeer.joined && otherPeer !== peer)
|
this._createConsumer(
|
||||||
{
|
{
|
||||||
this._createConsumer(
|
consumerPeer : otherPeer,
|
||||||
{
|
producerPeer : peer,
|
||||||
consumerPeer : otherPeer,
|
producer
|
||||||
producerPeer : peer,
|
});
|
||||||
producer
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add into the audioLevelObserver.
|
// Add into the audioLevelObserver.
|
||||||
if (kind === 'audio')
|
if (kind === 'audio')
|
||||||
|
|
@ -717,6 +771,25 @@ class Room extends EventEmitter
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'setConsumerPriority':
|
||||||
|
{
|
||||||
|
// Ensure the Peer is joined.
|
||||||
|
if (!peer.joined)
|
||||||
|
throw new Error('Peer not yet joined');
|
||||||
|
|
||||||
|
const { consumerId, priority } = request.data;
|
||||||
|
const consumer = peer.getConsumer(consumerId);
|
||||||
|
|
||||||
|
if (!consumer)
|
||||||
|
throw new Error(`consumer with id "${consumerId}" not found`);
|
||||||
|
|
||||||
|
await consumer.setPriority(priority);
|
||||||
|
|
||||||
|
cb();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'requestConsumerKeyFrame':
|
case 'requestConsumerKeyFrame':
|
||||||
{
|
{
|
||||||
// Ensure the Peer is joined.
|
// Ensure the Peer is joined.
|
||||||
|
|
@ -1120,7 +1193,7 @@ class Room extends EventEmitter
|
||||||
'newConsumer',
|
'newConsumer',
|
||||||
{
|
{
|
||||||
peerId : producerPeer.id,
|
peerId : producerPeer.id,
|
||||||
kind : producer.kind,
|
kind : consumer.kind,
|
||||||
producerId : producer.id,
|
producerId : producer.id,
|
||||||
id : consumer.id,
|
id : consumer.id,
|
||||||
rtpParameters : consumer.rtpParameters,
|
rtpParameters : consumer.rtpParameters,
|
||||||
|
|
@ -1132,8 +1205,7 @@ class Room extends EventEmitter
|
||||||
|
|
||||||
// Now that we got the positive response from the remote Peer and, if
|
// Now that we got the positive response from the remote Peer and, if
|
||||||
// video, resume the Consumer to ask for an efficient key frame.
|
// video, resume the Consumer to ask for an efficient key frame.
|
||||||
if (producer.kind === 'video')
|
await consumer.resume();
|
||||||
await consumer.resume();
|
|
||||||
|
|
||||||
this._notification(
|
this._notification(
|
||||||
consumerPeer.socket,
|
consumerPeer.socket,
|
||||||
|
|
@ -1150,6 +1222,15 @@ class Room extends EventEmitter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to get the list of joined peers.
|
||||||
|
*/
|
||||||
|
_getJoinedPeers({ excludePeer = undefined } = {})
|
||||||
|
{
|
||||||
|
return Object.values(this._peers)
|
||||||
|
.filter((peer) => peer.joined && peer !== excludePeer);
|
||||||
|
}
|
||||||
|
|
||||||
_timeoutCallback(callback)
|
_timeoutCallback(callback)
|
||||||
{
|
{
|
||||||
let called = false;
|
let called = false;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
const net = require('net');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const SOCKET_PATH_UNIX = '/tmp/multiparty-meeting-server.sock';
|
||||||
|
const SOCKET_PATH_WIN = path.join('\\\\?\\pipe', process.cwd(), 'multiparty-meeting-server');
|
||||||
|
const SOCKET_PATH = os.platform() === 'win32'? SOCKET_PATH_WIN : SOCKET_PATH_UNIX;
|
||||||
|
|
||||||
|
module.exports = async function()
|
||||||
|
{
|
||||||
|
const socket = net.connect(SOCKET_PATH);
|
||||||
|
|
||||||
|
process.stdin.pipe(socket);
|
||||||
|
socket.pipe(process.stdout);
|
||||||
|
|
||||||
|
socket.on('connect', () => process.stdin.setRawMode(true));
|
||||||
|
|
||||||
|
socket.on('close', () => process.exit(0));
|
||||||
|
socket.on('exit', () => socket.end());
|
||||||
|
|
||||||
|
if (process.argv && process.argv[2] === '--stats')
|
||||||
|
{
|
||||||
|
await socket.write('stats\n');
|
||||||
|
|
||||||
|
socket.end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,699 @@
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
const repl = require('repl');
|
||||||
|
const readline = require('readline');
|
||||||
|
const net = require('net');
|
||||||
|
const fs = require('fs');
|
||||||
|
const mediasoup = require('mediasoup');
|
||||||
|
const colors = require('colors/safe');
|
||||||
|
const pidusage = require('pidusage');
|
||||||
|
|
||||||
|
const SOCKET_PATH_UNIX = '/tmp/multiparty-meeting-server.sock';
|
||||||
|
const SOCKET_PATH_WIN = path.join('\\\\?\\pipe', process.cwd(), 'multiparty-meeting-server');
|
||||||
|
const SOCKET_PATH = os.platform() === 'win32' ? SOCKET_PATH_WIN : SOCKET_PATH_UNIX;
|
||||||
|
|
||||||
|
// Maps to store all mediasoup objects.
|
||||||
|
const workers = new Map();
|
||||||
|
const routers = new Map();
|
||||||
|
const transports = new Map();
|
||||||
|
const producers = new Map();
|
||||||
|
const consumers = new Map();
|
||||||
|
const dataProducers = new Map();
|
||||||
|
const dataConsumers = new Map();
|
||||||
|
|
||||||
|
class Interactive
|
||||||
|
{
|
||||||
|
constructor(socket)
|
||||||
|
{
|
||||||
|
this._socket = socket;
|
||||||
|
|
||||||
|
this._isTerminalOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
openCommandConsole()
|
||||||
|
{
|
||||||
|
const cmd = readline.createInterface(
|
||||||
|
{
|
||||||
|
input : this._socket,
|
||||||
|
output : this._socket,
|
||||||
|
terminal : true
|
||||||
|
});
|
||||||
|
|
||||||
|
cmd.on('close', () =>
|
||||||
|
{
|
||||||
|
if (this._isTerminalOpen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.log('\nexiting...');
|
||||||
|
|
||||||
|
this._socket.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
const readStdin = () =>
|
||||||
|
{
|
||||||
|
cmd.question('cmd> ', async (input) =>
|
||||||
|
{
|
||||||
|
const params = input.split(/[\s\t]+/);
|
||||||
|
const command = params.shift();
|
||||||
|
|
||||||
|
switch (command)
|
||||||
|
{
|
||||||
|
case '':
|
||||||
|
{
|
||||||
|
readStdin();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
case 'help':
|
||||||
|
{
|
||||||
|
this.log('');
|
||||||
|
this.log('available commands:');
|
||||||
|
this.log('- h, help : show this message');
|
||||||
|
this.log('- usage : show CPU and memory usage of the Node.js and mediasoup-worker processes');
|
||||||
|
this.log('- logLevel level : changes logLevel in all mediasoup Workers');
|
||||||
|
this.log('- logTags [tag] [tag] : changes logTags in all mediasoup Workers (values separated by space)');
|
||||||
|
this.log('- dumpRooms : dump all rooms');
|
||||||
|
this.log('- dumpPeers : dump all peers');
|
||||||
|
this.log('- dw, dumpWorkers : dump mediasoup Workers');
|
||||||
|
this.log('- dr, dumpRouter [id] : dump mediasoup Router with given id (or the latest created one)');
|
||||||
|
this.log('- dt, dumpTransport [id] : dump mediasoup Transport with given id (or the latest created one)');
|
||||||
|
this.log('- dp, dumpProducer [id] : dump mediasoup Producer with given id (or the latest created one)');
|
||||||
|
this.log('- dc, dumpConsumer [id] : dump mediasoup Consumer with given id (or the latest created one)');
|
||||||
|
this.log('- ddp, dumpDataProducer [id] : dump mediasoup DataProducer with given id (or the latest created one)');
|
||||||
|
this.log('- ddc, dumpDataConsumer [id] : dump mediasoup DataConsumer with given id (or the latest created one)');
|
||||||
|
this.log('- st, statsTransport [id] : get stats for mediasoup Transport with given id (or the latest created one)');
|
||||||
|
this.log('- sp, statsProducer [id] : get stats for mediasoup Producer with given id (or the latest created one)');
|
||||||
|
this.log('- sc, statsConsumer [id] : get stats for mediasoup Consumer with given id (or the latest created one)');
|
||||||
|
this.log('- sdp, statsDataProducer [id] : get stats for mediasoup DataProducer with given id (or the latest created one)');
|
||||||
|
this.log('- sdc, statsDataConsumer [id] : get stats for mediasoup DataConsumer with given id (or the latest created one)');
|
||||||
|
this.log('- t, terminal : open Node REPL Terminal');
|
||||||
|
this.log('');
|
||||||
|
readStdin();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'u':
|
||||||
|
case 'usage':
|
||||||
|
{
|
||||||
|
let usage = await pidusage(process.pid);
|
||||||
|
|
||||||
|
this.log(`Node.js process [pid:${process.pid}]:\n${JSON.stringify(usage, null, ' ')}`);
|
||||||
|
|
||||||
|
for (const worker of workers.values())
|
||||||
|
{
|
||||||
|
usage = await pidusage(worker.pid);
|
||||||
|
|
||||||
|
this.log(`mediasoup-worker process [pid:${worker.pid}]:\n${JSON.stringify(usage, null, ' ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'logLevel':
|
||||||
|
{
|
||||||
|
const level = params[0];
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for (const worker of workers.values())
|
||||||
|
{
|
||||||
|
promises.push(worker.updateSettings({ logLevel: level }));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
this.log('done');
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(String(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'logTags':
|
||||||
|
{
|
||||||
|
const tags = params;
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for (const worker of workers.values())
|
||||||
|
{
|
||||||
|
promises.push(worker.updateSettings({ logTags: tags }));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
this.log('done');
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(String(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'stats':
|
||||||
|
{
|
||||||
|
this.log(`rooms:${global.rooms.size}\npeers:${global.peers.size}`);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'dumpRooms':
|
||||||
|
{
|
||||||
|
for (const room of global.rooms.values())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const dump = await room.dump();
|
||||||
|
|
||||||
|
this.log(`room.dump():\n${JSON.stringify(dump, null, ' ')}`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(`room.dump() failed: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'dumpPeers':
|
||||||
|
{
|
||||||
|
for (const peer of global.peers.values())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const dump = await peer.peerInfo;
|
||||||
|
|
||||||
|
this.log(`peer.peerInfo():\n${JSON.stringify(dump, null, ' ')}`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(`peer.peerInfo() failed: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'dw':
|
||||||
|
case 'dumpWorkers':
|
||||||
|
{
|
||||||
|
for (const worker of workers.values())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const dump = await worker.dump();
|
||||||
|
|
||||||
|
this.log(`worker.dump():\n${JSON.stringify(dump, null, ' ')}`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(`worker.dump() failed: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'dr':
|
||||||
|
case 'dumpRouter':
|
||||||
|
{
|
||||||
|
const id = params[0] || Array.from(routers.keys()).pop();
|
||||||
|
const router = routers.get(id);
|
||||||
|
|
||||||
|
if (!router)
|
||||||
|
{
|
||||||
|
this.error('Router not found');
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const dump = await router.dump();
|
||||||
|
|
||||||
|
this.log(`router.dump():\n${JSON.stringify(dump, null, ' ')}`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(`router.dump() failed: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'dt':
|
||||||
|
case 'dumpTransport':
|
||||||
|
{
|
||||||
|
const id = params[0] || Array.from(transports.keys()).pop();
|
||||||
|
const transport = transports.get(id);
|
||||||
|
|
||||||
|
if (!transport)
|
||||||
|
{
|
||||||
|
this.error('Transport not found');
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const dump = await transport.dump();
|
||||||
|
|
||||||
|
this.log(`transport.dump():\n${JSON.stringify(dump, null, ' ')}`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(`transport.dump() failed: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'dp':
|
||||||
|
case 'dumpProducer':
|
||||||
|
{
|
||||||
|
const id = params[0] || Array.from(producers.keys()).pop();
|
||||||
|
const producer = producers.get(id);
|
||||||
|
|
||||||
|
if (!producer)
|
||||||
|
{
|
||||||
|
this.error('Producer not found');
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const dump = await producer.dump();
|
||||||
|
|
||||||
|
this.log(`producer.dump():\n${JSON.stringify(dump, null, ' ')}`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(`producer.dump() failed: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'dc':
|
||||||
|
case 'dumpConsumer':
|
||||||
|
{
|
||||||
|
const id = params[0] || Array.from(consumers.keys()).pop();
|
||||||
|
const consumer = consumers.get(id);
|
||||||
|
|
||||||
|
if (!consumer)
|
||||||
|
{
|
||||||
|
this.error('Consumer not found');
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const dump = await consumer.dump();
|
||||||
|
|
||||||
|
this.log(`consumer.dump():\n${JSON.stringify(dump, null, ' ')}`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(`consumer.dump() failed: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'ddp':
|
||||||
|
case 'dumpDataProducer':
|
||||||
|
{
|
||||||
|
const id = params[0] || Array.from(dataProducers.keys()).pop();
|
||||||
|
const dataProducer = dataProducers.get(id);
|
||||||
|
|
||||||
|
if (!dataProducer)
|
||||||
|
{
|
||||||
|
this.error('DataProducer not found');
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const dump = await dataProducer.dump();
|
||||||
|
|
||||||
|
this.log(`dataProducer.dump():\n${JSON.stringify(dump, null, ' ')}`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(`dataProducer.dump() failed: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'ddc':
|
||||||
|
case 'dumpDataConsumer':
|
||||||
|
{
|
||||||
|
const id = params[0] || Array.from(dataConsumers.keys()).pop();
|
||||||
|
const dataConsumer = dataConsumers.get(id);
|
||||||
|
|
||||||
|
if (!dataConsumer)
|
||||||
|
{
|
||||||
|
this.error('DataConsumer not found');
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const dump = await dataConsumer.dump();
|
||||||
|
|
||||||
|
this.log(`dataConsumer.dump():\n${JSON.stringify(dump, null, ' ')}`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(`dataConsumer.dump() failed: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'st':
|
||||||
|
case 'statsTransport':
|
||||||
|
{
|
||||||
|
const id = params[0] || Array.from(transports.keys()).pop();
|
||||||
|
const transport = transports.get(id);
|
||||||
|
|
||||||
|
if (!transport)
|
||||||
|
{
|
||||||
|
this.error('Transport not found');
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const stats = await transport.getStats();
|
||||||
|
|
||||||
|
this.log(`transport.getStats():\n${JSON.stringify(stats, null, ' ')}`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(`transport.getStats() failed: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'sp':
|
||||||
|
case 'statsProducer':
|
||||||
|
{
|
||||||
|
const id = params[0] || Array.from(producers.keys()).pop();
|
||||||
|
const producer = producers.get(id);
|
||||||
|
|
||||||
|
if (!producer)
|
||||||
|
{
|
||||||
|
this.error('Producer not found');
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const stats = await producer.getStats();
|
||||||
|
|
||||||
|
this.log(`producer.getStats():\n${JSON.stringify(stats, null, ' ')}`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(`producer.getStats() failed: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'sc':
|
||||||
|
case 'statsConsumer':
|
||||||
|
{
|
||||||
|
const id = params[0] || Array.from(consumers.keys()).pop();
|
||||||
|
const consumer = consumers.get(id);
|
||||||
|
|
||||||
|
if (!consumer)
|
||||||
|
{
|
||||||
|
this.error('Consumer not found');
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const stats = await consumer.getStats();
|
||||||
|
|
||||||
|
this.log(`consumer.getStats():\n${JSON.stringify(stats, null, ' ')}`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(`consumer.getStats() failed: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'sdp':
|
||||||
|
case 'statsDataProducer':
|
||||||
|
{
|
||||||
|
const id = params[0] || Array.from(dataProducers.keys()).pop();
|
||||||
|
const dataProducer = dataProducers.get(id);
|
||||||
|
|
||||||
|
if (!dataProducer)
|
||||||
|
{
|
||||||
|
this.error('DataProducer not found');
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const stats = await dataProducer.getStats();
|
||||||
|
|
||||||
|
this.log(`dataProducer.getStats():\n${JSON.stringify(stats, null, ' ')}`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(`dataProducer.getStats() failed: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'sdc':
|
||||||
|
case 'statsDataConsumer':
|
||||||
|
{
|
||||||
|
const id = params[0] || Array.from(dataConsumers.keys()).pop();
|
||||||
|
const dataConsumer = dataConsumers.get(id);
|
||||||
|
|
||||||
|
if (!dataConsumer)
|
||||||
|
{
|
||||||
|
this.error('DataConsumer not found');
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const stats = await dataConsumer.getStats();
|
||||||
|
|
||||||
|
this.log(`dataConsumer.getStats():\n${JSON.stringify(stats, null, ' ')}`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
this.error(`dataConsumer.getStats() failed: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
case 'terminal':
|
||||||
|
{
|
||||||
|
this._isTerminalOpen = true;
|
||||||
|
|
||||||
|
cmd.close();
|
||||||
|
this.openTerminal();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
this.error(`unknown command '${command}'`);
|
||||||
|
this.log('press \'h\' or \'help\' to get the list of available commands');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readStdin();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
readStdin();
|
||||||
|
}
|
||||||
|
|
||||||
|
openTerminal()
|
||||||
|
{
|
||||||
|
this.log('\n[opening Node REPL Terminal...]');
|
||||||
|
this.log('here you have access to workers, routers, transports, producers, consumers, dataProducers and dataConsumers ES6 maps');
|
||||||
|
|
||||||
|
const terminal = repl.start(
|
||||||
|
{
|
||||||
|
input : this._socket,
|
||||||
|
output : this._socket,
|
||||||
|
terminal : true,
|
||||||
|
prompt : 'terminal> ',
|
||||||
|
useColors : true,
|
||||||
|
useGlobal : true,
|
||||||
|
ignoreUndefined : false
|
||||||
|
});
|
||||||
|
|
||||||
|
this._isTerminalOpen = true;
|
||||||
|
|
||||||
|
terminal.on('exit', () =>
|
||||||
|
{
|
||||||
|
this.log('\n[exiting Node REPL Terminal...]');
|
||||||
|
|
||||||
|
this._isTerminalOpen = false;
|
||||||
|
|
||||||
|
this.openCommandConsole();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log(msg)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this._socket.write(`${colors.green(msg)}\n`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
|
||||||
|
error(msg)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this._socket.write(`${colors.red.bold('ERROR: ')}${colors.red(msg)}\n`);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runMediasoupObserver()
|
||||||
|
{
|
||||||
|
mediasoup.observer.on('newworker', (worker) =>
|
||||||
|
{
|
||||||
|
// Store the latest worker in a global variable.
|
||||||
|
global.worker = worker;
|
||||||
|
|
||||||
|
workers.set(worker.pid, worker);
|
||||||
|
worker.observer.on('close', () => workers.delete(worker.pid));
|
||||||
|
|
||||||
|
worker.observer.on('newrouter', (router) =>
|
||||||
|
{
|
||||||
|
// Store the latest router in a global variable.
|
||||||
|
global.router = router;
|
||||||
|
|
||||||
|
routers.set(router.id, router);
|
||||||
|
router.observer.on('close', () => routers.delete(router.id));
|
||||||
|
|
||||||
|
router.observer.on('newtransport', (transport) =>
|
||||||
|
{
|
||||||
|
// Store the latest transport in a global variable.
|
||||||
|
global.transport = transport;
|
||||||
|
|
||||||
|
transports.set(transport.id, transport);
|
||||||
|
transport.observer.on('close', () => transports.delete(transport.id));
|
||||||
|
|
||||||
|
transport.observer.on('newproducer', (producer) =>
|
||||||
|
{
|
||||||
|
// Store the latest producer in a global variable.
|
||||||
|
global.producer = producer;
|
||||||
|
|
||||||
|
producers.set(producer.id, producer);
|
||||||
|
producer.observer.on('close', () => producers.delete(producer.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
transport.observer.on('newconsumer', (consumer) =>
|
||||||
|
{
|
||||||
|
// Store the latest consumer in a global variable.
|
||||||
|
global.consumer = consumer;
|
||||||
|
|
||||||
|
consumers.set(consumer.id, consumer);
|
||||||
|
consumer.observer.on('close', () => consumers.delete(consumer.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
transport.observer.on('newdataproducer', (dataProducer) =>
|
||||||
|
{
|
||||||
|
// Store the latest dataProducer in a global variable.
|
||||||
|
global.dataProducer = dataProducer;
|
||||||
|
|
||||||
|
dataProducers.set(dataProducer.id, dataProducer);
|
||||||
|
dataProducer.observer.on('close', () => dataProducers.delete(dataProducer.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
transport.observer.on('newdataconsumer', (dataConsumer) =>
|
||||||
|
{
|
||||||
|
// Store the latest dataConsumer in a global variable.
|
||||||
|
global.dataConsumer = dataConsumer;
|
||||||
|
|
||||||
|
dataConsumers.set(dataConsumer.id, dataConsumer);
|
||||||
|
dataConsumer.observer.on('close', () => dataConsumers.delete(dataConsumer.id));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async function(rooms, peers)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Run the mediasoup observer API.
|
||||||
|
runMediasoupObserver();
|
||||||
|
|
||||||
|
// Make maps global so they can be used during the REPL terminal.
|
||||||
|
global.rooms = rooms;
|
||||||
|
global.peers = peers;
|
||||||
|
global.workers = workers;
|
||||||
|
global.routers = routers;
|
||||||
|
global.transports = transports;
|
||||||
|
global.producers = producers;
|
||||||
|
global.consumers = consumers;
|
||||||
|
global.dataProducers = dataProducers;
|
||||||
|
global.dataConsumers = dataConsumers;
|
||||||
|
|
||||||
|
const server = net.createServer((socket) =>
|
||||||
|
{
|
||||||
|
const interactive = new Interactive(socket);
|
||||||
|
|
||||||
|
interactive.openCommandConsole();
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve) =>
|
||||||
|
{
|
||||||
|
try { fs.unlinkSync(SOCKET_PATH); }
|
||||||
|
catch (error) {}
|
||||||
|
|
||||||
|
server.listen(SOCKET_PATH, resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
@ -6,6 +6,10 @@
|
||||||
"author": "Håvar Aambø Fosstveit <h@fosstveit.net>",
|
"author": "Håvar Aambø Fosstveit <h@fosstveit.net>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "DEBUG=${DEBUG:='*mediasoup* *INFO* *WARN* *ERROR*'} INTERACTIVE=${INTERACTIVE:='true'} node server.js",
|
||||||
|
"connect": "node connect.js"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"awaitqueue": "^1.0.0",
|
"awaitqueue": "^1.0.0",
|
||||||
"base-64": "^0.1.0",
|
"base-64": "^0.1.0",
|
||||||
|
|
@ -19,9 +23,12 @@
|
||||||
"express-session": "^1.17.0",
|
"express-session": "^1.17.0",
|
||||||
"express-socket.io-session": "^1.3.5",
|
"express-socket.io-session": "^1.3.5",
|
||||||
"helmet": "^3.21.2",
|
"helmet": "^3.21.2",
|
||||||
"mediasoup": "^3.0.12",
|
"ims-lti": "^3.0.2",
|
||||||
|
"mediasoup": "^3.5.5",
|
||||||
"openid-client": "^3.7.3",
|
"openid-client": "^3.7.3",
|
||||||
"passport": "^0.4.0",
|
"passport": "^0.4.0",
|
||||||
|
"passport-lti": "0.0.7",
|
||||||
|
"pidusage": "^2.0.17",
|
||||||
"redis": "^2.8.0",
|
"redis": "^2.8.0",
|
||||||
"socket.io": "^2.3.0",
|
"socket.io": "^2.3.0",
|
||||||
"spdy": "^4.0.1"
|
"spdy": "^4.0.1"
|
||||||
|
|
|
||||||
236
server/server.js
236
server/server.js
|
|
@ -17,18 +17,22 @@ const Room = require('./lib/Room');
|
||||||
const Peer = require('./lib/Peer');
|
const Peer = require('./lib/Peer');
|
||||||
const base64 = require('base-64');
|
const base64 = require('base-64');
|
||||||
const helmet = require('helmet');
|
const helmet = require('helmet');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
loginHelper,
|
loginHelper,
|
||||||
logoutHelper
|
logoutHelper
|
||||||
} = require('./httpHelper');
|
} = require('./httpHelper');
|
||||||
// auth
|
// auth
|
||||||
const passport = require('passport');
|
const passport = require('passport');
|
||||||
|
const LTIStrategy = require('passport-lti');
|
||||||
|
const imsLti = require('ims-lti');
|
||||||
const redis = require('redis');
|
const redis = require('redis');
|
||||||
const client = redis.createClient();
|
const redisClient = redis.createClient(config.redisOptions);
|
||||||
const { Issuer, Strategy } = require('openid-client');
|
const { Issuer, Strategy } = require('openid-client');
|
||||||
const expressSession = require('express-session');
|
const expressSession = require('express-session');
|
||||||
const RedisStore = require('connect-redis')(expressSession);
|
const RedisStore = require('connect-redis')(expressSession);
|
||||||
const sharedSession = require('express-socket.io-session');
|
const sharedSession = require('express-socket.io-session');
|
||||||
|
const interactiveServer = require('./lib/interactiveServer');
|
||||||
|
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
console.log('- process.env.DEBUG:', process.env.DEBUG);
|
console.log('- process.env.DEBUG:', process.env.DEBUG);
|
||||||
|
|
@ -87,7 +91,7 @@ const session = expressSession({
|
||||||
name : config.cookieName,
|
name : config.cookieName,
|
||||||
resave : true,
|
resave : true,
|
||||||
saveUninitialized : true,
|
saveUninitialized : true,
|
||||||
store : new RedisStore({ client }),
|
store : new RedisStore({ client: redisClient }),
|
||||||
cookie : {
|
cookie : {
|
||||||
secure : true,
|
secure : true,
|
||||||
httpOnly : true,
|
httpOnly : true,
|
||||||
|
|
@ -107,54 +111,34 @@ passport.deserializeUser((user, done) =>
|
||||||
done(null, user);
|
done(null, user);
|
||||||
});
|
});
|
||||||
|
|
||||||
let httpsServer;
|
let mainListener;
|
||||||
let io;
|
let io;
|
||||||
let oidcClient;
|
let oidcClient;
|
||||||
let oidcStrategy;
|
let oidcStrategy;
|
||||||
|
|
||||||
const auth = config.auth;
|
|
||||||
|
|
||||||
async function run()
|
async function run()
|
||||||
{
|
{
|
||||||
if (
|
// Open the interactive server.
|
||||||
typeof(auth) !== 'undefined' &&
|
await interactiveServer(rooms, peers);
|
||||||
typeof(auth.issuerURL) !== 'undefined' &&
|
|
||||||
typeof(auth.clientOptions) !== 'undefined'
|
if (typeof(config.auth) === 'undefined')
|
||||||
)
|
|
||||||
{
|
{
|
||||||
Issuer.discover(auth.issuerURL).then(async (oidcIssuer) =>
|
logger.warn('Auth is not configured properly!');
|
||||||
{
|
|
||||||
// Setup authentication
|
|
||||||
await setupAuth(oidcIssuer);
|
|
||||||
|
|
||||||
// Run a mediasoup Worker.
|
|
||||||
await runMediasoupWorkers();
|
|
||||||
|
|
||||||
// Run HTTPS server.
|
|
||||||
await runHttpsServer();
|
|
||||||
|
|
||||||
// Run WebSocketServer.
|
|
||||||
await runWebSocketServer();
|
|
||||||
})
|
|
||||||
.catch((err) =>
|
|
||||||
{
|
|
||||||
logger.error(err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.error('Auth is not configure properly!');
|
await setupAuth();
|
||||||
|
|
||||||
// Run a mediasoup Worker.
|
|
||||||
await runMediasoupWorkers();
|
|
||||||
|
|
||||||
// Run HTTPS server.
|
|
||||||
await runHttpsServer();
|
|
||||||
|
|
||||||
// Run WebSocketServer.
|
|
||||||
await runWebSocketServer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run a mediasoup Worker.
|
||||||
|
await runMediasoupWorkers();
|
||||||
|
|
||||||
|
// Run HTTPS server.
|
||||||
|
await runHttpsServer();
|
||||||
|
|
||||||
|
// Run WebSocketServer.
|
||||||
|
await runWebSocketServer();
|
||||||
|
|
||||||
// Log rooms status every 30 seconds.
|
// Log rooms status every 30 seconds.
|
||||||
setInterval(() =>
|
setInterval(() =>
|
||||||
{
|
{
|
||||||
|
|
@ -174,21 +158,72 @@ async function run()
|
||||||
}, 10000);
|
}, 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupAuth(oidcIssuer)
|
function setupLTI(ltiConfig)
|
||||||
{
|
{
|
||||||
oidcClient = new oidcIssuer.Client(auth.clientOptions);
|
|
||||||
|
// Add redis nonce store
|
||||||
|
ltiConfig.nonceStore = new imsLti.Stores.RedisStore(ltiConfig.consumerKey, redisClient);
|
||||||
|
ltiConfig.passReqToCallback= true;
|
||||||
|
|
||||||
|
const ltiStrategy = new LTIStrategy(
|
||||||
|
ltiConfig,
|
||||||
|
function(req, lti, done)
|
||||||
|
{
|
||||||
|
// LTI launch parameters
|
||||||
|
if (lti)
|
||||||
|
{
|
||||||
|
const user = {};
|
||||||
|
|
||||||
|
if (lti.user_id && lti.custom_room)
|
||||||
|
{
|
||||||
|
user.id = lti.user_id;
|
||||||
|
user._lti = lti;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lti.custom_room)
|
||||||
|
{
|
||||||
|
user.room = lti.custom_room;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
user.room = '';
|
||||||
|
}
|
||||||
|
if (lti.lis_person_name_full)
|
||||||
|
{
|
||||||
|
user.displayName=lti.lis_person_name_full;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform local authentication if necessary
|
||||||
|
return done(null, user);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return done('LTI error');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
passport.use('lti', ltiStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupOIDC(oidcIssuer)
|
||||||
|
{
|
||||||
|
|
||||||
|
oidcClient = new oidcIssuer.Client(config.auth.oidc.clientOptions);
|
||||||
|
|
||||||
// ... any authorization request parameters go here
|
// ... any authorization request parameters go here
|
||||||
// client_id defaults to client.client_id
|
// client_id defaults to client.client_id
|
||||||
// redirect_uri defaults to client.redirect_uris[0]
|
// redirect_uri defaults to client.redirect_uris[0]
|
||||||
// response type defaults to client.response_types[0], then 'code'
|
// response type defaults to client.response_types[0], then 'code'
|
||||||
// scope defaults to 'openid'
|
// scope defaults to 'openid'
|
||||||
const params = auth.clientOptions;
|
const params = config.auth.oidc.clientOptions;
|
||||||
|
|
||||||
// optional, defaults to false, when true req is passed as a first
|
// optional, defaults to false, when true req is passed as a first
|
||||||
// argument to verify fn
|
// argument to verify fn
|
||||||
const passReqToCallback = false;
|
const passReqToCallback = false;
|
||||||
|
|
||||||
// optional, defaults to false, when true the code_challenge_method will be
|
// optional, defaults to false, when true the code_challenge_method will be
|
||||||
// resolved from the issuer configuration, instead of true you may provide
|
// resolved from the issuer configuration, instead of true you may provide
|
||||||
// any of the supported values directly, i.e. "S256" (recommended) or "plain"
|
// any of the supported values directly, i.e. "S256" (recommended) or "plain"
|
||||||
|
|
@ -235,16 +270,19 @@ async function setupAuth(oidcIssuer)
|
||||||
|
|
||||||
if (userinfo.given_name != null)
|
if (userinfo.given_name != null)
|
||||||
{
|
{
|
||||||
|
user.name={};
|
||||||
user.name.givenName = userinfo.given_name;
|
user.name.givenName = userinfo.given_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userinfo.family_name != null)
|
if (userinfo.family_name != null)
|
||||||
{
|
{
|
||||||
|
if (user.name == null) user.name={};
|
||||||
user.name.familyName = userinfo.family_name;
|
user.name.familyName = userinfo.family_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userinfo.middle_name != null)
|
if (userinfo.middle_name != null)
|
||||||
{
|
{
|
||||||
|
if (user.name == null) user.name={};
|
||||||
user.name.middleName = userinfo.middle_name;
|
user.name.middleName = userinfo.middle_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,6 +291,30 @@ async function setupAuth(oidcIssuer)
|
||||||
);
|
);
|
||||||
|
|
||||||
passport.use('oidc', oidcStrategy);
|
passport.use('oidc', oidcStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupAuth()
|
||||||
|
{
|
||||||
|
// LTI
|
||||||
|
if (
|
||||||
|
typeof(config.auth.lti) !== 'undefined' &&
|
||||||
|
typeof(config.auth.lti.consumerKey) !== 'undefined' &&
|
||||||
|
typeof(config.auth.lti.consumerSecret) !== 'undefined'
|
||||||
|
) setupLTI(config.auth.lti);
|
||||||
|
|
||||||
|
// OIDC
|
||||||
|
if (
|
||||||
|
typeof(config.auth.oidc) !== 'undefined' &&
|
||||||
|
typeof(config.auth.oidc.issuerURL) !== 'undefined' &&
|
||||||
|
typeof(config.auth.oidc.clientOptions) !== 'undefined'
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const oidcIssuer = await Issuer.discover(config.auth.oidc.issuerURL);
|
||||||
|
|
||||||
|
// Setup authentication
|
||||||
|
setupOIDC(oidcIssuer);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
app.use(passport.initialize());
|
app.use(passport.initialize());
|
||||||
app.use(passport.session());
|
app.use(passport.session());
|
||||||
|
|
@ -267,6 +329,15 @@ async function setupAuth(oidcIssuer)
|
||||||
})(req, res, next);
|
})(req, res, next);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// lti launch
|
||||||
|
app.post('/auth/lti',
|
||||||
|
passport.authenticate('lti', { failureRedirect: '/' }),
|
||||||
|
function(req, res)
|
||||||
|
{
|
||||||
|
res.redirect(`/${req.user.room}`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// logout
|
// logout
|
||||||
app.get('/auth/logout', (req, res) =>
|
app.get('/auth/logout', (req, res) =>
|
||||||
{
|
{
|
||||||
|
|
@ -318,19 +389,31 @@ async function runHttpsServer()
|
||||||
|
|
||||||
app.use('/.well-known/acme-challenge', express.static('public/.well-known/acme-challenge'));
|
app.use('/.well-known/acme-challenge', express.static('public/.well-known/acme-challenge'));
|
||||||
|
|
||||||
app.all('*', (req, res, next) =>
|
app.all('*', async (req, res, next) =>
|
||||||
{
|
{
|
||||||
if (req.secure)
|
if (req.secure)
|
||||||
{
|
{
|
||||||
return next();
|
const ltiURL = new URL(`${req.protocol }://${ req.get('host') }${req.originalUrl}`);
|
||||||
|
|
||||||
|
if (
|
||||||
|
req.isAuthenticated &&
|
||||||
|
req.user &&
|
||||||
|
req.user.displayName &&
|
||||||
|
!ltiURL.searchParams.get('displayName') &&
|
||||||
|
!isPathAlreadyTaken(req.url)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
|
||||||
|
ltiURL.searchParams.append('displayName', req.user.displayName);
|
||||||
|
|
||||||
|
res.redirect(ltiURL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return next();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
res.redirect(`https://${req.hostname}${req.url}`);
|
||||||
|
|
||||||
res.redirect(`https://${req.hostname}${req.url}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/', (req, res) =>
|
|
||||||
{
|
|
||||||
res.sendFile(`${__dirname}/public/chooseRoom.html`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serve all files in the public folder as static files.
|
// Serve all files in the public folder as static files.
|
||||||
|
|
@ -338,19 +421,45 @@ async function runHttpsServer()
|
||||||
|
|
||||||
app.use((req, res) => res.sendFile(`${__dirname}/public/index.html`));
|
app.use((req, res) => res.sendFile(`${__dirname}/public/index.html`));
|
||||||
|
|
||||||
httpsServer = spdy.createServer(tls, app);
|
if (config.httpOnly === true)
|
||||||
|
|
||||||
httpsServer.listen(config.listeningPort, '0.0.0.0', () =>
|
|
||||||
{
|
{
|
||||||
logger.info('Server running on port: ', config.listeningPort);
|
// http
|
||||||
|
mainListener = http.createServer(app);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// https
|
||||||
|
mainListener = spdy.createServer(tls, app);
|
||||||
|
|
||||||
|
// http
|
||||||
|
const redirectListener = http.createServer(app);
|
||||||
|
|
||||||
|
redirectListener.listen(config.listeningRedirectPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https or http
|
||||||
|
mainListener.listen(config.listeningPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPathAlreadyTaken(url)
|
||||||
|
{
|
||||||
|
const alreadyTakenPath =
|
||||||
|
[
|
||||||
|
'/config/',
|
||||||
|
'/static/',
|
||||||
|
'/images/',
|
||||||
|
'/sounds/',
|
||||||
|
'/favicon.',
|
||||||
|
'/auth/'
|
||||||
|
];
|
||||||
|
|
||||||
|
alreadyTakenPath.forEach((path) =>
|
||||||
|
{
|
||||||
|
if (url.toString().startsWith(path))
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const httpServer = http.createServer(app);
|
return false;
|
||||||
|
|
||||||
httpServer.listen(config.listeningRedirectPort, '0.0.0.0', () =>
|
|
||||||
{
|
|
||||||
logger.info('Server redirecting port: ', config.listeningRedirectPort);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -358,7 +467,7 @@ async function runHttpsServer()
|
||||||
*/
|
*/
|
||||||
async function runWebSocketServer()
|
async function runWebSocketServer()
|
||||||
{
|
{
|
||||||
io = require('socket.io')(httpsServer);
|
io = require('socket.io')(mainListener);
|
||||||
|
|
||||||
io.use(
|
io.use(
|
||||||
sharedSession(session, {
|
sharedSession(session, {
|
||||||
|
|
@ -466,10 +575,11 @@ async function getOrCreateRoom({ roomId })
|
||||||
room = await Room.create({ mediasoupWorker, roomId });
|
room = await Room.create({ mediasoupWorker, roomId });
|
||||||
|
|
||||||
rooms.set(roomId, room);
|
rooms.set(roomId, room);
|
||||||
|
|
||||||
room.on('close', () => rooms.delete(roomId));
|
room.on('close', () => rooms.delete(roomId));
|
||||||
}
|
}
|
||||||
|
|
||||||
return room;
|
return room;
|
||||||
}
|
}
|
||||||
|
|
||||||
run();
|
run();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue