Merge branch 'develop'
commit
a4a7fbe4f7
|
|
@ -1,10 +1,11 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
/app/build/
|
/app/build/
|
||||||
/app/public/config.js
|
/app/public/config/config.js
|
||||||
/app/public/images/logo.*
|
/app/public/images/logo.*
|
||||||
/server/config/
|
/server/config/
|
||||||
!/server/config/config.example.js
|
!/server/config/config.example.js
|
||||||
/server/public/
|
/server/public/
|
||||||
/server/certs/
|
/server/certs/
|
||||||
!/server/certs/mediasoup-demo.localhost.*
|
!/server/certs/mediasoup-demo.localhost.*
|
||||||
|
.vscode
|
||||||
18
CHANGELOG.md
18
CHANGELOG.md
|
|
@ -1,5 +1,23 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### 3.0
|
||||||
|
* Updated to mediasoup v3
|
||||||
|
* Replace lib "passport-datporten" with "openid-client" (a general OIDC certified client)
|
||||||
|
- OpenID Connect discovery
|
||||||
|
- Auth code flow
|
||||||
|
* Add spdy http2 support.
|
||||||
|
- Notice it does not supports node 11.x
|
||||||
|
* Updated to Material UI v4
|
||||||
|
|
||||||
|
### 2.0
|
||||||
|
* Material UI
|
||||||
|
* Separate settings for lastN for desktop and mobile
|
||||||
|
|
||||||
|
### 1.2
|
||||||
|
* Add Lock Room feature
|
||||||
|
* Fix suspended Web Audio context / fixed delayed getUsermedia
|
||||||
|
* 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
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -9,7 +9,7 @@ Try it online at https://letsmeet.no. You can add /roomname to the URL for speci
|
||||||
* Chat
|
* Chat
|
||||||
* Screen sharing
|
* Screen sharing
|
||||||
* File sharing
|
* File sharing
|
||||||
* Different video layouts
|
* Different layouts
|
||||||
|
|
||||||
There is also a SIP gateway that can be found [here](https://github.com/havfo/multiparty-meeting-sipgw). To try it, call: roomname@letsmeet.no.
|
There is also a SIP gateway that can be found [here](https://github.com/havfo/multiparty-meeting-sipgw). To try it, call: roomname@letsmeet.no.
|
||||||
|
|
||||||
|
|
@ -25,16 +25,16 @@ $ git clone https://github.com/havfo/multiparty-meeting.git
|
||||||
$ cd multiparty-meeting
|
$ cd multiparty-meeting
|
||||||
```
|
```
|
||||||
|
|
||||||
* Copy `server/config.example.js` to `server/config.js` :
|
* Copy `server/config/config.example.js` to `server/config/config.js` :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cp server/config.example.js server/config.js
|
$ cp server/config/config.example.js server/config/config.js
|
||||||
```
|
```
|
||||||
|
|
||||||
* Copy `app/public/config.example.js` to `app/public/config.js` :
|
* Copy `app/public/config/config.example.js` to `app/public/config/config.js` :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cp app/public/config.example.js app/public/config.js
|
$ cp app/public/config/config.example.js app/public/config/config.js
|
||||||
```
|
```
|
||||||
|
|
||||||
* Edit your two `config.js` with appropriate settings (listening IP/port, logging options, **valid** TLS certificate, etc).
|
* Edit your two `config.js` with appropriate settings (listening IP/port, logging options, **valid** TLS certificate, etc).
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,43 @@
|
||||||
{
|
{
|
||||||
"name": "multiparty-meeting",
|
"name": "multiparty-meeting",
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"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",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "^3.9.2",
|
"@material-ui/core": "^4.1.2",
|
||||||
"@material-ui/icons": "^3.0.2",
|
"@material-ui/icons": "^4.2.1",
|
||||||
|
"bowser": "^2.4.0",
|
||||||
"create-torrent": "^3.33.0",
|
"create-torrent": "^3.33.0",
|
||||||
"domready": "^1.0.8",
|
"domready": "^1.0.8",
|
||||||
"file-saver": "^2.0.1",
|
"file-saver": "^2.0.1",
|
||||||
"hark": "^1.2.3",
|
"hark": "^1.2.3",
|
||||||
"js-cookie": "^2.2.0",
|
|
||||||
"marked": "^0.6.1",
|
"marked": "^0.6.1",
|
||||||
"mediasoup-client": "^2.4.10",
|
"mediasoup-client": "^3.0.6",
|
||||||
"notistack": "^0.5.1",
|
"notistack": "^0.5.1",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"random-string": "^0.2.0",
|
"random-string": "^0.2.0",
|
||||||
"react": "^16.8.5",
|
"react": "^16.8.5",
|
||||||
"react-cookie-consent": "^2.2.2",
|
"react-cookie-consent": "^2.2.2",
|
||||||
"react-dom": "^16.8.5",
|
"react-dom": "^16.8.5",
|
||||||
"react-draggable": "^3.2.1",
|
|
||||||
"react-redux": "^6.0.1",
|
"react-redux": "^6.0.1",
|
||||||
"react-scripts": "2.1.8",
|
"react-scripts": "2.1.8",
|
||||||
"react-tooltip": "^3.10.0",
|
|
||||||
"redux": "^4.0.1",
|
"redux": "^4.0.1",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"redux-persist": "^5.10.0",
|
"redux-persist": "^5.10.0",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"reselect": "^4.0.0",
|
"reselect": "^4.0.0",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
|
||||||
"riek": "^1.1.0",
|
"riek": "^1.1.0",
|
||||||
"socket.io-client": "^2.2.0",
|
"socket.io-client": "^2.2.0",
|
||||||
"source-map-explorer": "^1.8.0",
|
"source-map-explorer": "^1.8.0",
|
||||||
"url-parse": "^1.4.4",
|
|
||||||
"webtorrent": "^0.103.1"
|
"webtorrent": "^0.103.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze-main": "source-map-explorer build/static/js/main.*",
|
"analyze-main": "source-map-explorer build/static/js/main.*",
|
||||||
"analyze-chunk": "source-map-explorer build/static/js/2.*",
|
"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 && 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"
|
||||||
},
|
},
|
||||||
|
|
@ -169,7 +165,12 @@
|
||||||
"no-case-declarations": 2,
|
"no-case-declarations": 2,
|
||||||
"no-catch-shadow": 2,
|
"no-catch-shadow": 2,
|
||||||
"no-class-assign": 2,
|
"no-class-assign": 2,
|
||||||
"no-confusing-arrow": ["error", {"allowParens": true}],
|
"no-confusing-arrow": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allowParens": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"no-console": 2,
|
"no-console": 2,
|
||||||
"no-const-assign": 2,
|
"no-const-assign": 2,
|
||||||
"no-debugger": 2,
|
"no-debugger": 2,
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,13 @@
|
||||||
<meta name='description' content='multiparty meeting - Simple web meetings'>
|
<meta name='description' content='multiparty meeting - Simple web meetings'>
|
||||||
<meta name='theme-color' content='#000000' />
|
<meta name='theme-color' content='#000000' />
|
||||||
|
|
||||||
<link rel='chrome-webstore-item' href='https://chrome.google.com/webstore/detail/fckajcjdaabdgnbdcmhhebdglogjfodi'>
|
<link rel='preconnect' href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet'>
|
||||||
<link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet'>
|
|
||||||
<link rel='shortcut icon' href='%PUBLIC_URL%/favicon.ico' />
|
<link rel='shortcut icon' href='%PUBLIC_URL%/favicon.ico' />
|
||||||
<link rel='manifest' href='%PUBLIC_URL%/manifest.json' />
|
<link rel='manifest' href='%PUBLIC_URL%/manifest.json' />
|
||||||
|
|
||||||
<title>Multiparty Meeting</title>
|
<title>Multiparty Meeting</title>
|
||||||
|
|
||||||
<script src='%PUBLIC_URL%/config.js' type='text/javascript'></script>
|
<script src='%PUBLIC_URL%/config/config.js' type='text/javascript'></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Allow crawling of all content
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,113 +1,4 @@
|
||||||
class ChromeScreenShare
|
class DisplayMediaScreenShare
|
||||||
{
|
|
||||||
constructor()
|
|
||||||
{
|
|
||||||
this._stream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
start(options = { })
|
|
||||||
{
|
|
||||||
const state = this;
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) =>
|
|
||||||
{
|
|
||||||
window.addEventListener('message', _onExtensionMessage, false);
|
|
||||||
window.postMessage({ type: 'getStreamId' }, '*');
|
|
||||||
|
|
||||||
function _onExtensionMessage({ data })
|
|
||||||
{
|
|
||||||
if (data.type !== 'gotStreamId')
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const constraints = state._toConstraints(options, data.streamId);
|
|
||||||
|
|
||||||
navigator.mediaDevices.getUserMedia(constraints)
|
|
||||||
.then((stream) =>
|
|
||||||
{
|
|
||||||
window.removeEventListener('message', _onExtensionMessage);
|
|
||||||
|
|
||||||
state._stream = stream;
|
|
||||||
resolve(stream);
|
|
||||||
})
|
|
||||||
.catch((err) =>
|
|
||||||
{
|
|
||||||
window.removeEventListener('message', _onExtensionMessage);
|
|
||||||
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
stop()
|
|
||||||
{
|
|
||||||
if (this._stream instanceof MediaStream === false)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._stream.getTracks().forEach((track) => track.stop());
|
|
||||||
this._stream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
isScreenShareAvailable()
|
|
||||||
{
|
|
||||||
if ('__multipartyMeetingScreenShareExtensionAvailable__' in window)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
needExtension()
|
|
||||||
{
|
|
||||||
if ('__multipartyMeetingScreenShareExtensionAvailable__' in window)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_toConstraints(options, streamId)
|
|
||||||
{
|
|
||||||
const constraints = {
|
|
||||||
video : {
|
|
||||||
mandatory : {
|
|
||||||
chromeMediaSource : 'desktop',
|
|
||||||
chromeMediaSourceId : streamId
|
|
||||||
},
|
|
||||||
optional : [ {
|
|
||||||
googTemporalLayeredScreencast : true
|
|
||||||
} ]
|
|
||||||
},
|
|
||||||
audio : false
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isFinite(options.width))
|
|
||||||
{
|
|
||||||
constraints.video.mandatory.maxWidth = options.width;
|
|
||||||
constraints.video.mandatory.minWidth = options.width;
|
|
||||||
}
|
|
||||||
if (isFinite(options.height))
|
|
||||||
{
|
|
||||||
constraints.video.mandatory.maxHeight = options.height;
|
|
||||||
constraints.video.mandatory.minHeight = options.height;
|
|
||||||
}
|
|
||||||
if (isFinite(options.frameRate))
|
|
||||||
{
|
|
||||||
constraints.video.mandatory.maxFrameRate = options.frameRate;
|
|
||||||
constraints.video.mandatory.minFrameRate = options.frameRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
return constraints;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Chrome72ScreenShare
|
|
||||||
{
|
{
|
||||||
constructor()
|
constructor()
|
||||||
{
|
{
|
||||||
|
|
@ -143,11 +34,6 @@ class Chrome72ScreenShare
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
needExtension()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_toConstraints()
|
_toConstraints()
|
||||||
{
|
{
|
||||||
const constraints = {
|
const constraints = {
|
||||||
|
|
@ -194,11 +80,6 @@ class FirefoxScreenShare
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
needExtension()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_toConstraints(options)
|
_toConstraints(options)
|
||||||
{
|
{
|
||||||
const constraints = {
|
const constraints = {
|
||||||
|
|
@ -238,119 +119,12 @@ class FirefoxScreenShare
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Firefox66ScreenShare
|
|
||||||
{
|
|
||||||
constructor()
|
|
||||||
{
|
|
||||||
this._stream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
start(options = {})
|
|
||||||
{
|
|
||||||
const constraints = this._toConstraints(options);
|
|
||||||
|
|
||||||
return navigator.mediaDevices.getDisplayMedia(constraints)
|
|
||||||
.then((stream) =>
|
|
||||||
{
|
|
||||||
this._stream = stream;
|
|
||||||
|
|
||||||
return Promise.resolve(stream);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
stop()
|
|
||||||
{
|
|
||||||
if (this._stream instanceof MediaStream === false)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._stream.getTracks().forEach((track) => track.stop());
|
|
||||||
this._stream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
isScreenShareAvailable()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
needExtension()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_toConstraints()
|
|
||||||
{
|
|
||||||
const constraints = {
|
|
||||||
video : true
|
|
||||||
};
|
|
||||||
|
|
||||||
return constraints;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EdgeScreenShare
|
|
||||||
{
|
|
||||||
constructor()
|
|
||||||
{
|
|
||||||
this._stream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
start(options = {})
|
|
||||||
{
|
|
||||||
const constraints = this._toConstraints(options);
|
|
||||||
|
|
||||||
return navigator.getDisplayMedia(constraints)
|
|
||||||
.then((stream) =>
|
|
||||||
{
|
|
||||||
this._stream = stream;
|
|
||||||
|
|
||||||
return Promise.resolve(stream);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
stop()
|
|
||||||
{
|
|
||||||
if (this._stream instanceof MediaStream === false)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._stream.getTracks().forEach((track) => track.stop());
|
|
||||||
this._stream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
isScreenShareAvailable()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
needExtension()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_toConstraints()
|
|
||||||
{
|
|
||||||
const constraints = {
|
|
||||||
video : true
|
|
||||||
};
|
|
||||||
|
|
||||||
return constraints;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DefaultScreenShare
|
class DefaultScreenShare
|
||||||
{
|
{
|
||||||
isScreenShareAvailable()
|
isScreenShareAvailable()
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
needExtension()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ScreenShare
|
export default class ScreenShare
|
||||||
|
|
@ -364,18 +138,15 @@ export default class ScreenShare
|
||||||
if (device.version < 66.0)
|
if (device.version < 66.0)
|
||||||
return new FirefoxScreenShare();
|
return new FirefoxScreenShare();
|
||||||
else
|
else
|
||||||
return new Firefox66ScreenShare();
|
return new DisplayMediaScreenShare();
|
||||||
}
|
}
|
||||||
case 'chrome':
|
case 'chrome':
|
||||||
{
|
{
|
||||||
if (device.version < 72.0)
|
return new DisplayMediaScreenShare();
|
||||||
return new ChromeScreenShare();
|
|
||||||
else
|
|
||||||
return new Chrome72ScreenShare();
|
|
||||||
}
|
}
|
||||||
case 'msedge':
|
case 'msedge':
|
||||||
{
|
{
|
||||||
return new EdgeScreenShare();
|
return new DisplayMediaScreenShare();
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@ const logger = new Logger('Spotlight');
|
||||||
|
|
||||||
export default class Spotlights extends EventEmitter
|
export default class Spotlights extends EventEmitter
|
||||||
{
|
{
|
||||||
constructor(maxSpotlights, room)
|
constructor(maxSpotlights, signalingSocket)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._room = room;
|
this._signalingSocket = signalingSocket;
|
||||||
this._maxSpotlights = maxSpotlights;
|
this._maxSpotlights = maxSpotlights;
|
||||||
this._peerList = [];
|
this._peerList = [];
|
||||||
this._selectedSpotlights = [];
|
this._selectedSpotlights = [];
|
||||||
|
|
@ -19,24 +19,25 @@ export default class Spotlights extends EventEmitter
|
||||||
|
|
||||||
start()
|
start()
|
||||||
{
|
{
|
||||||
const peers = this._room.peers;
|
this._handleSignaling();
|
||||||
|
|
||||||
for (const peer of peers)
|
|
||||||
{
|
|
||||||
this._handlePeer(peer);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._handleRoom();
|
|
||||||
|
|
||||||
this._started = true;
|
this._started = true;
|
||||||
this._spotlightsUpdated();
|
this._spotlightsUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
peerInSpotlights(peerName)
|
addPeers(peers)
|
||||||
|
{
|
||||||
|
for (const peer of peers)
|
||||||
|
{
|
||||||
|
this._newPeer(peer.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
peerInSpotlights(peerId)
|
||||||
{
|
{
|
||||||
if (this._started)
|
if (this._started)
|
||||||
{
|
{
|
||||||
return this._currentSpotlights.indexOf(peerName) !== -1;
|
return this._currentSpotlights.indexOf(peerId) !== -1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -44,11 +45,11 @@ export default class Spotlights extends EventEmitter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setPeerSpotlight(peerName)
|
setPeerSpotlight(peerId)
|
||||||
{
|
{
|
||||||
logger.debug('setPeerSpotlight() [peerName:"%s"]', peerName);
|
logger.debug('setPeerSpotlight() [peerId:"%s"]', peerId);
|
||||||
|
|
||||||
const index = this._selectedSpotlights.indexOf(peerName);
|
const index = this._selectedSpotlights.indexOf(peerId);
|
||||||
|
|
||||||
if (index !== -1)
|
if (index !== -1)
|
||||||
{
|
{
|
||||||
|
|
@ -56,13 +57,13 @@ export default class Spotlights extends EventEmitter
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this._selectedSpotlights = [ peerName ];
|
this._selectedSpotlights = [ peerId ];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (index === -1) // We don't have this peer in the list, adding
|
if (index === -1) // We don't have this peer in the list, adding
|
||||||
{
|
{
|
||||||
this._selectedSpotlights.push(peerName);
|
this._selectedSpotlights.push(peerId);
|
||||||
}
|
}
|
||||||
else // We have this peer, remove
|
else // We have this peer, remove
|
||||||
{
|
{
|
||||||
|
|
@ -74,16 +75,65 @@ export default class Spotlights extends EventEmitter
|
||||||
this._spotlightsUpdated();
|
this._spotlightsUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleRoom()
|
_handleSignaling()
|
||||||
{
|
{
|
||||||
this._room.on('newpeer', (peer) =>
|
this._signalingSocket.on('notification', (notification) =>
|
||||||
{
|
{
|
||||||
logger.debug(
|
if (notification.method === 'newPeer')
|
||||||
'room "newpeer" event [name:"%s", peer:%o]', peer.name, peer);
|
{
|
||||||
this._handlePeer(peer);
|
const { id } = notification.data;
|
||||||
|
|
||||||
|
this._newPeer(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notification.method === 'peerClosed')
|
||||||
|
{
|
||||||
|
const { peerId } = notification.data;
|
||||||
|
|
||||||
|
this._closePeer(peerId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_newPeer(id)
|
||||||
|
{
|
||||||
|
logger.debug(
|
||||||
|
'room "newpeer" event [id: "%s"]', id);
|
||||||
|
|
||||||
|
if (this._peerList.indexOf(id) === -1) // We don't have this peer in the list
|
||||||
|
{
|
||||||
|
logger.debug('_handlePeer() | adding peer [peerId: "%s"]', id);
|
||||||
|
|
||||||
|
this._peerList.push(id);
|
||||||
|
|
||||||
|
if (this._started)
|
||||||
|
this._spotlightsUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_closePeer(id)
|
||||||
|
{
|
||||||
|
logger.debug(
|
||||||
|
'room "peerClosed" event [peerId:%o]', id);
|
||||||
|
|
||||||
|
let index = this._peerList.indexOf(id);
|
||||||
|
|
||||||
|
if (index !== -1) // We have this peer in the list, remove
|
||||||
|
{
|
||||||
|
this._peerList.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
index = this._selectedSpotlights.indexOf(id);
|
||||||
|
|
||||||
|
if (index !== -1) // We have this peer in the list, remove
|
||||||
|
{
|
||||||
|
this._selectedSpotlights.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._started)
|
||||||
|
this._spotlightsUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
addSpeakerList(speakerList)
|
addSpeakerList(speakerList)
|
||||||
{
|
{
|
||||||
this._peerList = [ ...new Set([ ...speakerList, ...this._peerList ]) ];
|
this._peerList = [ ...new Set([ ...speakerList, ...this._peerList ]) ];
|
||||||
|
|
@ -92,49 +142,16 @@ export default class Spotlights extends EventEmitter
|
||||||
this._spotlightsUpdated();
|
this._spotlightsUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
_handlePeer(peer)
|
handleActiveSpeaker(peerId)
|
||||||
{
|
{
|
||||||
logger.debug('_handlePeer() [peerName:"%s"]', peer.name);
|
logger.debug('handleActiveSpeaker() [peerId:"%s"]', peerId);
|
||||||
|
|
||||||
if (this._peerList.indexOf(peer.name) === -1) // We don't have this peer in the list
|
const index = this._peerList.indexOf(peerId);
|
||||||
{
|
|
||||||
peer.on('close', () =>
|
|
||||||
{
|
|
||||||
let index = this._peerList.indexOf(peer.name);
|
|
||||||
|
|
||||||
if (index !== -1) // We have this peer in the list, remove
|
|
||||||
{
|
|
||||||
this._peerList.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
index = this._selectedSpotlights.indexOf(peer.name);
|
|
||||||
|
|
||||||
if (index !== -1) // We have this peer in the list, remove
|
|
||||||
{
|
|
||||||
this._selectedSpotlights.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._spotlightsUpdated();
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.debug('_handlePeer() | adding peer [peerName:"%s"]', peer.name);
|
|
||||||
|
|
||||||
this._peerList.push(peer.name);
|
|
||||||
|
|
||||||
this._spotlightsUpdated();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleActiveSpeaker(peerName)
|
|
||||||
{
|
|
||||||
logger.debug('handleActiveSpeaker() [peerName:"%s"]', peerName);
|
|
||||||
|
|
||||||
const index = this._peerList.indexOf(peerName);
|
|
||||||
|
|
||||||
if (index > -1)
|
if (index > -1)
|
||||||
{
|
{
|
||||||
this._peerList.splice(index, 1);
|
this._peerList.splice(index, 1);
|
||||||
this._peerList = [ peerName ].concat(this._peerList);
|
this._peerList = [ peerId ].concat(this._peerList);
|
||||||
|
|
||||||
this._spotlightsUpdated();
|
this._spotlightsUpdated();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,11 @@ export const setRoomState = (state) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setRoomActiveSpeaker = (peerName) =>
|
export const setRoomActiveSpeaker = (peerId) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'SET_ROOM_ACTIVE_SPEAKER',
|
type : 'SET_ROOM_ACTIVE_SPEAKER',
|
||||||
payload : { peerName }
|
payload : { peerId }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -43,41 +43,30 @@ export const setRoomLockedOut = () =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setAudioSuspended = ({ audioSuspended }) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'SET_AUDIO_SUSPENDED',
|
|
||||||
payload : { audioSuspended }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setSettingsOpen = ({ settingsOpen }) =>
|
export const setSettingsOpen = ({ settingsOpen }) =>
|
||||||
({
|
({
|
||||||
type : 'SET_SETTINGS_OPEN',
|
type : 'SET_SETTINGS_OPEN',
|
||||||
payload : { settingsOpen }
|
payload : { settingsOpen }
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setMe = ({ peerName, device, loginEnabled }) =>
|
export const setMe = ({ peerId, device, loginEnabled }) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'SET_ME',
|
type : 'SET_ME',
|
||||||
payload : { peerName, device, loginEnabled }
|
payload : { peerId, device, loginEnabled }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setMediaCapabilities = ({ canSendMic, canSendWebcam }) =>
|
export const setMediaCapabilities = ({
|
||||||
|
canSendMic,
|
||||||
|
canSendWebcam,
|
||||||
|
canShareScreen,
|
||||||
|
canShareFiles
|
||||||
|
}) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'SET_MEDIA_CAPABILITIES',
|
type : 'SET_MEDIA_CAPABILITIES',
|
||||||
payload : { canSendMic, canSendWebcam }
|
payload : { canSendMic, canSendWebcam, canShareScreen, canShareFiles }
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setScreenCapabilities = ({ canShareScreen, needExtension }) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'SET_SCREEN_CAPABILITIES',
|
|
||||||
payload : { canShareScreen, needExtension }
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -150,27 +139,27 @@ export const setDisplayMode = (mode) =>
|
||||||
payload : { mode }
|
payload : { mode }
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setPeerVideoInProgress = (peerName, flag) =>
|
export const setPeerVideoInProgress = (peerId, flag) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'SET_PEER_VIDEO_IN_PROGRESS',
|
type : 'SET_PEER_VIDEO_IN_PROGRESS',
|
||||||
payload : { peerName, flag }
|
payload : { peerId, flag }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setPeerAudioInProgress = (peerName, flag) =>
|
export const setPeerAudioInProgress = (peerId, flag) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'SET_PEER_AUDIO_IN_PROGRESS',
|
type : 'SET_PEER_AUDIO_IN_PROGRESS',
|
||||||
payload : { peerName, flag }
|
payload : { peerId, flag }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setPeerScreenInProgress = (peerName, flag) =>
|
export const setPeerScreenInProgress = (peerId, flag) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'SET_PEER_SCREEN_IN_PROGRESS',
|
type : 'SET_PEER_SCREEN_IN_PROGRESS',
|
||||||
payload : { peerName, flag }
|
payload : { peerId, flag }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -226,11 +215,11 @@ export const setMyRaiseHandStateInProgress = (flag) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setPeerRaiseHandState = (peerName, raiseHandState) =>
|
export const setPeerRaiseHandState = (peerId, raiseHandState) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'SET_PEER_RAISE_HAND_STATE',
|
type : 'SET_PEER_RAISE_HAND_STATE',
|
||||||
payload : { peerName, raiseHandState }
|
payload : { peerId, raiseHandState }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -274,6 +263,14 @@ export const setProducerTrack = (producerId, track) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setProducerScore = (producerId, score) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'SET_PRODUCER_SCORE',
|
||||||
|
payload : { producerId, score }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const setAudioInProgress = (flag) =>
|
export const setAudioInProgress = (flag) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
|
|
@ -306,35 +303,35 @@ export const addPeer = (peer) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removePeer = (peerName) =>
|
export const removePeer = (peerId) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'REMOVE_PEER',
|
type : 'REMOVE_PEER',
|
||||||
payload : { peerName }
|
payload : { peerId }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setPeerDisplayName = (displayName, peerName) =>
|
export const setPeerDisplayName = (displayName, peerId) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'SET_PEER_DISPLAY_NAME',
|
type : 'SET_PEER_DISPLAY_NAME',
|
||||||
payload : { displayName, peerName }
|
payload : { displayName, peerId }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addConsumer = (consumer, peerName) =>
|
export const addConsumer = (consumer, peerId) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'ADD_CONSUMER',
|
type : 'ADD_CONSUMER',
|
||||||
payload : { consumer, peerName }
|
payload : { consumer, peerId }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeConsumer = (consumerId, peerName) =>
|
export const removeConsumer = (consumerId, peerId) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'REMOVE_CONSUMER',
|
type : 'REMOVE_CONSUMER',
|
||||||
payload : { consumerId, peerName }
|
payload : { consumerId, peerId }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -354,11 +351,19 @@ export const setConsumerResumed = (consumerId, originator) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setConsumerEffectiveProfile = (consumerId, profile) =>
|
export const setConsumerCurrentLayers = (consumerId, spatialLayer, temporalLayer) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'SET_CONSUMER_EFFECTIVE_PROFILE',
|
type : 'SET_CONSUMER_CURRENT_LAYERS',
|
||||||
payload : { consumerId, profile }
|
payload : { consumerId, spatialLayer, temporalLayer }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setConsumerPreferredLayers = (consumerId, spatialLayer, temporalLayer) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'SET_CONSUMER_PREFERRED_LAYERS',
|
||||||
|
payload : { consumerId, spatialLayer, temporalLayer }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -370,11 +375,19 @@ export const setConsumerTrack = (consumerId, track) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setPeerVolume = (peerName, volume) =>
|
export const setConsumerScore = (consumerId, score) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'SET_CONSUMER_SCORE',
|
||||||
|
payload : { consumerId, score }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setPeerVolume = (peerId, volume) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'SET_PEER_VOLUME',
|
type : 'SET_PEER_VOLUME',
|
||||||
payload : { peerName, volume }
|
payload : { peerId, volume }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -482,11 +495,11 @@ export const dropMessages = () =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addFile = (file) =>
|
export const addFile = (peerId, magnetUri) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'ADD_FILE',
|
type : 'ADD_FILE',
|
||||||
payload : { file }
|
payload : { peerId, magnetUri }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -536,10 +549,10 @@ export const setPicture = (picture) =>
|
||||||
payload : { picture }
|
payload : { picture }
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setPeerPicture = (peerName, picture) =>
|
export const setPeerPicture = (peerId, picture) =>
|
||||||
({
|
({
|
||||||
type : 'SET_PEER_PICTURE',
|
type : 'SET_PEER_PICTURE',
|
||||||
payload : { peerName, picture }
|
payload : { peerId, picture }
|
||||||
});
|
});
|
||||||
|
|
||||||
export const loggedIn = () =>
|
export const loggedIn = () =>
|
||||||
|
|
@ -547,10 +560,15 @@ export const loggedIn = () =>
|
||||||
type : 'LOGGED_IN'
|
type : 'LOGGED_IN'
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setSelectedPeer = (selectedPeerName) =>
|
export const toggleJoined = () =>
|
||||||
|
({
|
||||||
|
type : 'TOGGLE_JOINED'
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setSelectedPeer = (selectedPeerId) =>
|
||||||
({
|
({
|
||||||
type : 'SET_SELECTED_PEER',
|
type : 'SET_SELECTED_PEER',
|
||||||
payload : { selectedPeerName }
|
payload : { selectedPeerId }
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setSpotlights = (spotlights) =>
|
export const setSpotlights = (spotlights) =>
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,27 @@
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { meProducersSelector } from '../Selectors';
|
import { meProducersSelector } from '../Selectors';
|
||||||
import { withRoomContext } from '../../RoomContext';
|
import { withRoomContext } from '../../RoomContext';
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
|
import useMediaQuery from '@material-ui/core/useMediaQuery';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import * as appPropTypes from '../appPropTypes';
|
import * as appPropTypes from '../appPropTypes';
|
||||||
import VideoView from '../VideoContainers/VideoView';
|
import VideoView from '../VideoContainers/VideoView';
|
||||||
import Volume from './Volume';
|
import Volume from './Volume';
|
||||||
|
import Fab from '@material-ui/core/Fab';
|
||||||
|
import Tooltip from '@material-ui/core/Tooltip';
|
||||||
|
import MicIcon from '@material-ui/icons/Mic';
|
||||||
|
import MicOffIcon from '@material-ui/icons/MicOff';
|
||||||
|
import VideoIcon from '@material-ui/icons/Videocam';
|
||||||
|
import VideoOffIcon from '@material-ui/icons/VideocamOff';
|
||||||
|
import ScreenIcon from '@material-ui/icons/ScreenShare';
|
||||||
|
import ScreenOffIcon from '@material-ui/icons/StopScreenShare';
|
||||||
|
|
||||||
const styles = () =>
|
const styles = (theme) =>
|
||||||
({
|
({
|
||||||
root :
|
root :
|
||||||
{
|
{
|
||||||
flexDirection : 'row',
|
|
||||||
margin : 6,
|
|
||||||
flex : '0 0 auto',
|
flex : '0 0 auto',
|
||||||
boxShadow : 'var(--peer-shadow)',
|
boxShadow : 'var(--peer-shadow)',
|
||||||
border : 'var(--peer-border)',
|
border : 'var(--peer-border)',
|
||||||
|
|
@ -23,38 +30,88 @@ const styles = () =>
|
||||||
backgroundPosition : 'bottom',
|
backgroundPosition : 'bottom',
|
||||||
backgroundSize : 'auto 85%',
|
backgroundSize : 'auto 85%',
|
||||||
backgroundRepeat : 'no-repeat',
|
backgroundRepeat : 'no-repeat',
|
||||||
|
'&.webcam' :
|
||||||
|
{
|
||||||
|
order : 1
|
||||||
|
},
|
||||||
|
'&.screen' :
|
||||||
|
{
|
||||||
|
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' :
|
'&.active-speaker' :
|
||||||
{
|
{
|
||||||
borderColor : 'var(--active-speaker-border-color)'
|
borderColor : 'var(--active-speaker-border-color)'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
fab :
|
||||||
|
{
|
||||||
|
margin : theme.spacing(1),
|
||||||
|
pointerEvents : 'auto'
|
||||||
|
},
|
||||||
viewContainer :
|
viewContainer :
|
||||||
{
|
{
|
||||||
position : 'relative',
|
position : 'relative',
|
||||||
'&.webcam' :
|
width : '100%',
|
||||||
|
height : '100%'
|
||||||
|
},
|
||||||
|
controls :
|
||||||
|
{
|
||||||
|
position : 'absolute',
|
||||||
|
width : '100%',
|
||||||
|
height : '100%',
|
||||||
|
backgroundColor : 'rgba(0, 0, 0, 0.3)',
|
||||||
|
display : 'flex',
|
||||||
|
flexDirection : 'column',
|
||||||
|
justifyContent : 'center',
|
||||||
|
alignItems : 'flex-end',
|
||||||
|
padding : theme.spacing(1),
|
||||||
|
zIndex : 21,
|
||||||
|
opacity : 0,
|
||||||
|
transition : 'opacity 0.3s',
|
||||||
|
touchAction : 'none',
|
||||||
|
pointerEvents : 'none',
|
||||||
|
'&.hover' :
|
||||||
{
|
{
|
||||||
order : 2
|
opacity : 1
|
||||||
},
|
},
|
||||||
'&.screen' :
|
'& p' :
|
||||||
{
|
{
|
||||||
order : 1
|
position : 'absolute',
|
||||||
|
float : 'left',
|
||||||
|
top : '50%',
|
||||||
|
left : '50%',
|
||||||
|
transform : 'translate(-50%, -50%)',
|
||||||
|
color : 'rgba(255, 255, 255, 0.5)',
|
||||||
|
fontSize : '7em',
|
||||||
|
margin : 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const Me = (props) =>
|
const Me = (props) =>
|
||||||
{
|
{
|
||||||
|
const [ hover, setHover ] = useState(false);
|
||||||
|
|
||||||
|
let touchTimeout = null;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
roomClient,
|
roomClient,
|
||||||
me,
|
me,
|
||||||
settings,
|
settings,
|
||||||
activeSpeaker,
|
activeSpeaker,
|
||||||
|
spacing,
|
||||||
style,
|
style,
|
||||||
|
smallButtons,
|
||||||
advancedMode,
|
advancedMode,
|
||||||
micProducer,
|
micProducer,
|
||||||
webcamProducer,
|
webcamProducer,
|
||||||
screenProducer,
|
screenProducer,
|
||||||
classes
|
classes,
|
||||||
|
theme
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const videoVisible = (
|
const videoVisible = (
|
||||||
|
|
@ -69,17 +126,225 @@ const Me = (props) =>
|
||||||
!screenProducer.remotelyPaused
|
!screenProducer.remotelyPaused
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let micState;
|
||||||
|
|
||||||
|
let micTip;
|
||||||
|
|
||||||
|
if (!me.canSendMic)
|
||||||
|
{
|
||||||
|
micState = 'unsupported';
|
||||||
|
micTip = 'Audio unsupported';
|
||||||
|
}
|
||||||
|
else if (!micProducer)
|
||||||
|
{
|
||||||
|
micState = 'off';
|
||||||
|
micTip = 'Activate audio';
|
||||||
|
}
|
||||||
|
else if (!micProducer.locallyPaused && !micProducer.remotelyPaused)
|
||||||
|
{
|
||||||
|
micState = 'on';
|
||||||
|
micTip = 'Mute audio';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
micState = 'muted';
|
||||||
|
micTip = 'Unmute audio';
|
||||||
|
}
|
||||||
|
|
||||||
|
let webcamState;
|
||||||
|
|
||||||
|
let webcamTip;
|
||||||
|
|
||||||
|
if (!me.canSendWebcam)
|
||||||
|
{
|
||||||
|
webcamState = 'unsupported';
|
||||||
|
webcamTip = 'Video unsupported';
|
||||||
|
}
|
||||||
|
else if (webcamProducer)
|
||||||
|
{
|
||||||
|
webcamState = 'on';
|
||||||
|
webcamTip = 'Stop video';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
webcamState = 'off';
|
||||||
|
webcamTip = 'Start video';
|
||||||
|
}
|
||||||
|
|
||||||
|
let screenState;
|
||||||
|
|
||||||
|
let screenTip;
|
||||||
|
|
||||||
|
if (!me.canShareScreen)
|
||||||
|
{
|
||||||
|
screenState = 'unsupported';
|
||||||
|
screenTip = 'Screen sharing not supported';
|
||||||
|
}
|
||||||
|
else if (screenProducer)
|
||||||
|
{
|
||||||
|
screenState = 'on';
|
||||||
|
screenTip = 'Stop screen sharing';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
screenState = 'off';
|
||||||
|
screenTip = 'Start screen sharing';
|
||||||
|
}
|
||||||
|
|
||||||
|
const spacingStyle =
|
||||||
|
{
|
||||||
|
'margin' : spacing
|
||||||
|
};
|
||||||
|
|
||||||
|
const smallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
classnames(
|
classnames(
|
||||||
classes.root,
|
classes.root,
|
||||||
|
'webcam',
|
||||||
|
hover ? 'hover' : null,
|
||||||
activeSpeaker ? 'active-speaker' : null
|
activeSpeaker ? 'active-speaker' : null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
onMouseOver={() => setHover(true)}
|
||||||
|
onMouseOut={() => setHover(false)}
|
||||||
|
onTouchStart={() =>
|
||||||
|
{
|
||||||
|
if (touchTimeout)
|
||||||
|
clearTimeout(touchTimeout);
|
||||||
|
|
||||||
|
setHover(true);
|
||||||
|
}}
|
||||||
|
onTouchEnd={() =>
|
||||||
|
{
|
||||||
|
if (touchTimeout)
|
||||||
|
clearTimeout(touchTimeout);
|
||||||
|
|
||||||
|
touchTimeout = setTimeout(() =>
|
||||||
|
{
|
||||||
|
setHover(false);
|
||||||
|
}, 2000);
|
||||||
|
}}
|
||||||
|
style={spacingStyle}
|
||||||
>
|
>
|
||||||
<div className={classnames(classes.viewContainer, 'webcam')} style={style}>
|
<div className={classnames(classes.viewContainer)} style={style}>
|
||||||
|
<div
|
||||||
|
className={classnames(classes.controls, hover ? 'hover' : null)}
|
||||||
|
onMouseOver={() => setHover(true)}
|
||||||
|
onMouseOut={() => setHover(false)}
|
||||||
|
onTouchStart={() =>
|
||||||
|
{
|
||||||
|
if (touchTimeout)
|
||||||
|
clearTimeout(touchTimeout);
|
||||||
|
|
||||||
|
setHover(true);
|
||||||
|
}}
|
||||||
|
onTouchEnd={() =>
|
||||||
|
{
|
||||||
|
if (touchTimeout)
|
||||||
|
clearTimeout(touchTimeout);
|
||||||
|
|
||||||
|
touchTimeout = setTimeout(() =>
|
||||||
|
{
|
||||||
|
setHover(false);
|
||||||
|
}, 2000);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>ME</p>
|
||||||
|
<Tooltip title={micTip} placement={smallScreen ? 'top' : 'right'}>
|
||||||
|
<div>
|
||||||
|
<Fab
|
||||||
|
aria-label='Mute mic'
|
||||||
|
className={classes.fab}
|
||||||
|
disabled={!me.canSendMic || me.audioInProgress}
|
||||||
|
color={micState === 'on' ? 'default' : 'secondary'}
|
||||||
|
size={smallButtons ? 'small' : 'large'}
|
||||||
|
onClick={() =>
|
||||||
|
{
|
||||||
|
if (micState === 'off')
|
||||||
|
roomClient.enableMic();
|
||||||
|
else if (micState === 'on')
|
||||||
|
roomClient.muteMic();
|
||||||
|
else
|
||||||
|
roomClient.unmuteMic();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ micState === 'on' ?
|
||||||
|
<MicIcon />
|
||||||
|
:
|
||||||
|
<MicOffIcon />
|
||||||
|
}
|
||||||
|
</Fab>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={webcamTip} placement={smallScreen ? 'top' : 'right'}>
|
||||||
|
<div>
|
||||||
|
<Fab
|
||||||
|
aria-label='Mute video'
|
||||||
|
className={classes.fab}
|
||||||
|
disabled={!me.canSendWebcam || me.webcamInProgress}
|
||||||
|
color={webcamState === 'on' ? 'default' : 'secondary'}
|
||||||
|
size={smallButtons ? 'small' : 'large'}
|
||||||
|
onClick={() =>
|
||||||
|
{
|
||||||
|
webcamState === 'on' ?
|
||||||
|
roomClient.disableWebcam() :
|
||||||
|
roomClient.enableWebcam();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ webcamState === 'on' ?
|
||||||
|
<VideoIcon />
|
||||||
|
:
|
||||||
|
<VideoOffIcon />
|
||||||
|
}
|
||||||
|
</Fab>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={screenTip} placement={smallScreen ? 'top' : 'right'}>
|
||||||
|
<div>
|
||||||
|
<Fab
|
||||||
|
aria-label='Share screen'
|
||||||
|
className={classes.fab}
|
||||||
|
disabled={!me.canShareScreen || me.screenShareInProgress}
|
||||||
|
color={screenState === 'on' ? 'primary' : 'default'}
|
||||||
|
size={smallButtons ? 'small' : 'large'}
|
||||||
|
onClick={() =>
|
||||||
|
{
|
||||||
|
switch (screenState)
|
||||||
|
{
|
||||||
|
case 'on':
|
||||||
|
{
|
||||||
|
roomClient.disableScreenSharing();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'off':
|
||||||
|
{
|
||||||
|
roomClient.enableScreenSharing();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ screenState === 'on' || screenState === 'unsupported' ?
|
||||||
|
<ScreenOffIcon/>
|
||||||
|
:null
|
||||||
|
}
|
||||||
|
{ screenState === 'off' ?
|
||||||
|
<ScreenIcon/>
|
||||||
|
:null
|
||||||
|
}
|
||||||
|
</Fab>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
<VideoView
|
<VideoView
|
||||||
isMe
|
isMe
|
||||||
advancedMode={advancedMode}
|
advancedMode={advancedMode}
|
||||||
|
|
@ -95,15 +360,64 @@ const Me = (props) =>
|
||||||
roomClient.changeDisplayName(displayName);
|
roomClient.changeDisplayName(displayName);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Volume name={me.name} />
|
<Volume id={me.id} />
|
||||||
</VideoView>
|
</VideoView>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{ screenProducer ?
|
{ screenProducer ?
|
||||||
<div className={classes.root}>
|
<div
|
||||||
<div className={classnames(classes.viewContainer, 'screen')} style={style}>
|
className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
|
||||||
|
onMouseOver={() => setHover(true)}
|
||||||
|
onMouseOut={() => setHover(false)}
|
||||||
|
onTouchStart={() =>
|
||||||
|
{
|
||||||
|
if (touchTimeout)
|
||||||
|
clearTimeout(touchTimeout);
|
||||||
|
|
||||||
|
setHover(true);
|
||||||
|
}}
|
||||||
|
onTouchEnd={() =>
|
||||||
|
{
|
||||||
|
if (touchTimeout)
|
||||||
|
clearTimeout(touchTimeout);
|
||||||
|
|
||||||
|
touchTimeout = setTimeout(() =>
|
||||||
|
{
|
||||||
|
setHover(false);
|
||||||
|
}, 2000);
|
||||||
|
}}
|
||||||
|
style={spacingStyle}
|
||||||
|
>
|
||||||
|
<div className={classnames(classes.viewContainer)} style={style}>
|
||||||
|
<div
|
||||||
|
className={classnames(classes.controls, hover ? 'hover' : null)}
|
||||||
|
onMouseOver={() => setHover(true)}
|
||||||
|
onMouseOut={() => setHover(false)}
|
||||||
|
onTouchStart={() =>
|
||||||
|
{
|
||||||
|
if (touchTimeout)
|
||||||
|
clearTimeout(touchTimeout);
|
||||||
|
|
||||||
|
setHover(true);
|
||||||
|
}}
|
||||||
|
onTouchEnd={() =>
|
||||||
|
{
|
||||||
|
|
||||||
|
if (touchTimeout)
|
||||||
|
clearTimeout(touchTimeout);
|
||||||
|
|
||||||
|
touchTimeout = setTimeout(() =>
|
||||||
|
{
|
||||||
|
setHover(false);
|
||||||
|
}, 2000);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>ME</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<VideoView
|
<VideoView
|
||||||
isMe
|
isMe
|
||||||
|
isScreen
|
||||||
advancedMode={advancedMode}
|
advancedMode={advancedMode}
|
||||||
videoContain
|
videoContain
|
||||||
videoTrack={screenProducer ? screenProducer.track : null}
|
videoTrack={screenProducer ? screenProducer.track : null}
|
||||||
|
|
@ -128,8 +442,11 @@ Me.propTypes =
|
||||||
micProducer : appPropTypes.Producer,
|
micProducer : appPropTypes.Producer,
|
||||||
webcamProducer : appPropTypes.Producer,
|
webcamProducer : appPropTypes.Producer,
|
||||||
screenProducer : appPropTypes.Producer,
|
screenProducer : appPropTypes.Producer,
|
||||||
|
spacing : PropTypes.number,
|
||||||
style : PropTypes.object,
|
style : PropTypes.object,
|
||||||
classes : PropTypes.object.isRequired
|
smallButtons : PropTypes.bool,
|
||||||
|
classes : PropTypes.object.isRequired,
|
||||||
|
theme : PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
|
|
@ -138,7 +455,7 @@ const mapStateToProps = (state) =>
|
||||||
me : state.me,
|
me : state.me,
|
||||||
...meProducersSelector(state),
|
...meProducersSelector(state),
|
||||||
settings : state.settings,
|
settings : state.settings,
|
||||||
activeSpeaker : state.me.name === state.room.activeSpeakerName
|
activeSpeaker : state.me.id === state.room.activeSpeakerId
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -153,8 +470,8 @@ export default withRoomContext(connect(
|
||||||
prev.me === next.me &&
|
prev.me === next.me &&
|
||||||
prev.producers === next.producers &&
|
prev.producers === next.producers &&
|
||||||
prev.settings === next.settings &&
|
prev.settings === next.settings &&
|
||||||
prev.room.activeSpeakerName === next.room.activeSpeakerName
|
prev.room.activeSpeakerId === next.room.activeSpeakerId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)(withStyles(styles)(Me)));
|
)(withStyles(styles, { withTheme: true })(Me)));
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import classnames from 'classnames';
|
||||||
import * as appPropTypes from '../appPropTypes';
|
import * as appPropTypes from '../appPropTypes';
|
||||||
import { withRoomContext } from '../../RoomContext';
|
import { withRoomContext } from '../../RoomContext';
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import { unstable_useMediaQuery as useMediaQuery } from '@material-ui/core/useMediaQuery';
|
import useMediaQuery from '@material-ui/core/useMediaQuery';
|
||||||
import * as stateActions from '../../actions/stateActions';
|
import * as stateActions from '../../actions/stateActions';
|
||||||
import VideoView from '../VideoContainers/VideoView';
|
import VideoView from '../VideoContainers/VideoView';
|
||||||
import Fab from '@material-ui/core/Fab';
|
import Fab from '@material-ui/core/Fab';
|
||||||
|
|
@ -21,7 +21,6 @@ const styles = (theme) =>
|
||||||
root :
|
root :
|
||||||
{
|
{
|
||||||
flex : '0 0 auto',
|
flex : '0 0 auto',
|
||||||
margin : 6,
|
|
||||||
boxShadow : 'var(--peer-shadow)',
|
boxShadow : 'var(--peer-shadow)',
|
||||||
border : 'var(--peer-border)',
|
border : 'var(--peer-border)',
|
||||||
touchAction : 'none',
|
touchAction : 'none',
|
||||||
|
|
@ -32,11 +31,11 @@ const styles = (theme) =>
|
||||||
backgroundRepeat : 'no-repeat',
|
backgroundRepeat : 'no-repeat',
|
||||||
'&.webcam' :
|
'&.webcam' :
|
||||||
{
|
{
|
||||||
order : 2
|
order : 4
|
||||||
},
|
},
|
||||||
'&.screen' :
|
'&.screen' :
|
||||||
{
|
{
|
||||||
order : 1
|
order : 3
|
||||||
},
|
},
|
||||||
'&.hover' :
|
'&.hover' :
|
||||||
{
|
{
|
||||||
|
|
@ -49,19 +48,13 @@ const styles = (theme) =>
|
||||||
},
|
},
|
||||||
fab :
|
fab :
|
||||||
{
|
{
|
||||||
margin : theme.spacing.unit
|
margin : theme.spacing(1)
|
||||||
},
|
},
|
||||||
viewContainer :
|
viewContainer :
|
||||||
{
|
{
|
||||||
position : 'relative',
|
position : 'relative',
|
||||||
'&.webcam' :
|
width : '100%',
|
||||||
{
|
height : '100%'
|
||||||
order : 2
|
|
||||||
},
|
|
||||||
'&.screen' :
|
|
||||||
{
|
|
||||||
order : 1
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
controls :
|
controls :
|
||||||
{
|
{
|
||||||
|
|
@ -73,8 +66,8 @@ const styles = (theme) =>
|
||||||
flexDirection : 'column',
|
flexDirection : 'column',
|
||||||
justifyContent : 'center',
|
justifyContent : 'center',
|
||||||
alignItems : 'flex-end',
|
alignItems : 'flex-end',
|
||||||
padding : '0.4vmin',
|
padding : theme.spacing(1),
|
||||||
zIndex : 20,
|
zIndex : 21,
|
||||||
opacity : 0,
|
opacity : 0,
|
||||||
transition : 'opacity 0.3s',
|
transition : 'opacity 0.3s',
|
||||||
touchAction : 'none',
|
touchAction : 'none',
|
||||||
|
|
@ -92,8 +85,8 @@ const styles = (theme) =>
|
||||||
display : 'flex',
|
display : 'flex',
|
||||||
justifyContent : 'center',
|
justifyContent : 'center',
|
||||||
alignItems : 'center',
|
alignItems : 'center',
|
||||||
padding : '0.4vmin',
|
padding : theme.spacing(1),
|
||||||
zIndex : 21,
|
zIndex : 20,
|
||||||
'& p' :
|
'& p' :
|
||||||
{
|
{
|
||||||
padding : '6px 12px',
|
padding : '6px 12px',
|
||||||
|
|
@ -109,15 +102,9 @@ const styles = (theme) =>
|
||||||
const Peer = (props) =>
|
const Peer = (props) =>
|
||||||
{
|
{
|
||||||
const [ hover, setHover ] = useState(false);
|
const [ hover, setHover ] = useState(false);
|
||||||
const [ webcamHover, setWebcamHover ] = useState(false);
|
|
||||||
const [ screenHover, setScreenHover ] = useState(false);
|
|
||||||
|
|
||||||
let touchTimeout = null;
|
let touchTimeout = null;
|
||||||
|
|
||||||
let touchWebcamTimeout = null;
|
|
||||||
|
|
||||||
let touchScreenTimeout = null;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
roomClient,
|
roomClient,
|
||||||
advancedMode,
|
advancedMode,
|
||||||
|
|
@ -128,7 +115,9 @@ const Peer = (props) =>
|
||||||
screenConsumer,
|
screenConsumer,
|
||||||
toggleConsumerFullscreen,
|
toggleConsumerFullscreen,
|
||||||
toggleConsumerWindow,
|
toggleConsumerWindow,
|
||||||
|
spacing,
|
||||||
style,
|
style,
|
||||||
|
smallButtons,
|
||||||
windowConsumer,
|
windowConsumer,
|
||||||
classes,
|
classes,
|
||||||
theme
|
theme
|
||||||
|
|
@ -164,6 +153,12 @@ const Peer = (props) =>
|
||||||
|
|
||||||
const smallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
const smallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
|
const rootStyle =
|
||||||
|
{
|
||||||
|
'margin' : spacing,
|
||||||
|
...style
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div
|
<div
|
||||||
|
|
@ -194,15 +189,9 @@ const Peer = (props) =>
|
||||||
setHover(false);
|
setHover(false);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}}
|
}}
|
||||||
|
style={rootStyle}
|
||||||
>
|
>
|
||||||
<div className={classnames(classes.viewContainer)} style={style}>
|
<div className={classnames(classes.viewContainer)}>
|
||||||
{ videoVisible && !webcamConsumer.supported ?
|
|
||||||
<div className={classes.videoInfo}>
|
|
||||||
<p>incompatible video</p>
|
|
||||||
</div>
|
|
||||||
:null
|
|
||||||
}
|
|
||||||
|
|
||||||
{ !videoVisible ?
|
{ !videoVisible ?
|
||||||
<div className={classes.videoInfo}>
|
<div className={classes.videoInfo}>
|
||||||
<p>this video is paused</p>
|
<p>this video is paused</p>
|
||||||
|
|
@ -210,79 +199,80 @@ const Peer = (props) =>
|
||||||
:null
|
:null
|
||||||
}
|
}
|
||||||
|
|
||||||
{ videoVisible && webcamConsumer.supported ?
|
<div
|
||||||
<div
|
className={classnames(classes.controls, hover ? 'hover' : null)}
|
||||||
className={classnames(classes.controls, webcamHover ? 'hover' : null)}
|
onMouseOver={() => setHover(true)}
|
||||||
onMouseOver={() => setWebcamHover(true)}
|
onMouseOut={() => setHover(false)}
|
||||||
onMouseOut={() => setWebcamHover(false)}
|
onTouchStart={() =>
|
||||||
onTouchStart={() =>
|
{
|
||||||
|
if (touchTimeout)
|
||||||
|
clearTimeout(touchTimeout);
|
||||||
|
|
||||||
|
setHover(true);
|
||||||
|
}}
|
||||||
|
onTouchEnd={() =>
|
||||||
|
{
|
||||||
|
if (touchTimeout)
|
||||||
|
clearTimeout(touchTimeout);
|
||||||
|
|
||||||
|
touchTimeout = setTimeout(() =>
|
||||||
{
|
{
|
||||||
if (touchWebcamTimeout)
|
setHover(false);
|
||||||
clearTimeout(touchWebcamTimeout);
|
}, 2000);
|
||||||
|
}}
|
||||||
setWebcamHover(true);
|
>
|
||||||
}}
|
<Fab
|
||||||
onTouchEnd={() =>
|
aria-label='Mute mic'
|
||||||
|
className={classes.fab}
|
||||||
|
disabled={!micConsumer}
|
||||||
|
color={micEnabled ? 'default' : 'secondary'}
|
||||||
|
size={smallButtons ? 'small' : 'large'}
|
||||||
|
onClick={() =>
|
||||||
{
|
{
|
||||||
if (touchWebcamTimeout)
|
micEnabled ?
|
||||||
clearTimeout(touchWebcamTimeout);
|
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
|
||||||
|
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
|
||||||
touchWebcamTimeout = setTimeout(() =>
|
|
||||||
{
|
|
||||||
setWebcamHover(false);
|
|
||||||
}, 2000);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Fab
|
{ micEnabled ?
|
||||||
aria-label='Mute mic'
|
<MicIcon />
|
||||||
className={classes.fab}
|
:
|
||||||
color={micEnabled ? 'default' : 'secondary'}
|
<MicOffIcon />
|
||||||
onClick={() =>
|
|
||||||
{
|
|
||||||
micEnabled ?
|
|
||||||
roomClient.modifyPeerConsumer(peer.name, 'mic', true) :
|
|
||||||
roomClient.modifyPeerConsumer(peer.name, 'mic', false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{ micEnabled ?
|
|
||||||
<MicIcon />
|
|
||||||
:
|
|
||||||
<MicOffIcon />
|
|
||||||
}
|
|
||||||
</Fab>
|
|
||||||
|
|
||||||
{ !smallScreen ?
|
|
||||||
<Fab
|
|
||||||
aria-label='New window'
|
|
||||||
className={classes.fab}
|
|
||||||
disabled={
|
|
||||||
!videoVisible ||
|
|
||||||
(windowConsumer === webcamConsumer.id)
|
|
||||||
}
|
|
||||||
onClick={() =>
|
|
||||||
{
|
|
||||||
toggleConsumerWindow(webcamConsumer);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<NewWindowIcon />
|
|
||||||
</Fab>
|
|
||||||
:null
|
|
||||||
}
|
}
|
||||||
|
</Fab>
|
||||||
|
|
||||||
|
{ !smallScreen ?
|
||||||
<Fab
|
<Fab
|
||||||
aria-label='Fullscreen'
|
aria-label='New window'
|
||||||
className={classes.fab}
|
className={classes.fab}
|
||||||
disabled={!videoVisible}
|
disabled={
|
||||||
|
!videoVisible ||
|
||||||
|
(windowConsumer === webcamConsumer.id)
|
||||||
|
}
|
||||||
|
size={smallButtons ? 'small' : 'large'}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
{
|
{
|
||||||
toggleConsumerFullscreen(webcamConsumer);
|
toggleConsumerWindow(webcamConsumer);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FullScreenIcon />
|
<NewWindowIcon />
|
||||||
</Fab>
|
</Fab>
|
||||||
</div>
|
:null
|
||||||
:null
|
}
|
||||||
}
|
|
||||||
|
<Fab
|
||||||
|
aria-label='Fullscreen'
|
||||||
|
className={classes.fab}
|
||||||
|
disabled={!videoVisible}
|
||||||
|
size={smallButtons ? 'small' : 'large'}
|
||||||
|
onClick={() =>
|
||||||
|
{
|
||||||
|
toggleConsumerFullscreen(webcamConsumer);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FullScreenIcon />
|
||||||
|
</Fab>
|
||||||
|
</div>
|
||||||
|
|
||||||
<VideoView
|
<VideoView
|
||||||
advancedMode={advancedMode}
|
advancedMode={advancedMode}
|
||||||
|
|
@ -295,7 +285,7 @@ const Peer = (props) =>
|
||||||
audioCodec={micConsumer ? micConsumer.codec : null}
|
audioCodec={micConsumer ? micConsumer.codec : null}
|
||||||
videoCodec={webcamConsumer ? webcamConsumer.codec : null}
|
videoCodec={webcamConsumer ? webcamConsumer.codec : null}
|
||||||
>
|
>
|
||||||
<Volume name={peer.name} />
|
<Volume id={peer.id} />
|
||||||
</VideoView>
|
</VideoView>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -322,43 +312,37 @@ const Peer = (props) =>
|
||||||
setHover(false);
|
setHover(false);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}}
|
}}
|
||||||
|
style={rootStyle}
|
||||||
>
|
>
|
||||||
{ screenVisible && !screenConsumer.supported ?
|
|
||||||
<div className={classes.videoInfo} style={style}>
|
|
||||||
<p>incompatible video</p>
|
|
||||||
</div>
|
|
||||||
:null
|
|
||||||
}
|
|
||||||
|
|
||||||
{ !screenVisible ?
|
{ !screenVisible ?
|
||||||
<div className={classes.videoInfo} style={style}>
|
<div className={classes.videoInfo}>
|
||||||
<p>this video is paused</p>
|
<p>this video is paused</p>
|
||||||
</div>
|
</div>
|
||||||
:null
|
:null
|
||||||
}
|
}
|
||||||
|
|
||||||
{ screenVisible && screenConsumer.supported ?
|
{ screenVisible ?
|
||||||
<div className={classnames(classes.viewContainer)} style={style}>
|
<div className={classnames(classes.viewContainer)}>
|
||||||
<div
|
<div
|
||||||
className={classnames(classes.controls, screenHover ? 'hover' : null)}
|
className={classnames(classes.controls, hover ? 'hover' : null)}
|
||||||
onMouseOver={() => setScreenHover(true)}
|
onMouseOver={() => setHover(true)}
|
||||||
onMouseOut={() => setScreenHover(false)}
|
onMouseOut={() => setHover(false)}
|
||||||
onTouchStart={() =>
|
onTouchStart={() =>
|
||||||
{
|
{
|
||||||
if (touchScreenTimeout)
|
if (touchTimeout)
|
||||||
clearTimeout(touchScreenTimeout);
|
clearTimeout(touchTimeout);
|
||||||
|
|
||||||
setScreenHover(true);
|
setHover(true);
|
||||||
}}
|
}}
|
||||||
onTouchEnd={() =>
|
onTouchEnd={() =>
|
||||||
{
|
{
|
||||||
|
|
||||||
if (touchScreenTimeout)
|
if (touchTimeout)
|
||||||
clearTimeout(touchScreenTimeout);
|
clearTimeout(touchTimeout);
|
||||||
|
|
||||||
touchScreenTimeout = setTimeout(() =>
|
touchTimeout = setTimeout(() =>
|
||||||
{
|
{
|
||||||
setScreenHover(false);
|
setHover(false);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -370,6 +354,7 @@ const Peer = (props) =>
|
||||||
!screenVisible ||
|
!screenVisible ||
|
||||||
(windowConsumer === screenConsumer.id)
|
(windowConsumer === screenConsumer.id)
|
||||||
}
|
}
|
||||||
|
size={smallButtons ? 'small' : 'large'}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
{
|
{
|
||||||
toggleConsumerWindow(screenConsumer);
|
toggleConsumerWindow(screenConsumer);
|
||||||
|
|
@ -384,6 +369,7 @@ const Peer = (props) =>
|
||||||
aria-label='Fullscreen'
|
aria-label='Fullscreen'
|
||||||
className={classes.fab}
|
className={classes.fab}
|
||||||
disabled={!screenVisible}
|
disabled={!screenVisible}
|
||||||
|
size={smallButtons ? 'small' : 'large'}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
{
|
{
|
||||||
toggleConsumerFullscreen(screenConsumer);
|
toggleConsumerFullscreen(screenConsumer);
|
||||||
|
|
@ -418,9 +404,11 @@ Peer.propTypes =
|
||||||
micConsumer : appPropTypes.Consumer,
|
micConsumer : appPropTypes.Consumer,
|
||||||
webcamConsumer : appPropTypes.Consumer,
|
webcamConsumer : appPropTypes.Consumer,
|
||||||
screenConsumer : appPropTypes.Consumer,
|
screenConsumer : appPropTypes.Consumer,
|
||||||
windowConsumer : PropTypes.number,
|
windowConsumer : PropTypes.string,
|
||||||
activeSpeaker : PropTypes.bool,
|
activeSpeaker : PropTypes.bool,
|
||||||
|
spacing : PropTypes.number,
|
||||||
style : PropTypes.object,
|
style : PropTypes.object,
|
||||||
|
smallButtons : PropTypes.bool,
|
||||||
toggleConsumerFullscreen : PropTypes.func.isRequired,
|
toggleConsumerFullscreen : PropTypes.func.isRequired,
|
||||||
toggleConsumerWindow : PropTypes.func.isRequired,
|
toggleConsumerWindow : PropTypes.func.isRequired,
|
||||||
classes : PropTypes.object.isRequired,
|
classes : PropTypes.object.isRequired,
|
||||||
|
|
@ -434,10 +422,10 @@ const makeMapStateToProps = (initialState, props) =>
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
peer : state.peers[props.name],
|
peer : state.peers[props.id],
|
||||||
...getPeerConsumers(state, props),
|
...getPeerConsumers(state, props),
|
||||||
windowConsumer : state.room.windowConsumer,
|
windowConsumer : state.room.windowConsumer,
|
||||||
activeSpeaker : props.name === state.room.activeSpeakerName
|
activeSpeaker : props.id === state.room.activeSpeakerId
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -470,7 +458,7 @@ export default withRoomContext(connect(
|
||||||
return (
|
return (
|
||||||
prev.peers === next.peers &&
|
prev.peers === next.peers &&
|
||||||
prev.consumers === next.consumers &&
|
prev.consumers === next.consumers &&
|
||||||
prev.room.activeSpeakerName === next.room.activeSpeakerName &&
|
prev.room.activeSpeakerId === next.room.activeSpeakerId &&
|
||||||
prev.room.windowConsumer === next.room.windowConsumer
|
prev.room.windowConsumer === next.room.windowConsumer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { makePeerConsumerSelector } from '../Selectors';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import * as appPropTypes from '../appPropTypes';
|
||||||
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
|
import VideoView from '../VideoContainers/VideoView';
|
||||||
|
import Volume from './Volume';
|
||||||
|
|
||||||
|
const styles = (theme) =>
|
||||||
|
({
|
||||||
|
root :
|
||||||
|
{
|
||||||
|
flex : '0 0 auto',
|
||||||
|
boxShadow : 'var(--peer-shadow)',
|
||||||
|
border : 'var(--peer-border)',
|
||||||
|
touchAction : 'none',
|
||||||
|
backgroundColor : 'var(--peer-bg-color)',
|
||||||
|
backgroundImage : 'var(--peer-empty-avatar)',
|
||||||
|
backgroundPosition : 'bottom',
|
||||||
|
backgroundSize : 'auto 85%',
|
||||||
|
backgroundRepeat : 'no-repeat',
|
||||||
|
'&.webcam' :
|
||||||
|
{
|
||||||
|
order : 2
|
||||||
|
},
|
||||||
|
'&.screen' :
|
||||||
|
{
|
||||||
|
order : 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
viewContainer :
|
||||||
|
{
|
||||||
|
position : 'relative',
|
||||||
|
'&.webcam' :
|
||||||
|
{
|
||||||
|
order : 2
|
||||||
|
},
|
||||||
|
'&.screen' :
|
||||||
|
{
|
||||||
|
order : 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
videoInfo :
|
||||||
|
{
|
||||||
|
position : 'absolute',
|
||||||
|
width : '100%',
|
||||||
|
height : '100%',
|
||||||
|
backgroundColor : 'rgba(0, 0, 0, 0.3)',
|
||||||
|
display : 'flex',
|
||||||
|
justifyContent : 'center',
|
||||||
|
alignItems : 'center',
|
||||||
|
padding : theme.spacing(1),
|
||||||
|
zIndex : 21,
|
||||||
|
'& p' :
|
||||||
|
{
|
||||||
|
padding : '6px 12px',
|
||||||
|
borderRadius : 6,
|
||||||
|
userSelect : 'none',
|
||||||
|
pointerEvents : 'none',
|
||||||
|
fontSize : 20,
|
||||||
|
color : 'rgba(255, 255, 255, 0.55)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const SpeakerPeer = (props) =>
|
||||||
|
{
|
||||||
|
const {
|
||||||
|
advancedMode,
|
||||||
|
peer,
|
||||||
|
micConsumer,
|
||||||
|
webcamConsumer,
|
||||||
|
screenConsumer,
|
||||||
|
spacing,
|
||||||
|
style,
|
||||||
|
classes
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const videoVisible = (
|
||||||
|
Boolean(webcamConsumer) &&
|
||||||
|
!webcamConsumer.locallyPaused &&
|
||||||
|
!webcamConsumer.remotelyPaused
|
||||||
|
);
|
||||||
|
|
||||||
|
const screenVisible = (
|
||||||
|
Boolean(screenConsumer) &&
|
||||||
|
!screenConsumer.locallyPaused &&
|
||||||
|
!screenConsumer.remotelyPaused
|
||||||
|
);
|
||||||
|
|
||||||
|
let videoProfile;
|
||||||
|
|
||||||
|
if (webcamConsumer)
|
||||||
|
videoProfile = webcamConsumer.profile;
|
||||||
|
|
||||||
|
let screenProfile;
|
||||||
|
|
||||||
|
if (screenConsumer)
|
||||||
|
screenProfile = screenConsumer.profile;
|
||||||
|
|
||||||
|
const spacingStyle =
|
||||||
|
{
|
||||||
|
'margin' : spacing
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
classnames(
|
||||||
|
classes.root,
|
||||||
|
'webcam'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
style={spacingStyle}
|
||||||
|
>
|
||||||
|
<div className={classnames(classes.viewContainer)} style={style}>
|
||||||
|
{ !videoVisible ?
|
||||||
|
<div className={classes.videoInfo}>
|
||||||
|
<p>this video is paused</p>
|
||||||
|
</div>
|
||||||
|
:null
|
||||||
|
}
|
||||||
|
|
||||||
|
<VideoView
|
||||||
|
advancedMode={advancedMode}
|
||||||
|
peer={peer}
|
||||||
|
displayName={peer.displayName}
|
||||||
|
showPeerInfo
|
||||||
|
videoTrack={webcamConsumer ? webcamConsumer.track : null}
|
||||||
|
videoVisible={videoVisible}
|
||||||
|
videoProfile={videoProfile}
|
||||||
|
audioCodec={micConsumer ? micConsumer.codec : null}
|
||||||
|
videoCodec={webcamConsumer ? webcamConsumer.codec : null}
|
||||||
|
>
|
||||||
|
<Volume id={peer.id} />
|
||||||
|
</VideoView>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ screenConsumer ?
|
||||||
|
<div
|
||||||
|
className={classnames(classes.root, 'screen')}
|
||||||
|
>
|
||||||
|
{ !screenVisible ?
|
||||||
|
<div className={classes.videoInfo} style={style}>
|
||||||
|
<p>this video is paused</p>
|
||||||
|
</div>
|
||||||
|
:null
|
||||||
|
}
|
||||||
|
|
||||||
|
{ screenVisible ?
|
||||||
|
<div className={classnames(classes.viewContainer)} style={style}>
|
||||||
|
<VideoView
|
||||||
|
advancedMode={advancedMode}
|
||||||
|
videoContain
|
||||||
|
videoTrack={screenConsumer ? screenConsumer.track : null}
|
||||||
|
videoVisible={screenVisible}
|
||||||
|
videoProfile={screenProfile}
|
||||||
|
videoCodec={screenConsumer ? screenConsumer.codec : null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
:null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
:null
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SpeakerPeer.propTypes =
|
||||||
|
{
|
||||||
|
advancedMode : PropTypes.bool,
|
||||||
|
peer : appPropTypes.Peer,
|
||||||
|
micConsumer : appPropTypes.Consumer,
|
||||||
|
webcamConsumer : appPropTypes.Consumer,
|
||||||
|
screenConsumer : appPropTypes.Consumer,
|
||||||
|
spacing : PropTypes.number,
|
||||||
|
style : PropTypes.object,
|
||||||
|
classes : PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) =>
|
||||||
|
{
|
||||||
|
const getPeerConsumers = makePeerConsumerSelector();
|
||||||
|
|
||||||
|
return {
|
||||||
|
peer : state.peers[props.id],
|
||||||
|
...getPeerConsumers(state, props)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
areStatesEqual : (next, prev) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
prev.peers === next.peers &&
|
||||||
|
prev.consumers === next.consumers &&
|
||||||
|
prev.room.activeSpeakerId === next.room.activeSpeakerId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)(withStyles(styles, { withTheme: true })(SpeakerPeer));
|
||||||
|
|
@ -150,7 +150,7 @@ const makeMapStateToProps = (initialState, props) =>
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
volume : state.peerVolumes[props.name]
|
volume : state.peerVolumes[props.id]
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,332 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { meProducersSelector } from '../Selectors';
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
|
||||||
import { unstable_useMediaQuery as useMediaQuery } from '@material-ui/core/useMediaQuery';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import * as appPropTypes from '../appPropTypes';
|
|
||||||
import { withRoomContext } from '../../RoomContext';
|
|
||||||
import Fab from '@material-ui/core/Fab';
|
|
||||||
import Tooltip from '@material-ui/core/Tooltip';
|
|
||||||
import MicIcon from '@material-ui/icons/Mic';
|
|
||||||
import MicOffIcon from '@material-ui/icons/MicOff';
|
|
||||||
import VideoIcon from '@material-ui/icons/Videocam';
|
|
||||||
import VideoOffIcon from '@material-ui/icons/VideocamOff';
|
|
||||||
import ScreenIcon from '@material-ui/icons/ScreenShare';
|
|
||||||
import ScreenOffIcon from '@material-ui/icons/StopScreenShare';
|
|
||||||
import ExtensionIcon from '@material-ui/icons/Extension';
|
|
||||||
import LockIcon from '@material-ui/icons/Lock';
|
|
||||||
import LockOpenIcon from '@material-ui/icons/LockOpen';
|
|
||||||
import LeaveIcon from '@material-ui/icons/Cancel';
|
|
||||||
|
|
||||||
const styles = (theme) =>
|
|
||||||
({
|
|
||||||
root :
|
|
||||||
{
|
|
||||||
position : 'fixed',
|
|
||||||
zIndex : 500,
|
|
||||||
display : 'flex',
|
|
||||||
[theme.breakpoints.up('md')] :
|
|
||||||
{
|
|
||||||
top : '50%',
|
|
||||||
transform : 'translate(0%, -50%)',
|
|
||||||
flexDirection : 'column',
|
|
||||||
justifyContent : 'center',
|
|
||||||
alignItems : 'center',
|
|
||||||
left : '1.0em',
|
|
||||||
width : '2.6em'
|
|
||||||
},
|
|
||||||
[theme.breakpoints.down('sm')] :
|
|
||||||
{
|
|
||||||
flexDirection : 'row',
|
|
||||||
bottom : '0.5em',
|
|
||||||
left : '50%',
|
|
||||||
transform : 'translate(-50%, -0%)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fab :
|
|
||||||
{
|
|
||||||
margin : theme.spacing.unit
|
|
||||||
},
|
|
||||||
show :
|
|
||||||
{
|
|
||||||
opacity : 1,
|
|
||||||
transition : 'opacity .5s'
|
|
||||||
},
|
|
||||||
hide :
|
|
||||||
{
|
|
||||||
opacity : 0,
|
|
||||||
transition : 'opacity .5s'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const Sidebar = (props) =>
|
|
||||||
{
|
|
||||||
const {
|
|
||||||
roomClient,
|
|
||||||
toolbarsVisible,
|
|
||||||
me,
|
|
||||||
micProducer,
|
|
||||||
webcamProducer,
|
|
||||||
screenProducer,
|
|
||||||
locked,
|
|
||||||
classes,
|
|
||||||
theme
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
let micState;
|
|
||||||
|
|
||||||
let micTip;
|
|
||||||
|
|
||||||
if (!me.canSendMic || !micProducer)
|
|
||||||
{
|
|
||||||
micState = 'unsupported';
|
|
||||||
micTip = 'Audio unsupported';
|
|
||||||
}
|
|
||||||
else if (!micProducer.locallyPaused && !micProducer.remotelyPaused)
|
|
||||||
{
|
|
||||||
micState = 'on';
|
|
||||||
micTip = 'Mute audio';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
micState = 'off';
|
|
||||||
micTip = 'Unmute audio';
|
|
||||||
}
|
|
||||||
|
|
||||||
let webcamState;
|
|
||||||
|
|
||||||
let webcamTip;
|
|
||||||
|
|
||||||
if (!me.canSendWebcam)
|
|
||||||
{
|
|
||||||
webcamState = 'unsupported';
|
|
||||||
webcamTip = 'Video unsupported';
|
|
||||||
}
|
|
||||||
else if (webcamProducer)
|
|
||||||
{
|
|
||||||
webcamState = 'on';
|
|
||||||
webcamTip = 'Stop video';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
webcamState = 'off';
|
|
||||||
webcamTip = 'Start video';
|
|
||||||
}
|
|
||||||
|
|
||||||
let screenState;
|
|
||||||
|
|
||||||
let screenTip;
|
|
||||||
|
|
||||||
if (me.needExtension)
|
|
||||||
{
|
|
||||||
screenState = 'need-extension';
|
|
||||||
screenTip = 'Install screen sharing extension';
|
|
||||||
}
|
|
||||||
else if (!me.canShareScreen)
|
|
||||||
{
|
|
||||||
screenState = 'unsupported';
|
|
||||||
screenTip = 'Screen sharing not supported';
|
|
||||||
}
|
|
||||||
else if (screenProducer)
|
|
||||||
{
|
|
||||||
screenState = 'on';
|
|
||||||
screenTip = 'Stop screen sharing';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
screenState = 'off';
|
|
||||||
screenTip = 'Start screen sharing';
|
|
||||||
}
|
|
||||||
|
|
||||||
const smallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
classnames(classes.root, toolbarsVisible ? classes.show : classes.hide)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Tooltip title={micTip} placement={smallScreen ? 'top' : 'right'}>
|
|
||||||
<Fab
|
|
||||||
aria-label='Mute mic'
|
|
||||||
className={classes.fab}
|
|
||||||
disabled={!me.canSendMic || me.audioInProgress}
|
|
||||||
color={micState === 'on' ? 'default' : 'secondary'}
|
|
||||||
size={smallScreen ? 'large' : 'medium'}
|
|
||||||
onClick={() =>
|
|
||||||
{
|
|
||||||
micState === 'on' ?
|
|
||||||
roomClient.muteMic() :
|
|
||||||
roomClient.unmuteMic();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{ micState === 'on' ?
|
|
||||||
<MicIcon />
|
|
||||||
:
|
|
||||||
<MicOffIcon />
|
|
||||||
}
|
|
||||||
</Fab>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={webcamTip} placement={smallScreen ? 'top' : 'right'}>
|
|
||||||
<Fab
|
|
||||||
aria-label='Mute video'
|
|
||||||
className={classes.fab}
|
|
||||||
disabled={!me.canSendWebcam || me.webcamInProgress}
|
|
||||||
color={webcamState === 'on' ? 'default' : 'secondary'}
|
|
||||||
size={smallScreen ? 'large' : 'medium'}
|
|
||||||
onClick={() =>
|
|
||||||
{
|
|
||||||
webcamState === 'on' ?
|
|
||||||
roomClient.disableWebcam() :
|
|
||||||
roomClient.enableWebcam();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{ webcamState === 'on' ?
|
|
||||||
<VideoIcon />
|
|
||||||
:
|
|
||||||
<VideoOffIcon />
|
|
||||||
}
|
|
||||||
</Fab>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={screenTip} placement={smallScreen ? 'top' : 'right'}>
|
|
||||||
<Fab
|
|
||||||
aria-label='Share screen'
|
|
||||||
className={classes.fab}
|
|
||||||
disabled={!me.canShareScreen || me.screenShareInProgress}
|
|
||||||
color={screenState === 'on' ? 'primary' : 'default'}
|
|
||||||
size={smallScreen ? 'large' : 'medium'}
|
|
||||||
onClick={() =>
|
|
||||||
{
|
|
||||||
switch (screenState)
|
|
||||||
{
|
|
||||||
case 'on':
|
|
||||||
{
|
|
||||||
roomClient.disableScreenSharing();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'off':
|
|
||||||
{
|
|
||||||
roomClient.enableScreenSharing();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'need-extension':
|
|
||||||
{
|
|
||||||
roomClient.installExtension();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{ screenState === 'on' || screenState === 'unsupported' ?
|
|
||||||
<ScreenOffIcon/>
|
|
||||||
:null
|
|
||||||
}
|
|
||||||
{ screenState === 'off' ?
|
|
||||||
<ScreenIcon/>
|
|
||||||
:null
|
|
||||||
}
|
|
||||||
{ screenState === 'need-extension' ?
|
|
||||||
<ExtensionIcon/>
|
|
||||||
:null
|
|
||||||
}
|
|
||||||
</Fab>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip
|
|
||||||
title={locked ? 'Unlock room' : 'Lock room'}
|
|
||||||
placement={smallScreen ? 'top' : 'right'}
|
|
||||||
>
|
|
||||||
<Fab
|
|
||||||
aria-label='Room lock'
|
|
||||||
className={classes.fab}
|
|
||||||
color={locked ? 'primary' : 'default'}
|
|
||||||
size={smallScreen ? 'large' : 'medium'}
|
|
||||||
onClick={() =>
|
|
||||||
{
|
|
||||||
if (locked)
|
|
||||||
{
|
|
||||||
roomClient.unlockRoom();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
roomClient.lockRoom();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{ locked ?
|
|
||||||
<LockIcon />
|
|
||||||
:
|
|
||||||
<LockOpenIcon />
|
|
||||||
}
|
|
||||||
</Fab>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{ /* <Fab
|
|
||||||
aria-label='Raise hand'
|
|
||||||
className={classes.fab}
|
|
||||||
disabled={me.raiseHandInProgress}
|
|
||||||
color={me.raiseHand ? 'primary' : 'default'}
|
|
||||||
size='large'
|
|
||||||
onClick={() => roomClient.sendRaiseHandState(!me.raiseHand)}
|
|
||||||
>
|
|
||||||
<Avatar alt='Hand' src={me.raiseHand ? HandOn : HandOff} />
|
|
||||||
</Fab> */ }
|
|
||||||
|
|
||||||
<Tooltip title='Leave meeting' placement={smallScreen ? 'top' : 'right'}>
|
|
||||||
<Fab
|
|
||||||
aria-label='Leave meeting'
|
|
||||||
className={classes.fab}
|
|
||||||
color='secondary'
|
|
||||||
size={smallScreen ? 'large' : 'medium'}
|
|
||||||
onClick={() => roomClient.close()}
|
|
||||||
>
|
|
||||||
<LeaveIcon />
|
|
||||||
</Fab>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Sidebar.propTypes =
|
|
||||||
{
|
|
||||||
roomClient : PropTypes.any.isRequired,
|
|
||||||
toolbarsVisible : PropTypes.bool.isRequired,
|
|
||||||
me : appPropTypes.Me.isRequired,
|
|
||||||
micProducer : appPropTypes.Producer,
|
|
||||||
webcamProducer : appPropTypes.Producer,
|
|
||||||
screenProducer : appPropTypes.Producer,
|
|
||||||
locked : PropTypes.bool.isRequired,
|
|
||||||
classes : PropTypes.object.isRequired,
|
|
||||||
theme : PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
|
||||||
({
|
|
||||||
toolbarsVisible : state.room.toolbarsVisible,
|
|
||||||
...meProducersSelector(state),
|
|
||||||
me : state.me,
|
|
||||||
locked : state.room.locked
|
|
||||||
});
|
|
||||||
|
|
||||||
export default withRoomContext(connect(
|
|
||||||
mapStateToProps,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
areStatesEqual : (next, prev) =>
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
prev.room.toolbarsVisible === next.room.toolbarsVisible &&
|
|
||||||
prev.room.locked === next.room.locked &&
|
|
||||||
prev.producers === next.producers &&
|
|
||||||
prev.me === next.me
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)(withStyles(styles, { withTheme: true })(Sidebar)));
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
|
import { withRoomContext } from '../RoomContext';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Dialog from '@material-ui/core/Dialog';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import DialogActions from '@material-ui/core/DialogActions';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
|
||||||
|
const styles = (theme) =>
|
||||||
|
({
|
||||||
|
root :
|
||||||
|
{
|
||||||
|
},
|
||||||
|
dialogPaper :
|
||||||
|
{
|
||||||
|
width : '20vw',
|
||||||
|
padding : theme.spacing(2),
|
||||||
|
[theme.breakpoints.down('lg')] :
|
||||||
|
{
|
||||||
|
width : '30vw'
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('md')] :
|
||||||
|
{
|
||||||
|
width : '40vw'
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('sm')] :
|
||||||
|
{
|
||||||
|
width : '60vw'
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('xs')] :
|
||||||
|
{
|
||||||
|
width : '80vw'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
logo :
|
||||||
|
{
|
||||||
|
display : 'block'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const JoinDialog = ({
|
||||||
|
roomClient,
|
||||||
|
classes
|
||||||
|
}) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
className={classes.root}
|
||||||
|
open
|
||||||
|
classes={{
|
||||||
|
paper : classes.dialogPaper
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ window.config.logo ?
|
||||||
|
<img alt='Logo' className={classes.logo} src={window.config.logo} />
|
||||||
|
:null
|
||||||
|
}
|
||||||
|
<Typography variant='subtitle1'>You are about to join a meeting, how would you like to join?</Typography>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
{
|
||||||
|
roomClient.join({ joinVideo: false });
|
||||||
|
}}
|
||||||
|
variant='contained'
|
||||||
|
>
|
||||||
|
Audio only
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
{
|
||||||
|
roomClient.join({ joinVideo: true });
|
||||||
|
}}
|
||||||
|
variant='contained'
|
||||||
|
>
|
||||||
|
Audio and Video
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
JoinDialog.propTypes =
|
||||||
|
{
|
||||||
|
roomClient : PropTypes.any.isRequired,
|
||||||
|
classes : PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withRoomContext(withStyles(styles)(JoinDialog));
|
||||||
|
|
@ -12,7 +12,7 @@ const styles = (theme) =>
|
||||||
({
|
({
|
||||||
root :
|
root :
|
||||||
{
|
{
|
||||||
padding : theme.spacing.unit,
|
padding : theme.spacing(1),
|
||||||
display : 'flex',
|
display : 'flex',
|
||||||
alignItems : 'center',
|
alignItems : 'center',
|
||||||
borderRadius : 0
|
borderRadius : 0
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ const styles = (theme) =>
|
||||||
root :
|
root :
|
||||||
{
|
{
|
||||||
display : 'flex',
|
display : 'flex',
|
||||||
marginBottom : theme.spacing.unit,
|
marginBottom : theme.spacing(1),
|
||||||
padding : theme.spacing.unit,
|
padding : theme.spacing(1),
|
||||||
flexShrink : 0
|
flexShrink : 0
|
||||||
},
|
},
|
||||||
selfMessage :
|
selfMessage :
|
||||||
|
|
@ -42,7 +42,7 @@ const styles = (theme) =>
|
||||||
},
|
},
|
||||||
content :
|
content :
|
||||||
{
|
{
|
||||||
marginLeft : theme.spacing.unit
|
marginLeft : theme.spacing(1)
|
||||||
},
|
},
|
||||||
avatar :
|
avatar :
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ const styles = (theme) =>
|
||||||
flexDirection : 'column',
|
flexDirection : 'column',
|
||||||
alignItems : 'center',
|
alignItems : 'center',
|
||||||
overflowY : 'auto',
|
overflowY : 'auto',
|
||||||
padding : theme.spacing.unit
|
padding : theme.spacing(1)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import { withStyles } from '@material-ui/core/styles';
|
||||||
import magnet from 'magnet-uri';
|
import magnet from 'magnet-uri';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
|
||||||
|
|
||||||
const styles = (theme) =>
|
const styles = (theme) =>
|
||||||
({
|
({
|
||||||
|
|
@ -15,11 +14,11 @@ const styles = (theme) =>
|
||||||
display : 'flex',
|
display : 'flex',
|
||||||
alignItems : 'center',
|
alignItems : 'center',
|
||||||
width : '100%',
|
width : '100%',
|
||||||
padding : theme.spacing.unit,
|
padding : theme.spacing(1),
|
||||||
boxShadow : '0px 1px 5px 0px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 3px 1px -2px rgba(0, 0, 0, 0.12)',
|
boxShadow : '0px 1px 5px 0px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 3px 1px -2px rgba(0, 0, 0, 0.12)',
|
||||||
'&:not(:last-child)' :
|
'&:not(:last-child)' :
|
||||||
{
|
{
|
||||||
marginBottom : theme.spacing.unit
|
marginBottom : theme.spacing(1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
avatar :
|
avatar :
|
||||||
|
|
@ -30,7 +29,7 @@ const styles = (theme) =>
|
||||||
text :
|
text :
|
||||||
{
|
{
|
||||||
margin : 0,
|
margin : 0,
|
||||||
padding : theme.spacing.unit
|
padding : theme.spacing(1)
|
||||||
},
|
},
|
||||||
fileContent :
|
fileContent :
|
||||||
{
|
{
|
||||||
|
|
@ -41,7 +40,7 @@ const styles = (theme) =>
|
||||||
{
|
{
|
||||||
display : 'flex',
|
display : 'flex',
|
||||||
alignItems : 'center',
|
alignItems : 'center',
|
||||||
padding : theme.spacing.unit
|
padding : theme.spacing(1)
|
||||||
},
|
},
|
||||||
button :
|
button :
|
||||||
{
|
{
|
||||||
|
|
@ -55,15 +54,18 @@ class File extends React.PureComponent
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
roomClient,
|
roomClient,
|
||||||
torrentSupport,
|
displayName,
|
||||||
|
picture,
|
||||||
|
canShareFiles,
|
||||||
|
magnetUri,
|
||||||
file,
|
file,
|
||||||
classes
|
classes
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<img alt='Peer avatar' className={classes.avatar} src={file.picture || EmptyAvatar} />
|
<img alt='Avatar' className={classes.avatar} src={picture} />
|
||||||
|
|
||||||
<div className={classes.fileContent}>
|
<div className={classes.fileContent}>
|
||||||
{ file.files ?
|
{ file.files ?
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|
@ -93,26 +95,22 @@ class File extends React.PureComponent
|
||||||
:null
|
:null
|
||||||
}
|
}
|
||||||
<Typography className={classes.text}>
|
<Typography className={classes.text}>
|
||||||
{ file.me ?
|
{ `${displayName} shared a file` }
|
||||||
'You shared a file'
|
|
||||||
:
|
|
||||||
`${file.displayName} shared a file`
|
|
||||||
}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{ !file.active && !file.files ?
|
{ !file.active && !file.files ?
|
||||||
<div className={classes.fileInfo}>
|
<div className={classes.fileInfo}>
|
||||||
<Typography className={classes.text}>
|
<Typography className={classes.text}>
|
||||||
{magnet.decode(file.magnetUri).dn}
|
{ magnet.decode(magnetUri).dn }
|
||||||
</Typography>
|
</Typography>
|
||||||
{ torrentSupport ?
|
{ canShareFiles ?
|
||||||
<Button
|
<Button
|
||||||
variant='contained'
|
variant='contained'
|
||||||
component='span'
|
component='span'
|
||||||
className={classes.button}
|
className={classes.button}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
{
|
{
|
||||||
roomClient.handleDownload(file.magnetUri);
|
roomClient.handleDownload(magnetUri);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Download
|
Download
|
||||||
|
|
@ -145,20 +143,34 @@ class File extends React.PureComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
File.propTypes = {
|
File.propTypes = {
|
||||||
roomClient : PropTypes.object.isRequired,
|
roomClient : PropTypes.object.isRequired,
|
||||||
torrentSupport : PropTypes.bool.isRequired,
|
magnetUri : PropTypes.string.isRequired,
|
||||||
file : PropTypes.object.isRequired,
|
displayName : PropTypes.string.isRequired,
|
||||||
classes : PropTypes.object.isRequired
|
picture : PropTypes.string,
|
||||||
|
canShareFiles : PropTypes.bool.isRequired,
|
||||||
|
file : PropTypes.object.isRequired,
|
||||||
|
classes : PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, { magnetUri }) =>
|
const mapStateToProps = (state, { magnetUri }) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
file : state.files[magnetUri],
|
file : state.files[magnetUri],
|
||||||
torrentSupport : state.room.torrentSupport
|
canShareFiles : state.me.canShareFiles
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withRoomContext(connect(
|
export default withRoomContext(connect(
|
||||||
mapStateToProps
|
mapStateToProps,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
areStatesEqual : (next, prev) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
prev.files === next.files &&
|
||||||
|
prev.me.canShareFiles === next.me.canShareFiles
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
)(withStyles(styles)(File)));
|
)(withStyles(styles)(File)));
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import * as appPropTypes from '../../appPropTypes';
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import File from './File';
|
import File from './File';
|
||||||
|
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
||||||
|
|
||||||
const styles = (theme) =>
|
const styles = (theme) =>
|
||||||
({
|
({
|
||||||
|
|
@ -13,7 +15,7 @@ const styles = (theme) =>
|
||||||
flexDirection : 'column',
|
flexDirection : 'column',
|
||||||
alignItems : 'center',
|
alignItems : 'center',
|
||||||
overflowY : 'auto',
|
overflowY : 'auto',
|
||||||
padding : theme.spacing.unit
|
padding : theme.spacing(1)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -42,14 +44,44 @@ class FileList extends React.PureComponent
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
files,
|
files,
|
||||||
|
me,
|
||||||
|
picture,
|
||||||
|
peers,
|
||||||
classes
|
classes
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root} ref={(node) => { this.node = node; }}>
|
<div className={classes.root} ref={(node) => { this.node = node; }}>
|
||||||
{ Object.keys(files).map((magnetUri) =>
|
{ Object.entries(files).map(([ magnetUri, file ]) =>
|
||||||
<File key={magnetUri} magnetUri={magnetUri} />
|
{
|
||||||
)}
|
let displayName;
|
||||||
|
|
||||||
|
let filePicture;
|
||||||
|
|
||||||
|
if (me.id === file.peerId)
|
||||||
|
{
|
||||||
|
displayName = 'You';
|
||||||
|
filePicture = picture;
|
||||||
|
}
|
||||||
|
else if (peers[file.peerId])
|
||||||
|
{
|
||||||
|
displayName = peers[file.peerId].displayName;
|
||||||
|
filePicture = peers[file.peerId].picture;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
displayName = 'Unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<File
|
||||||
|
key={magnetUri}
|
||||||
|
magnetUri={magnetUri}
|
||||||
|
displayName={displayName}
|
||||||
|
picture={filePicture || EmptyAvatar}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -58,14 +90,35 @@ class FileList extends React.PureComponent
|
||||||
FileList.propTypes =
|
FileList.propTypes =
|
||||||
{
|
{
|
||||||
files : PropTypes.object.isRequired,
|
files : PropTypes.object.isRequired,
|
||||||
|
me : appPropTypes.Me.isRequired,
|
||||||
|
picture : PropTypes.string,
|
||||||
|
peers : PropTypes.object.isRequired,
|
||||||
classes : PropTypes.object.isRequired
|
classes : PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
files : state.files
|
files : state.files,
|
||||||
|
me : state.me,
|
||||||
|
picture : state.settings.picture,
|
||||||
|
peers : state.peers
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withStyles(styles)(FileList));
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
areStatesEqual : (next, prev) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
prev.files === next.files &&
|
||||||
|
prev.me === next.me &&
|
||||||
|
prev.settings.picture === next.settings.picture &&
|
||||||
|
prev.peers === next.peers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)(withStyles(styles)(FileList));
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ const styles = (theme) =>
|
||||||
},
|
},
|
||||||
button :
|
button :
|
||||||
{
|
{
|
||||||
margin : theme.spacing.unit
|
margin : theme.spacing(1)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -46,11 +46,11 @@ class FileSharing extends React.PureComponent
|
||||||
render()
|
render()
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
torrentSupport,
|
canShareFiles,
|
||||||
classes
|
classes
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const buttonDescription = torrentSupport ?
|
const buttonDescription = canShareFiles ?
|
||||||
'Share file' : 'File sharing not supported';
|
'Share file' : 'File sharing not supported';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -67,7 +67,7 @@ class FileSharing extends React.PureComponent
|
||||||
variant='contained'
|
variant='contained'
|
||||||
component='span'
|
component='span'
|
||||||
className={classes.button}
|
className={classes.button}
|
||||||
disabled={!torrentSupport}
|
disabled={!canShareFiles}
|
||||||
>
|
>
|
||||||
{buttonDescription}
|
{buttonDescription}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -80,17 +80,17 @@ class FileSharing extends React.PureComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
FileSharing.propTypes = {
|
FileSharing.propTypes = {
|
||||||
roomClient : PropTypes.any.isRequired,
|
roomClient : PropTypes.any.isRequired,
|
||||||
torrentSupport : PropTypes.bool.isRequired,
|
canShareFiles : PropTypes.bool.isRequired,
|
||||||
tabOpen : PropTypes.bool.isRequired,
|
tabOpen : PropTypes.bool.isRequired,
|
||||||
classes : PropTypes.object.isRequired
|
classes : PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
torrentSupport : state.room.torrentSupport,
|
canShareFiles : state.me.canShareFiles,
|
||||||
tabOpen : state.toolarea.currentToolTab === 'files'
|
tabOpen : state.toolarea.currentToolTab === 'files'
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ import * as appPropTypes from '../../appPropTypes';
|
||||||
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
||||||
import HandIcon from '../../../images/icon-hand-white.svg';
|
import HandIcon from '../../../images/icon-hand-white.svg';
|
||||||
|
|
||||||
const styles = () =>
|
const styles = (theme) =>
|
||||||
({
|
({
|
||||||
root :
|
root :
|
||||||
{
|
{
|
||||||
padding : '0.5rem',
|
padding : theme.spacing(1),
|
||||||
width : '100%',
|
width : '100%',
|
||||||
overflow : 'hidden',
|
overflow : 'hidden',
|
||||||
cursor : 'auto',
|
cursor : 'auto',
|
||||||
|
|
@ -31,7 +31,7 @@ const styles = () =>
|
||||||
fontSize : '1rem',
|
fontSize : '1rem',
|
||||||
border : 'none',
|
border : 'none',
|
||||||
display : 'flex',
|
display : 'flex',
|
||||||
paddingLeft : '0.5rem',
|
paddingLeft : theme.spacing(1),
|
||||||
flexGrow : 1,
|
flexGrow : 1,
|
||||||
alignItems : 'center'
|
alignItems : 'center'
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ import ScreenOffIcon from '@material-ui/icons/StopScreenShare';
|
||||||
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
||||||
import HandIcon from '../../../images/icon-hand-white.svg';
|
import HandIcon from '../../../images/icon-hand-white.svg';
|
||||||
|
|
||||||
const styles = () =>
|
const styles = (theme) =>
|
||||||
({
|
({
|
||||||
root :
|
root :
|
||||||
{
|
{
|
||||||
padding : '0.5rem',
|
padding : theme.spacing(1),
|
||||||
width : '100%',
|
width : '100%',
|
||||||
overflow : 'hidden',
|
overflow : 'hidden',
|
||||||
cursor : 'auto',
|
cursor : 'auto',
|
||||||
|
|
@ -37,7 +37,7 @@ const styles = () =>
|
||||||
fontSize : '1rem',
|
fontSize : '1rem',
|
||||||
border : 'none',
|
border : 'none',
|
||||||
display : 'flex',
|
display : 'flex',
|
||||||
paddingLeft : '0.5rem',
|
paddingLeft : theme.spacing(1),
|
||||||
flexGrow : 1,
|
flexGrow : 1,
|
||||||
alignItems : 'center'
|
alignItems : 'center'
|
||||||
},
|
},
|
||||||
|
|
@ -185,8 +185,8 @@ const ListPeer = (props) =>
|
||||||
{
|
{
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
screenVisible ?
|
screenVisible ?
|
||||||
roomClient.modifyPeerConsumer(peer.name, 'screen', true) :
|
roomClient.modifyPeerConsumer(peer.id, 'screen', true) :
|
||||||
roomClient.modifyPeerConsumer(peer.name, 'screen', false);
|
roomClient.modifyPeerConsumer(peer.id, 'screen', false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ screenVisible ?
|
{ screenVisible ?
|
||||||
|
|
@ -207,8 +207,8 @@ const ListPeer = (props) =>
|
||||||
{
|
{
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
micEnabled ?
|
micEnabled ?
|
||||||
roomClient.modifyPeerConsumer(peer.name, 'mic', true) :
|
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
|
||||||
roomClient.modifyPeerConsumer(peer.name, 'mic', false);
|
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ micEnabled ?
|
{ micEnabled ?
|
||||||
|
|
@ -241,7 +241,7 @@ const makeMapStateToProps = (initialState, props) =>
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
peer : state.peers[props.name],
|
peer : state.peers[props.id],
|
||||||
...getPeerConsumers(state, props)
|
...getPeerConsumers(state, props)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,23 +18,23 @@ const styles = (theme) =>
|
||||||
{
|
{
|
||||||
width : '100%',
|
width : '100%',
|
||||||
overflowY : 'auto',
|
overflowY : 'auto',
|
||||||
padding : 6
|
padding : theme.spacing(1)
|
||||||
},
|
},
|
||||||
list :
|
list :
|
||||||
{
|
{
|
||||||
listStyleType : 'none',
|
listStyleType : 'none',
|
||||||
padding : theme.spacing.unit,
|
padding : theme.spacing(1),
|
||||||
boxShadow : '0 2px 5px 2px rgba(0, 0, 0, 0.2)',
|
boxShadow : '0 2px 5px 2px rgba(0, 0, 0, 0.2)',
|
||||||
backgroundColor : 'rgba(255, 255, 255, 1)'
|
backgroundColor : 'rgba(255, 255, 255, 1)'
|
||||||
},
|
},
|
||||||
listheader :
|
listheader :
|
||||||
{
|
{
|
||||||
padding : '0.5rem',
|
padding : theme.spacing(1),
|
||||||
fontWeight : 'bolder'
|
fontWeight : 'bolder'
|
||||||
},
|
},
|
||||||
listItem :
|
listItem :
|
||||||
{
|
{
|
||||||
padding : '0.5rem',
|
padding : theme.spacing(1),
|
||||||
width : '100%',
|
width : '100%',
|
||||||
overflow : 'hidden',
|
overflow : 'hidden',
|
||||||
cursor : 'pointer',
|
cursor : 'pointer',
|
||||||
|
|
@ -76,7 +76,7 @@ class ParticipantList extends React.PureComponent
|
||||||
roomClient,
|
roomClient,
|
||||||
advancedMode,
|
advancedMode,
|
||||||
passivePeers,
|
passivePeers,
|
||||||
selectedPeerName,
|
selectedPeerId,
|
||||||
spotlightPeers,
|
spotlightPeers,
|
||||||
classes
|
classes
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
@ -92,14 +92,14 @@ class ParticipantList extends React.PureComponent
|
||||||
<li className={classes.listheader}>Participants in Spotlight:</li>
|
<li className={classes.listheader}>Participants in Spotlight:</li>
|
||||||
{ spotlightPeers.map((peer) => (
|
{ spotlightPeers.map((peer) => (
|
||||||
<li
|
<li
|
||||||
key={peer.name}
|
key={peer.id}
|
||||||
className={classNames(classes.listItem, {
|
className={classNames(classes.listItem, {
|
||||||
selected : peer.name === selectedPeerName
|
selected : peer.id === selectedPeerId
|
||||||
})}
|
})}
|
||||||
onClick={() => roomClient.setSelectedPeer(peer.name)}
|
onClick={() => roomClient.setSelectedPeer(peer.id)}
|
||||||
>
|
>
|
||||||
<ListPeer name={peer.name} advancedMode={advancedMode}>
|
<ListPeer id={peer.id} advancedMode={advancedMode}>
|
||||||
<Volume small name={peer.name} />
|
<Volume small id={peer.id} />
|
||||||
</ListPeer>
|
</ListPeer>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|
@ -107,15 +107,15 @@ class ParticipantList extends React.PureComponent
|
||||||
<br />
|
<br />
|
||||||
<ul className={classes.list}>
|
<ul className={classes.list}>
|
||||||
<li className={classes.listheader}>Passive Participants:</li>
|
<li className={classes.listheader}>Passive Participants:</li>
|
||||||
{ passivePeers.map((peerName) => (
|
{ passivePeers.map((peerId) => (
|
||||||
<li
|
<li
|
||||||
key={peerName}
|
key={peerId}
|
||||||
className={classNames(classes.listItem, {
|
className={classNames(classes.listItem, {
|
||||||
selected : peerName === selectedPeerName
|
selected : peerId === selectedPeerId
|
||||||
})}
|
})}
|
||||||
onClick={() => roomClient.setSelectedPeer(peerName)}
|
onClick={() => roomClient.setSelectedPeer(peerId)}
|
||||||
>
|
>
|
||||||
<ListPeer name={peerName} advancedMode={advancedMode} />
|
<ListPeer id={peerId} advancedMode={advancedMode} />
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -126,20 +126,20 @@ class ParticipantList extends React.PureComponent
|
||||||
|
|
||||||
ParticipantList.propTypes =
|
ParticipantList.propTypes =
|
||||||
{
|
{
|
||||||
roomClient : PropTypes.any.isRequired,
|
roomClient : PropTypes.any.isRequired,
|
||||||
advancedMode : PropTypes.bool,
|
advancedMode : PropTypes.bool,
|
||||||
passivePeers : PropTypes.array,
|
passivePeers : PropTypes.array,
|
||||||
selectedPeerName : PropTypes.string,
|
selectedPeerId : PropTypes.string,
|
||||||
spotlightPeers : PropTypes.array,
|
spotlightPeers : PropTypes.array,
|
||||||
classes : PropTypes.object.isRequired
|
classes : PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
passivePeers : passivePeersSelector(state),
|
passivePeers : passivePeersSelector(state),
|
||||||
selectedPeerName : state.room.selectedPeerName,
|
selectedPeerId : state.room.selectedPeerId,
|
||||||
spotlightPeers : spotlightPeersSelector(state)
|
spotlightPeers : spotlightPeersSelector(state)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -153,7 +153,7 @@ const ParticipantListContainer = withRoomContext(connect(
|
||||||
return (
|
return (
|
||||||
prev.peers === next.peers &&
|
prev.peers === next.peers &&
|
||||||
prev.room.spotlights === next.room.spotlights &&
|
prev.room.spotlights === next.room.spotlights &&
|
||||||
prev.room.selectedPeerName === next.room.selectedPeerName
|
prev.room.selectedPeerId === next.room.selectedPeerId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,10 @@ import {
|
||||||
spotlightsLengthSelector
|
spotlightsLengthSelector
|
||||||
} from '../Selectors';
|
} from '../Selectors';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import debounce from 'lodash/debounce';
|
|
||||||
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';
|
import HiddenPeers from '../Containers/HiddenPeers';
|
||||||
import ResizeObserver from 'resize-observer-polyfill';
|
|
||||||
|
|
||||||
const RATIO = 1.334;
|
const RATIO = 1.334;
|
||||||
const PADDING_V = 50;
|
const PADDING_V = 50;
|
||||||
|
|
@ -43,15 +41,14 @@ class Democratic extends React.PureComponent
|
||||||
{
|
{
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {};
|
||||||
peerWidth : 400,
|
|
||||||
peerHeight : 300
|
this.resizeTimeout = null;
|
||||||
};
|
|
||||||
|
|
||||||
this.peersRef = React.createRef();
|
this.peersRef = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDimensions = debounce(() =>
|
updateDimensions = () =>
|
||||||
{
|
{
|
||||||
if (!this.peersRef.current)
|
if (!this.peersRef.current)
|
||||||
{
|
{
|
||||||
|
|
@ -93,14 +90,21 @@ class Democratic extends React.PureComponent
|
||||||
peerHeight : 0.9 * y
|
peerHeight : 0.9 * y
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 200);
|
};
|
||||||
|
|
||||||
componentDidMount()
|
componentDidMount()
|
||||||
{
|
{
|
||||||
window.addEventListener('resize', this.updateDimensions);
|
// window.resize event listener
|
||||||
const observer = new ResizeObserver(this.updateDimensions);
|
window.addEventListener('resize', () =>
|
||||||
|
{
|
||||||
|
// clear the timeout
|
||||||
|
clearTimeout(this.resizeTimeout);
|
||||||
|
|
||||||
observer.observe(this.peersRef.current);
|
// start timing for event "completion"
|
||||||
|
this.resizeTimeout = setTimeout(() => this.updateDimensions(), 250);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateDimensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount()
|
componentWillUnmount()
|
||||||
|
|
@ -108,9 +112,10 @@ class Democratic extends React.PureComponent
|
||||||
window.removeEventListener('resize', this.updateDimensions);
|
window.removeEventListener('resize', this.updateDimensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate()
|
componentDidUpdate(prevProps)
|
||||||
{
|
{
|
||||||
this.updateDimensions();
|
if (prevProps !== this.props)
|
||||||
|
this.updateDimensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
render()
|
render()
|
||||||
|
|
@ -125,23 +130,25 @@ class Democratic extends React.PureComponent
|
||||||
|
|
||||||
const style =
|
const style =
|
||||||
{
|
{
|
||||||
'width' : this.state.peerWidth,
|
'width' : this.state.peerWidth ? this.state.peerWidth : 0,
|
||||||
'height' : this.state.peerHeight
|
'height' : this.state.peerHeight ? this.state.peerHeight : 0
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root} ref={this.peersRef}>
|
<div className={classes.root} ref={this.peersRef}>
|
||||||
<Me
|
<Me
|
||||||
advancedMode={advancedMode}
|
advancedMode={advancedMode}
|
||||||
|
spacing={6}
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
{ spotlightsPeers.map((peer) =>
|
{ spotlightsPeers.map((peer) =>
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<Peer
|
<Peer
|
||||||
key={peer.name}
|
key={peer.id}
|
||||||
advancedMode={advancedMode}
|
advancedMode={advancedMode}
|
||||||
name={peer.name}
|
id={peer.id}
|
||||||
|
spacing={6}
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,89 +1,54 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ResizeObserver from 'resize-observer-polyfill';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import debounce from 'lodash/debounce';
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import {
|
||||||
|
spotlightsLengthSelector,
|
||||||
|
videoBoxesSelector
|
||||||
|
} from '../Selectors';
|
||||||
import { withRoomContext } from '../../RoomContext';
|
import { withRoomContext } from '../../RoomContext';
|
||||||
|
import Me from '../Containers/Me';
|
||||||
import Peer from '../Containers/Peer';
|
import Peer from '../Containers/Peer';
|
||||||
|
import SpeakerPeer from '../Containers/SpeakerPeer';
|
||||||
import HiddenPeers from '../Containers/HiddenPeers';
|
import HiddenPeers from '../Containers/HiddenPeers';
|
||||||
|
import Grid from '@material-ui/core/Grid';
|
||||||
|
|
||||||
const styles = () =>
|
const styles = () =>
|
||||||
({
|
({
|
||||||
root :
|
root :
|
||||||
{
|
{
|
||||||
display : 'flex',
|
height : '100%',
|
||||||
flexDirection : 'column',
|
width : '100%',
|
||||||
alignItems : 'center',
|
display : 'grid',
|
||||||
height : '100%',
|
gridTemplateColumns : '1fr',
|
||||||
width : '100%'
|
gridTemplateRows : '1.6fr minmax(0, 0.4fr)'
|
||||||
},
|
},
|
||||||
activePeerContainer :
|
speaker :
|
||||||
{
|
{
|
||||||
width : '100%',
|
gridArea : '1 / 1 / 2 / 2',
|
||||||
height : '80vh',
|
|
||||||
display : 'flex',
|
display : 'flex',
|
||||||
justifyContent : 'center',
|
justifyContent : 'center',
|
||||||
alignItems : 'center'
|
alignItems : 'center',
|
||||||
},
|
paddingTop : 40
|
||||||
activePeer :
|
|
||||||
{
|
|
||||||
width : '100%',
|
|
||||||
border : '5px solid rgba(255, 255, 255, 0.15)',
|
|
||||||
boxShadow : '0px 5px 12px 2px rgba(17, 17, 17, 0.5)',
|
|
||||||
marginTop : 60
|
|
||||||
},
|
},
|
||||||
filmStrip :
|
filmStrip :
|
||||||
{
|
{
|
||||||
display : 'flex',
|
gridArea : '2 / 1 / 3 / 2'
|
||||||
background : 'rgba(0, 0, 0 , 0.5)',
|
|
||||||
width : '100%',
|
|
||||||
overflowX : 'auto',
|
|
||||||
height : '20vh',
|
|
||||||
alignItems : 'center'
|
|
||||||
},
|
},
|
||||||
filmStripContent :
|
filmItem :
|
||||||
{
|
{
|
||||||
margin : '0 auto',
|
display : 'flex',
|
||||||
display : 'flex',
|
marginLeft : '6px',
|
||||||
height : '100%',
|
border : 'var(--peer-border)',
|
||||||
alignItems : 'center'
|
|
||||||
},
|
|
||||||
film :
|
|
||||||
{
|
|
||||||
height : '18vh',
|
|
||||||
flexShrink : 0,
|
|
||||||
paddingLeft : '1vh',
|
|
||||||
'& .active' :
|
|
||||||
{
|
|
||||||
borderColor : 'var(--active-speaker-border-color)'
|
|
||||||
},
|
|
||||||
'&.selected' :
|
'&.selected' :
|
||||||
{
|
{
|
||||||
borderColor : 'var(--selected-peer-border-color)'
|
borderColor : 'var(--selected-peer-border-color)'
|
||||||
},
|
},
|
||||||
'&:last-child' :
|
'&.active' :
|
||||||
{
|
{
|
||||||
paddingRight : '1vh'
|
opacity : '0.6'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
filmContent :
|
|
||||||
{
|
|
||||||
height : '100%',
|
|
||||||
width : '100%',
|
|
||||||
border : '1px solid rgba(255,255,255,0.15)',
|
|
||||||
maxWidth : 'calc(18vh * (4 / 3))',
|
|
||||||
cursor : 'pointer',
|
|
||||||
'& .screen' :
|
|
||||||
{
|
|
||||||
maxWidth : 'calc(18vh * (2 * 4 / 3))',
|
|
||||||
border : 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hiddenPeers :
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -93,80 +58,114 @@ class Filmstrip extends React.PureComponent
|
||||||
{
|
{
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.resizeTimeout = null;
|
||||||
|
|
||||||
this.activePeerContainer = React.createRef();
|
this.activePeerContainer = React.createRef();
|
||||||
|
|
||||||
|
this.filmStripContainer = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
lastSpeaker : null,
|
lastSpeaker : null
|
||||||
width : 400
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Find the name of the peer which is currently speaking. This is either
|
// Find the name of the peer which is currently speaking. This is either
|
||||||
// the latest active speaker, or the manually selected peer, or, if no
|
// the latest active speaker, or the manually selected peer, or, if no
|
||||||
// person has spoken yet, the first peer in the list of peers.
|
// person has spoken yet, the first peer in the list of peers.
|
||||||
getActivePeerName = () =>
|
getActivePeerId = () =>
|
||||||
{
|
{
|
||||||
if (this.props.selectedPeerName)
|
const {
|
||||||
|
selectedPeerId,
|
||||||
|
peers
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { lastSpeaker } = this.state;
|
||||||
|
|
||||||
|
if (selectedPeerId && peers[selectedPeerId])
|
||||||
{
|
{
|
||||||
return this.props.selectedPeerName;
|
return this.props.selectedPeerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.lastSpeaker)
|
if (lastSpeaker && peers[lastSpeaker])
|
||||||
{
|
{
|
||||||
return this.state.lastSpeaker;
|
return this.state.lastSpeaker;
|
||||||
}
|
}
|
||||||
|
|
||||||
const peerNames = Object.keys(this.props.peers);
|
const peerIds = Object.keys(peers);
|
||||||
|
|
||||||
if (peerNames.length > 0)
|
if (peerIds.length > 0)
|
||||||
{
|
{
|
||||||
return peerNames[0];
|
return peerIds[0];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
isSharingCamera = (peerName) => this.props.peers[peerName] &&
|
isSharingCamera = (peerId) => this.props.peers[peerId] &&
|
||||||
this.props.peers[peerName].consumers.some((consumer) =>
|
this.props.peers[peerId].consumers.some((consumer) =>
|
||||||
this.props.consumers[consumer].source === 'screen');
|
this.props.consumers[consumer].source === 'screen');
|
||||||
|
|
||||||
getRatio = () =>
|
updateDimensions = () =>
|
||||||
{
|
{
|
||||||
let ratio = 4 / 3;
|
const newState = {};
|
||||||
|
|
||||||
if (this.isSharingCamera(this.getActivePeerName()))
|
const speaker = this.activePeerContainer.current;
|
||||||
|
|
||||||
|
if (speaker)
|
||||||
{
|
{
|
||||||
ratio *= 2;
|
let speakerWidth = (speaker.clientWidth - 100);
|
||||||
}
|
|
||||||
|
|
||||||
return ratio;
|
let speakerHeight = (speakerWidth / 4) * 3;
|
||||||
};
|
|
||||||
|
if (this.isSharingCamera(this.getActivePeerId()))
|
||||||
updateDimensions = debounce(() =>
|
|
||||||
{
|
|
||||||
const container = this.activePeerContainer.current;
|
|
||||||
|
|
||||||
if (container)
|
|
||||||
{
|
|
||||||
const ratio = this.getRatio();
|
|
||||||
|
|
||||||
let width = container.clientWidth;
|
|
||||||
|
|
||||||
if (width / ratio > (container.clientHeight - 100))
|
|
||||||
{
|
{
|
||||||
width = (container.clientHeight - 100) * ratio;
|
speakerWidth /= 2;
|
||||||
|
speakerHeight = (speakerWidth / 4) * 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (speakerHeight > (speaker.clientHeight - 60))
|
||||||
|
{
|
||||||
|
speakerHeight = (speaker.clientHeight - 60);
|
||||||
|
speakerWidth = (speakerHeight / 3) * 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
newState.speakerWidth = speakerWidth;
|
||||||
width
|
newState.speakerHeight = speakerHeight;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, 200);
|
|
||||||
|
const filmStrip = this.filmStripContainer.current;
|
||||||
|
|
||||||
|
if (filmStrip)
|
||||||
|
{
|
||||||
|
let filmStripHeight = filmStrip.clientHeight - 10;
|
||||||
|
|
||||||
|
let filmStripWidth = (filmStripHeight / 3) * 4;
|
||||||
|
|
||||||
|
if (filmStripWidth * this.props.boxes > (filmStrip.clientWidth - 50))
|
||||||
|
{
|
||||||
|
filmStripWidth = (filmStrip.clientWidth - 50) / this.props.boxes;
|
||||||
|
filmStripHeight = (filmStripWidth / 4) * 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
newState.filmStripWidth = filmStripWidth;
|
||||||
|
newState.filmStripHeight = filmStripHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
...newState
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount()
|
componentDidMount()
|
||||||
{
|
{
|
||||||
window.addEventListener('resize', this.updateDimensions);
|
// window.resize event listener
|
||||||
const observer = new ResizeObserver(this.updateDimensions);
|
window.addEventListener('resize', () =>
|
||||||
|
{
|
||||||
|
// clear the timeout
|
||||||
|
clearTimeout(this.resizeTimeout);
|
||||||
|
|
||||||
|
// start timing for event "completion"
|
||||||
|
this.resizeTimeout = setTimeout(() => this.updateDimensions(), 250);
|
||||||
|
});
|
||||||
|
|
||||||
observer.observe(this.activePeerContainer.current);
|
|
||||||
this.updateDimensions();
|
this.updateDimensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,19 +174,28 @@ class Filmstrip extends React.PureComponent
|
||||||
window.removeEventListener('resize', this.updateDimensions);
|
window.removeEventListener('resize', this.updateDimensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUpdate(nextProps)
|
||||||
|
{
|
||||||
|
if (nextProps !== this.props)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
nextProps.activeSpeakerId != null &&
|
||||||
|
nextProps.activeSpeakerId !== this.props.myId
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line react/no-did-update-set-state
|
||||||
|
this.setState({
|
||||||
|
lastSpeaker : nextProps.activeSpeakerId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps)
|
componentDidUpdate(prevProps)
|
||||||
{
|
{
|
||||||
if (prevProps !== this.props)
|
if (prevProps !== this.props)
|
||||||
{
|
{
|
||||||
this.updateDimensions();
|
this.updateDimensions();
|
||||||
|
|
||||||
if (this.props.activeSpeakerName !== this.props.myName)
|
|
||||||
{
|
|
||||||
// eslint-disable-next-line react/no-did-update-set-state
|
|
||||||
this.setState({
|
|
||||||
lastSpeaker : this.props.activeSpeakerName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,56 +204,78 @@ class Filmstrip extends React.PureComponent
|
||||||
const {
|
const {
|
||||||
roomClient,
|
roomClient,
|
||||||
peers,
|
peers,
|
||||||
|
myId,
|
||||||
advancedMode,
|
advancedMode,
|
||||||
spotlights,
|
spotlights,
|
||||||
spotlightsLength,
|
spotlightsLength,
|
||||||
classes
|
classes
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const activePeerName = this.getActivePeerName();
|
const activePeerId = this.getActivePeerId();
|
||||||
|
|
||||||
|
const speakerStyle =
|
||||||
|
{
|
||||||
|
width : this.state.speakerWidth,
|
||||||
|
height : this.state.speakerHeight
|
||||||
|
};
|
||||||
|
|
||||||
|
const peerStyle =
|
||||||
|
{
|
||||||
|
width : this.state.filmStripWidth,
|
||||||
|
height : this.state.filmStripHeight
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<div className={classes.activePeerContainer} ref={this.activePeerContainer}>
|
<div className={classes.speaker} ref={this.activePeerContainer}>
|
||||||
{ peers[activePeerName] ?
|
{ peers[activePeerId] ?
|
||||||
<div
|
<SpeakerPeer
|
||||||
className={classes.activePeer}
|
advancedMode={advancedMode}
|
||||||
style={{
|
id={activePeerId}
|
||||||
width : this.state.width,
|
style={speakerStyle}
|
||||||
height : this.state.width / this.getRatio()
|
/>
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Peer
|
|
||||||
advancedMode={advancedMode}
|
|
||||||
name={activePeerName}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
:null
|
:null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={classes.filmStrip}>
|
<div className={classes.filmStrip} ref={this.filmStripContainer}>
|
||||||
<div className={classes.filmStripContent}>
|
<Grid container justify='center' spacing={0}>
|
||||||
{ Object.keys(peers).map((peerName) =>
|
<Grid item>
|
||||||
|
<div
|
||||||
|
className={classnames(classes.filmItem, {
|
||||||
|
active : myId === activePeerId
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Me
|
||||||
|
advancedMode={advancedMode}
|
||||||
|
style={peerStyle}
|
||||||
|
smallButtons
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{ Object.keys(peers).map((peerId) =>
|
||||||
{
|
{
|
||||||
if (spotlights.find((spotlightsElement) => spotlightsElement === peerName))
|
if (spotlights.find((spotlightsElement) => spotlightsElement === peerId))
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<div
|
<Grid key={peerId} item>
|
||||||
key={peerName}
|
<div
|
||||||
onClick={() => roomClient.setSelectedPeer(peerName)}
|
key={peerId}
|
||||||
className={classnames(classes.film, {
|
onClick={() => roomClient.setSelectedPeer(peerId)}
|
||||||
selected : this.props.selectedPeerName === peerName,
|
className={classnames(classes.filmItem, {
|
||||||
active : this.state.lastSpeaker === peerName
|
selected : this.props.selectedPeerId === peerId,
|
||||||
})}
|
active : peerId === activePeerId
|
||||||
>
|
})}
|
||||||
<div className={classes.filmContent}>
|
>
|
||||||
<Peer
|
<Peer
|
||||||
advancedMode={advancedMode}
|
advancedMode={advancedMode}
|
||||||
name={peerName}
|
id={peerId}
|
||||||
|
style={peerStyle}
|
||||||
|
smallButtons
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -253,51 +283,63 @@ class Filmstrip extends React.PureComponent
|
||||||
return ('');
|
return ('');
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</div>
|
</Grid>
|
||||||
</div>
|
|
||||||
<div className={classes.hiddenPeers}>
|
|
||||||
{ spotlightsLength<Object.keys(peers).length ?
|
|
||||||
<HiddenPeers
|
|
||||||
hiddenPeersCount={Object.keys(peers).length-spotlightsLength}
|
|
||||||
/>
|
|
||||||
:null
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{ spotlightsLength<Object.keys(peers).length ?
|
||||||
|
<HiddenPeers
|
||||||
|
hiddenPeersCount={Object.keys(peers).length-spotlightsLength}
|
||||||
|
/>
|
||||||
|
:null
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Filmstrip.propTypes = {
|
Filmstrip.propTypes = {
|
||||||
roomClient : PropTypes.any.isRequired,
|
roomClient : PropTypes.any.isRequired,
|
||||||
activeSpeakerName : PropTypes.string,
|
activeSpeakerId : PropTypes.string,
|
||||||
advancedMode : PropTypes.bool,
|
advancedMode : PropTypes.bool,
|
||||||
peers : PropTypes.object.isRequired,
|
peers : PropTypes.object.isRequired,
|
||||||
consumers : PropTypes.object.isRequired,
|
consumers : PropTypes.object.isRequired,
|
||||||
myName : PropTypes.string.isRequired,
|
myId : PropTypes.string.isRequired,
|
||||||
selectedPeerName : PropTypes.string,
|
selectedPeerId : PropTypes.string,
|
||||||
spotlightsLength : PropTypes.number,
|
spotlightsLength : PropTypes.number,
|
||||||
spotlights : PropTypes.array.isRequired,
|
spotlights : PropTypes.array.isRequired,
|
||||||
classes : PropTypes.object.isRequired
|
boxes : PropTypes.number,
|
||||||
|
classes : PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
{
|
{
|
||||||
const spotlightsLength = state.room.spotlights ? state.room.spotlights.length : 0;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activeSpeakerName : state.room.activeSpeakerName,
|
activeSpeakerId : state.room.activeSpeakerId,
|
||||||
selectedPeerName : state.room.selectedPeerName,
|
selectedPeerId : state.room.selectedPeerId,
|
||||||
peers : state.peers,
|
peers : state.peers,
|
||||||
consumers : state.consumers,
|
consumers : state.consumers,
|
||||||
myName : state.me.name,
|
myId : state.me.id,
|
||||||
spotlights : state.room.spotlights,
|
spotlights : state.room.spotlights,
|
||||||
spotlightsLength
|
spotlightsLength : spotlightsLengthSelector(state),
|
||||||
|
boxes : videoBoxesSelector(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withRoomContext(connect(
|
export default withRoomContext(connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
undefined
|
null,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
areStatesEqual : (next, prev) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
prev.room.activeSpeakerId === next.room.activeSpeakerId &&
|
||||||
|
prev.room.selectedPeerId === next.room.selectedPeerId &&
|
||||||
|
prev.peers === next.peers &&
|
||||||
|
prev.consumers === next.consumers &&
|
||||||
|
prev.room.spotlights === next.room.spotlights &&
|
||||||
|
prev.me.id === next.me.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
)(withStyles(styles)(Filmstrip)));
|
)(withStyles(styles)(Filmstrip)));
|
||||||
|
|
@ -15,7 +15,6 @@ import SwipeableDrawer from '@material-ui/core/SwipeableDrawer';
|
||||||
import Hidden from '@material-ui/core/Hidden';
|
import Hidden from '@material-ui/core/Hidden';
|
||||||
import Paper from '@material-ui/core/Paper';
|
import Paper from '@material-ui/core/Paper';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import Button from '@material-ui/core/Button';
|
|
||||||
import IconButton from '@material-ui/core/IconButton';
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
import MenuIcon from '@material-ui/icons/Menu';
|
import MenuIcon from '@material-ui/icons/Menu';
|
||||||
import Avatar from '@material-ui/core/Avatar';
|
import Avatar from '@material-ui/core/Avatar';
|
||||||
|
|
@ -28,11 +27,14 @@ import Filmstrip from './MeetingViews/Filmstrip';
|
||||||
import AudioPeers from './PeerAudio/AudioPeers';
|
import AudioPeers from './PeerAudio/AudioPeers';
|
||||||
import FullScreenView from './VideoContainers/FullScreenView';
|
import FullScreenView from './VideoContainers/FullScreenView';
|
||||||
import VideoWindow from './VideoWindow/VideoWindow';
|
import VideoWindow from './VideoWindow/VideoWindow';
|
||||||
import Sidebar from './Controls/Sidebar';
|
|
||||||
import FullScreenIcon from '@material-ui/icons/Fullscreen';
|
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 LockIcon from '@material-ui/icons/Lock';
|
||||||
|
import LockOpenIcon from '@material-ui/icons/LockOpen';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
import Settings from './Settings/Settings';
|
import Settings from './Settings/Settings';
|
||||||
|
import JoinDialog from './JoinDialog';
|
||||||
|
|
||||||
const TIMEOUT = 10 * 1000;
|
const TIMEOUT = 10 * 1000;
|
||||||
|
|
||||||
|
|
@ -58,7 +60,7 @@ const styles = (theme) =>
|
||||||
left : '50%',
|
left : '50%',
|
||||||
transform : 'translateX(-50%) translateY(-50%)',
|
transform : 'translateX(-50%) translateY(-50%)',
|
||||||
width : '30vw',
|
width : '30vw',
|
||||||
padding : theme.spacing.unit * 2,
|
padding : theme.spacing(2),
|
||||||
flexDirection : 'column',
|
flexDirection : 'column',
|
||||||
justifyContent : 'center',
|
justifyContent : 'center',
|
||||||
alignItems : 'center'
|
alignItems : 'center'
|
||||||
|
|
@ -125,6 +127,11 @@ const styles = (theme) =>
|
||||||
{
|
{
|
||||||
display : 'flex'
|
display : 'flex'
|
||||||
},
|
},
|
||||||
|
actionButton :
|
||||||
|
{
|
||||||
|
margin : theme.spacing(1),
|
||||||
|
padding : 0
|
||||||
|
},
|
||||||
meContainer :
|
meContainer :
|
||||||
{
|
{
|
||||||
position : 'fixed',
|
position : 'fixed',
|
||||||
|
|
@ -176,10 +183,6 @@ class Room extends React.PureComponent
|
||||||
|
|
||||||
componentDidMount()
|
componentDidMount()
|
||||||
{
|
{
|
||||||
const { roomClient } = this.props;
|
|
||||||
|
|
||||||
roomClient.join();
|
|
||||||
|
|
||||||
if (this.fullscreen.fullscreenEnabled)
|
if (this.fullscreen.fullscreenEnabled)
|
||||||
{
|
{
|
||||||
this.fullscreen.addEventListener('fullscreenchange', this.handleFullscreenChange);
|
this.fullscreen.addEventListener('fullscreenchange', this.handleFullscreenChange);
|
||||||
|
|
@ -242,29 +245,7 @@ class Room extends React.PureComponent
|
||||||
democratic : Democratic
|
democratic : Democratic
|
||||||
}[room.mode];
|
}[room.mode];
|
||||||
|
|
||||||
if (room.audioSuspended)
|
if (room.lockedOut)
|
||||||
{
|
|
||||||
return (
|
|
||||||
<div className={classes.root}>
|
|
||||||
<Paper className={classes.message}>
|
|
||||||
<Typography variant='h2'>
|
|
||||||
This webpage required sound and video to play, please click to allow.
|
|
||||||
</Typography>
|
|
||||||
<Button
|
|
||||||
variant='contained'
|
|
||||||
onClick={() =>
|
|
||||||
{
|
|
||||||
roomClient.notify('Joining.');
|
|
||||||
roomClient.resumeAudio();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Allow
|
|
||||||
</Button>
|
|
||||||
</Paper>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else if (room.lockedOut)
|
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
|
|
@ -274,6 +255,14 @@ class Room extends React.PureComponent
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
else if (!room.joined)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<JoinDialog />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
|
|
@ -324,9 +313,32 @@ class Room extends React.PureComponent
|
||||||
</Typography>
|
</Typography>
|
||||||
<div className={classes.grow} />
|
<div className={classes.grow} />
|
||||||
<div className={classes.actionButtons}>
|
<div className={classes.actionButtons}>
|
||||||
|
<IconButton
|
||||||
|
aria-label='Lock room'
|
||||||
|
className={classes.actionButton}
|
||||||
|
color='inherit'
|
||||||
|
onClick={() =>
|
||||||
|
{
|
||||||
|
if (room.locked)
|
||||||
|
{
|
||||||
|
roomClient.unlockRoom();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
roomClient.lockRoom();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ room.locked ?
|
||||||
|
<LockIcon />
|
||||||
|
:
|
||||||
|
<LockOpenIcon />
|
||||||
|
}
|
||||||
|
</IconButton>
|
||||||
{ this.fullscreen.fullscreenEnabled ?
|
{ this.fullscreen.fullscreenEnabled ?
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label='Fullscreen'
|
aria-label='Fullscreen'
|
||||||
|
className={classes.actionButton}
|
||||||
color='inherit'
|
color='inherit'
|
||||||
onClick={this.handleToggleFullscreen}
|
onClick={this.handleToggleFullscreen}
|
||||||
>
|
>
|
||||||
|
|
@ -340,6 +352,7 @@ class Room extends React.PureComponent
|
||||||
}
|
}
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label='Settings'
|
aria-label='Settings'
|
||||||
|
className={classes.actionButton}
|
||||||
color='inherit'
|
color='inherit'
|
||||||
onClick={() => setSettingsOpen(!room.settingsOpen)}
|
onClick={() => setSettingsOpen(!room.settingsOpen)}
|
||||||
>
|
>
|
||||||
|
|
@ -348,6 +361,7 @@ class Room extends React.PureComponent
|
||||||
{ loginEnabled ?
|
{ loginEnabled ?
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label='Account'
|
aria-label='Account'
|
||||||
|
className={classes.actionButton}
|
||||||
color='inherit'
|
color='inherit'
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
{
|
{
|
||||||
|
|
@ -362,6 +376,15 @@ class Room extends React.PureComponent
|
||||||
</IconButton>
|
</IconButton>
|
||||||
:null
|
:null
|
||||||
}
|
}
|
||||||
|
<Button
|
||||||
|
aria-label='Leave meeting'
|
||||||
|
className={classes.actionButton}
|
||||||
|
variant='contained'
|
||||||
|
color='secondary'
|
||||||
|
onClick={() => roomClient.close()}
|
||||||
|
>
|
||||||
|
Leave
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
|
|
@ -384,8 +407,6 @@ class Room extends React.PureComponent
|
||||||
|
|
||||||
<View advancedMode={advancedMode} />
|
<View advancedMode={advancedMode} />
|
||||||
|
|
||||||
<Sidebar />
|
|
||||||
|
|
||||||
<Settings />
|
<Settings />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ 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 getPeerConsumers = (state, props) =>
|
const getPeerConsumers = (state, props) =>
|
||||||
(state.peers[props.name] ? state.peers[props.name].consumers : null);
|
(state.peers[props.id] ? state.peers[props.id].consumers : null);
|
||||||
const getAllConsumers = (state) => state.consumers;
|
const getAllConsumers = (state) => state.consumers;
|
||||||
const peersKeySelector = createSelector(
|
const peersKeySelector = createSelector(
|
||||||
peersSelector,
|
peersSelector,
|
||||||
|
|
@ -66,10 +66,10 @@ export const spotlightPeersSelector = createSelector(
|
||||||
spotlightsSelector,
|
spotlightsSelector,
|
||||||
peersSelector,
|
peersSelector,
|
||||||
(spotlights, peers) =>
|
(spotlights, peers) =>
|
||||||
spotlights.reduce((result, peerName) =>
|
spotlights.reduce((result, peerId) =>
|
||||||
{
|
{
|
||||||
if (peers[peerName])
|
if (peers[peerId])
|
||||||
result.push(peers[peerName]);
|
result.push(peers[peerId]);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, [])
|
}, [])
|
||||||
|
|
@ -83,7 +83,7 @@ export const peersLengthSelector = createSelector(
|
||||||
export const passivePeersSelector = createSelector(
|
export const passivePeersSelector = createSelector(
|
||||||
peersKeySelector,
|
peersKeySelector,
|
||||||
spotlightsSelector,
|
spotlightsSelector,
|
||||||
(peers, spotlights) => peers.filter((peerName) => !spotlights.includes(peerName))
|
(peers, spotlights) => peers.filter((peerId) => !spotlights.includes(peerId))
|
||||||
);
|
);
|
||||||
|
|
||||||
export const videoBoxesSelector = createSelector(
|
export const videoBoxesSelector = createSelector(
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ const styles = (theme) =>
|
||||||
},
|
},
|
||||||
setting :
|
setting :
|
||||||
{
|
{
|
||||||
padding : theme.spacing.unit * 2
|
padding : theme.spacing(2)
|
||||||
},
|
},
|
||||||
formControl :
|
formControl :
|
||||||
{
|
{
|
||||||
|
|
@ -51,13 +51,13 @@ const styles = (theme) =>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* const modes = [ {
|
const modes = [ {
|
||||||
value : 'democratic',
|
value : 'democratic',
|
||||||
label : 'Democratic view'
|
label : 'Democratic view'
|
||||||
}, {
|
}, {
|
||||||
value : 'filmstrip',
|
value : 'filmstrip',
|
||||||
label : 'Filmstrip view'
|
label : 'Filmstrip view'
|
||||||
} ]; */
|
} ];
|
||||||
|
|
||||||
const resolutions = [ {
|
const resolutions = [ {
|
||||||
value : 'low',
|
value : 'low',
|
||||||
|
|
@ -87,6 +87,7 @@ const Settings = ({
|
||||||
settings,
|
settings,
|
||||||
onToggleAdvancedMode,
|
onToggleAdvancedMode,
|
||||||
handleCloseSettings,
|
handleCloseSettings,
|
||||||
|
handleChangeMode,
|
||||||
classes
|
classes
|
||||||
}) =>
|
}) =>
|
||||||
{
|
{
|
||||||
|
|
@ -203,6 +204,33 @@ const Settings = ({
|
||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</form>
|
</form>
|
||||||
|
<form className={classes.setting} autoComplete='off'>
|
||||||
|
<FormControl className={classes.formControl}>
|
||||||
|
<Select
|
||||||
|
value={room.mode || ''}
|
||||||
|
onChange={(event) =>
|
||||||
|
{
|
||||||
|
if (event.target.value)
|
||||||
|
handleChangeMode(event.target.value);
|
||||||
|
}}
|
||||||
|
name='Room layout'
|
||||||
|
autoWidth
|
||||||
|
className={classes.selectEmpty}
|
||||||
|
>
|
||||||
|
{ modes.map((mode, index) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<MenuItem key={index} value={mode.value}>
|
||||||
|
{mode.label}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
<FormHelperText>
|
||||||
|
Select room layout
|
||||||
|
</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
</form>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
className={classes.setting}
|
className={classes.setting}
|
||||||
control={<Checkbox checked={settings.advancedMode} onChange={onToggleAdvancedMode} value='advancedMode' />}
|
control={<Checkbox checked={settings.advancedMode} onChange={onToggleAdvancedMode} value='advancedMode' />}
|
||||||
|
|
@ -241,7 +269,7 @@ const mapStateToProps = (state) =>
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
onToggleAdvancedMode : stateActions.toggleAdvancedMode,
|
onToggleAdvancedMode : stateActions.toggleAdvancedMode,
|
||||||
handleChangeMode : stateActions.setDisplayMode,
|
handleChangeMode : stateActions.setDisplayMode,
|
||||||
handleCloseSettings : stateActions.setSettingsOpen
|
handleCloseSettings : stateActions.setSettingsOpen,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withRoomContext(connect(
|
export default withRoomContext(connect(
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import * as stateActions from '../../actions/stateActions';
|
||||||
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
|
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
|
||||||
import VideoView from './VideoView';
|
import VideoView from './VideoView';
|
||||||
|
|
||||||
const styles = () =>
|
const styles = (theme) =>
|
||||||
({
|
({
|
||||||
root :
|
root :
|
||||||
{
|
{
|
||||||
|
|
@ -29,7 +29,7 @@ const styles = () =>
|
||||||
flexDirection : 'row',
|
flexDirection : 'row',
|
||||||
justifyContent : 'flex-start',
|
justifyContent : 'flex-start',
|
||||||
alignItems : 'center',
|
alignItems : 'center',
|
||||||
padding : '0.4vmin'
|
padding : theme.spacing(1)
|
||||||
},
|
},
|
||||||
button :
|
button :
|
||||||
{
|
{
|
||||||
|
|
@ -102,13 +102,6 @@ const FullScreenView = (props) =>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
{ consumerVisible && !consumer.supported ?
|
|
||||||
<div className={classes.incompatibleVideo}>
|
|
||||||
<p>incompatible video</p>
|
|
||||||
</div>
|
|
||||||
:null
|
|
||||||
}
|
|
||||||
|
|
||||||
<div className={classes.controls}>
|
<div className={classes.controls}>
|
||||||
<div
|
<div
|
||||||
className={classnames(classes.button, {
|
className={classnames(classes.button, {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { withStyles } from '@material-ui/core/styles';
|
||||||
import * as appPropTypes from '../appPropTypes';
|
import * as appPropTypes from '../appPropTypes';
|
||||||
import EditableInput from '../Controls/EditableInput';
|
import EditableInput from '../Controls/EditableInput';
|
||||||
|
|
||||||
const styles = () =>
|
const styles = (theme) =>
|
||||||
({
|
({
|
||||||
root :
|
root :
|
||||||
{
|
{
|
||||||
|
|
@ -27,7 +27,7 @@ const styles = () =>
|
||||||
transitionProperty : 'opacity',
|
transitionProperty : 'opacity',
|
||||||
transitionDuration : '.15s',
|
transitionDuration : '.15s',
|
||||||
backgroundColor : 'var(--peer-video-bg-color)',
|
backgroundColor : 'var(--peer-video-bg-color)',
|
||||||
'&.is-me' :
|
'&.isMe' :
|
||||||
{
|
{
|
||||||
transform : 'scaleX(-1)'
|
transform : 'scaleX(-1)'
|
||||||
},
|
},
|
||||||
|
|
@ -48,54 +48,42 @@ const styles = () =>
|
||||||
},
|
},
|
||||||
info :
|
info :
|
||||||
{
|
{
|
||||||
|
width : '100%',
|
||||||
|
height : '100%',
|
||||||
|
padding : theme.spacing(1),
|
||||||
position : 'absolute',
|
position : 'absolute',
|
||||||
zIndex : 10,
|
zIndex : 10,
|
||||||
top : '0.6vmin',
|
|
||||||
left : '0.6vmin',
|
|
||||||
bottom : 0,
|
|
||||||
right : 0,
|
|
||||||
display : 'flex',
|
display : 'flex',
|
||||||
flexDirection : 'column',
|
flexDirection : 'column',
|
||||||
justifyContent : 'space-between'
|
justifyContent : 'space-between'
|
||||||
},
|
},
|
||||||
media :
|
media :
|
||||||
{
|
{
|
||||||
flex : '0 0 auto',
|
display : 'flex',
|
||||||
display : 'flex',
|
transitionProperty : 'opacity',
|
||||||
flexDirection : 'row'
|
transitionDuration : '.15s',
|
||||||
|
'&.hidden' :
|
||||||
|
{
|
||||||
|
opacity : 0,
|
||||||
|
transitionDuration : '0s'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
box :
|
box :
|
||||||
{
|
{
|
||||||
padding : '0.4vmin',
|
padding : theme.spacing(0.5),
|
||||||
borderRadius : 2,
|
borderRadius : 2,
|
||||||
backgroundColor : 'rgba(0, 0, 0, 0.25)',
|
backgroundColor : 'rgba(0, 0, 0, 0.25)',
|
||||||
'& p' :
|
'& p' :
|
||||||
{
|
{
|
||||||
userSelect : 'none',
|
userSelect : 'none',
|
||||||
pointerEvents : 'none',
|
margin : 0,
|
||||||
margin : 0,
|
color : 'rgba(255, 255, 255, 0.7)',
|
||||||
color : 'rgba(255, 255, 255, 0.7)',
|
fontSize : '0.8em'
|
||||||
fontSize : 10,
|
|
||||||
|
|
||||||
'&:last-child' :
|
|
||||||
{
|
|
||||||
marginBottom : 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
peer :
|
peer :
|
||||||
{
|
{
|
||||||
flex : '0 0 auto',
|
display : 'flex'
|
||||||
display : 'flex',
|
|
||||||
flexDirection : 'column',
|
|
||||||
justifyContent : 'flex-end',
|
|
||||||
position : 'absolute',
|
|
||||||
bottom : '0.6vmin',
|
|
||||||
left : 0,
|
|
||||||
borderRadius : 2,
|
|
||||||
backgroundColor : 'rgba(0, 0, 0, 0.25)',
|
|
||||||
padding : '0.5vmin',
|
|
||||||
alignItems : 'flex-start'
|
|
||||||
},
|
},
|
||||||
displayNameEdit :
|
displayNameEdit :
|
||||||
{
|
{
|
||||||
|
|
@ -120,12 +108,7 @@ const styles = () =>
|
||||||
},
|
},
|
||||||
deviceInfo :
|
deviceInfo :
|
||||||
{
|
{
|
||||||
marginTop : '0.4vmin',
|
'& span' :
|
||||||
display : 'flex',
|
|
||||||
flexDirection : 'row',
|
|
||||||
justifyContent : 'flex-start',
|
|
||||||
alignItems : 'flex-end',
|
|
||||||
'& span' :
|
|
||||||
{
|
{
|
||||||
userSelect : 'none',
|
userSelect : 'none',
|
||||||
pointerEvents : 'none',
|
pointerEvents : 'none',
|
||||||
|
|
@ -159,6 +142,7 @@ class VideoView extends React.PureComponent
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
isMe,
|
isMe,
|
||||||
|
isScreen,
|
||||||
peer,
|
peer,
|
||||||
displayName,
|
displayName,
|
||||||
showPeerInfo,
|
showPeerInfo,
|
||||||
|
|
@ -181,59 +165,62 @@ class VideoView extends React.PureComponent
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<div className={classes.info}>
|
<div className={classes.info}>
|
||||||
{ advancedMode ?
|
<div className={classnames(classes.media,
|
||||||
<div className={classes.media}>
|
{
|
||||||
<div className={classes.box}>
|
hidden : !advancedMode
|
||||||
{ audioCodec ?
|
})}
|
||||||
<p>{audioCodec}</p>
|
>
|
||||||
:null
|
<div className={classes.box}>
|
||||||
}
|
{ audioCodec ?
|
||||||
|
<p>{audioCodec}</p>
|
||||||
|
:null
|
||||||
|
}
|
||||||
|
|
||||||
{ videoCodec ?
|
{ videoCodec ?
|
||||||
<p>{videoCodec} {videoProfile}</p>
|
<p>{videoCodec} {videoProfile}</p>
|
||||||
:null
|
:null
|
||||||
}
|
}
|
||||||
|
|
||||||
{ (videoVisible && videoWidth !== null) ?
|
{ (videoVisible && videoWidth !== null) ?
|
||||||
<p>{videoWidth}x{videoHeight}</p>
|
<p>{videoWidth}x{videoHeight}</p>
|
||||||
:null
|
:null
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
:null
|
</div>
|
||||||
}
|
|
||||||
|
|
||||||
{ showPeerInfo ?
|
{ showPeerInfo ?
|
||||||
<div className={classes.peer}>
|
<div className={classes.peer}>
|
||||||
{ isMe ?
|
<div className={classes.box}>
|
||||||
<EditableInput
|
{ isMe ?
|
||||||
value={displayName}
|
<EditableInput
|
||||||
propName='newDisplayName'
|
value={displayName}
|
||||||
className={classnames(classes.displayNameEdit, 'display-name')}
|
propName='newDisplayName'
|
||||||
classLoading='loading'
|
className={classes.displayNameEdit}
|
||||||
classInvalid='invalid'
|
classLoading='loading'
|
||||||
shouldBlockWhileLoading
|
classInvalid='invalid'
|
||||||
editProps={{
|
shouldBlockWhileLoading
|
||||||
maxLength : 30,
|
editProps={{
|
||||||
autoCorrect : false,
|
maxLength : 30,
|
||||||
spellCheck : false
|
autoCorrect : 'off',
|
||||||
}}
|
spellCheck : false
|
||||||
onChange={({ newDisplayName }) => onChangeDisplayName(newDisplayName)}
|
}}
|
||||||
/>
|
onChange={({ newDisplayName }) => onChangeDisplayName(newDisplayName)}
|
||||||
:
|
/>
|
||||||
<span className={classes.displayNameStatic}>
|
:
|
||||||
{displayName}
|
<span className={classes.displayNameStatic}>
|
||||||
</span>
|
{displayName}
|
||||||
}
|
|
||||||
|
|
||||||
{ advancedMode ?
|
|
||||||
<div className={classes.deviceInfo}>
|
|
||||||
<span>
|
|
||||||
{peer.device.name} {Math.floor(peer.device.version) || null}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
}
|
||||||
:null
|
|
||||||
}
|
{ advancedMode ?
|
||||||
|
<div className={classes.deviceInfo}>
|
||||||
|
<span>
|
||||||
|
{peer.device.name} {Math.floor(peer.device.version) || null}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
:null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
:null
|
:null
|
||||||
}
|
}
|
||||||
|
|
@ -243,7 +230,7 @@ class VideoView extends React.PureComponent
|
||||||
ref='video'
|
ref='video'
|
||||||
className={classnames(classes.video, {
|
className={classnames(classes.video, {
|
||||||
hidden : !videoVisible,
|
hidden : !videoVisible,
|
||||||
'is-me' : isMe,
|
'isMe' : isMe && !isScreen,
|
||||||
loading : videoProfile === 'none',
|
loading : videoProfile === 'none',
|
||||||
contain : videoContain
|
contain : videoContain
|
||||||
})}
|
})}
|
||||||
|
|
@ -334,8 +321,9 @@ class VideoView extends React.PureComponent
|
||||||
|
|
||||||
VideoView.propTypes =
|
VideoView.propTypes =
|
||||||
{
|
{
|
||||||
isMe : PropTypes.bool,
|
isMe : PropTypes.bool,
|
||||||
peer : PropTypes.oneOfType(
|
isScreen : PropTypes.bool,
|
||||||
|
peer : PropTypes.oneOfType(
|
||||||
[ appPropTypes.Me, appPropTypes.Peer ]),
|
[ appPropTypes.Me, appPropTypes.Peer ]),
|
||||||
displayName : PropTypes.string,
|
displayName : PropTypes.string,
|
||||||
showPeerInfo : PropTypes.bool,
|
showPeerInfo : PropTypes.bool,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import FullScreen from '../FullScreen';
|
||||||
import FullScreenIcon from '@material-ui/icons/Fullscreen';
|
import FullScreenIcon from '@material-ui/icons/Fullscreen';
|
||||||
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
|
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
|
||||||
|
|
||||||
const styles = () =>
|
const styles = (theme) =>
|
||||||
({
|
({
|
||||||
root :
|
root :
|
||||||
{
|
{
|
||||||
|
|
@ -27,7 +27,7 @@ const styles = () =>
|
||||||
flexDirection : 'row',
|
flexDirection : 'row',
|
||||||
justifyContent : 'flex-start',
|
justifyContent : 'flex-start',
|
||||||
alignItems : 'center',
|
alignItems : 'center',
|
||||||
padding : '0.4vmin'
|
padding : theme.spacing(1)
|
||||||
},
|
},
|
||||||
button :
|
button :
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ export const Room = PropTypes.shape(
|
||||||
url : PropTypes.string.isRequired,
|
url : PropTypes.string.isRequired,
|
||||||
state : PropTypes.oneOf(
|
state : PropTypes.oneOf(
|
||||||
[ 'new', 'connecting', 'connected', 'closed' ]).isRequired,
|
[ 'new', 'connecting', 'connected', 'closed' ]).isRequired,
|
||||||
activeSpeakerName : PropTypes.string
|
activeSpeakerId : PropTypes.string
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Device = PropTypes.shape(
|
export const Device = PropTypes.shape(
|
||||||
|
|
@ -17,7 +17,7 @@ export const Device = PropTypes.shape(
|
||||||
|
|
||||||
export const Me = PropTypes.shape(
|
export const Me = PropTypes.shape(
|
||||||
{
|
{
|
||||||
name : PropTypes.string.isRequired,
|
id : PropTypes.string.isRequired,
|
||||||
device : Device.isRequired,
|
device : Device.isRequired,
|
||||||
canSendMic : PropTypes.bool.isRequired,
|
canSendMic : PropTypes.bool.isRequired,
|
||||||
canSendWebcam : PropTypes.bool.isRequired,
|
canSendWebcam : PropTypes.bool.isRequired,
|
||||||
|
|
@ -26,30 +26,28 @@ export const Me = PropTypes.shape(
|
||||||
|
|
||||||
export const Producer = PropTypes.shape(
|
export const Producer = PropTypes.shape(
|
||||||
{
|
{
|
||||||
id : PropTypes.number.isRequired,
|
id : PropTypes.string.isRequired,
|
||||||
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired,
|
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired,
|
||||||
deviceLabel : PropTypes.string,
|
deviceLabel : PropTypes.string,
|
||||||
type : PropTypes.oneOf([ 'front', 'back', 'screen' ]),
|
type : PropTypes.oneOf([ 'front', 'back', 'screen' ]),
|
||||||
locallyPaused : PropTypes.bool.isRequired,
|
paused : PropTypes.bool.isRequired,
|
||||||
remotelyPaused : PropTypes.bool.isRequired,
|
track : PropTypes.any,
|
||||||
track : PropTypes.any,
|
codec : PropTypes.string.isRequired
|
||||||
codec : PropTypes.string.isRequired
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Peer = PropTypes.shape(
|
export const Peer = PropTypes.shape(
|
||||||
{
|
{
|
||||||
name : PropTypes.string.isRequired,
|
id : PropTypes.string.isRequired,
|
||||||
displayName : PropTypes.string,
|
displayName : PropTypes.string,
|
||||||
device : Device.isRequired,
|
device : Device.isRequired,
|
||||||
consumers : PropTypes.arrayOf(PropTypes.number).isRequired
|
consumers : PropTypes.arrayOf(PropTypes.string).isRequired
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Consumer = PropTypes.shape(
|
export const Consumer = PropTypes.shape(
|
||||||
{
|
{
|
||||||
id : PropTypes.number.isRequired,
|
id : PropTypes.string.isRequired,
|
||||||
peerName : PropTypes.string.isRequired,
|
peerId : PropTypes.string.isRequired,
|
||||||
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired,
|
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired,
|
||||||
supported : PropTypes.bool.isRequired,
|
|
||||||
locallyPaused : PropTypes.bool.isRequired,
|
locallyPaused : PropTypes.bool.isRequired,
|
||||||
remotelyPaused : PropTypes.bool.isRequired,
|
remotelyPaused : PropTypes.bool.isRequired,
|
||||||
profile : PropTypes.oneOf([ 'none', 'default', 'low', 'medium', 'high' ]),
|
profile : PropTypes.oneOf([ 'none', 'default', 'low', 'medium', 'high' ]),
|
||||||
|
|
@ -75,7 +73,7 @@ export const Message = PropTypes.shape(
|
||||||
export const FileEntryProps = PropTypes.shape(
|
export const FileEntryProps = PropTypes.shape(
|
||||||
{
|
{
|
||||||
data : PropTypes.shape({
|
data : PropTypes.shape({
|
||||||
name : PropTypes.string.isRequired,
|
id : PropTypes.string.isRequired,
|
||||||
picture : PropTypes.string,
|
picture : PropTypes.string,
|
||||||
file : PropTypes.shape({
|
file : PropTypes.shape({
|
||||||
magnet : PropTypes.string.isRequired
|
magnet : PropTypes.string.isRequired
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import bowser from 'bowser';
|
||||||
|
|
||||||
|
window.BB = bowser;
|
||||||
|
|
||||||
|
export default function()
|
||||||
|
{
|
||||||
|
const ua = navigator.userAgent;
|
||||||
|
const browser = bowser.getParser(ua);
|
||||||
|
|
||||||
|
let flag;
|
||||||
|
|
||||||
|
if (browser.satisfies({ chrome: '>=0', chromium: '>=0' }))
|
||||||
|
flag = 'chrome';
|
||||||
|
else if (browser.satisfies({ firefox: '>=0' }))
|
||||||
|
flag = 'firefox';
|
||||||
|
else if (browser.satisfies({ safari: '>=0' }))
|
||||||
|
flag = 'safari';
|
||||||
|
else if (browser.satisfies({ opera: '>=0' }))
|
||||||
|
flag = 'opera';
|
||||||
|
else if (browser.satisfies({ 'microsoft edge': '>=0' }))
|
||||||
|
flag = 'edge';
|
||||||
|
else
|
||||||
|
flag = 'unknown';
|
||||||
|
|
||||||
|
return {
|
||||||
|
flag,
|
||||||
|
name : browser.getBrowserName(),
|
||||||
|
version : browser.getBrowserVersion(),
|
||||||
|
bowser : browser
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import domready from 'domready';
|
import domready from 'domready';
|
||||||
import UrlParse from 'url-parse';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { getDeviceInfo } from 'mediasoup-client';
|
|
||||||
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 * as stateActions from './actions/stateActions';
|
import * as stateActions from './actions/stateActions';
|
||||||
import Room from './components/Room';
|
import Room from './components/Room';
|
||||||
import LoadingView from './components/LoadingView';
|
import LoadingView from './components/LoadingView';
|
||||||
|
|
@ -44,57 +43,48 @@ function run()
|
||||||
{
|
{
|
||||||
logger.debug('run() [environment:%s]', process.env.NODE_ENV);
|
logger.debug('run() [environment:%s]', process.env.NODE_ENV);
|
||||||
|
|
||||||
const peerName = randomString({ length: 8 }).toLowerCase();
|
const peerId = randomString({ length: 8 }).toLowerCase();
|
||||||
const urlParser = new UrlParse(window.location.href, true);
|
const urlParser = new URL(window.location);
|
||||||
|
const parameters = urlParser.searchParams;
|
||||||
|
|
||||||
let roomId = (urlParser.pathname).substr(1)
|
let roomId = (urlParser.pathname).substr(1);
|
||||||
? (urlParser.pathname).substr(1).toLowerCase() : urlParser.query.roomId.toLowerCase();
|
|
||||||
const produce = urlParser.query.produce !== 'false';
|
|
||||||
const useSimulcast = urlParser.query.simulcast === 'true';
|
|
||||||
|
|
||||||
if (!roomId)
|
if (!roomId)
|
||||||
|
roomId = parameters.get('roomId');
|
||||||
|
|
||||||
|
if (roomId)
|
||||||
|
roomId = roomId.toLowerCase();
|
||||||
|
else
|
||||||
{
|
{
|
||||||
roomId = randomString({ length: 8 }).toLowerCase();
|
roomId = randomString({ length: 8 }).toLowerCase();
|
||||||
|
|
||||||
urlParser.query.roomId = roomId;
|
parameters.set('roomId', roomId);
|
||||||
window.history.pushState('', '', urlParser.toString());
|
window.history.pushState('', '', urlParser.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the effective/shareable Room URL.
|
const produce = parameters.get('produce') !== 'false';
|
||||||
const roomUrlParser = new UrlParse(window.location.href, true);
|
const consume = parameters.get('consume') !== 'false';
|
||||||
|
const useSimulcast = parameters.get('simulcast') === 'true';
|
||||||
|
const forceTcp = parameters.get('forceTcp') === 'true';
|
||||||
|
|
||||||
for (const key of Object.keys(roomUrlParser.query))
|
const roomUrl = window.location.href.split('?')[0];
|
||||||
{
|
|
||||||
// Don't keep some custom params.
|
|
||||||
switch (key)
|
|
||||||
{
|
|
||||||
case 'roomId':
|
|
||||||
case 'simulcast':
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
delete roomUrlParser.query[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete roomUrlParser.hash;
|
|
||||||
|
|
||||||
const roomUrl = roomUrlParser.toString();
|
|
||||||
|
|
||||||
// Get current device.
|
// Get current device.
|
||||||
const device = getDeviceInfo();
|
const device = deviceInfo();
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
stateActions.setRoomUrl(roomUrl));
|
stateActions.setRoomUrl(roomUrl));
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
stateActions.setMe({
|
stateActions.setMe({
|
||||||
peerName,
|
peerId,
|
||||||
device,
|
device,
|
||||||
loginEnabled : window.config.loginEnabled
|
loginEnabled : window.config.loginEnabled
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
roomClient = new RoomClient(
|
roomClient = new RoomClient(
|
||||||
{ roomId, peerName, device, useSimulcast, produce });
|
{ roomId, peerId, device, useSimulcast, produce, consume, forceTcp });
|
||||||
|
|
||||||
global.CLIENT = roomClient;
|
global.CLIENT = roomClient;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,11 +51,30 @@ const consumers = (state = initialState, action) =>
|
||||||
return { ...state, [consumerId]: newConsumer };
|
return { ...state, [consumerId]: newConsumer };
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'SET_CONSUMER_EFFECTIVE_PROFILE':
|
case 'SET_CONSUMER_CURRENT_LAYERS':
|
||||||
{
|
{
|
||||||
const { consumerId, profile } = action.payload;
|
const { consumerId, spatialLayer, temporalLayer } = action.payload;
|
||||||
const consumer = state[consumerId];
|
const consumer = state[consumerId];
|
||||||
const newConsumer = { ...consumer, profile };
|
const newConsumer =
|
||||||
|
{
|
||||||
|
...consumer,
|
||||||
|
currentSpatialLayer : spatialLayer,
|
||||||
|
currentTemporalLayer : temporalLayer
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...state, [consumerId]: newConsumer };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'SET_CONSUMER_PREFERRED_LAYERS':
|
||||||
|
{
|
||||||
|
const { consumerId, spatialLayer, temporalLayer } = action.payload;
|
||||||
|
const consumer = state[consumerId];
|
||||||
|
const newConsumer =
|
||||||
|
{
|
||||||
|
...consumer,
|
||||||
|
preferredSpatialLayer : spatialLayer,
|
||||||
|
preferredTemporalLayer : temporalLayer
|
||||||
|
};
|
||||||
|
|
||||||
return { ...state, [consumerId]: newConsumer };
|
return { ...state, [consumerId]: newConsumer };
|
||||||
}
|
}
|
||||||
|
|
@ -69,6 +88,19 @@ const consumers = (state = initialState, action) =>
|
||||||
return { ...state, [consumerId]: newConsumer };
|
return { ...state, [consumerId]: newConsumer };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'SET_CONSUMER_SCORE':
|
||||||
|
{
|
||||||
|
const { consumerId, score } = action.payload;
|
||||||
|
const consumer = state[consumerId];
|
||||||
|
|
||||||
|
if (!consumer)
|
||||||
|
return state;
|
||||||
|
|
||||||
|
const newConsumer = { ...consumer, score };
|
||||||
|
|
||||||
|
return { ...state, [consumerId]: newConsumer };
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,17 @@ const files = (state = {}, action) =>
|
||||||
{
|
{
|
||||||
case 'ADD_FILE':
|
case 'ADD_FILE':
|
||||||
{
|
{
|
||||||
const { file } = action.payload;
|
const { peerId, magnetUri } = action.payload;
|
||||||
|
|
||||||
const newFile = {
|
const newFile = {
|
||||||
active : false,
|
active : false,
|
||||||
progress : 0,
|
progress : 0,
|
||||||
files : null,
|
files : null,
|
||||||
me : false,
|
peerId : peerId,
|
||||||
...file
|
magnetUri : magnetUri
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...state, [file.magnetUri]: newFile };
|
return { ...state, [magnetUri]: newFile };
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'ADD_FILE_HISTORY':
|
case 'ADD_FILE_HISTORY':
|
||||||
|
|
@ -30,7 +30,6 @@ const files = (state = {}, action) =>
|
||||||
active : false,
|
active : false,
|
||||||
progress : 0,
|
progress : 0,
|
||||||
files : null,
|
files : null,
|
||||||
me : false,
|
|
||||||
...file
|
...file
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
const initialState =
|
const initialState =
|
||||||
{
|
{
|
||||||
name : null,
|
id : null,
|
||||||
device : null,
|
device : null,
|
||||||
canSendMic : false,
|
canSendMic : false,
|
||||||
canSendWebcam : false,
|
canSendWebcam : false,
|
||||||
canShareScreen : false,
|
canShareScreen : false,
|
||||||
needExtension : false,
|
canShareFiles : false,
|
||||||
audioDevices : null,
|
audioDevices : null,
|
||||||
webcamDevices : null,
|
webcamDevices : null,
|
||||||
webcamInProgress : false,
|
webcamInProgress : false,
|
||||||
|
|
@ -24,14 +24,14 @@ const me = (state = initialState, action) =>
|
||||||
case 'SET_ME':
|
case 'SET_ME':
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
peerName,
|
peerId,
|
||||||
device,
|
device,
|
||||||
loginEnabled
|
loginEnabled
|
||||||
} = action.payload;
|
} = action.payload;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
name : peerName,
|
id : peerId,
|
||||||
device,
|
device,
|
||||||
loginEnabled
|
loginEnabled
|
||||||
};
|
};
|
||||||
|
|
@ -45,16 +45,20 @@ const me = (state = initialState, action) =>
|
||||||
|
|
||||||
case 'SET_MEDIA_CAPABILITIES':
|
case 'SET_MEDIA_CAPABILITIES':
|
||||||
{
|
{
|
||||||
const { canSendMic, canSendWebcam } = action.payload;
|
const {
|
||||||
|
canSendMic,
|
||||||
|
canSendWebcam,
|
||||||
|
canShareScreen,
|
||||||
|
canShareFiles
|
||||||
|
} = action.payload;
|
||||||
|
|
||||||
return { ...state, canSendMic, canSendWebcam };
|
return {
|
||||||
}
|
...state,
|
||||||
|
canSendMic,
|
||||||
case 'SET_SCREEN_CAPABILITIES':
|
canSendWebcam,
|
||||||
{
|
canShareScreen,
|
||||||
const { canShareScreen, needExtension } = action.payload;
|
canShareFiles
|
||||||
|
};
|
||||||
return { ...state, canShareScreen, needExtension };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'SET_AUDIO_DEVICES':
|
case 'SET_AUDIO_DEVICES':
|
||||||
|
|
|
||||||
|
|
@ -7,33 +7,33 @@ const peerVolumes = (state = initialState, action) =>
|
||||||
case 'SET_ME':
|
case 'SET_ME':
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
peerName
|
peerId
|
||||||
} = action.payload;
|
} = action.payload;
|
||||||
|
|
||||||
return { ...state, [peerName]: 0 };
|
return { ...state, [peerId]: 0 };
|
||||||
}
|
}
|
||||||
case 'ADD_PEER':
|
case 'ADD_PEER':
|
||||||
{
|
{
|
||||||
const { peer } = action.payload;
|
const { peer } = action.payload;
|
||||||
|
|
||||||
return { ...state, [peer.name]: 0 };
|
return { ...state, [peer.id]: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'REMOVE_PEER':
|
case 'REMOVE_PEER':
|
||||||
{
|
{
|
||||||
const { peerName } = action.payload;
|
const { peerId } = action.payload;
|
||||||
const newState = { ...state };
|
const newState = { ...state };
|
||||||
|
|
||||||
delete newState[peerName];
|
delete newState[peerId];
|
||||||
|
|
||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'SET_PEER_VOLUME':
|
case 'SET_PEER_VOLUME':
|
||||||
{
|
{
|
||||||
const { peerName, volume } = action.payload;
|
const { peerId, volume } = action.payload;
|
||||||
|
|
||||||
return { ...state, [peerName]: volume };
|
return { ...state, [peerId]: volume };
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import omit from 'lodash/omit';
|
|
||||||
|
|
||||||
const peer = (state = {}, action) =>
|
const peer = (state = {}, action) =>
|
||||||
{
|
{
|
||||||
switch (action.type)
|
switch (action.type)
|
||||||
|
|
@ -53,12 +51,17 @@ const peers = (state = {}, action) =>
|
||||||
{
|
{
|
||||||
case 'ADD_PEER':
|
case 'ADD_PEER':
|
||||||
{
|
{
|
||||||
return { ...state, [action.payload.peer.name]: peer(undefined, action) };
|
return { ...state, [action.payload.peer.id]: peer(undefined, action) };
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'REMOVE_PEER':
|
case 'REMOVE_PEER':
|
||||||
{
|
{
|
||||||
return omit(state, [ action.payload.peerName ]);
|
const { peerId } = action.payload;
|
||||||
|
const newState = { ...state };
|
||||||
|
|
||||||
|
delete newState[peerId];
|
||||||
|
|
||||||
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'SET_PEER_DISPLAY_NAME':
|
case 'SET_PEER_DISPLAY_NAME':
|
||||||
|
|
@ -69,25 +72,25 @@ const peers = (state = {}, action) =>
|
||||||
case 'SET_PEER_PICTURE':
|
case 'SET_PEER_PICTURE':
|
||||||
case 'ADD_CONSUMER':
|
case 'ADD_CONSUMER':
|
||||||
{
|
{
|
||||||
const oldPeer = state[action.payload.peerName];
|
const oldPeer = state[action.payload.peerId];
|
||||||
|
|
||||||
if (!oldPeer)
|
if (!oldPeer)
|
||||||
{
|
{
|
||||||
throw new Error('no Peer found');
|
throw new Error('no Peer found');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...state, [oldPeer.name]: peer(oldPeer, action) };
|
return { ...state, [oldPeer.id]: peer(oldPeer, action) };
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'REMOVE_CONSUMER':
|
case 'REMOVE_CONSUMER':
|
||||||
{
|
{
|
||||||
const oldPeer = state[action.payload.peerName];
|
const oldPeer = state[action.payload.peerId];
|
||||||
|
|
||||||
// NOTE: This means that the Peer was closed before, so it's ok.
|
// NOTE: This means that the Peer was closed before, so it's ok.
|
||||||
if (!oldPeer)
|
if (!oldPeer)
|
||||||
return state;
|
return state;
|
||||||
|
|
||||||
return { ...state, [oldPeer.name]: peer(oldPeer, action) };
|
return { ...state, [oldPeer.id]: peer(oldPeer, action) };
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,17 @@ const initialState =
|
||||||
state : 'new', // new/connecting/connected/disconnected/closed,
|
state : 'new', // new/connecting/connected/disconnected/closed,
|
||||||
locked : false,
|
locked : false,
|
||||||
lockedOut : false,
|
lockedOut : false,
|
||||||
audioSuspended : false,
|
activeSpeakerId : null,
|
||||||
activeSpeakerName : null,
|
|
||||||
torrentSupport : false,
|
torrentSupport : false,
|
||||||
showSettings : false,
|
showSettings : false,
|
||||||
fullScreenConsumer : null, // ConsumerID
|
fullScreenConsumer : null, // ConsumerID
|
||||||
windowConsumer : null, // ConsumerID
|
windowConsumer : null, // ConsumerID
|
||||||
toolbarsVisible : true,
|
toolbarsVisible : true,
|
||||||
mode : 'democratic',
|
mode : 'democratic',
|
||||||
selectedPeerName : null,
|
selectedPeerId : null,
|
||||||
spotlights : [],
|
spotlights : [],
|
||||||
settingsOpen : false
|
settingsOpen : false,
|
||||||
|
joined : false
|
||||||
};
|
};
|
||||||
|
|
||||||
const room = (state = initialState, action) =>
|
const room = (state = initialState, action) =>
|
||||||
|
|
@ -35,7 +35,7 @@ const room = (state = initialState, action) =>
|
||||||
if (roomState === 'connected')
|
if (roomState === 'connected')
|
||||||
return { ...state, state: roomState };
|
return { ...state, state: roomState };
|
||||||
else
|
else
|
||||||
return { ...state, state: roomState, activeSpeakerName: null };
|
return { ...state, state: roomState, activeSpeakerId: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'SET_ROOM_LOCKED':
|
case 'SET_ROOM_LOCKED':
|
||||||
|
|
@ -53,13 +53,6 @@ const room = (state = initialState, action) =>
|
||||||
return { ...state, lockedOut: true };
|
return { ...state, lockedOut: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'SET_AUDIO_SUSPENDED':
|
|
||||||
{
|
|
||||||
const { audioSuspended } = action.payload;
|
|
||||||
|
|
||||||
return { ...state, audioSuspended };
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'SET_SETTINGS_OPEN':
|
case 'SET_SETTINGS_OPEN':
|
||||||
{
|
{
|
||||||
const { settingsOpen } = action.payload;
|
const { settingsOpen } = action.payload;
|
||||||
|
|
@ -69,9 +62,9 @@ const room = (state = initialState, action) =>
|
||||||
|
|
||||||
case 'SET_ROOM_ACTIVE_SPEAKER':
|
case 'SET_ROOM_ACTIVE_SPEAKER':
|
||||||
{
|
{
|
||||||
const { peerName } = action.payload;
|
const { peerId } = action.payload;
|
||||||
|
|
||||||
return { ...state, activeSpeakerName: peerName };
|
return { ...state, activeSpeakerId: peerId };
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'FILE_SHARING_SUPPORTED':
|
case 'FILE_SHARING_SUPPORTED':
|
||||||
|
|
@ -88,6 +81,13 @@ const room = (state = initialState, action) =>
|
||||||
return { ...state, showSettings };
|
return { ...state, showSettings };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'TOGGLE_JOINED':
|
||||||
|
{
|
||||||
|
const joined = !state.joined;
|
||||||
|
|
||||||
|
return { ...state, joined };
|
||||||
|
}
|
||||||
|
|
||||||
case 'TOGGLE_FULLSCREEN_CONSUMER':
|
case 'TOGGLE_FULLSCREEN_CONSUMER':
|
||||||
{
|
{
|
||||||
const { consumerId } = action.payload;
|
const { consumerId } = action.payload;
|
||||||
|
|
@ -119,13 +119,13 @@ const room = (state = initialState, action) =>
|
||||||
|
|
||||||
case 'SET_SELECTED_PEER':
|
case 'SET_SELECTED_PEER':
|
||||||
{
|
{
|
||||||
const { selectedPeerName } = action.payload;
|
const { selectedPeerId } = action.payload;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
||||||
selectedPeerName : state.selectedPeerName === selectedPeerName ?
|
selectedPeerId : state.selectedPeerId === selectedPeerId ?
|
||||||
null : selectedPeerName
|
null : selectedPeerId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
export function getSignalingUrl(peerName, roomId)
|
export function getSignalingUrl(peerId, roomId)
|
||||||
{
|
{
|
||||||
const hostname = window.location.hostname;
|
const hostname = window.location.hostname;
|
||||||
|
|
||||||
const port = process.env.NODE_ENV !== 'production' ? window.config.developmentPort : window.location.port;
|
const port = process.env.NODE_ENV !== 'production' ? window.config.developmentPort : window.location.port;
|
||||||
|
|
||||||
const url = `wss://${hostname}:${port}/?peerName=${peerName}&roomId=${roomId}`;
|
const url = `wss://${hostname}:${port}/?peerId=${peerId}&roomId=${roomId}`;
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,29 @@
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
{
|
{
|
||||||
// oAuth2 conf
|
// oAuth2 conf
|
||||||
oauth2 :
|
auth :
|
||||||
{
|
{
|
||||||
clientID : '',
|
/*
|
||||||
clientSecret : '',
|
The issuer URL for OpenID Connect discovery
|
||||||
callbackURL : 'https://mYDomainName:port/auth-callback'
|
The OpenID Provider Configuration Document
|
||||||
|
could be discovered on:
|
||||||
|
issuerURL + '/.well-known/openid-configuration'
|
||||||
|
*/
|
||||||
|
issuerURL : 'https://example.com',
|
||||||
|
clientOptions :
|
||||||
|
{
|
||||||
|
client_id : '',
|
||||||
|
client_secret : '',
|
||||||
|
scope : 'openid email profile',
|
||||||
|
// where client.example.com is your multiparty meeting server
|
||||||
|
redirect_uri : 'https://client.example.com/auth/callback'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// Listening hostname for `gulp live|open`.
|
// session cookie secret
|
||||||
domain : 'localhost',
|
cookieSecret : 'T0P-S3cR3t_cook!e',
|
||||||
tls :
|
tls :
|
||||||
{
|
{
|
||||||
cert : `${__dirname}/../certs/mediasoup-demo.localhost.cert.pem`,
|
cert : `${__dirname}/../certs/mediasoup-demo.localhost.cert.pem`,
|
||||||
key : `${__dirname}/../certs/mediasoup-demo.localhost.key.pem`
|
key : `${__dirname}/../certs/mediasoup-demo.localhost.key.pem`
|
||||||
|
|
@ -19,59 +33,61 @@ 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,
|
||||||
// STUN/TURN
|
// Mediasoup settings
|
||||||
mediasoup :
|
mediasoup :
|
||||||
{
|
{
|
||||||
// mediasoup Server settings.
|
numWorkers : Object.keys(os.cpus()).length,
|
||||||
logLevel : 'warn',
|
// mediasoup Worker settings.
|
||||||
logTags :
|
worker :
|
||||||
[
|
{
|
||||||
'info',
|
logLevel : 'warn',
|
||||||
'ice',
|
logTags :
|
||||||
'dtls',
|
[
|
||||||
'rtp',
|
'info',
|
||||||
'srtp',
|
'ice',
|
||||||
'rtcp',
|
'dtls',
|
||||||
'rbe',
|
'rtp',
|
||||||
'rtx'
|
'srtp',
|
||||||
],
|
'rtcp'
|
||||||
rtcIPv4 : true,
|
],
|
||||||
rtcIPv6 : true,
|
rtcMinPort : 40000,
|
||||||
rtcAnnouncedIPv4 : null,
|
rtcMaxPort : 49999
|
||||||
rtcAnnouncedIPv6 : null,
|
},
|
||||||
rtcMinPort : 40000,
|
// mediasoup Router settings.
|
||||||
rtcMaxPort : 49999,
|
router :
|
||||||
// mediasoup Room codecs.
|
{
|
||||||
mediaCodecs :
|
// Router media codecs.
|
||||||
[
|
mediaCodecs :
|
||||||
{
|
[
|
||||||
kind : 'audio',
|
|
||||||
name : 'opus',
|
|
||||||
clockRate : 48000,
|
|
||||||
channels : 2,
|
|
||||||
parameters :
|
|
||||||
{
|
{
|
||||||
useinbandfec : 1
|
kind : 'audio',
|
||||||
}
|
mimeType : 'audio/opus',
|
||||||
},
|
clockRate : 48000,
|
||||||
// {
|
channels : 2
|
||||||
// kind : 'video',
|
},
|
||||||
// name : 'VP8',
|
|
||||||
// clockRate : 90000
|
|
||||||
// }
|
|
||||||
{
|
|
||||||
kind : 'video',
|
|
||||||
name : 'H264',
|
|
||||||
clockRate : 90000,
|
|
||||||
parameters :
|
|
||||||
{
|
{
|
||||||
'packetization-mode' : 1,
|
kind : 'video',
|
||||||
'profile-level-id' : '42e01f',
|
mimeType : 'video/h264',
|
||||||
'level-asymmetry-allowed' : 1
|
clockRate : 90000,
|
||||||
|
parameters :
|
||||||
|
{
|
||||||
|
'packetization-mode' : 1,
|
||||||
|
'profile-level-id' : '42e01f',
|
||||||
|
'level-asymmetry-allowed' : 1,
|
||||||
|
'x-google-start-bitrate' : 1000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
],
|
},
|
||||||
// mediasoup per Peer max sending bitrate (in bps).
|
// mediasoup WebRtcTransport settings.
|
||||||
maxBitrate : 500000
|
webRtcTransport :
|
||||||
|
{
|
||||||
|
listenIps :
|
||||||
|
[
|
||||||
|
{ ip: '1.2.3.4', announcedIp: null }
|
||||||
|
],
|
||||||
|
maxIncomingBitrate : 1500000,
|
||||||
|
initialAvailableOutgoingBitrate : 1000000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
1400
server/lib/Room.js
1400
server/lib/Room.js
File diff suppressed because it is too large
Load Diff
|
|
@ -54,7 +54,7 @@ function handleRoom(room, stream)
|
||||||
Object.assign({}, baseEvent,
|
Object.assign({}, baseEvent,
|
||||||
{
|
{
|
||||||
event : 'room.newpeer',
|
event : 'room.newpeer',
|
||||||
peerName : peer.name,
|
peerId : peer.id,
|
||||||
rtpCapabilities : peer.rtpCapabilities
|
rtpCapabilities : peer.rtpCapabilities
|
||||||
}),
|
}),
|
||||||
stream);
|
stream);
|
||||||
|
|
@ -67,7 +67,7 @@ function handlePeer(peer, baseEvent, stream)
|
||||||
{
|
{
|
||||||
baseEvent = Object.assign({}, baseEvent,
|
baseEvent = Object.assign({}, baseEvent,
|
||||||
{
|
{
|
||||||
peerName : peer.name
|
peerId : peer.id
|
||||||
});
|
});
|
||||||
|
|
||||||
peer.on('close', (originator) =>
|
peer.on('close', (originator) =>
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,24 @@
|
||||||
{
|
{
|
||||||
"name": "multiparty-meeting-server",
|
"name": "multiparty-meeting-server",
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "multiparty meeting server",
|
"description": "multiparty meeting server",
|
||||||
"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",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"awaitqueue": "^1.0.0",
|
||||||
"base-64": "^0.1.0",
|
"base-64": "^0.1.0",
|
||||||
"colors": "^1.1.2",
|
"colors": "^1.1.2",
|
||||||
"compression": "^1.7.3",
|
"compression": "^1.7.3",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"express": "^4.16.3",
|
"express": "^4.16.3",
|
||||||
"mediasoup": "^2.6.11",
|
"express-session": "^1.16.1",
|
||||||
"passport-dataporten": "^1.3.0",
|
"mediasoup": "^3.0.12",
|
||||||
"socket.io": "^2.1.1"
|
"openid-client": "^2.5.0",
|
||||||
|
"passport": "^0.4.0",
|
||||||
|
"socket.io": "^2.1.1",
|
||||||
|
"spdy": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
|
|
|
||||||
470
server/server.js
470
server/server.js
|
|
@ -6,15 +6,20 @@ process.title = 'multiparty-meeting-server';
|
||||||
|
|
||||||
const config = require('./config/config');
|
const config = require('./config/config');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const https = require('https');
|
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
|
const spdy = require('spdy');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const compression = require('compression');
|
const compression = require('compression');
|
||||||
|
const mediasoup = require('mediasoup');
|
||||||
|
const AwaitQueue = require('awaitqueue');
|
||||||
const Logger = require('./lib/Logger');
|
const Logger = require('./lib/Logger');
|
||||||
const Room = require('./lib/Room');
|
const Room = require('./lib/Room');
|
||||||
const Dataporten = require('passport-dataporten');
|
|
||||||
const utils = require('./util');
|
const utils = require('./util');
|
||||||
const base64 = require('base-64');
|
const base64 = require('base-64');
|
||||||
|
// auth
|
||||||
|
const passport = require('passport');
|
||||||
|
const { Issuer, Strategy } = require('openid-client');
|
||||||
|
const session = require('express-session');
|
||||||
|
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
console.log('- process.env.DEBUG:', process.env.DEBUG);
|
console.log('- process.env.DEBUG:', process.env.DEBUG);
|
||||||
|
|
@ -22,11 +27,18 @@ console.log('- config.mediasoup.logLevel:', config.mediasoup.logLevel);
|
||||||
console.log('- config.mediasoup.logTags:', config.mediasoup.logTags);
|
console.log('- config.mediasoup.logTags:', config.mediasoup.logTags);
|
||||||
/* eslint-enable no-console */
|
/* eslint-enable no-console */
|
||||||
|
|
||||||
// Start the mediasoup server.
|
|
||||||
const mediaServer = require('./mediasoup');
|
|
||||||
|
|
||||||
const logger = new Logger();
|
const logger = new Logger();
|
||||||
|
|
||||||
|
const queue = new AwaitQueue();
|
||||||
|
|
||||||
|
// mediasoup Workers.
|
||||||
|
// @type {Array<mediasoup.Worker>}
|
||||||
|
const mediasoupWorkers = [];
|
||||||
|
|
||||||
|
// Index of next mediasoup Worker to use.
|
||||||
|
// @type {Number}
|
||||||
|
let nextMediasoupWorkerIdx = 0;
|
||||||
|
|
||||||
// Map of Room instances indexed by roomId.
|
// Map of Room instances indexed by roomId.
|
||||||
const rooms = new Map();
|
const rooms = new Map();
|
||||||
|
|
||||||
|
|
@ -38,147 +50,377 @@ const tls =
|
||||||
};
|
};
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
let httpsServer;
|
||||||
|
let oidcClient;
|
||||||
|
let oidcStrategy;
|
||||||
|
|
||||||
app.use(compression());
|
passport.serializeUser((user, done) =>
|
||||||
|
|
||||||
const dataporten = new Dataporten.Setup(config.oauth2);
|
|
||||||
|
|
||||||
app.all('*', (req, res, next) =>
|
|
||||||
{
|
{
|
||||||
if (req.secure)
|
done(null, user);
|
||||||
|
});
|
||||||
|
|
||||||
|
passport.deserializeUser((user, done) =>
|
||||||
|
{
|
||||||
|
done(null, user);
|
||||||
|
});
|
||||||
|
|
||||||
|
const auth = config.auth;
|
||||||
|
|
||||||
|
async function run()
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
typeof(auth) !== 'undefined' &&
|
||||||
|
typeof(auth.issuerURL) !== 'undefined' &&
|
||||||
|
typeof(auth.clientOptions) !== 'undefined'
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return next();
|
Issuer.discover(auth.issuerURL).then( async (oidcIssuer) =>
|
||||||
|
{
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
logger.error('Auth is not configure properly!');
|
||||||
|
|
||||||
|
// Run a mediasoup Worker.
|
||||||
|
await runMediasoupWorkers();
|
||||||
|
|
||||||
|
// Run HTTPS server.
|
||||||
|
await runHttpsServer();
|
||||||
|
|
||||||
|
// Run WebSocketServer.
|
||||||
|
await runWebSocketServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
res.redirect(`https://${req.hostname}${req.url}`);
|
// Log rooms status every 30 seconds.
|
||||||
});
|
setInterval(() =>
|
||||||
|
|
||||||
app.use(dataporten.passport.initialize());
|
|
||||||
app.use(dataporten.passport.session());
|
|
||||||
|
|
||||||
app.get('/login', (req, res, next) =>
|
|
||||||
{
|
|
||||||
dataporten.passport.authenticate('dataporten', {
|
|
||||||
state : base64.encode(JSON.stringify({
|
|
||||||
roomId : req.query.roomId,
|
|
||||||
peerName : req.query.peerName,
|
|
||||||
code : utils.random(10)
|
|
||||||
}))
|
|
||||||
|
|
||||||
})(req, res, next);
|
|
||||||
});
|
|
||||||
|
|
||||||
dataporten.setupLogout(app, '/logout');
|
|
||||||
|
|
||||||
app.get('/', (req, res) =>
|
|
||||||
{
|
|
||||||
res.sendFile(`${__dirname}/public/chooseRoom.html`);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get(
|
|
||||||
'/auth-callback',
|
|
||||||
dataporten.passport.authenticate('dataporten', { failureRedirect: '/login' }),
|
|
||||||
(req, res) =>
|
|
||||||
{
|
{
|
||||||
const state = JSON.parse(base64.decode(req.query.state));
|
for (const room of rooms.values())
|
||||||
|
|
||||||
if (rooms.has(state.roomId))
|
|
||||||
{
|
{
|
||||||
const data =
|
room.logStatus();
|
||||||
|
}
|
||||||
|
}, 120000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupAuth(oidcIssuer)
|
||||||
|
{
|
||||||
|
oidcClient = new oidcIssuer.Client(auth.clientOptions);
|
||||||
|
|
||||||
|
// ... any authorization request parameters go here
|
||||||
|
// client_id defaults to client.client_id
|
||||||
|
// redirect_uri defaults to client.redirect_uris[0]
|
||||||
|
// response type defaults to client.response_types[0], then 'code'
|
||||||
|
// scope defaults to 'openid'
|
||||||
|
const params = auth.clientOptions;
|
||||||
|
|
||||||
|
// optional, defaults to false, when true req is passed as a first
|
||||||
|
// argument to verify fn
|
||||||
|
const passReqToCallback = false;
|
||||||
|
|
||||||
|
// optional, defaults to false, when true the code_challenge_method will be
|
||||||
|
// resolved from the issuer configuration, instead of true you may provide
|
||||||
|
// any of the supported values directly, i.e. "S256" (recommended) or "plain"
|
||||||
|
const usePKCE = false;
|
||||||
|
const client = oidcClient;
|
||||||
|
|
||||||
|
oidcStrategy = new Strategy(
|
||||||
|
{ client, params, passReqToCallback, usePKCE },
|
||||||
|
(tokenset, userinfo, done) =>
|
||||||
|
{
|
||||||
|
const user =
|
||||||
{
|
{
|
||||||
peerName : state.peerName,
|
id : tokenset.claims.sub,
|
||||||
name : req.user.data.displayName,
|
provider : tokenset.claims.iss,
|
||||||
picture : req.user.data.photos[0]
|
_userinfo : userinfo,
|
||||||
|
_claims : tokenset.claims
|
||||||
};
|
};
|
||||||
|
|
||||||
const room = rooms.get(state.roomId);
|
if (typeof(userinfo.picture) !== 'undefined')
|
||||||
|
{
|
||||||
|
if (!userinfo.picture.match(/^http/g))
|
||||||
|
{
|
||||||
|
user.Photos = [ { value: `data:image/jpeg;base64, ${userinfo.picture}` } ];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
user.Photos = [ { value: userinfo.picture } ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
room.authCallback(data);
|
if (userinfo.nickname != null)
|
||||||
|
{
|
||||||
|
user.displayName = userinfo.nickname;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userinfo.name != null)
|
||||||
|
{
|
||||||
|
user.displayName = userinfo.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userinfo.email != null)
|
||||||
|
{
|
||||||
|
user.emails = [ { value: userinfo.email } ];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userinfo.given_name != null)
|
||||||
|
{
|
||||||
|
user.name = { givenName: userinfo.given_name };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userinfo.family_name != null)
|
||||||
|
{
|
||||||
|
user.name = { familyName: userinfo.family_name };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userinfo.middle_name != null)
|
||||||
|
{
|
||||||
|
user.name = { middleName: userinfo.middle_name };
|
||||||
|
}
|
||||||
|
|
||||||
|
return done(null, user);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
passport.use('oidc', oidcStrategy);
|
||||||
|
|
||||||
|
app.use(session({
|
||||||
|
secret : config.cookieSecret,
|
||||||
|
resave : true,
|
||||||
|
saveUninitialized : true,
|
||||||
|
cookie : { secure: true }
|
||||||
|
}));
|
||||||
|
|
||||||
|
app.use(passport.initialize());
|
||||||
|
app.use(passport.session());
|
||||||
|
|
||||||
|
// login
|
||||||
|
app.get('/auth/login', (req, res, next) =>
|
||||||
|
{
|
||||||
|
passport.authenticate('oidc', {
|
||||||
|
state : base64.encode(JSON.stringify({
|
||||||
|
roomId : req.query.roomId,
|
||||||
|
peerId : req.query.peerId,
|
||||||
|
code : utils.random(10)
|
||||||
|
}))
|
||||||
|
})(req, res, next);
|
||||||
|
});
|
||||||
|
|
||||||
|
// logout
|
||||||
|
app.get('/auth/logout', (req, res) =>
|
||||||
|
{
|
||||||
|
req.logout();
|
||||||
|
res.redirect('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
// callback
|
||||||
|
app.get(
|
||||||
|
'/auth/callback',
|
||||||
|
passport.authenticate('oidc', { failureRedirect: '/auth/login' }),
|
||||||
|
(req, res) =>
|
||||||
|
{
|
||||||
|
const state = JSON.parse(base64.decode(req.query.state));
|
||||||
|
|
||||||
|
if (rooms.has(state.roomId))
|
||||||
|
{
|
||||||
|
let displayName;
|
||||||
|
let photo;
|
||||||
|
|
||||||
|
if (req.user != null)
|
||||||
|
{
|
||||||
|
if (req.user.displayName != null)
|
||||||
|
displayName = req.user.displayName;
|
||||||
|
else
|
||||||
|
displayName = '';
|
||||||
|
|
||||||
|
if (
|
||||||
|
req.user.Photos != null &&
|
||||||
|
req.user.Photos[0] != null &&
|
||||||
|
req.user.Photos[0].value != null
|
||||||
|
)
|
||||||
|
photo = req.user.Photos[0].value;
|
||||||
|
else
|
||||||
|
photo = '/static/media/buddy.403cb9f6.svg';
|
||||||
|
}
|
||||||
|
|
||||||
|
const data =
|
||||||
|
{
|
||||||
|
peerId : state.peerId,
|
||||||
|
displayName : displayName,
|
||||||
|
picture : photo
|
||||||
|
};
|
||||||
|
|
||||||
|
const room = rooms.get(state.roomId);
|
||||||
|
|
||||||
|
room.authCallback(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send('');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runHttpsServer()
|
||||||
|
{
|
||||||
|
app.use(compression());
|
||||||
|
|
||||||
|
app.use('/.well-known/acme-challenge', express.static('public/.well-known/acme-challenge'));
|
||||||
|
|
||||||
|
app.all('*', (req, res, next) =>
|
||||||
|
{
|
||||||
|
if (req.secure)
|
||||||
|
{
|
||||||
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
res.send('');
|
res.redirect(`https://${req.hostname}${req.url}`);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
// Serve all files in the public folder as static files.
|
app.get('/', (req, res) =>
|
||||||
app.use(express.static('public'));
|
|
||||||
|
|
||||||
app.use((req, res) => res.sendFile(`${__dirname}/public/index.html`));
|
|
||||||
|
|
||||||
const httpsServer = https.createServer(tls, app);
|
|
||||||
|
|
||||||
httpsServer.listen(config.listeningPort, '0.0.0.0', () =>
|
|
||||||
{
|
|
||||||
logger.info('Server running on port: ', config.listeningPort);
|
|
||||||
});
|
|
||||||
|
|
||||||
const httpServer = http.createServer(app);
|
|
||||||
|
|
||||||
httpServer.listen(config.listeningRedirectPort, '0.0.0.0', () =>
|
|
||||||
{
|
|
||||||
logger.info('Server redirecting port: ', config.listeningRedirectPort);
|
|
||||||
});
|
|
||||||
|
|
||||||
const io = require('socket.io')(httpsServer);
|
|
||||||
|
|
||||||
// Handle connections from clients.
|
|
||||||
io.on('connection', (socket) =>
|
|
||||||
{
|
|
||||||
const { roomId, peerName } = socket.handshake.query;
|
|
||||||
|
|
||||||
if (!roomId || !peerName)
|
|
||||||
{
|
{
|
||||||
logger.warn('connection request without roomId and/or peerName');
|
res.sendFile(`${__dirname}/public/chooseRoom.html`);
|
||||||
|
});
|
||||||
|
|
||||||
socket.disconnect(true);
|
// Serve all files in the public folder as static files.
|
||||||
|
app.use(express.static('public'));
|
||||||
|
|
||||||
return;
|
app.use((req, res) => res.sendFile(`${__dirname}/public/index.html`));
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(
|
httpsServer = spdy.createServer(tls, app);
|
||||||
'connection request [roomId:"%s", peerName:"%s"]', roomId, peerName);
|
|
||||||
|
|
||||||
let room;
|
httpsServer.listen(config.listeningPort, '0.0.0.0', () =>
|
||||||
|
|
||||||
// If an unknown roomId, create a new Room.
|
|
||||||
if (!rooms.has(roomId))
|
|
||||||
{
|
{
|
||||||
logger.info('creating a new Room [roomId:"%s"]', roomId);
|
logger.info('Server running on port: ', config.listeningPort);
|
||||||
|
});
|
||||||
|
|
||||||
try
|
const httpServer = http.createServer(app);
|
||||||
{
|
|
||||||
room = new Room(roomId, mediaServer, io);
|
|
||||||
|
|
||||||
global.APP_ROOM = room;
|
httpServer.listen(config.listeningRedirectPort, '0.0.0.0', () =>
|
||||||
}
|
{
|
||||||
catch (error)
|
logger.info('Server redirecting port: ', config.listeningRedirectPort);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a protoo WebSocketServer to allow WebSocket connections from browsers.
|
||||||
|
*/
|
||||||
|
async function runWebSocketServer()
|
||||||
|
{
|
||||||
|
const io = require('socket.io')(httpsServer);
|
||||||
|
|
||||||
|
// Handle connections from clients.
|
||||||
|
io.on('connection', (socket) =>
|
||||||
|
{
|
||||||
|
const { roomId, peerId } = socket.handshake.query;
|
||||||
|
|
||||||
|
if (!roomId || !peerId)
|
||||||
{
|
{
|
||||||
logger.error('error creating a new Room: %s', error);
|
logger.warn('connection request without roomId and/or peerId');
|
||||||
|
|
||||||
socket.disconnect(true);
|
socket.disconnect(true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logStatusTimer = setInterval(() =>
|
logger.info(
|
||||||
|
'connection request [roomId:"%s", peerId:"%s"]', roomId, peerId);
|
||||||
|
|
||||||
|
queue.push(async () =>
|
||||||
{
|
{
|
||||||
room.logStatus();
|
const room = await getOrCreateRoom({ roomId });
|
||||||
}, 30000);
|
|
||||||
|
room.handleConnection({ peerId, socket });
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
{
|
||||||
|
logger.error('room creation or room joining failed:%o', error);
|
||||||
|
|
||||||
|
socket.disconnect(true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch as many mediasoup Workers as given in the configuration file.
|
||||||
|
*/
|
||||||
|
async function runMediasoupWorkers()
|
||||||
|
{
|
||||||
|
const { numWorkers } = config.mediasoup;
|
||||||
|
|
||||||
|
logger.info('running %d mediasoup Workers...', numWorkers);
|
||||||
|
|
||||||
|
for (let i = 0; i < numWorkers; ++i)
|
||||||
|
{
|
||||||
|
const worker = await mediasoup.createWorker(
|
||||||
|
{
|
||||||
|
logLevel : config.mediasoup.worker.logLevel,
|
||||||
|
logTags : config.mediasoup.worker.logTags,
|
||||||
|
rtcMinPort : config.mediasoup.worker.rtcMinPort,
|
||||||
|
rtcMaxPort : config.mediasoup.worker.rtcMaxPort
|
||||||
|
});
|
||||||
|
|
||||||
|
worker.on('died', () =>
|
||||||
|
{
|
||||||
|
logger.error(
|
||||||
|
'mediasoup Worker died, exiting in 2 seconds... [pid:%d]', worker.pid);
|
||||||
|
|
||||||
|
setTimeout(() => process.exit(1), 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
mediasoupWorkers.push(worker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get next mediasoup Worker.
|
||||||
|
*/
|
||||||
|
function getMediasoupWorker()
|
||||||
|
{
|
||||||
|
const worker = mediasoupWorkers[nextMediasoupWorkerIdx];
|
||||||
|
|
||||||
|
if (++nextMediasoupWorkerIdx === mediasoupWorkers.length)
|
||||||
|
nextMediasoupWorkerIdx = 0;
|
||||||
|
|
||||||
|
return worker;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a Room instance (or create one if it does not exist).
|
||||||
|
*/
|
||||||
|
async function getOrCreateRoom({ roomId })
|
||||||
|
{
|
||||||
|
let room = rooms.get(roomId);
|
||||||
|
|
||||||
|
// If the Room does not exist create a new one.
|
||||||
|
if (!room)
|
||||||
|
{
|
||||||
|
logger.info('creating a new Room [roomId:%s]', roomId);
|
||||||
|
|
||||||
|
const mediasoupWorker = getMediasoupWorker();
|
||||||
|
|
||||||
|
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);
|
|
||||||
clearInterval(logStatusTimer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
room = rooms.get(roomId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.room = roomId;
|
return room;
|
||||||
|
}
|
||||||
|
|
||||||
room.handleConnection(peerName, socket);
|
run();
|
||||||
});
|
|
||||||
Loading…
Reference in New Issue