Merge remote-tracking branch 'upstream/develop' into mm-exporter
commit
85c9062f86
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 GÉANT Association
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -134,7 +134,7 @@ This started as a fork of the [work](https://github.com/versatica/mediasoup-demo
|
|||
|
||||
## License
|
||||
|
||||
MIT
|
||||
MIT License (see `LICENSE.md`)
|
||||
|
||||
|
||||
Contributions to this work were made on behalf of the GÉANT project, a project that has received funding from the European Union’s Horizon 2020 research and innovation programme under Grant Agreement No. 731122 (GN4-2). On behalf of GÉANT project, GÉANT Association is the sole owner of the copyright in all material which was developed by a member of the GÉANT project.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "multiparty-meeting",
|
||||
"version": "3.2.0",
|
||||
"version": "3.3.0",
|
||||
"private": true,
|
||||
"description": "multiparty meeting service",
|
||||
"author": "Håvar Aambø Fosstveit <h@fosstveit.net>",
|
||||
|
|
|
|||
|
|
@ -233,6 +233,9 @@ export default class RoomClient
|
|||
// Local webcam mediasoup Producer.
|
||||
this._webcamProducer = null;
|
||||
|
||||
// Extra videos being produced
|
||||
this._extraVideoProducers = new Map();
|
||||
|
||||
// Map of webcam MediaDeviceInfos indexed by deviceId.
|
||||
// @type {Map<String, MediaDeviceInfos>}
|
||||
this._webcams = {};
|
||||
|
|
@ -516,16 +519,21 @@ export default class RoomClient
|
|||
|
||||
_soundNotification()
|
||||
{
|
||||
const alertPromise = this._soundAlert.play();
|
||||
const { notificationSounds } = store.getState().settings;
|
||||
|
||||
if (alertPromise !== undefined)
|
||||
if (notificationSounds)
|
||||
{
|
||||
alertPromise
|
||||
.then()
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('_soundAlert.play() | failed: %o', error);
|
||||
});
|
||||
const alertPromise = this._soundAlert.play();
|
||||
|
||||
if (alertPromise !== undefined)
|
||||
{
|
||||
alertPromise
|
||||
.then()
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('_soundAlert.play() | failed: %o', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -844,62 +852,6 @@ export default class RoomClient
|
|||
}
|
||||
}
|
||||
|
||||
async getServerHistory()
|
||||
{
|
||||
logger.debug('getServerHistory()');
|
||||
|
||||
try
|
||||
{
|
||||
const {
|
||||
chatHistory,
|
||||
fileHistory,
|
||||
lastNHistory,
|
||||
locked,
|
||||
lobbyPeers,
|
||||
accessCode
|
||||
} = await this.sendRequest('serverHistory');
|
||||
|
||||
(chatHistory.length > 0) && store.dispatch(
|
||||
chatActions.addChatHistory(chatHistory));
|
||||
|
||||
(fileHistory.length > 0) && store.dispatch(
|
||||
fileActions.addFileHistory(fileHistory));
|
||||
|
||||
if (lastNHistory.length > 0)
|
||||
{
|
||||
logger.debug('Got lastNHistory');
|
||||
|
||||
// Remove our self from list
|
||||
const index = lastNHistory.indexOf(this._peerId);
|
||||
|
||||
lastNHistory.splice(index, 1);
|
||||
|
||||
this._spotlights.addSpeakerList(lastNHistory);
|
||||
}
|
||||
|
||||
locked ?
|
||||
store.dispatch(roomActions.setRoomLocked()) :
|
||||
store.dispatch(roomActions.setRoomUnLocked());
|
||||
|
||||
(lobbyPeers.length > 0) && lobbyPeers.forEach((peer) =>
|
||||
{
|
||||
store.dispatch(
|
||||
lobbyPeerActions.addLobbyPeer(peer.peerId));
|
||||
store.dispatch(
|
||||
lobbyPeerActions.setLobbyPeerDisplayName(peer.displayName, peer.peerId));
|
||||
store.dispatch(
|
||||
lobbyPeerActions.setLobbyPeerPicture(peer.picture));
|
||||
});
|
||||
|
||||
(accessCode != null) && store.dispatch(
|
||||
roomActions.setAccessCode(accessCode));
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('getServerHistory() | failed: %o', error);
|
||||
}
|
||||
}
|
||||
|
||||
async muteMic()
|
||||
{
|
||||
logger.debug('muteMic()');
|
||||
|
|
@ -1561,30 +1513,30 @@ export default class RoomClient
|
|||
}
|
||||
}
|
||||
|
||||
async sendRaiseHandState(state)
|
||||
async setRaisedHand(raisedHand)
|
||||
{
|
||||
logger.debug('sendRaiseHandState: ', state);
|
||||
logger.debug('setRaisedHand: ', raisedHand);
|
||||
|
||||
store.dispatch(
|
||||
meActions.setMyRaiseHandStateInProgress(true));
|
||||
meActions.setRaisedHandInProgress(true));
|
||||
|
||||
try
|
||||
{
|
||||
await this.sendRequest('raiseHand', { raiseHandState: state });
|
||||
await this.sendRequest('raisedHand', { raisedHand });
|
||||
|
||||
store.dispatch(
|
||||
meActions.setMyRaiseHandState(state));
|
||||
meActions.setRaisedHand(raisedHand));
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('sendRaiseHandState() | failed: %o', error);
|
||||
logger.error('setRaisedHand() | [error:"%o"]', error);
|
||||
|
||||
// We need to refresh the component for it to render changed state
|
||||
store.dispatch(meActions.setMyRaiseHandState(!state));
|
||||
store.dispatch(meActions.setRaisedHand(!raisedHand));
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
meActions.setMyRaiseHandStateInProgress(false));
|
||||
meActions.setRaisedHandInProgress(false));
|
||||
}
|
||||
|
||||
async setMaxSendingSpatialLayer(spatialLayer)
|
||||
|
|
@ -2053,6 +2005,8 @@ export default class RoomClient
|
|||
store.dispatch(
|
||||
roomActions.setToolbarsVisible(true));
|
||||
|
||||
this._soundNotification();
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : intl.formatMessage({
|
||||
|
|
@ -2064,6 +2018,43 @@ export default class RoomClient
|
|||
break;
|
||||
}
|
||||
|
||||
case 'parkedPeers':
|
||||
{
|
||||
const { lobbyPeers } = notification.data;
|
||||
|
||||
if (lobbyPeers.length > 0)
|
||||
{
|
||||
lobbyPeers.forEach((peer) =>
|
||||
{
|
||||
store.dispatch(
|
||||
lobbyPeerActions.addLobbyPeer(peer.peerId));
|
||||
store.dispatch(
|
||||
lobbyPeerActions.setLobbyPeerDisplayName(
|
||||
peer.displayName,
|
||||
peer.peerId
|
||||
)
|
||||
);
|
||||
store.dispatch(
|
||||
lobbyPeerActions.setLobbyPeerPicture(peer.picture));
|
||||
});
|
||||
|
||||
store.dispatch(
|
||||
roomActions.setToolbarsVisible(true));
|
||||
|
||||
this._soundNotification();
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : intl.formatMessage({
|
||||
id : 'room.newLobbyPeer',
|
||||
defaultMessage : 'New participant entered the lobby'
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'lobby:peerClosed':
|
||||
{
|
||||
const { peerId } = notification.data;
|
||||
|
|
@ -2222,6 +2213,48 @@ export default class RoomClient
|
|||
break;
|
||||
}
|
||||
|
||||
case 'raisedHand':
|
||||
{
|
||||
const { peerId, raisedHand } = notification.data;
|
||||
|
||||
store.dispatch(peerActions.setPeerRaisedHand(peerId, raisedHand));
|
||||
|
||||
const { displayName } = store.getState().peers[peerId];
|
||||
|
||||
let text;
|
||||
|
||||
if (raisedHand)
|
||||
{
|
||||
text = intl.formatMessage({
|
||||
id : 'room.raisedHand',
|
||||
defaultMessage : '{displayName} raised their hand'
|
||||
}, {
|
||||
displayName
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
text = intl.formatMessage({
|
||||
id : 'room.loweredHand',
|
||||
defaultMessage : '{displayName} put their hand down'
|
||||
}, {
|
||||
displayName
|
||||
});
|
||||
}
|
||||
|
||||
if (displayName)
|
||||
{
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text
|
||||
}));
|
||||
}
|
||||
|
||||
this._soundNotification();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'chatMessage':
|
||||
{
|
||||
const { peerId, chatMessage } = notification.data;
|
||||
|
|
@ -2318,6 +2351,8 @@ export default class RoomClient
|
|||
store.dispatch(
|
||||
peerActions.addPeer({ id, displayName, picture, roles, consumers: [] }));
|
||||
|
||||
this._soundNotification();
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : intl.formatMessage({
|
||||
|
|
@ -2474,7 +2509,9 @@ export default class RoomClient
|
|||
{
|
||||
text : intl.formatMessage({
|
||||
id : 'roles.gotRole',
|
||||
defaultMessage : `You got the role: ${role}`
|
||||
defaultMessage : 'You got the role: {role}'
|
||||
}, {
|
||||
role
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
|
@ -2496,7 +2533,9 @@ export default class RoomClient
|
|||
{
|
||||
text : intl.formatMessage({
|
||||
id : 'roles.lostRole',
|
||||
defaultMessage : `You lost the role: ${role}`
|
||||
defaultMessage : 'You lost the role: {role}'
|
||||
}, {
|
||||
role
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
|
@ -2694,7 +2733,13 @@ export default class RoomClient
|
|||
peers,
|
||||
tracker,
|
||||
permissionsFromRoles,
|
||||
userRoles
|
||||
userRoles,
|
||||
chatHistory,
|
||||
fileHistory,
|
||||
lastNHistory,
|
||||
locked,
|
||||
lobbyPeers,
|
||||
accessCode
|
||||
} = await this.sendRequest(
|
||||
'join',
|
||||
{
|
||||
|
|
@ -2749,6 +2794,38 @@ export default class RoomClient
|
|||
this.updateSpotlights(spotlights);
|
||||
});
|
||||
|
||||
(chatHistory.length > 0) && store.dispatch(
|
||||
chatActions.addChatHistory(chatHistory));
|
||||
|
||||
(fileHistory.length > 0) && store.dispatch(
|
||||
fileActions.addFileHistory(fileHistory));
|
||||
|
||||
if (lastNHistory.length > 0)
|
||||
{
|
||||
logger.debug('_joinRoom() | got lastN history');
|
||||
|
||||
this._spotlights.addSpeakerList(
|
||||
lastNHistory.filter((peerId) => peerId !== this._peerId)
|
||||
);
|
||||
}
|
||||
|
||||
locked ?
|
||||
store.dispatch(roomActions.setRoomLocked()) :
|
||||
store.dispatch(roomActions.setRoomUnLocked());
|
||||
|
||||
(lobbyPeers.length > 0) && lobbyPeers.forEach((peer) =>
|
||||
{
|
||||
store.dispatch(
|
||||
lobbyPeerActions.addLobbyPeer(peer.peerId));
|
||||
store.dispatch(
|
||||
lobbyPeerActions.setLobbyPeerDisplayName(peer.displayName, peer.peerId));
|
||||
store.dispatch(
|
||||
lobbyPeerActions.setLobbyPeerPicture(peer.picture));
|
||||
});
|
||||
|
||||
(accessCode != null) && store.dispatch(
|
||||
roomActions.setAccessCode(accessCode));
|
||||
|
||||
// Don't produce if explicitly requested to not to do it.
|
||||
if (this._produce)
|
||||
{
|
||||
|
|
@ -2782,8 +2859,6 @@ export default class RoomClient
|
|||
// Clean all the existing notifications.
|
||||
store.dispatch(notificationActions.removeAllNotifications());
|
||||
|
||||
this.getServerHistory();
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : intl.formatMessage({
|
||||
|
|
@ -2933,6 +3008,159 @@ export default class RoomClient
|
|||
}
|
||||
}
|
||||
|
||||
async addExtraVideo(videoDeviceId)
|
||||
{
|
||||
logger.debug(
|
||||
'addExtraVideo() [videoDeviceId:"%s"]',
|
||||
videoDeviceId
|
||||
);
|
||||
|
||||
store.dispatch(
|
||||
roomActions.setExtraVideoOpen(false));
|
||||
|
||||
if (!this._mediasoupDevice.canProduce('video'))
|
||||
{
|
||||
logger.error('enableWebcam() | cannot produce video');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let track;
|
||||
|
||||
store.dispatch(
|
||||
meActions.setWebcamInProgress(true));
|
||||
|
||||
try
|
||||
{
|
||||
const device = this._webcams[videoDeviceId];
|
||||
const resolution = store.getState().settings.resolution;
|
||||
|
||||
if (!device)
|
||||
throw new Error('no webcam devices');
|
||||
|
||||
logger.debug(
|
||||
'addExtraVideo() | new selected webcam [device:%o]',
|
||||
device);
|
||||
|
||||
logger.debug('_setWebcamProducer() | calling getUserMedia()');
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia(
|
||||
{
|
||||
video :
|
||||
{
|
||||
deviceId : { ideal: videoDeviceId },
|
||||
...VIDEO_CONSTRAINS[resolution]
|
||||
}
|
||||
});
|
||||
|
||||
track = stream.getVideoTracks()[0];
|
||||
|
||||
let producer;
|
||||
|
||||
if (this._useSimulcast)
|
||||
{
|
||||
// If VP9 is the only available video codec then use SVC.
|
||||
const firstVideoCodec = this._mediasoupDevice
|
||||
.rtpCapabilities
|
||||
.codecs
|
||||
.find((c) => c.kind === 'video');
|
||||
|
||||
let encodings;
|
||||
|
||||
if (firstVideoCodec.mimeType.toLowerCase() === 'video/vp9')
|
||||
encodings = VIDEO_KSVC_ENCODINGS;
|
||||
else if ('simulcastEncodings' in window.config)
|
||||
encodings = window.config.simulcastEncodings;
|
||||
else
|
||||
encodings = VIDEO_SIMULCAST_ENCODINGS;
|
||||
|
||||
producer = await this._sendTransport.produce(
|
||||
{
|
||||
track,
|
||||
encodings,
|
||||
codecOptions :
|
||||
{
|
||||
videoGoogleStartBitrate : 1000
|
||||
},
|
||||
appData :
|
||||
{
|
||||
source : 'extravideo'
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
producer = await this._sendTransport.produce({
|
||||
track,
|
||||
appData :
|
||||
{
|
||||
source : 'extravideo'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._extraVideoProducers.set(producer.id, producer);
|
||||
|
||||
store.dispatch(producerActions.addProducer(
|
||||
{
|
||||
id : producer.id,
|
||||
deviceLabel : device.label,
|
||||
source : 'extravideo',
|
||||
paused : producer.paused,
|
||||
track : producer.track,
|
||||
rtpParameters : producer.rtpParameters,
|
||||
codec : producer.rtpParameters.codecs[0].mimeType.split('/')[1]
|
||||
}));
|
||||
|
||||
// store.dispatch(settingsActions.setSelectedWebcamDevice(deviceId));
|
||||
|
||||
await this._updateWebcams();
|
||||
|
||||
producer.on('transportclose', () =>
|
||||
{
|
||||
this._extraVideoProducers.delete(producer.id);
|
||||
|
||||
producer = null;
|
||||
});
|
||||
|
||||
producer.on('trackended', () =>
|
||||
{
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
type : 'error',
|
||||
text : intl.formatMessage({
|
||||
id : 'devices.cameraDisconnected',
|
||||
defaultMessage : 'Camera disconnected'
|
||||
})
|
||||
}));
|
||||
|
||||
this.disableExtraVideo(producer.id)
|
||||
.catch(() => {});
|
||||
});
|
||||
|
||||
logger.debug('addExtraVideo() succeeded');
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('addExtraVideo() failed:%o', error);
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
type : 'error',
|
||||
text : intl.formatMessage({
|
||||
id : 'devices.cameraError',
|
||||
defaultMessage : 'An error occurred while accessing your camera'
|
||||
})
|
||||
}));
|
||||
|
||||
if (track)
|
||||
track.stop();
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
meActions.setWebcamInProgress(false));
|
||||
}
|
||||
|
||||
async enableMic()
|
||||
{
|
||||
if (this._micProducer)
|
||||
|
|
@ -3433,6 +3661,37 @@ export default class RoomClient
|
|||
meActions.setWebcamInProgress(false));
|
||||
}
|
||||
|
||||
async disableExtraVideo(id)
|
||||
{
|
||||
logger.debug('disableExtraVideo()');
|
||||
|
||||
const producer = this._extraVideoProducers.get(id);
|
||||
|
||||
if (!producer)
|
||||
return;
|
||||
|
||||
store.dispatch(meActions.setWebcamInProgress(true));
|
||||
|
||||
producer.close();
|
||||
|
||||
store.dispatch(
|
||||
producerActions.removeProducer(id));
|
||||
|
||||
try
|
||||
{
|
||||
await this.sendRequest(
|
||||
'closeProducer', { producerId: id });
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('disableWebcam() [error:"%o"]', error);
|
||||
}
|
||||
|
||||
this._extraVideoProducers.delete(id);
|
||||
|
||||
store.dispatch(meActions.setWebcamInProgress(false));
|
||||
}
|
||||
|
||||
async disableWebcam()
|
||||
{
|
||||
logger.debug('disableWebcam()');
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ beforeEach(() =>
|
|||
loggedIn : false,
|
||||
loginEnabled : true,
|
||||
picture : null,
|
||||
raiseHand : false,
|
||||
raiseHandInProgress : false,
|
||||
raisedHand : false,
|
||||
raisedHandInProgress : false,
|
||||
screenShareInProgress : false,
|
||||
webcamDevices : null,
|
||||
webcamInProgress : false
|
||||
|
|
|
|||
|
|
@ -63,9 +63,9 @@ export const setWebcamDevices = (devices) =>
|
|||
payload : { devices }
|
||||
});
|
||||
|
||||
export const setMyRaiseHandState = (flag) =>
|
||||
export const setRaisedHand = (flag) =>
|
||||
({
|
||||
type : 'SET_MY_RAISE_HAND_STATE',
|
||||
type : 'SET_RAISED_HAND',
|
||||
payload : { flag }
|
||||
});
|
||||
|
||||
|
|
@ -93,9 +93,9 @@ export const setScreenShareInProgress = (flag) =>
|
|||
payload : { flag }
|
||||
});
|
||||
|
||||
export const setMyRaiseHandStateInProgress = (flag) =>
|
||||
export const setRaisedHandInProgress = (flag) =>
|
||||
({
|
||||
type : 'SET_MY_RAISE_HAND_STATE_IN_PROGRESS',
|
||||
type : 'SET_RAISED_HAND_IN_PROGRESS',
|
||||
payload : { flag }
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -34,10 +34,10 @@ export const setPeerScreenInProgress = (peerId, flag) =>
|
|||
payload : { peerId, flag }
|
||||
});
|
||||
|
||||
export const setPeerRaiseHandState = (peerId, raiseHandState) =>
|
||||
export const setPeerRaisedHand = (peerId, raisedHand) =>
|
||||
({
|
||||
type : 'SET_PEER_RAISE_HAND_STATE',
|
||||
payload : { peerId, raiseHandState }
|
||||
type : 'SET_PEER_RAISED_HAND',
|
||||
payload : { peerId, raisedHand }
|
||||
});
|
||||
|
||||
export const setPeerPicture = (peerId, picture) =>
|
||||
|
|
|
|||
|
|
@ -52,13 +52,25 @@ export const setJoinByAccessCode = (joinByAccessCode) =>
|
|||
payload : { joinByAccessCode }
|
||||
});
|
||||
|
||||
export const setSettingsOpen = ({ settingsOpen }) =>
|
||||
export const setSettingsOpen = (settingsOpen) =>
|
||||
({
|
||||
type : 'SET_SETTINGS_OPEN',
|
||||
payload : { settingsOpen }
|
||||
});
|
||||
|
||||
export const setLockDialogOpen = ({ lockDialogOpen }) =>
|
||||
export const setExtraVideoOpen = (extraVideoOpen) =>
|
||||
({
|
||||
type : 'SET_EXTRA_VIDEO_OPEN',
|
||||
payload : { extraVideoOpen }
|
||||
});
|
||||
|
||||
export const setSettingsTab = (tab) =>
|
||||
({
|
||||
type : 'SET_SETTINGS_TAB',
|
||||
payload : { tab }
|
||||
});
|
||||
|
||||
export const setLockDialogOpen = (lockDialogOpen) =>
|
||||
({
|
||||
type : 'SET_LOCK_DIALOG_OPEN',
|
||||
payload : { lockDialogOpen }
|
||||
|
|
|
|||
|
|
@ -38,6 +38,16 @@ export const togglePermanentTopBar = () =>
|
|||
type : 'TOGGLE_PERMANENT_TOPBAR'
|
||||
});
|
||||
|
||||
export const toggleHiddenControls = () =>
|
||||
({
|
||||
type : 'TOGGLE_HIDDEN_CONTROLS'
|
||||
});
|
||||
|
||||
export const toggleNotificationSounds = () =>
|
||||
({
|
||||
type : 'TOGGLE_NOTIFICATION_SOUNDS'
|
||||
});
|
||||
|
||||
export const setLastN = (lastN) =>
|
||||
({
|
||||
type : 'SET_LAST_N',
|
||||
|
|
|
|||
|
|
@ -15,14 +15,6 @@ import DialogActions from '@material-ui/core/DialogActions';
|
|||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import Button from '@material-ui/core/Button';
|
||||
// import FormLabel from '@material-ui/core/FormLabel';
|
||||
// import FormControl from '@material-ui/core/FormControl';
|
||||
// import FormGroup from '@material-ui/core/FormGroup';
|
||||
// import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
// import Checkbox from '@material-ui/core/Checkbox';
|
||||
// import InputLabel from '@material-ui/core/InputLabel';
|
||||
// import OutlinedInput from '@material-ui/core/OutlinedInput';
|
||||
// import Switch from '@material-ui/core/Switch';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListSubheader from '@material-ui/core/ListSubheader';
|
||||
import ListLobbyPeer from './ListLobbyPeer';
|
||||
|
|
@ -59,10 +51,8 @@ const styles = (theme) =>
|
|||
});
|
||||
|
||||
const LockDialog = ({
|
||||
// roomClient,
|
||||
room,
|
||||
handleCloseLockDialog,
|
||||
// handleAccessCode,
|
||||
lobbyPeers,
|
||||
classes
|
||||
}) =>
|
||||
|
|
@ -71,7 +61,7 @@ const LockDialog = ({
|
|||
<Dialog
|
||||
className={classes.root}
|
||||
open={room.lockDialogOpen}
|
||||
onClose={() => handleCloseLockDialog({ lockDialogOpen: false })}
|
||||
onClose={() => handleCloseLockDialog(false)}
|
||||
classes={{
|
||||
paper : classes.dialogPaper
|
||||
}}
|
||||
|
|
@ -82,54 +72,6 @@ const LockDialog = ({
|
|||
defaultMessage='Lobby administration'
|
||||
/>
|
||||
</DialogTitle>
|
||||
{/*
|
||||
<FormControl component='fieldset' className={classes.formControl}>
|
||||
<FormLabel component='legend'>Room lock</FormLabel>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch checked={room.locked} onChange={() =>
|
||||
{
|
||||
if (room.locked)
|
||||
{
|
||||
roomClient.unlockRoom();
|
||||
}
|
||||
else
|
||||
{
|
||||
roomClient.lockRoom();
|
||||
}
|
||||
}}
|
||||
/>}
|
||||
label='Lock'
|
||||
/>
|
||||
TODO: access code
|
||||
<FormControlLabel disabled={ room.locked ? false : true }
|
||||
control={
|
||||
<Checkbox checked={room.joinByAccessCode}
|
||||
onChange={
|
||||
(event) => roomClient.setJoinByAccessCode(event.target.checked)
|
||||
}
|
||||
/>}
|
||||
label='Join by Access code'
|
||||
/>
|
||||
<InputLabel htmlFor='access-code-input' />
|
||||
<OutlinedInput
|
||||
disabled={ room.locked ? false : true }
|
||||
id='acces-code-input'
|
||||
label='Access code'
|
||||
labelWidth={0}
|
||||
variant='outlined'
|
||||
value={room.accessCode}
|
||||
onChange={(event) => handleAccessCode(event.target.value)}
|
||||
>
|
||||
</OutlinedInput>
|
||||
<Button onClick={() => roomClient.setAccessCode(room.accessCode)} color='primary'>
|
||||
save
|
||||
</Button>
|
||||
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
*/}
|
||||
{ lobbyPeers.length > 0 ?
|
||||
<List
|
||||
dense
|
||||
|
|
@ -160,7 +102,7 @@ const LockDialog = ({
|
|||
</DialogContent>
|
||||
}
|
||||
<DialogActions>
|
||||
<Button onClick={() => handleCloseLockDialog({ lockDialogOpen: false })} color='primary'>
|
||||
<Button onClick={() => handleCloseLockDialog(false)} color='primary'>
|
||||
<FormattedMessage
|
||||
id='label.close'
|
||||
defaultMessage='Close'
|
||||
|
|
@ -173,7 +115,6 @@ const LockDialog = ({
|
|||
|
||||
LockDialog.propTypes =
|
||||
{
|
||||
// roomClient : PropTypes.any.isRequired,
|
||||
room : appPropTypes.Room.isRequired,
|
||||
handleCloseLockDialog : PropTypes.func.isRequired,
|
||||
handleAccessCode : PropTypes.func.isRequired,
|
||||
|
|
@ -202,12 +143,7 @@ export default withRoomContext(connect(
|
|||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.room.locked === next.room.locked &&
|
||||
prev.room.joinByAccessCode === next.room.joinByAccessCode &&
|
||||
prev.room.accessCode === next.room.accessCode &&
|
||||
prev.room.code === next.room.code &&
|
||||
prev.room.lockDialogOpen === next.room.lockDialogOpen &&
|
||||
prev.room.codeHidden === next.room.codeHidden &&
|
||||
prev.room === next.room &&
|
||||
prev.lobbyPeers === next.lobbyPeers
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,16 @@ const styles = (theme) =>
|
|||
zIndex : 21,
|
||||
touchAction : 'none',
|
||||
pointerEvents : 'none',
|
||||
'& p' :
|
||||
'&.hide' :
|
||||
{
|
||||
transition : 'opacity 0.1s ease-in-out',
|
||||
opacity : 0
|
||||
},
|
||||
'&.hover' :
|
||||
{
|
||||
opacity : 1
|
||||
},
|
||||
'& p' :
|
||||
{
|
||||
position : 'absolute',
|
||||
float : 'left',
|
||||
|
|
@ -107,7 +116,8 @@ const styles = (theme) =>
|
|||
fontSize : '2vs',
|
||||
backgroundColor : 'rgba(255, 0, 0, 0.5)',
|
||||
margin : '4px',
|
||||
padding : '15px',
|
||||
padding : theme.spacing(2),
|
||||
zIndex : 31,
|
||||
borderRadius : '20px',
|
||||
textAlign : 'center',
|
||||
opacity : 0,
|
||||
|
|
@ -140,6 +150,7 @@ const Me = (props) =>
|
|||
micProducer,
|
||||
webcamProducer,
|
||||
screenProducer,
|
||||
extraVideoProducers,
|
||||
canShareScreen,
|
||||
classes
|
||||
} = props;
|
||||
|
|
@ -289,8 +300,22 @@ const Me = (props) =>
|
|||
style={spacingStyle}
|
||||
>
|
||||
<div className={classes.viewContainer} style={style}>
|
||||
<div className={classnames(
|
||||
classes.ptt,
|
||||
(micState === 'muted' && me.isSpeaking) ? 'enabled' : null
|
||||
)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='me.mutedPTT'
|
||||
defaultMessage='You are muted, hold down SPACE-BAR to talk'
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={classes.controls}
|
||||
className={classnames(
|
||||
classes.controls,
|
||||
settings.hiddenControls ? 'hide' : null,
|
||||
hover ? 'hover' : null
|
||||
)}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
|
|
@ -318,17 +343,6 @@ const Me = (props) =>
|
|||
/>
|
||||
</p>
|
||||
|
||||
<div className={classnames(
|
||||
classes.ptt,
|
||||
(micState === 'muted' && me.isSpeaking) ? 'enabled' : null
|
||||
)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='me.mutedPTT'
|
||||
defaultMessage='You are muted, hold down SPACE-BAR to talk'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<React.Fragment>
|
||||
<Tooltip title={micTip} placement='left'>
|
||||
<div>
|
||||
|
|
@ -454,6 +468,112 @@ const Me = (props) =>
|
|||
</VideoView>
|
||||
</div>
|
||||
</div>
|
||||
{ extraVideoProducers.map((producer) =>
|
||||
{
|
||||
return (
|
||||
<div key={producer.id}
|
||||
className={
|
||||
classnames(
|
||||
classes.root,
|
||||
'webcam',
|
||||
hover ? 'hover' : 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={classes.viewContainer} style={style}>
|
||||
<div
|
||||
className={classnames(
|
||||
classes.controls,
|
||||
settings.hiddenControls ? 'hide' : null,
|
||||
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 className={hover ? 'hover' : null}>
|
||||
<FormattedMessage
|
||||
id='room.me'
|
||||
defaultMessage='ME'
|
||||
/>
|
||||
</p>
|
||||
|
||||
<Tooltip title={webcamTip} placement='left'>
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'device.stopVideo',
|
||||
defaultMessage : 'Stop video'
|
||||
})}
|
||||
className={classes.fab}
|
||||
disabled={!me.canSendWebcam || me.webcamInProgress}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
roomClient.disableExtraVideo(producer.id);
|
||||
}}
|
||||
>
|
||||
<VideoIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<VideoView
|
||||
isMe
|
||||
advancedMode={advancedMode}
|
||||
peer={me}
|
||||
displayName={settings.displayName}
|
||||
showPeerInfo
|
||||
videoTrack={producer && producer.track}
|
||||
videoVisible={videoVisible}
|
||||
videoCodec={producer && producer.codec}
|
||||
onChangeDisplayName={(displayName) =>
|
||||
{
|
||||
roomClient.changeDisplayName(displayName);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{ screenProducer &&
|
||||
<div
|
||||
className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
|
||||
|
|
@ -480,7 +600,11 @@ const Me = (props) =>
|
|||
>
|
||||
<div className={classes.viewContainer} style={style}>
|
||||
<div
|
||||
className={classes.controls}
|
||||
className={classnames(
|
||||
classes.controls,
|
||||
settings.hiddenControls ? 'hide' : null,
|
||||
hover ? 'hover' : null
|
||||
)}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
|
|
@ -528,20 +652,21 @@ const Me = (props) =>
|
|||
|
||||
Me.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
advancedMode : PropTypes.bool,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
settings : PropTypes.object,
|
||||
activeSpeaker : PropTypes.bool,
|
||||
micProducer : appPropTypes.Producer,
|
||||
webcamProducer : appPropTypes.Producer,
|
||||
screenProducer : appPropTypes.Producer,
|
||||
spacing : PropTypes.number,
|
||||
style : PropTypes.object,
|
||||
smallButtons : PropTypes.bool,
|
||||
canShareScreen : PropTypes.bool.isRequired,
|
||||
classes : PropTypes.object.isRequired,
|
||||
theme : PropTypes.object.isRequired
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
advancedMode : PropTypes.bool,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
settings : PropTypes.object,
|
||||
activeSpeaker : PropTypes.bool,
|
||||
micProducer : appPropTypes.Producer,
|
||||
webcamProducer : appPropTypes.Producer,
|
||||
screenProducer : appPropTypes.Producer,
|
||||
extraVideoProducers : PropTypes.arrayOf(appPropTypes.Producer),
|
||||
spacing : PropTypes.number,
|
||||
style : PropTypes.object,
|
||||
smallButtons : PropTypes.bool,
|
||||
canShareScreen : PropTypes.bool.isRequired,
|
||||
classes : PropTypes.object.isRequired,
|
||||
theme : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ const Peer = (props) =>
|
|||
micConsumer,
|
||||
webcamConsumer,
|
||||
screenConsumer,
|
||||
extraVideoConsumers,
|
||||
toggleConsumerFullscreen,
|
||||
toggleConsumerWindow,
|
||||
spacing,
|
||||
|
|
@ -351,6 +352,161 @@ const Peer = (props) =>
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{ extraVideoConsumers.map((consumer) =>
|
||||
{
|
||||
return (
|
||||
<div key={consumer.id}
|
||||
className={
|
||||
classnames(
|
||||
classes.root,
|
||||
'webcam',
|
||||
hover ? 'hover' : 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={rootStyle}
|
||||
>
|
||||
<div className={classnames(classes.viewContainer)}>
|
||||
{ !videoVisible &&
|
||||
<div className={classes.videoInfo}>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='room.videoPaused'
|
||||
defaultMessage='This video is paused'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<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);
|
||||
}}
|
||||
>
|
||||
{ browser.platform !== 'mobile' &&
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'label.newWindow',
|
||||
defaultMessage : 'New window'
|
||||
})}
|
||||
placement={smallScreen ? 'top' : 'left'}
|
||||
>
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.newWindow',
|
||||
defaultMessage : 'New window'
|
||||
})}
|
||||
className={classes.fab}
|
||||
disabled={
|
||||
!videoVisible ||
|
||||
(windowConsumer === consumer.id)
|
||||
}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerWindow(consumer);
|
||||
}}
|
||||
>
|
||||
<NewWindowIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'label.fullscreen',
|
||||
defaultMessage : 'Fullscreen'
|
||||
})}
|
||||
placement={smallScreen ? 'top' : 'left'}
|
||||
>
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.fullscreen',
|
||||
defaultMessage : 'Fullscreen'
|
||||
})}
|
||||
className={classes.fab}
|
||||
disabled={!videoVisible}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerFullscreen(consumer);
|
||||
}}
|
||||
>
|
||||
<FullScreenIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<VideoView
|
||||
advancedMode={advancedMode}
|
||||
peer={peer}
|
||||
displayName={peer.displayName}
|
||||
showPeerInfo
|
||||
consumerSpatialLayers={consumer ? consumer.spatialLayers : null}
|
||||
consumerTemporalLayers={consumer ? consumer.temporalLayers : null}
|
||||
consumerCurrentSpatialLayer={
|
||||
consumer ? consumer.currentSpatialLayer : null
|
||||
}
|
||||
consumerCurrentTemporalLayer={
|
||||
consumer ? consumer.currentTemporalLayer : null
|
||||
}
|
||||
consumerPreferredSpatialLayer={
|
||||
consumer ? consumer.preferredSpatialLayer : null
|
||||
}
|
||||
consumerPreferredTemporalLayer={
|
||||
consumer ? consumer.preferredTemporalLayer : null
|
||||
}
|
||||
videoMultiLayer={consumer && consumer.type !== 'simple'}
|
||||
videoTrack={consumer && consumer.track}
|
||||
videoVisible={videoVisible}
|
||||
videoCodec={consumer && consumer.codec}
|
||||
videoScore={consumer ? consumer.score : null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{ screenConsumer &&
|
||||
<div
|
||||
className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
|
||||
|
|
@ -508,6 +664,7 @@ Peer.propTypes =
|
|||
micConsumer : appPropTypes.Consumer,
|
||||
webcamConsumer : appPropTypes.Consumer,
|
||||
screenConsumer : appPropTypes.Consumer,
|
||||
extraVideoConsumers : PropTypes.arrayOf(appPropTypes.Consumer),
|
||||
windowConsumer : PropTypes.string,
|
||||
activeSpeaker : PropTypes.bool,
|
||||
browser : PropTypes.object.isRequired,
|
||||
|
|
|
|||
|
|
@ -91,16 +91,6 @@ const SpeakerPeer = (props) =>
|
|||
!screenConsumer.remotelyPaused
|
||||
);
|
||||
|
||||
let videoProfile;
|
||||
|
||||
if (webcamConsumer)
|
||||
videoProfile = webcamConsumer.profile;
|
||||
|
||||
let screenProfile;
|
||||
|
||||
if (screenConsumer)
|
||||
screenProfile = screenConsumer.profile;
|
||||
|
||||
const spacingStyle =
|
||||
{
|
||||
'margin' : spacing
|
||||
|
|
@ -134,11 +124,27 @@ const SpeakerPeer = (props) =>
|
|||
peer={peer}
|
||||
displayName={peer.displayName}
|
||||
showPeerInfo
|
||||
videoTrack={webcamConsumer ? webcamConsumer.track : null}
|
||||
consumerSpatialLayers={webcamConsumer ? webcamConsumer.spatialLayers : null}
|
||||
consumerTemporalLayers={webcamConsumer ? webcamConsumer.temporalLayers : null}
|
||||
consumerCurrentSpatialLayer={
|
||||
webcamConsumer ? webcamConsumer.currentSpatialLayer : null
|
||||
}
|
||||
consumerCurrentTemporalLayer={
|
||||
webcamConsumer ? webcamConsumer.currentTemporalLayer : null
|
||||
}
|
||||
consumerPreferredSpatialLayer={
|
||||
webcamConsumer ? webcamConsumer.preferredSpatialLayer : null
|
||||
}
|
||||
consumerPreferredTemporalLayer={
|
||||
webcamConsumer ? webcamConsumer.preferredTemporalLayer : null
|
||||
}
|
||||
videoMultiLayer={webcamConsumer && webcamConsumer.type !== 'simple'}
|
||||
videoTrack={webcamConsumer && webcamConsumer.track}
|
||||
videoVisible={videoVisible}
|
||||
videoProfile={videoProfile}
|
||||
audioCodec={micConsumer ? micConsumer.codec : null}
|
||||
videoCodec={webcamConsumer ? webcamConsumer.codec : null}
|
||||
audioCodec={micConsumer && micConsumer.codec}
|
||||
videoCodec={webcamConsumer && webcamConsumer.codec}
|
||||
audioScore={micConsumer ? micConsumer.score : null}
|
||||
videoScore={webcamConsumer ? webcamConsumer.score : null}
|
||||
>
|
||||
<Volume id={peer.id} />
|
||||
</VideoView>
|
||||
|
|
@ -165,10 +171,29 @@ const SpeakerPeer = (props) =>
|
|||
<VideoView
|
||||
advancedMode={advancedMode}
|
||||
videoContain
|
||||
videoTrack={screenConsumer ? screenConsumer.track : null}
|
||||
consumerSpatialLayers={
|
||||
screenConsumer ? screenConsumer.spatialLayers : null
|
||||
}
|
||||
consumerTemporalLayers={
|
||||
screenConsumer ? screenConsumer.temporalLayers : null
|
||||
}
|
||||
consumerCurrentSpatialLayer={
|
||||
screenConsumer ? screenConsumer.currentSpatialLayer : null
|
||||
}
|
||||
consumerCurrentTemporalLayer={
|
||||
screenConsumer ? screenConsumer.currentTemporalLayer : null
|
||||
}
|
||||
consumerPreferredSpatialLayer={
|
||||
screenConsumer ? screenConsumer.preferredSpatialLayer : null
|
||||
}
|
||||
consumerPreferredTemporalLayer={
|
||||
screenConsumer ? screenConsumer.preferredTemporalLayer : null
|
||||
}
|
||||
videoMultiLayer={screenConsumer && screenConsumer.type !== 'simple'}
|
||||
videoTrack={screenConsumer && screenConsumer.track}
|
||||
videoVisible={screenVisible}
|
||||
videoProfile={screenProfile}
|
||||
videoCodec={screenConsumer ? screenConsumer.codec : null}
|
||||
videoCodec={screenConsumer && screenConsumer.codec}
|
||||
videoScore={screenConsumer ? screenConsumer.score : null}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,167 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../RoomContext';
|
||||
import * as roomActions from '../../actions/roomActions';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import Select from '@material-ui/core/Select';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
dialogPaper :
|
||||
{
|
||||
width : '30vw',
|
||||
[theme.breakpoints.down('lg')] :
|
||||
{
|
||||
width : '40vw'
|
||||
},
|
||||
[theme.breakpoints.down('md')] :
|
||||
{
|
||||
width : '50vw'
|
||||
},
|
||||
[theme.breakpoints.down('sm')] :
|
||||
{
|
||||
width : '70vw'
|
||||
},
|
||||
[theme.breakpoints.down('xs')] :
|
||||
{
|
||||
width : '90vw'
|
||||
}
|
||||
},
|
||||
setting :
|
||||
{
|
||||
padding : theme.spacing(2)
|
||||
},
|
||||
formControl :
|
||||
{
|
||||
display : 'flex'
|
||||
}
|
||||
});
|
||||
|
||||
const ExtraVideo = ({
|
||||
roomClient,
|
||||
extraVideoOpen,
|
||||
webcamDevices,
|
||||
handleCloseExtraVideo,
|
||||
classes
|
||||
}) =>
|
||||
{
|
||||
const intl = useIntl();
|
||||
|
||||
const [ videoDevice, setVideoDevice ] = React.useState('');
|
||||
|
||||
const handleChange = (event) =>
|
||||
{
|
||||
setVideoDevice(event.target.value);
|
||||
};
|
||||
|
||||
let videoDevices;
|
||||
|
||||
if (webcamDevices)
|
||||
videoDevices = Object.values(webcamDevices);
|
||||
else
|
||||
videoDevices = [];
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={extraVideoOpen}
|
||||
onClose={() => handleCloseExtraVideo(false)}
|
||||
classes={{
|
||||
paper : classes.dialogPaper
|
||||
}}
|
||||
>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
<FormattedMessage
|
||||
id='room.extraVideo'
|
||||
defaultMessage='Extra video'
|
||||
/>
|
||||
</DialogTitle>
|
||||
<form className={classes.setting} autoComplete='off'>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={videoDevice}
|
||||
displayEmpty
|
||||
name={intl.formatMessage({
|
||||
id : 'settings.camera',
|
||||
defaultMessage : 'Camera'
|
||||
})}
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
disabled={videoDevices.length === 0}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{ videoDevices.map((webcam, index) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem key={index} value={webcam.deviceId}>{webcam.label}</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
{ videoDevices.length > 0 ?
|
||||
intl.formatMessage({
|
||||
id : 'settings.selectCamera',
|
||||
defaultMessage : 'Select video device'
|
||||
})
|
||||
:
|
||||
intl.formatMessage({
|
||||
id : 'settings.cantSelectCamera',
|
||||
defaultMessage : 'Unable to select video device'
|
||||
})
|
||||
}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
<DialogActions>
|
||||
<Button onClick={() => roomClient.addExtraVideo(videoDevice)} color='primary'>
|
||||
<FormattedMessage
|
||||
id='label.addVideo'
|
||||
defaultMessage='Add video'
|
||||
/>
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
ExtraVideo.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.object.isRequired,
|
||||
extraVideoOpen : PropTypes.bool.isRequired,
|
||||
webcamDevices : PropTypes.object,
|
||||
handleCloseExtraVideo : PropTypes.func.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
({
|
||||
webcamDevices : state.me.webcamDevices,
|
||||
extraVideoOpen : state.room.extraVideoOpen
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
handleCloseExtraVideo : roomActions.setExtraVideoOpen
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.me.webcamDevices === next.me.webcamDevices &&
|
||||
prev.room.extraVideoOpen === next.room.extraVideoOpen
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(ExtraVideo)));
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
lobbyPeersKeySelector,
|
||||
peersLengthSelector
|
||||
peersLengthSelector,
|
||||
raisedHandsSelector
|
||||
} from '../Selectors';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import { withRoomContext } from '../../RoomContext';
|
||||
|
|
@ -13,11 +14,14 @@ import * as toolareaActions from '../../actions/toolareaActions';
|
|||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import Menu from '@material-ui/core/Menu';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Badge from '@material-ui/core/Badge';
|
||||
import ExtensionIcon from '@material-ui/icons/Extension';
|
||||
import AccountCircle from '@material-ui/icons/AccountCircle';
|
||||
import FullScreenIcon from '@material-ui/icons/Fullscreen';
|
||||
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
|
||||
|
|
@ -26,6 +30,7 @@ import SecurityIcon from '@material-ui/icons/Security';
|
|||
import PeopleIcon from '@material-ui/icons/People';
|
||||
import LockIcon from '@material-ui/icons/Lock';
|
||||
import LockOpenIcon from '@material-ui/icons/LockOpen';
|
||||
import VideoCallIcon from '@material-ui/icons/VideoCall';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
|
||||
|
|
@ -81,9 +86,17 @@ const styles = (theme) =>
|
|||
margin : theme.spacing(1, 0),
|
||||
padding : theme.spacing(0, 1)
|
||||
},
|
||||
disabledButton :
|
||||
{
|
||||
margin : theme.spacing(1, 0)
|
||||
},
|
||||
green :
|
||||
{
|
||||
color : 'rgba(0, 153, 0, 1)'
|
||||
},
|
||||
moreAction :
|
||||
{
|
||||
margin : theme.spacing(0, 0, 0, 1)
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -122,6 +135,18 @@ const TopBar = (props) =>
|
|||
{
|
||||
const intl = useIntl();
|
||||
|
||||
const [ moreActionsElement, setMoreActionsElement ] = useState(null);
|
||||
|
||||
const handleMoreActionsOpen = (event) =>
|
||||
{
|
||||
setMoreActionsElement(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleMoreActionsClose = () =>
|
||||
{
|
||||
setMoreActionsElement(null);
|
||||
};
|
||||
|
||||
const {
|
||||
roomClient,
|
||||
room,
|
||||
|
|
@ -135,15 +160,19 @@ const TopBar = (props) =>
|
|||
fullscreen,
|
||||
onFullscreen,
|
||||
setSettingsOpen,
|
||||
setExtraVideoOpen,
|
||||
setLockDialogOpen,
|
||||
toggleToolArea,
|
||||
openUsersTab,
|
||||
unread,
|
||||
canProduceExtraVideo,
|
||||
canLock,
|
||||
canPromote,
|
||||
classes
|
||||
} = props;
|
||||
|
||||
const isMoreActionsMenuOpen = Boolean(moreActionsElement);
|
||||
|
||||
const lockTooltip = room.locked ?
|
||||
intl.formatMessage({
|
||||
id : 'tooltip.unLockRoom',
|
||||
|
|
@ -178,217 +207,264 @@ const TopBar = (props) =>
|
|||
});
|
||||
|
||||
return (
|
||||
<AppBar
|
||||
position='fixed'
|
||||
className={room.toolbarsVisible || permanentTopBar ? classes.show : classes.hide}
|
||||
>
|
||||
<Toolbar>
|
||||
<PulsingBadge
|
||||
color='secondary'
|
||||
badgeContent={unread}
|
||||
onClick={() => toggleToolArea()}
|
||||
>
|
||||
<IconButton
|
||||
color='inherit'
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.openDrawer',
|
||||
defaultMessage : 'Open drawer'
|
||||
})}
|
||||
className={classes.menuButton}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</PulsingBadge>
|
||||
{ window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
|
||||
<Typography
|
||||
className={classes.title}
|
||||
variant='h6'
|
||||
color='inherit'
|
||||
noWrap
|
||||
>
|
||||
{ window.config.title ? window.config.title : 'Multiparty meeting' }
|
||||
</Typography>
|
||||
<div className={classes.grow} />
|
||||
<div className={classes.actionButtons}>
|
||||
{ fullscreenEnabled &&
|
||||
<Tooltip title={fullscreenTooltip}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.enterFullscreen',
|
||||
defaultMessage : 'Enter fullscreen'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={onFullscreen}
|
||||
>
|
||||
{ fullscreen ?
|
||||
<FullScreenExitIcon />
|
||||
:
|
||||
<FullScreenIcon />
|
||||
}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.participants',
|
||||
defaultMessage : 'Show participants'
|
||||
})}
|
||||
<React.Fragment>
|
||||
<AppBar
|
||||
position='fixed'
|
||||
className={room.toolbarsVisible || permanentTopBar ? classes.show : classes.hide}
|
||||
>
|
||||
<Toolbar>
|
||||
<PulsingBadge
|
||||
color='secondary'
|
||||
badgeContent={unread}
|
||||
onClick={() => toggleToolArea()}
|
||||
>
|
||||
<IconButton
|
||||
color='inherit'
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.openDrawer',
|
||||
defaultMessage : 'Open drawer'
|
||||
})}
|
||||
className={classes.menuButton}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</PulsingBadge>
|
||||
{ window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
|
||||
<Typography
|
||||
className={classes.title}
|
||||
variant='h6'
|
||||
color='inherit'
|
||||
noWrap
|
||||
>
|
||||
{ window.config.title ? window.config.title : 'Multiparty meeting' }
|
||||
</Typography>
|
||||
<div className={classes.grow} />
|
||||
<div className={classes.actionButtons}>
|
||||
<IconButton
|
||||
aria-haspopup='true'
|
||||
onClick={handleMoreActionsOpen}
|
||||
color='inherit'
|
||||
>
|
||||
<ExtensionIcon />
|
||||
</IconButton>
|
||||
{ fullscreenEnabled &&
|
||||
<Tooltip title={fullscreenTooltip}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.enterFullscreen',
|
||||
defaultMessage : 'Enter fullscreen'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={onFullscreen}
|
||||
>
|
||||
{ fullscreen ?
|
||||
<FullScreenExitIcon />
|
||||
:
|
||||
<FullScreenIcon />
|
||||
}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.participants',
|
||||
defaultMessage : 'Show participants'
|
||||
})}
|
||||
color='inherit'
|
||||
onClick={() => openUsersTab()}
|
||||
>
|
||||
<Badge
|
||||
color='primary'
|
||||
badgeContent={peersLength + 1}
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.participants',
|
||||
defaultMessage : 'Show participants'
|
||||
})}
|
||||
color='inherit'
|
||||
onClick={() => openUsersTab()}
|
||||
>
|
||||
<PeopleIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.settings',
|
||||
defaultMessage : 'Show settings'
|
||||
})}
|
||||
>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
<Badge
|
||||
color='primary'
|
||||
badgeContent={peersLength + 1}
|
||||
>
|
||||
<PeopleIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.settings',
|
||||
defaultMessage : 'Show settings'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={() => setSettingsOpen(!room.settingsOpen)}
|
||||
>
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={lockTooltip}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.lockRoom',
|
||||
defaultMessage : 'Lock room'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
disabled={!canLock}
|
||||
onClick={() =>
|
||||
{
|
||||
if (room.locked)
|
||||
{
|
||||
roomClient.unlockRoom();
|
||||
}
|
||||
else
|
||||
{
|
||||
roomClient.lockRoom();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{ room.locked ?
|
||||
<LockIcon />
|
||||
:
|
||||
<LockOpenIcon />
|
||||
}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{ lobbyPeers.length > 0 &&
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.lobby',
|
||||
defaultMessage : 'Show lobby'
|
||||
})}
|
||||
>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.lobby',
|
||||
defaultMessage : 'Show lobby'
|
||||
})}
|
||||
color='inherit'
|
||||
disabled={!canPromote}
|
||||
onClick={() => setLockDialogOpen(!room.lockDialogOpen)}
|
||||
>
|
||||
<PulsingBadge
|
||||
color='secondary'
|
||||
badgeContent={lobbyPeers.length}
|
||||
>
|
||||
<SecurityIcon />
|
||||
</PulsingBadge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
{ loginEnabled &&
|
||||
<Tooltip title={loginTooltip}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.login',
|
||||
defaultMessage : 'Log in'
|
||||
id : 'tooltip.settings',
|
||||
defaultMessage : 'Show settings'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={() =>
|
||||
{
|
||||
loggedIn ? roomClient.logout() : roomClient.login();
|
||||
}}
|
||||
onClick={() => setSettingsOpen(!room.settingsOpen)}
|
||||
>
|
||||
{ myPicture ?
|
||||
<Avatar src={myPicture} />
|
||||
:
|
||||
<AccountCircle className={loggedIn && classes.green} />
|
||||
}
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
<div className={classes.divider} />
|
||||
<Button
|
||||
<Tooltip title={lockTooltip}>
|
||||
<span className={classes.disabledButton}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.lockRoom',
|
||||
defaultMessage : 'Lock room'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
disabled={!canLock}
|
||||
onClick={() =>
|
||||
{
|
||||
if (room.locked)
|
||||
{
|
||||
roomClient.unlockRoom();
|
||||
}
|
||||
else
|
||||
{
|
||||
roomClient.lockRoom();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{ room.locked ?
|
||||
<LockIcon />
|
||||
:
|
||||
<LockOpenIcon />
|
||||
}
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
{ lobbyPeers.length > 0 &&
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.lobby',
|
||||
defaultMessage : 'Show lobby'
|
||||
})}
|
||||
>
|
||||
<span className={classes.disabledButton}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.lobby',
|
||||
defaultMessage : 'Show lobby'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
disabled={!canPromote}
|
||||
onClick={() => setLockDialogOpen(!room.lockDialogOpen)}
|
||||
>
|
||||
<PulsingBadge
|
||||
color='secondary'
|
||||
badgeContent={lobbyPeers.length}
|
||||
>
|
||||
<SecurityIcon />
|
||||
</PulsingBadge>
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
}
|
||||
{ loginEnabled &&
|
||||
<Tooltip title={loginTooltip}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.login',
|
||||
defaultMessage : 'Log in'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={() =>
|
||||
{
|
||||
loggedIn ? roomClient.logout() : roomClient.login();
|
||||
}}
|
||||
>
|
||||
{ myPicture ?
|
||||
<Avatar src={myPicture} />
|
||||
:
|
||||
<AccountCircle className={loggedIn ? classes.green : null} />
|
||||
}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
<div className={classes.divider} />
|
||||
<Button
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.leave',
|
||||
defaultMessage : 'Leave'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
onClick={() => roomClient.close()}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='label.leave'
|
||||
defaultMessage='Leave'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Menu
|
||||
anchorEl={moreActionsElement}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
||||
open={isMoreActionsMenuOpen}
|
||||
onClose={handleMoreActionsClose}
|
||||
getContentAnchorEl={null}
|
||||
>
|
||||
<MenuItem
|
||||
dense
|
||||
disabled={!canProduceExtraVideo}
|
||||
onClick={() =>
|
||||
{
|
||||
handleMoreActionsClose();
|
||||
setExtraVideoOpen(!room.extraVideoOpen);
|
||||
}}
|
||||
>
|
||||
<VideoCallIcon
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.leave',
|
||||
defaultMessage : 'Leave'
|
||||
id : 'label.addVideo',
|
||||
defaultMessage : 'Add video'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
onClick={() => roomClient.close()}
|
||||
>
|
||||
/>
|
||||
<p className={classes.moreAction}>
|
||||
<FormattedMessage
|
||||
id='label.leave'
|
||||
defaultMessage='Leave'
|
||||
id='label.addVideo'
|
||||
defaultMessage='Add video'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</p>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
TopBar.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.object.isRequired,
|
||||
room : appPropTypes.Room.isRequired,
|
||||
peersLength : PropTypes.number,
|
||||
lobbyPeers : PropTypes.array,
|
||||
permanentTopBar : PropTypes.bool,
|
||||
myPicture : PropTypes.string,
|
||||
loggedIn : PropTypes.bool.isRequired,
|
||||
loginEnabled : PropTypes.bool.isRequired,
|
||||
fullscreenEnabled : PropTypes.bool,
|
||||
fullscreen : PropTypes.bool,
|
||||
onFullscreen : PropTypes.func.isRequired,
|
||||
setToolbarsVisible : PropTypes.func.isRequired,
|
||||
setSettingsOpen : PropTypes.func.isRequired,
|
||||
setLockDialogOpen : PropTypes.func.isRequired,
|
||||
toggleToolArea : PropTypes.func.isRequired,
|
||||
openUsersTab : PropTypes.func.isRequired,
|
||||
unread : PropTypes.number.isRequired,
|
||||
canLock : PropTypes.bool.isRequired,
|
||||
canPromote : PropTypes.bool.isRequired,
|
||||
classes : PropTypes.object.isRequired,
|
||||
theme : PropTypes.object.isRequired
|
||||
roomClient : PropTypes.object.isRequired,
|
||||
room : appPropTypes.Room.isRequired,
|
||||
peersLength : PropTypes.number,
|
||||
lobbyPeers : PropTypes.array,
|
||||
permanentTopBar : PropTypes.bool,
|
||||
myPicture : PropTypes.string,
|
||||
loggedIn : PropTypes.bool.isRequired,
|
||||
loginEnabled : PropTypes.bool.isRequired,
|
||||
fullscreenEnabled : PropTypes.bool,
|
||||
fullscreen : PropTypes.bool,
|
||||
onFullscreen : PropTypes.func.isRequired,
|
||||
setToolbarsVisible : PropTypes.func.isRequired,
|
||||
setSettingsOpen : PropTypes.func.isRequired,
|
||||
setExtraVideoOpen : PropTypes.func.isRequired,
|
||||
setLockDialogOpen : PropTypes.func.isRequired,
|
||||
toggleToolArea : PropTypes.func.isRequired,
|
||||
openUsersTab : PropTypes.func.isRequired,
|
||||
unread : PropTypes.number.isRequired,
|
||||
canProduceExtraVideo : PropTypes.bool.isRequired,
|
||||
canLock : PropTypes.bool.isRequired,
|
||||
canPromote : PropTypes.bool.isRequired,
|
||||
classes : PropTypes.object.isRequired,
|
||||
theme : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
|
|
@ -401,7 +477,10 @@ const mapStateToProps = (state) =>
|
|||
loginEnabled : state.me.loginEnabled,
|
||||
myPicture : state.me.picture,
|
||||
unread : state.toolarea.unreadMessages +
|
||||
state.toolarea.unreadFiles,
|
||||
state.toolarea.unreadFiles + raisedHandsSelector(state),
|
||||
canProduceExtraVideo :
|
||||
state.me.roles.some((role) =>
|
||||
state.room.permissionsFromRoles.EXTRA_VIDEO.includes(role)),
|
||||
canLock :
|
||||
state.me.roles.some((role) =>
|
||||
state.room.permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role)),
|
||||
|
|
@ -418,11 +497,15 @@ const mapDispatchToProps = (dispatch) =>
|
|||
},
|
||||
setSettingsOpen : (settingsOpen) =>
|
||||
{
|
||||
dispatch(roomActions.setSettingsOpen({ settingsOpen }));
|
||||
dispatch(roomActions.setSettingsOpen(settingsOpen));
|
||||
},
|
||||
setExtraVideoOpen : (extraVideoOpen) =>
|
||||
{
|
||||
dispatch(roomActions.setExtraVideoOpen(extraVideoOpen));
|
||||
},
|
||||
setLockDialogOpen : (lockDialogOpen) =>
|
||||
{
|
||||
dispatch(roomActions.setLockDialogOpen({ lockDialogOpen }));
|
||||
dispatch(roomActions.setLockDialogOpen(lockDialogOpen));
|
||||
},
|
||||
toggleToolArea : () =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ const DialogTitle = withStyles(styles)((props) =>
|
|||
<Avatar src={myPicture} className={classes.largeAvatar} />
|
||||
:
|
||||
<AccountCircle
|
||||
className={classnames(classes.largeIcon, loggedIn && classes.green)}
|
||||
className={classnames(classes.largeIcon, loggedIn ? classes.green : null)}
|
||||
/>
|
||||
}
|
||||
</IconButton>
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ const Message = (props) =>
|
|||
<Typography variant='caption'>
|
||||
{ self ?
|
||||
intl.formatMessage({
|
||||
id : 'room.me',
|
||||
id : 'room.me',
|
||||
defaultMessage : 'Me'
|
||||
})
|
||||
:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { raisedHandsSelector } from '../Selectors';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import * as toolareaActions from '../../actions/toolareaActions';
|
||||
|
|
@ -51,6 +52,7 @@ const MeetingDrawer = (props) =>
|
|||
currentToolTab,
|
||||
unreadMessages,
|
||||
unreadFiles,
|
||||
raisedHands,
|
||||
closeDrawer,
|
||||
setToolTab,
|
||||
classes,
|
||||
|
|
@ -93,10 +95,14 @@ const MeetingDrawer = (props) =>
|
|||
}
|
||||
/>
|
||||
<Tab
|
||||
label={intl.formatMessage({
|
||||
id : 'label.participants',
|
||||
defaultMessage : 'Participants'
|
||||
})}
|
||||
label={
|
||||
<Badge color='secondary' badgeContent={raisedHands}>
|
||||
{intl.formatMessage({
|
||||
id : 'label.participants',
|
||||
defaultMessage : 'Participants'
|
||||
})}
|
||||
</Badge>
|
||||
}
|
||||
/>
|
||||
</Tabs>
|
||||
<IconButton onClick={closeDrawer}>
|
||||
|
|
@ -116,16 +122,21 @@ MeetingDrawer.propTypes =
|
|||
setToolTab : PropTypes.func.isRequired,
|
||||
unreadMessages : PropTypes.number.isRequired,
|
||||
unreadFiles : PropTypes.number.isRequired,
|
||||
raisedHands : PropTypes.number.isRequired,
|
||||
closeDrawer : PropTypes.func.isRequired,
|
||||
classes : PropTypes.object.isRequired,
|
||||
theme : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
currentToolTab : state.toolarea.currentToolTab,
|
||||
unreadMessages : state.toolarea.unreadMessages,
|
||||
unreadFiles : state.toolarea.unreadFiles
|
||||
});
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
currentToolTab : state.toolarea.currentToolTab,
|
||||
unreadMessages : state.toolarea.unreadMessages,
|
||||
unreadFiles : state.toolarea.unreadFiles,
|
||||
raisedHands : raisedHandsSelector(state)
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setToolTab : toolareaActions.setToolTab
|
||||
|
|
@ -141,7 +152,8 @@ export default connect(
|
|||
return (
|
||||
prev.toolarea.currentToolTab === next.toolarea.currentToolTab &&
|
||||
prev.toolarea.unreadMessages === next.toolarea.unreadMessages &&
|
||||
prev.toolarea.unreadFiles === next.toolarea.unreadFiles
|
||||
prev.toolarea.unreadFiles === next.toolarea.unreadFiles &&
|
||||
prev.peers === next.peers
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,79 +1,50 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import classnames from 'classnames';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as appPropTypes from '../../appPropTypes';
|
||||
import { useIntl } from 'react-intl';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import PanIcon from '@material-ui/icons/PanTool';
|
||||
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
||||
import HandIcon from '../../../images/icon-hand-white.svg';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
root :
|
||||
{
|
||||
padding : theme.spacing(1),
|
||||
width : '100%',
|
||||
overflow : 'hidden',
|
||||
cursor : 'auto',
|
||||
display : 'flex'
|
||||
},
|
||||
listPeer :
|
||||
{
|
||||
display : 'flex'
|
||||
display : 'flex',
|
||||
padding : theme.spacing(1)
|
||||
},
|
||||
avatar :
|
||||
{
|
||||
borderRadius : '50%',
|
||||
height : '2rem'
|
||||
height : '2rem',
|
||||
marginTop : theme.spacing(1)
|
||||
},
|
||||
peerInfo :
|
||||
{
|
||||
fontSize : '1rem',
|
||||
border : 'none',
|
||||
display : 'flex',
|
||||
paddingLeft : theme.spacing(1),
|
||||
flexGrow : 1,
|
||||
alignItems : 'center'
|
||||
},
|
||||
indicators :
|
||||
green :
|
||||
{
|
||||
left : 0,
|
||||
top : 0,
|
||||
display : 'flex',
|
||||
flexDirection : 'row',
|
||||
justifyContent : 'flex-start',
|
||||
alignItems : 'center',
|
||||
transition : 'opacity 0.3s'
|
||||
},
|
||||
icon :
|
||||
{
|
||||
flex : '0 0 auto',
|
||||
margin : '0.3rem',
|
||||
borderRadius : 2,
|
||||
backgroundPosition : 'center',
|
||||
backgroundSize : '75%',
|
||||
backgroundRepeat : 'no-repeat',
|
||||
backgroundColor : 'rgba(0, 0, 0, 0.5)',
|
||||
transitionProperty : 'opacity, background-color',
|
||||
transitionDuration : '0.15s',
|
||||
width : 'var(--media-control-button-size)',
|
||||
height : 'var(--media-control-button-size)',
|
||||
opacity : 0.85,
|
||||
'&:hover' :
|
||||
{
|
||||
opacity : 1
|
||||
},
|
||||
'&.raise-hand' :
|
||||
{
|
||||
backgroundImage : `url(${HandIcon})`,
|
||||
opacity : 1
|
||||
}
|
||||
color : 'rgba(0, 153, 0, 1)'
|
||||
}
|
||||
});
|
||||
|
||||
const ListMe = (props) =>
|
||||
{
|
||||
const intl = useIntl();
|
||||
|
||||
const {
|
||||
roomClient,
|
||||
me,
|
||||
settings,
|
||||
classes
|
||||
|
|
@ -82,29 +53,38 @@ const ListMe = (props) =>
|
|||
const picture = me.picture || EmptyAvatar;
|
||||
|
||||
return (
|
||||
<li className={classes.root}>
|
||||
<div className={classes.listPeer}>
|
||||
<img alt='My avatar' className={classes.avatar} src={picture} />
|
||||
<div className={classes.root}>
|
||||
<img alt='My avatar' className={classes.avatar} src={picture} />
|
||||
|
||||
<div className={classes.peerInfo}>
|
||||
{settings.displayName}
|
||||
</div>
|
||||
|
||||
<div className={classes.indicators}>
|
||||
{ me.raisedHand &&
|
||||
<div className={classnames(classes.icon, 'raise-hand')} />
|
||||
}
|
||||
</div>
|
||||
<div className={classes.peerInfo}>
|
||||
{settings.displayName}
|
||||
</div>
|
||||
</li>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.raisedHand',
|
||||
defaultMessage : 'Raise hand'
|
||||
})}
|
||||
className={me.raisedHand ? classes.green : null}
|
||||
disabled={me.raisedHandInProgress}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
||||
roomClient.setRaisedHand(!me.raisedHand);
|
||||
}}
|
||||
>
|
||||
<PanIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ListMe.propTypes =
|
||||
{
|
||||
me : appPropTypes.Me.isRequired,
|
||||
settings : PropTypes.object.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
roomClient : PropTypes.object.isRequired,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
settings : PropTypes.object.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
|
|
@ -112,7 +92,7 @@ const mapStateToProps = (state) => ({
|
|||
settings : state.settings
|
||||
});
|
||||
|
||||
export default connect(
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
null,
|
||||
|
|
@ -125,4 +105,4 @@ export default connect(
|
|||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(ListMe));
|
||||
)(withStyles(styles)(ListMe)));
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { connect } from 'react-redux';
|
|||
import { makePeerConsumerSelector } from '../../Selectors';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import * as appPropTypes from '../../appPropTypes';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
|
@ -16,31 +15,26 @@ import ScreenIcon from '@material-ui/icons/ScreenShare';
|
|||
import ScreenOffIcon from '@material-ui/icons/StopScreenShare';
|
||||
import ExitIcon from '@material-ui/icons/ExitToApp';
|
||||
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
||||
import HandIcon from '../../../images/icon-hand-white.svg';
|
||||
import PanIcon from '@material-ui/icons/PanTool';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
root :
|
||||
{
|
||||
padding : theme.spacing(1),
|
||||
width : '100%',
|
||||
overflow : 'hidden',
|
||||
cursor : 'auto',
|
||||
display : 'flex'
|
||||
},
|
||||
listPeer :
|
||||
{
|
||||
display : 'flex'
|
||||
},
|
||||
avatar :
|
||||
{
|
||||
borderRadius : '50%',
|
||||
height : '2rem'
|
||||
height : '2rem',
|
||||
marginTop : theme.spacing(1)
|
||||
},
|
||||
peerInfo :
|
||||
{
|
||||
fontSize : '1rem',
|
||||
border : 'none',
|
||||
display : 'flex',
|
||||
paddingLeft : theme.spacing(1),
|
||||
flexGrow : 1,
|
||||
|
|
@ -48,52 +42,12 @@ const styles = (theme) =>
|
|||
},
|
||||
indicators :
|
||||
{
|
||||
left : 0,
|
||||
top : 0,
|
||||
display : 'flex',
|
||||
flexDirection : 'row',
|
||||
justifyContent : 'flex-start',
|
||||
alignItems : 'center',
|
||||
transition : 'opacity 0.3s'
|
||||
display : 'flex',
|
||||
padding : theme.spacing(1.5)
|
||||
},
|
||||
icon :
|
||||
green :
|
||||
{
|
||||
flex : '0 0 auto',
|
||||
margin : '0.3rem',
|
||||
borderRadius : 2,
|
||||
backgroundPosition : 'center',
|
||||
backgroundSize : '75%',
|
||||
backgroundRepeat : 'no-repeat',
|
||||
backgroundColor : 'rgba(0, 0, 0, 0.5)',
|
||||
transitionProperty : 'opacity, background-color',
|
||||
transitionDuration : '0.15s',
|
||||
width : 'var(--media-control-button-size)',
|
||||
height : 'var(--media-control-button-size)',
|
||||
opacity : 0.85,
|
||||
'&:hover' :
|
||||
{
|
||||
opacity : 1
|
||||
},
|
||||
'&.on' :
|
||||
{
|
||||
opacity : 1
|
||||
},
|
||||
'&.off' :
|
||||
{
|
||||
opacity : 0.2
|
||||
},
|
||||
'&.raise-hand' :
|
||||
{
|
||||
backgroundImage : `url(${HandIcon})`
|
||||
}
|
||||
},
|
||||
controls :
|
||||
{
|
||||
float : 'right',
|
||||
display : 'flex',
|
||||
flexDirection : 'row',
|
||||
justifyContent : 'flex-start',
|
||||
alignItems : 'center'
|
||||
color : 'rgba(0, 153, 0, 1)'
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -140,106 +94,96 @@ const ListPeer = (props) =>
|
|||
{peer.displayName}
|
||||
</div>
|
||||
<div className={classes.indicators}>
|
||||
{ peer.raiseHandState &&
|
||||
<div className={
|
||||
classnames(
|
||||
classes.icon, 'raise-hand', {
|
||||
on : peer.raiseHandState,
|
||||
off : !peer.raiseHandState
|
||||
}
|
||||
)
|
||||
}
|
||||
/>
|
||||
{ peer.raisedHand &&
|
||||
<PanIcon className={classes.green} />
|
||||
}
|
||||
</div>
|
||||
{ screenConsumer &&
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.muteScreenSharing',
|
||||
defaultMessage : 'Mute participant share'
|
||||
})}
|
||||
color={screenVisible ? 'primary' : 'secondary'}
|
||||
disabled={peer.peerScreenInProgress}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
||||
screenVisible ?
|
||||
roomClient.modifyPeerConsumer(peer.id, 'screen', true) :
|
||||
roomClient.modifyPeerConsumer(peer.id, 'screen', false);
|
||||
}}
|
||||
>
|
||||
{ screenVisible ?
|
||||
<ScreenIcon />
|
||||
:
|
||||
<ScreenOffIcon />
|
||||
}
|
||||
</IconButton>
|
||||
}
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.muteParticipantVideo',
|
||||
defaultMessage : 'Mute participant video'
|
||||
})}
|
||||
color={webcamEnabled ? 'primary' : 'secondary'}
|
||||
disabled={peer.peerVideoInProgress}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
||||
webcamEnabled ?
|
||||
roomClient.modifyPeerConsumer(peer.id, 'webcam', true) :
|
||||
roomClient.modifyPeerConsumer(peer.id, 'webcam', false);
|
||||
}}
|
||||
>
|
||||
{ webcamEnabled ?
|
||||
<VideocamIcon />
|
||||
:
|
||||
<VideocamOffIcon />
|
||||
}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.muteParticipant',
|
||||
defaultMessage : 'Mute participant'
|
||||
})}
|
||||
color={micEnabled ? 'primary' : 'secondary'}
|
||||
disabled={peer.peerAudioInProgress}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
||||
micEnabled ?
|
||||
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
|
||||
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
|
||||
}}
|
||||
>
|
||||
{ micEnabled ?
|
||||
<VolumeUpIcon />
|
||||
:
|
||||
<VolumeOffIcon />
|
||||
}
|
||||
</IconButton>
|
||||
{ isModerator &&
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.kickParticipant',
|
||||
defaultMessage : 'Kick out participant'
|
||||
})}
|
||||
disabled={peer.peerKickInProgress}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
||||
roomClient.kickPeer(peer.id);
|
||||
}}
|
||||
>
|
||||
<ExitIcon />
|
||||
</IconButton>
|
||||
}
|
||||
{children}
|
||||
<div className={classes.controls}>
|
||||
{ screenConsumer &&
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.muteScreenSharing',
|
||||
defaultMessage : 'Mute participant share'
|
||||
})}
|
||||
color={screenVisible ? 'primary' : 'secondary'}
|
||||
disabled={peer.peerScreenInProgress}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
||||
screenVisible ?
|
||||
roomClient.modifyPeerConsumer(peer.id, 'screen', true) :
|
||||
roomClient.modifyPeerConsumer(peer.id, 'screen', false);
|
||||
}}
|
||||
>
|
||||
{ screenVisible ?
|
||||
<ScreenIcon />
|
||||
:
|
||||
<ScreenOffIcon />
|
||||
}
|
||||
</IconButton>
|
||||
}
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.muteParticipantVideo',
|
||||
defaultMessage : 'Mute participant video'
|
||||
})}
|
||||
color={webcamEnabled ? 'primary' : 'secondary'}
|
||||
disabled={peer.peerVideoInProgress}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
||||
webcamEnabled ?
|
||||
roomClient.modifyPeerConsumer(peer.id, 'webcam', true) :
|
||||
roomClient.modifyPeerConsumer(peer.id, 'webcam', false);
|
||||
}}
|
||||
>
|
||||
{ webcamEnabled ?
|
||||
<VideocamIcon />
|
||||
:
|
||||
<VideocamOffIcon />
|
||||
}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.muteParticipant',
|
||||
defaultMessage : 'Mute participant'
|
||||
})}
|
||||
color={micEnabled ? 'primary' : 'secondary'}
|
||||
disabled={peer.peerAudioInProgress}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
||||
micEnabled ?
|
||||
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
|
||||
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
|
||||
}}
|
||||
>
|
||||
{ micEnabled ?
|
||||
<VolumeUpIcon />
|
||||
:
|
||||
<VolumeOffIcon />
|
||||
}
|
||||
</IconButton>
|
||||
{ isModerator &&
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.kickParticipant',
|
||||
defaultMessage : 'Kick out participant'
|
||||
})}
|
||||
disabled={peer.peerKickInProgress}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
||||
roomClient.kickPeer(peer.id);
|
||||
}}
|
||||
>
|
||||
<ExitIcon />
|
||||
</IconButton>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@ import Peer from '../Containers/Peer';
|
|||
import SpeakerPeer from '../Containers/SpeakerPeer';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
|
||||
const RATIO = 1.334;
|
||||
const PADDING_V = 40;
|
||||
const PADDING_H = 0;
|
||||
const FILMSTRING_PADDING_V = 10;
|
||||
const FILMSTRING_PADDING_H = 0;
|
||||
|
||||
const styles = () =>
|
||||
({
|
||||
root :
|
||||
|
|
@ -20,24 +26,22 @@ const styles = () =>
|
|||
width : '100%',
|
||||
display : 'grid',
|
||||
gridTemplateColumns : '1fr',
|
||||
gridTemplateRows : '1.6fr minmax(0, 0.4fr)'
|
||||
gridTemplateRows : '1fr 0.25fr'
|
||||
},
|
||||
speaker :
|
||||
{
|
||||
gridArea : '1 / 1 / 2 / 2',
|
||||
gridArea : '1 / 1 / 1 / 1',
|
||||
display : 'flex',
|
||||
justifyContent : 'center',
|
||||
alignItems : 'center',
|
||||
paddingTop : 40
|
||||
alignItems : 'center'
|
||||
},
|
||||
filmStrip :
|
||||
{
|
||||
gridArea : '2 / 1 / 3 / 2'
|
||||
gridArea : '2 / 1 / 2 / 1'
|
||||
},
|
||||
filmItem :
|
||||
{
|
||||
display : 'flex',
|
||||
marginLeft : '6px',
|
||||
border : 'var(--peer-border)',
|
||||
'&.selected' :
|
||||
{
|
||||
|
|
@ -47,6 +51,16 @@ const styles = () =>
|
|||
{
|
||||
opacity : '0.6'
|
||||
}
|
||||
},
|
||||
hiddenToolBar :
|
||||
{
|
||||
paddingTop : 0,
|
||||
transition : 'padding .5s'
|
||||
},
|
||||
showingToolBar :
|
||||
{
|
||||
paddingTop : 60,
|
||||
transition : 'padding .5s'
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -58,6 +72,8 @@ class Filmstrip extends React.PureComponent
|
|||
|
||||
this.resizeTimeout = null;
|
||||
|
||||
this.rootContainer = React.createRef();
|
||||
|
||||
this.activePeerContainer = React.createRef();
|
||||
|
||||
this.filmStripContainer = React.createRef();
|
||||
|
|
@ -105,24 +121,35 @@ class Filmstrip extends React.PureComponent
|
|||
{
|
||||
const newState = {};
|
||||
|
||||
const root = this.rootContainer.current;
|
||||
|
||||
const availableWidth = root.clientWidth;
|
||||
// Grid is:
|
||||
// 4/5 speaker
|
||||
// 1/5 filmstrip
|
||||
const availableSpeakerHeight = (root.clientHeight * 0.8) -
|
||||
(this.props.toolbarsVisible || this.props.permanentTopBar ? PADDING_V : PADDING_H);
|
||||
|
||||
const availableFilmstripHeight = root.clientHeight * 0.2;
|
||||
|
||||
const speaker = this.activePeerContainer.current;
|
||||
|
||||
if (speaker)
|
||||
{
|
||||
let speakerWidth = (speaker.clientWidth - 100);
|
||||
let speakerWidth = (availableWidth - PADDING_H);
|
||||
|
||||
let speakerHeight = (speakerWidth / 4) * 3;
|
||||
let speakerHeight = speakerWidth / RATIO;
|
||||
|
||||
if (this.isSharingCamera(this.getActivePeerId()))
|
||||
{
|
||||
speakerWidth /= 2;
|
||||
speakerHeight = (speakerWidth / 4) * 3;
|
||||
speakerHeight = speakerWidth / RATIO;
|
||||
}
|
||||
|
||||
if (speakerHeight > (speaker.clientHeight - 60))
|
||||
if (speakerHeight > (availableSpeakerHeight - PADDING_V))
|
||||
{
|
||||
speakerHeight = (speaker.clientHeight - 60);
|
||||
speakerWidth = (speakerHeight / 3) * 4;
|
||||
speakerHeight = (availableSpeakerHeight - PADDING_V);
|
||||
speakerWidth = speakerHeight * RATIO;
|
||||
}
|
||||
|
||||
newState.speakerWidth = speakerWidth;
|
||||
|
|
@ -133,14 +160,18 @@ class Filmstrip extends React.PureComponent
|
|||
|
||||
if (filmStrip)
|
||||
{
|
||||
let filmStripHeight = filmStrip.clientHeight - 10;
|
||||
let filmStripHeight = availableFilmstripHeight - FILMSTRING_PADDING_V;
|
||||
|
||||
let filmStripWidth = (filmStripHeight / 3) * 4;
|
||||
let filmStripWidth = filmStripHeight * RATIO;
|
||||
|
||||
if (filmStripWidth * this.props.boxes > (filmStrip.clientWidth - 50))
|
||||
if (
|
||||
(filmStripWidth * this.props.boxes) >
|
||||
(availableWidth - FILMSTRING_PADDING_H)
|
||||
)
|
||||
{
|
||||
filmStripWidth = (filmStrip.clientWidth - 50) / this.props.boxes;
|
||||
filmStripHeight = (filmStripWidth / 4) * 3;
|
||||
filmStripWidth = (availableWidth - FILMSTRING_PADDING_H) /
|
||||
this.props.boxes;
|
||||
filmStripHeight = filmStripWidth / RATIO;
|
||||
}
|
||||
|
||||
newState.filmStripWidth = filmStripWidth;
|
||||
|
|
@ -172,27 +203,21 @@ class Filmstrip extends React.PureComponent
|
|||
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)
|
||||
{
|
||||
if (prevProps !== this.props)
|
||||
{
|
||||
if (
|
||||
this.props.activeSpeakerId != null &&
|
||||
this.props.activeSpeakerId !== this.props.myId
|
||||
)
|
||||
{
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
this.setState({
|
||||
lastSpeaker : this.props.activeSpeakerId
|
||||
});
|
||||
}
|
||||
|
||||
this.updateDimensions();
|
||||
}
|
||||
}
|
||||
|
|
@ -205,6 +230,8 @@ class Filmstrip extends React.PureComponent
|
|||
myId,
|
||||
advancedMode,
|
||||
spotlights,
|
||||
toolbarsVisible,
|
||||
permanentTopBar,
|
||||
classes
|
||||
} = this.props;
|
||||
|
||||
|
|
@ -223,7 +250,14 @@ class Filmstrip extends React.PureComponent
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<div
|
||||
className={classnames(
|
||||
classes.root,
|
||||
toolbarsVisible || permanentTopBar ?
|
||||
classes.showingToolBar : classes.hiddenToolBar
|
||||
)}
|
||||
ref={this.rootContainer}
|
||||
>
|
||||
<div className={classes.speaker} ref={this.activePeerContainer}>
|
||||
{ peers[activePeerId] &&
|
||||
<SpeakerPeer
|
||||
|
|
@ -296,6 +330,8 @@ Filmstrip.propTypes = {
|
|||
selectedPeerId : PropTypes.string,
|
||||
spotlights : PropTypes.array.isRequired,
|
||||
boxes : PropTypes.number,
|
||||
toolbarsVisible : PropTypes.bool.isRequired,
|
||||
permanentTopBar : PropTypes.bool,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
|
|
@ -308,7 +344,9 @@ const mapStateToProps = (state) =>
|
|||
consumers : state.consumers,
|
||||
myId : state.me.id,
|
||||
spotlights : state.room.spotlights,
|
||||
boxes : videoBoxesSelector(state)
|
||||
boxes : videoBoxesSelector(state),
|
||||
toolbarsVisible : state.room.toolbarsVisible,
|
||||
permanentTopBar : state.settings.permanentTopBar
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -322,6 +360,8 @@ export default withRoomContext(connect(
|
|||
return (
|
||||
prev.room.activeSpeakerId === next.room.activeSpeakerId &&
|
||||
prev.room.selectedPeerId === next.room.selectedPeerId &&
|
||||
prev.room.toolbarsVisible === next.room.toolbarsVisible &&
|
||||
prev.settings.permanentTopBar === next.settings.permanentTopBar &&
|
||||
prev.peers === next.peers &&
|
||||
prev.consumers === next.consumers &&
|
||||
prev.room.spotlights === next.room.spotlights &&
|
||||
|
|
|
|||
|
|
@ -31,13 +31,15 @@ export default class PeerAudio extends React.PureComponent
|
|||
this._setOutputDevice(audioOutputDevice);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillReceiveProps(nextProps)
|
||||
componentDidUpdate(prevProps)
|
||||
{
|
||||
const { audioTrack, audioOutputDevice } = nextProps;
|
||||
if (prevProps !== this.props)
|
||||
{
|
||||
const { audioTrack, audioOutputDevice } = this.props;
|
||||
|
||||
this._setTrack(audioTrack);
|
||||
this._setOutputDevice(audioOutputDevice);
|
||||
this._setTrack(audioTrack);
|
||||
this._setOutputDevice(audioOutputDevice);
|
||||
}
|
||||
}
|
||||
|
||||
_setTrack(audioTrack)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import LockDialog from './AccessControl/LockDialog/LockDialog';
|
|||
import Settings from './Settings/Settings';
|
||||
import TopBar from './Controls/TopBar';
|
||||
import WakeLock from 'react-wakelock-react16';
|
||||
import ExtraVideo from './Controls/ExtraVideo';
|
||||
|
||||
const TIMEOUT = 5 * 1000;
|
||||
|
||||
|
|
@ -217,6 +218,10 @@ class Room extends React.PureComponent
|
|||
{ room.settingsOpen &&
|
||||
<Settings />
|
||||
}
|
||||
|
||||
{ room.extraVideoOpen &&
|
||||
<ExtraVideo />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ export const screenProducersSelector = createSelector(
|
|||
(producers) => Object.values(producers).filter((producer) => producer.source === 'screen')
|
||||
);
|
||||
|
||||
export const extraVideoProducersSelector = createSelector(
|
||||
producersSelect,
|
||||
(producers) => Object.values(producers).filter((producer) => producer.source === 'extravideo')
|
||||
);
|
||||
|
||||
export const micProducerSelector = createSelector(
|
||||
producersSelect,
|
||||
(producers) => Object.values(producers).find((producer) => producer.source === 'mic')
|
||||
|
|
@ -67,6 +72,24 @@ export const screenConsumerSelector = createSelector(
|
|||
(consumers) => Object.values(consumers).filter((consumer) => consumer.source === 'screen')
|
||||
);
|
||||
|
||||
export const spotlightScreenConsumerSelector = createSelector(
|
||||
spotlightsSelector,
|
||||
consumersSelect,
|
||||
(spotlights, consumers) =>
|
||||
Object.values(consumers).filter(
|
||||
(consumer) => consumer.source === 'screen' && spotlights.includes(consumer.peerId)
|
||||
)
|
||||
);
|
||||
|
||||
export const spotlightExtraVideoConsumerSelector = createSelector(
|
||||
spotlightsSelector,
|
||||
consumersSelect,
|
||||
(spotlights, consumers) =>
|
||||
Object.values(consumers).filter(
|
||||
(consumer) => consumer.source === 'extravideo' && spotlights.includes(consumer.peerId)
|
||||
)
|
||||
);
|
||||
|
||||
export const passiveMicConsumerSelector = createSelector(
|
||||
spotlightsSelector,
|
||||
consumersSelect,
|
||||
|
|
@ -106,24 +129,41 @@ export const passivePeersSelector = createSelector(
|
|||
.sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || '')))
|
||||
);
|
||||
|
||||
export const raisedHandsSelector = createSelector(
|
||||
peersValueSelector,
|
||||
(peers) => peers.reduce((a, b) => (a + (b.raisedHand ? 1 : 0)), 0)
|
||||
);
|
||||
|
||||
export const videoBoxesSelector = createSelector(
|
||||
spotlightsLengthSelector,
|
||||
screenProducersSelector,
|
||||
screenConsumerSelector,
|
||||
(spotlightsLength, screenProducers, screenConsumers) =>
|
||||
spotlightsLength + 1 + screenProducers.length + screenConsumers.length
|
||||
spotlightScreenConsumerSelector,
|
||||
extraVideoProducersSelector,
|
||||
spotlightExtraVideoConsumerSelector,
|
||||
(
|
||||
spotlightsLength,
|
||||
screenProducers,
|
||||
screenConsumers,
|
||||
extraVideoProducers,
|
||||
extraVideoConsumers
|
||||
) =>
|
||||
spotlightsLength + 1 + screenProducers.length +
|
||||
screenConsumers.length + extraVideoProducers.length +
|
||||
extraVideoConsumers.length
|
||||
);
|
||||
|
||||
export const meProducersSelector = createSelector(
|
||||
micProducerSelector,
|
||||
webcamProducerSelector,
|
||||
screenProducerSelector,
|
||||
(micProducer, webcamProducer, screenProducer) =>
|
||||
extraVideoProducersSelector,
|
||||
(micProducer, webcamProducer, screenProducer, extraVideoProducers) =>
|
||||
{
|
||||
return {
|
||||
micProducer,
|
||||
webcamProducer,
|
||||
screenProducer
|
||||
screenProducer,
|
||||
extraVideoProducers
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
@ -146,8 +186,10 @@ export const makePeerConsumerSelector = () =>
|
|||
consumersArray.find((consumer) => consumer.source === 'webcam');
|
||||
const screenConsumer =
|
||||
consumersArray.find((consumer) => consumer.source === 'screen');
|
||||
const extraVideoConsumers =
|
||||
consumersArray.filter((consumer) => consumer.source === 'extravideo');
|
||||
|
||||
return { micConsumer, webcamConsumer, screenConsumer };
|
||||
return { micConsumer, webcamConsumer, screenConsumer, extraVideoConsumers };
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../RoomContext';
|
||||
import * as settingsActions from '../../actions/settingsActions';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
setting :
|
||||
{
|
||||
padding : theme.spacing(2)
|
||||
},
|
||||
formControl :
|
||||
{
|
||||
display : 'flex'
|
||||
}
|
||||
});
|
||||
|
||||
const AdvancedSettings = ({
|
||||
roomClient,
|
||||
settings,
|
||||
onToggleAdvancedMode,
|
||||
onToggleNotificationSounds,
|
||||
classes
|
||||
}) =>
|
||||
{
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<FormControlLabel
|
||||
className={classes.setting}
|
||||
control={<Checkbox checked={settings.advancedMode} onChange={onToggleAdvancedMode} value='advancedMode' />}
|
||||
label={intl.formatMessage({
|
||||
id : 'settings.advancedMode',
|
||||
defaultMessage : 'Advanced mode'
|
||||
})}
|
||||
/>
|
||||
<FormControlLabel
|
||||
className={classes.setting}
|
||||
control={<Checkbox checked={settings.notificationSounds} onChange={onToggleNotificationSounds} value='notificationSounds' />}
|
||||
label={intl.formatMessage({
|
||||
id : 'settings.notificationSounds',
|
||||
defaultMessage : 'Notification sounds'
|
||||
})}
|
||||
/>
|
||||
{ !window.config.lockLastN &&
|
||||
<form className={classes.setting} autoComplete='off'>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={settings.lastN || ''}
|
||||
onChange={(event) =>
|
||||
{
|
||||
if (event.target.value)
|
||||
roomClient.changeMaxSpotlights(event.target.value);
|
||||
}}
|
||||
name='Last N'
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
>
|
||||
{ Array.from(
|
||||
{ length: window.config.maxLastN || 10 },
|
||||
(_, i) => i + 1
|
||||
).map((lastN) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem key={lastN} value={lastN}>
|
||||
{lastN}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
<FormattedMessage
|
||||
id='settings.lastn'
|
||||
defaultMessage='Number of visible videos'
|
||||
/>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
AdvancedSettings.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
settings : PropTypes.object.isRequired,
|
||||
onToggleAdvancedMode : PropTypes.func.isRequired,
|
||||
onToggleNotificationSounds : PropTypes.func.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
({
|
||||
settings : state.settings
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
onToggleAdvancedMode : settingsActions.toggleAdvancedMode,
|
||||
onToggleNotificationSounds : settingsActions.toggleNotificationSounds
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.settings === next.settings
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(AdvancedSettings)));
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import * as roomActions from '../../actions/roomActions';
|
||||
import * as settingsActions from '../../actions/settingsActions';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
setting :
|
||||
{
|
||||
padding : theme.spacing(2)
|
||||
},
|
||||
formControl :
|
||||
{
|
||||
display : 'flex'
|
||||
}
|
||||
});
|
||||
|
||||
const AppearenceSettings = ({
|
||||
room,
|
||||
settings,
|
||||
onTogglePermanentTopBar,
|
||||
onToggleHiddenControls,
|
||||
handleChangeMode,
|
||||
classes
|
||||
}) =>
|
||||
{
|
||||
const intl = useIntl();
|
||||
|
||||
const modes = [ {
|
||||
value : 'democratic',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.democratic',
|
||||
defaultMessage : 'Democratic view'
|
||||
})
|
||||
}, {
|
||||
value : 'filmstrip',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.filmstrip',
|
||||
defaultMessage : 'Filmstrip view'
|
||||
})
|
||||
} ];
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<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={intl.formatMessage({
|
||||
id : 'settings.layout',
|
||||
defaultMessage : 'Room layout'
|
||||
})}
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
>
|
||||
{ modes.map((mode, index) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem key={index} value={mode.value}>
|
||||
{mode.label}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
<FormattedMessage
|
||||
id='settings.selectRoomLayout'
|
||||
defaultMessage='Select room layout'
|
||||
/>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
<FormControlLabel
|
||||
className={classes.setting}
|
||||
control={<Checkbox checked={settings.permanentTopBar} onChange={onTogglePermanentTopBar} value='permanentTopBar' />}
|
||||
label={intl.formatMessage({
|
||||
id : 'settings.permanentTopBar',
|
||||
defaultMessage : 'Permanent top bar'
|
||||
})}
|
||||
/>
|
||||
<FormControlLabel
|
||||
className={classes.setting}
|
||||
control={<Checkbox checked={settings.hiddenControls} onChange={onToggleHiddenControls} value='hiddenControls' />}
|
||||
label={intl.formatMessage({
|
||||
id : 'settings.hiddenControls',
|
||||
defaultMessage : 'Hidden media controls'
|
||||
})}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
AppearenceSettings.propTypes =
|
||||
{
|
||||
room : appPropTypes.Room.isRequired,
|
||||
settings : PropTypes.object.isRequired,
|
||||
onTogglePermanentTopBar : PropTypes.func.isRequired,
|
||||
onToggleHiddenControls : PropTypes.func.isRequired,
|
||||
handleChangeMode : PropTypes.func.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
({
|
||||
room : state.room,
|
||||
settings : state.settings
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
onTogglePermanentTopBar : settingsActions.togglePermanentTopBar,
|
||||
onToggleHiddenControls : settingsActions.toggleHiddenControls,
|
||||
handleChangeMode : roomActions.setDisplayMode
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.room === next.room &&
|
||||
prev.settings === next.settings
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(AppearenceSettings));
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../RoomContext';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import Select from '@material-ui/core/Select';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
setting :
|
||||
{
|
||||
padding : theme.spacing(2)
|
||||
},
|
||||
formControl :
|
||||
{
|
||||
display : 'flex'
|
||||
}
|
||||
});
|
||||
|
||||
const MediaSettings = ({
|
||||
roomClient,
|
||||
me,
|
||||
settings,
|
||||
classes
|
||||
}) =>
|
||||
{
|
||||
const intl = useIntl();
|
||||
|
||||
const resolutions = [ {
|
||||
value : 'low',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.low',
|
||||
defaultMessage : 'Low'
|
||||
})
|
||||
},
|
||||
{
|
||||
value : 'medium',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.medium',
|
||||
defaultMessage : 'Medium'
|
||||
})
|
||||
},
|
||||
{
|
||||
value : 'high',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.high',
|
||||
defaultMessage : 'High (HD)'
|
||||
})
|
||||
},
|
||||
{
|
||||
value : 'veryhigh',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.veryHigh',
|
||||
defaultMessage : 'Very high (FHD)'
|
||||
})
|
||||
},
|
||||
{
|
||||
value : 'ultra',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.ultra',
|
||||
defaultMessage : 'Ultra (UHD)'
|
||||
})
|
||||
} ];
|
||||
|
||||
let webcams;
|
||||
|
||||
if (me.webcamDevices)
|
||||
webcams = Object.values(me.webcamDevices);
|
||||
else
|
||||
webcams = [];
|
||||
|
||||
let audioDevices;
|
||||
|
||||
if (me.audioDevices)
|
||||
audioDevices = Object.values(me.audioDevices);
|
||||
else
|
||||
audioDevices = [];
|
||||
|
||||
let audioOutputDevices;
|
||||
|
||||
if (me.audioOutputDevices)
|
||||
audioOutputDevices = Object.values(me.audioOutputDevices);
|
||||
else
|
||||
audioOutputDevices = [];
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<form className={classes.setting} autoComplete='off'>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={settings.selectedWebcam || ''}
|
||||
onChange={(event) =>
|
||||
{
|
||||
if (event.target.value)
|
||||
roomClient.changeWebcam(event.target.value);
|
||||
}}
|
||||
displayEmpty
|
||||
name={intl.formatMessage({
|
||||
id : 'settings.camera',
|
||||
defaultMessage : 'Camera'
|
||||
})}
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
disabled={webcams.length === 0 || me.webcamInProgress}
|
||||
>
|
||||
{ webcams.map((webcam, index) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem key={index} value={webcam.deviceId}>{webcam.label}</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
{ webcams.length > 0 ?
|
||||
intl.formatMessage({
|
||||
id : 'settings.selectCamera',
|
||||
defaultMessage : 'Select video device'
|
||||
})
|
||||
:
|
||||
intl.formatMessage({
|
||||
id : 'settings.cantSelectCamera',
|
||||
defaultMessage : 'Unable to select video device'
|
||||
})
|
||||
}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
<form className={classes.setting} autoComplete='off'>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={settings.selectedAudioDevice || ''}
|
||||
onChange={(event) =>
|
||||
{
|
||||
if (event.target.value)
|
||||
roomClient.changeAudioDevice(event.target.value);
|
||||
}}
|
||||
displayEmpty
|
||||
name={intl.formatMessage({
|
||||
id : 'settings.audio',
|
||||
defaultMessage : 'Audio device'
|
||||
})}
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
disabled={audioDevices.length === 0 || me.audioInProgress}
|
||||
>
|
||||
{ audioDevices.map((audio, index) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem key={index} value={audio.deviceId}>{audio.label}</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
{ audioDevices.length > 0 ?
|
||||
intl.formatMessage({
|
||||
id : 'settings.selectAudio',
|
||||
defaultMessage : 'Select audio device'
|
||||
})
|
||||
:
|
||||
intl.formatMessage({
|
||||
id : 'settings.cantSelectAudio',
|
||||
defaultMessage : 'Unable to select audio device'
|
||||
})
|
||||
}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
{ 'audioOutputSupportedBrowsers' in window.config &&
|
||||
window.config.audioOutputSupportedBrowsers.includes(me.browser.name) &&
|
||||
<form className={classes.setting} autoComplete='off'>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={settings.selectedAudioOutputDevice || ''}
|
||||
onChange={(event) =>
|
||||
{
|
||||
if (event.target.value)
|
||||
roomClient.changeAudioOutputDevice(event.target.value);
|
||||
}}
|
||||
displayEmpty
|
||||
name={intl.formatMessage({
|
||||
id : 'settings.audioOutput',
|
||||
defaultMessage : 'Audio output device'
|
||||
})}
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
disabled={audioOutputDevices.length === 0 || me.audioOutputInProgress}
|
||||
>
|
||||
{ audioOutputDevices.map((audioOutput, index) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem
|
||||
key={index}
|
||||
value={audioOutput.deviceId}
|
||||
>
|
||||
{audioOutput.label}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
{ audioOutputDevices.length > 0 ?
|
||||
intl.formatMessage({
|
||||
id : 'settings.selectAudioOutput',
|
||||
defaultMessage : 'Select audio output device'
|
||||
})
|
||||
:
|
||||
intl.formatMessage({
|
||||
id : 'settings.cantSelectAudioOutput',
|
||||
defaultMessage : 'Unable to select audio output device'
|
||||
})
|
||||
}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
}
|
||||
<form className={classes.setting} autoComplete='off'>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={settings.resolution || ''}
|
||||
onChange={(event) =>
|
||||
{
|
||||
if (event.target.value)
|
||||
roomClient.changeVideoResolution(event.target.value);
|
||||
}}
|
||||
name='Video resolution'
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
>
|
||||
{ resolutions.map((resolution, index) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem key={index} value={resolution.value}>
|
||||
{resolution.label}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
<FormattedMessage
|
||||
id='settings.resolution'
|
||||
defaultMessage='Select your video resolution'
|
||||
/>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
MediaSettings.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
settings : PropTypes.object.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
me : state.me,
|
||||
settings : state.settings
|
||||
};
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.me === next.me &&
|
||||
prev.settings === next.settings
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(MediaSettings)));
|
||||
|
|
@ -1,22 +1,25 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../RoomContext';
|
||||
import * as roomActions from '../../actions/roomActions';
|
||||
import * as settingsActions from '../../actions/settingsActions';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import Tabs from '@material-ui/core/Tabs';
|
||||
import Tab from '@material-ui/core/Tab';
|
||||
import MediaSettings from './MediaSettings';
|
||||
import AppearenceSettings from './AppearenceSettings';
|
||||
import AdvancedSettings from './AdvancedSettings';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
|
||||
const tabs =
|
||||
[
|
||||
'media',
|
||||
'appearence',
|
||||
'advanced'
|
||||
];
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
|
|
@ -43,106 +46,27 @@ const styles = (theme) =>
|
|||
width : '90vw'
|
||||
}
|
||||
},
|
||||
setting :
|
||||
tabsHeader :
|
||||
{
|
||||
padding : theme.spacing(2)
|
||||
},
|
||||
formControl :
|
||||
{
|
||||
display : 'flex'
|
||||
flexGrow : 1
|
||||
}
|
||||
});
|
||||
|
||||
const Settings = ({
|
||||
roomClient,
|
||||
room,
|
||||
me,
|
||||
settings,
|
||||
onToggleAdvancedMode,
|
||||
onTogglePermanentTopBar,
|
||||
currentSettingsTab,
|
||||
settingsOpen,
|
||||
handleCloseSettings,
|
||||
handleChangeMode,
|
||||
setSettingsTab,
|
||||
classes
|
||||
}) =>
|
||||
{
|
||||
const intl = useIntl();
|
||||
|
||||
const modes = [ {
|
||||
value : 'democratic',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.democratic',
|
||||
defaultMessage : 'Democratic view'
|
||||
})
|
||||
}, {
|
||||
value : 'filmstrip',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.filmstrip',
|
||||
defaultMessage : 'Filmstrip view'
|
||||
})
|
||||
} ];
|
||||
|
||||
const resolutions = [ {
|
||||
value : 'low',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.low',
|
||||
defaultMessage : 'Low'
|
||||
})
|
||||
},
|
||||
{
|
||||
value : 'medium',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.medium',
|
||||
defaultMessage : 'Medium'
|
||||
})
|
||||
},
|
||||
{
|
||||
value : 'high',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.high',
|
||||
defaultMessage : 'High (HD)'
|
||||
})
|
||||
},
|
||||
{
|
||||
value : 'veryhigh',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.veryHigh',
|
||||
defaultMessage : 'Very high (FHD)'
|
||||
})
|
||||
},
|
||||
{
|
||||
value : 'ultra',
|
||||
label : intl.formatMessage({
|
||||
id : 'label.ultra',
|
||||
defaultMessage : 'Ultra (UHD)'
|
||||
})
|
||||
} ];
|
||||
|
||||
let webcams;
|
||||
|
||||
if (me.webcamDevices)
|
||||
webcams = Object.values(me.webcamDevices);
|
||||
else
|
||||
webcams = [];
|
||||
|
||||
let audioDevices;
|
||||
|
||||
if (me.audioDevices)
|
||||
audioDevices = Object.values(me.audioDevices);
|
||||
else
|
||||
audioDevices = [];
|
||||
|
||||
let audioOutputDevices;
|
||||
|
||||
if (me.audioOutputDevices)
|
||||
audioOutputDevices = Object.values(me.audioOutputDevices);
|
||||
else
|
||||
audioOutputDevices = [];
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
className={classes.root}
|
||||
open={room.settingsOpen}
|
||||
onClose={() => handleCloseSettings({ settingsOpen: false })}
|
||||
open={settingsOpen}
|
||||
onClose={() => handleCloseSettings(false)}
|
||||
classes={{
|
||||
paper : classes.dialogPaper
|
||||
}}
|
||||
|
|
@ -153,254 +77,40 @@ const Settings = ({
|
|||
defaultMessage='Settings'
|
||||
/>
|
||||
</DialogTitle>
|
||||
<form className={classes.setting} autoComplete='off'>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={settings.selectedWebcam || ''}
|
||||
onChange={(event) =>
|
||||
{
|
||||
if (event.target.value)
|
||||
roomClient.changeWebcam(event.target.value);
|
||||
}}
|
||||
displayEmpty
|
||||
name={intl.formatMessage({
|
||||
id : 'settings.camera',
|
||||
defaultMessage : 'Camera'
|
||||
})}
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
disabled={webcams.length === 0 || me.webcamInProgress}
|
||||
>
|
||||
{ webcams.map((webcam, index) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem key={index} value={webcam.deviceId}>{webcam.label}</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
{ webcams.length > 0 ?
|
||||
intl.formatMessage({
|
||||
id : 'settings.selectCamera',
|
||||
defaultMessage : 'Select video device'
|
||||
})
|
||||
:
|
||||
intl.formatMessage({
|
||||
id : 'settings.cantSelectCamera',
|
||||
defaultMessage : 'Unable to select video device'
|
||||
})
|
||||
}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
<form className={classes.setting} autoComplete='off'>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={settings.selectedAudioDevice || ''}
|
||||
onChange={(event) =>
|
||||
{
|
||||
if (event.target.value)
|
||||
roomClient.changeAudioDevice(event.target.value);
|
||||
}}
|
||||
displayEmpty
|
||||
name={intl.formatMessage({
|
||||
id : 'settings.audio',
|
||||
defaultMessage : 'Audio device'
|
||||
})}
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
disabled={audioDevices.length === 0 || me.audioInProgress}
|
||||
>
|
||||
{ audioDevices.map((audio, index) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem key={index} value={audio.deviceId}>{audio.label}</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
{ audioDevices.length > 0 ?
|
||||
intl.formatMessage({
|
||||
id : 'settings.selectAudio',
|
||||
defaultMessage : 'Select audio device'
|
||||
})
|
||||
:
|
||||
intl.formatMessage({
|
||||
id : 'settings.cantSelectAudio',
|
||||
defaultMessage : 'Unable to select audio device'
|
||||
})
|
||||
}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
{ 'audioOutputSupportedBrowsers' in window.config &&
|
||||
window.config.audioOutputSupportedBrowsers.includes(me.browser.name) &&
|
||||
<form className={classes.setting} autoComplete='off'>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={settings.selectedAudioOutputDevice || ''}
|
||||
onChange={(event) =>
|
||||
{
|
||||
if (event.target.value)
|
||||
roomClient.changeAudioOutputDevice(event.target.value);
|
||||
}}
|
||||
displayEmpty
|
||||
name={intl.formatMessage({
|
||||
id : 'settings.audioOutput',
|
||||
defaultMessage : 'Audio output device'
|
||||
})}
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
disabled={audioOutputDevices.length === 0 || me.audioOutputInProgress}
|
||||
>
|
||||
{ audioOutputDevices.map((audioOutput, index) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem
|
||||
key={index}
|
||||
value={audioOutput.deviceId}
|
||||
>
|
||||
{audioOutput.label}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
{ audioOutputDevices.length > 0 ?
|
||||
intl.formatMessage({
|
||||
id : 'settings.selectAudioOutput',
|
||||
defaultMessage : 'Select audio output device'
|
||||
})
|
||||
:
|
||||
intl.formatMessage({
|
||||
id : 'settings.cantSelectAudioOutput',
|
||||
defaultMessage : 'Unable to select audio output device'
|
||||
})
|
||||
}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
}
|
||||
<form className={classes.setting} autoComplete='off'>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={settings.resolution || ''}
|
||||
onChange={(event) =>
|
||||
{
|
||||
if (event.target.value)
|
||||
roomClient.changeVideoResolution(event.target.value);
|
||||
}}
|
||||
name='Video resolution'
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
>
|
||||
{ resolutions.map((resolution, index) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem key={index} value={resolution.value}>
|
||||
{resolution.label}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
<FormattedMessage
|
||||
id='settings.resolution'
|
||||
defaultMessage='Select your video resolution'
|
||||
/>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</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={intl.formatMessage({
|
||||
id : 'settings.layout',
|
||||
defaultMessage : 'Room layout'
|
||||
})}
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
>
|
||||
{ modes.map((mode, index) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem key={index} value={mode.value}>
|
||||
{mode.label}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
<FormattedMessage
|
||||
id='settings.selectRoomLayout'
|
||||
defaultMessage='Select room layout'
|
||||
/>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
<FormControlLabel
|
||||
className={classes.setting}
|
||||
control={<Checkbox checked={settings.advancedMode} onChange={onToggleAdvancedMode} value='advancedMode' />}
|
||||
label={intl.formatMessage({
|
||||
id : 'settings.advancedMode',
|
||||
defaultMessage : 'Advanced mode'
|
||||
})}
|
||||
/>
|
||||
{ settings.advancedMode &&
|
||||
<React.Fragment>
|
||||
{ !window.config.lockLastN &&
|
||||
<form className={classes.setting} autoComplete='off'>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={settings.lastN || ''}
|
||||
onChange={(event) =>
|
||||
{
|
||||
if (event.target.value)
|
||||
roomClient.changeMaxSpotlights(event.target.value);
|
||||
}}
|
||||
name='Last N'
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
>
|
||||
{ Array.from(
|
||||
{ length: window.config.maxLastN || 10 },
|
||||
(_, i) => i + 1
|
||||
).map((lastN) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem key={lastN} value={lastN}>
|
||||
{lastN}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
<FormattedMessage
|
||||
id='settings.lastn'
|
||||
defaultMessage='Number of visible videos'
|
||||
/>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
<Tabs
|
||||
className={classes.tabsHeader}
|
||||
value={tabs.indexOf(currentSettingsTab)}
|
||||
onChange={(event, value) => setSettingsTab(tabs[value])}
|
||||
indicatorColor='primary'
|
||||
textColor='primary'
|
||||
variant='fullWidth'
|
||||
>
|
||||
<Tab
|
||||
label={
|
||||
intl.formatMessage({
|
||||
id : 'label.media',
|
||||
defaultMessage : 'Media'
|
||||
})
|
||||
}
|
||||
<FormControlLabel
|
||||
className={classes.setting}
|
||||
control={<Checkbox checked={settings.permanentTopBar} onChange={onTogglePermanentTopBar} value='permanentTopBar' />}
|
||||
label={intl.formatMessage({
|
||||
id : 'settings.permanentTopBar',
|
||||
defaultMessage : 'Permanent top bar'
|
||||
})}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
<Tab
|
||||
label={intl.formatMessage({
|
||||
id : 'label.appearence',
|
||||
defaultMessage : 'Appearence'
|
||||
})}
|
||||
/>
|
||||
<Tab
|
||||
label={intl.formatMessage({
|
||||
id : 'label.advanced',
|
||||
defaultMessage : 'Advanced'
|
||||
})}
|
||||
/>
|
||||
</Tabs>
|
||||
{currentSettingsTab === 'media' && <MediaSettings />}
|
||||
{currentSettingsTab === 'appearence' && <AppearenceSettings />}
|
||||
{currentSettingsTab === 'advanced' && <AdvancedSettings />}
|
||||
<DialogActions>
|
||||
<Button onClick={() => handleCloseSettings({ settingsOpen: false })} color='primary'>
|
||||
<Button onClick={() => handleCloseSettings(false)} color='primary'>
|
||||
<FormattedMessage
|
||||
id='label.close'
|
||||
defaultMessage='Close'
|
||||
|
|
@ -413,34 +123,25 @@ const Settings = ({
|
|||
|
||||
Settings.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
room : appPropTypes.Room.isRequired,
|
||||
settings : PropTypes.object.isRequired,
|
||||
onToggleAdvancedMode : PropTypes.func.isRequired,
|
||||
onTogglePermanentTopBar : PropTypes.func.isRequired,
|
||||
handleChangeMode : PropTypes.func.isRequired,
|
||||
handleCloseSettings : PropTypes.func.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
currentSettingsTab : PropTypes.string.isRequired,
|
||||
settingsOpen : PropTypes.bool.isRequired,
|
||||
handleCloseSettings : PropTypes.func.isRequired,
|
||||
setSettingsTab : PropTypes.func.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
me : state.me,
|
||||
room : state.room,
|
||||
settings : state.settings
|
||||
};
|
||||
};
|
||||
({
|
||||
currentSettingsTab : state.room.currentSettingsTab,
|
||||
settingsOpen : state.room.settingsOpen
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
onToggleAdvancedMode : settingsActions.toggleAdvancedMode,
|
||||
onTogglePermanentTopBar : settingsActions.togglePermanentTopBar,
|
||||
handleChangeMode : roomActions.setDisplayMode,
|
||||
handleCloseSettings : roomActions.setSettingsOpen
|
||||
handleCloseSettings : roomActions.setSettingsOpen,
|
||||
setSettingsTab : roomActions.setSettingsTab
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
|
|
@ -448,10 +149,9 @@ export default withRoomContext(connect(
|
|||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.me === next.me &&
|
||||
prev.room === next.room &&
|
||||
prev.settings === next.settings
|
||||
prev.room.currentSettingsTab === next.room.currentSettingsTab &&
|
||||
prev.room.settingsOpen === next.room.settingsOpen
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(Settings)));
|
||||
)(withStyles(styles)(Settings));
|
||||
|
|
@ -96,11 +96,6 @@ const FullScreenView = (props) =>
|
|||
!consumer.remotelyPaused
|
||||
);
|
||||
|
||||
let consumerProfile;
|
||||
|
||||
if (consumer)
|
||||
consumerProfile = consumer.profile;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<div className={classes.controls}>
|
||||
|
|
@ -121,9 +116,25 @@ const FullScreenView = (props) =>
|
|||
<VideoView
|
||||
advancedMode={advancedMode}
|
||||
videoContain
|
||||
videoTrack={consumer ? consumer.track : null}
|
||||
consumerSpatialLayers={consumer ? consumer.spatialLayers : null}
|
||||
consumerTemporalLayers={consumer ? consumer.temporalLayers : null}
|
||||
consumerCurrentSpatialLayer={
|
||||
consumer ? consumer.currentSpatialLayer : null
|
||||
}
|
||||
consumerCurrentTemporalLayer={
|
||||
consumer ? consumer.currentTemporalLayer : null
|
||||
}
|
||||
consumerPreferredSpatialLayer={
|
||||
consumer ? consumer.preferredSpatialLayer : null
|
||||
}
|
||||
consumerPreferredTemporalLayer={
|
||||
consumer ? consumer.preferredTemporalLayer : null
|
||||
}
|
||||
videoMultiLayer={consumer && consumer.type !== 'simple'}
|
||||
videoTrack={consumer && consumer.track}
|
||||
videoVisible={consumerVisible}
|
||||
videoProfile={consumerProfile}
|
||||
videoCodec={consumer && consumer.codec}
|
||||
videoScore={consumer ? consumer.score : null}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -81,11 +81,14 @@ class FullView extends React.PureComponent
|
|||
this._setTracks(videoTrack);
|
||||
}
|
||||
|
||||
componentDidUpdate()
|
||||
componentDidUpdate(prevProps)
|
||||
{
|
||||
const { videoTrack } = this.props;
|
||||
if (prevProps !== this.props)
|
||||
{
|
||||
const { videoTrack } = this.props;
|
||||
|
||||
this._setTracks(videoTrack);
|
||||
this._setTracks(videoTrack);
|
||||
}
|
||||
}
|
||||
|
||||
_setTracks(videoTrack)
|
||||
|
|
|
|||
|
|
@ -345,11 +345,14 @@ class VideoView extends React.PureComponent
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUpdate()
|
||||
componentDidUpdate(prevProps)
|
||||
{
|
||||
const { videoTrack, audioTrack } = this.props;
|
||||
if (prevProps !== this.props)
|
||||
{
|
||||
const { videoTrack, audioTrack } = this.props;
|
||||
|
||||
this._setTracks(videoTrack, audioTrack);
|
||||
this._setTracks(videoTrack, audioTrack);
|
||||
}
|
||||
}
|
||||
|
||||
_setTracks(videoTrack, audioTrack)
|
||||
|
|
|
|||
|
|
@ -23,18 +23,29 @@ const VideoWindow = (props) =>
|
|||
!consumer.remotelyPaused
|
||||
);
|
||||
|
||||
let consumerProfile;
|
||||
|
||||
if (consumer)
|
||||
consumerProfile = consumer.profile;
|
||||
|
||||
return (
|
||||
<NewWindow onUnload={toggleConsumerWindow}>
|
||||
<FullView
|
||||
advancedMode={advancedMode}
|
||||
videoTrack={consumer ? consumer.track : null}
|
||||
consumerSpatialLayers={consumer ? consumer.spatialLayers : null}
|
||||
consumerTemporalLayers={consumer ? consumer.temporalLayers : null}
|
||||
consumerCurrentSpatialLayer={
|
||||
consumer ? consumer.currentSpatialLayer : null
|
||||
}
|
||||
consumerCurrentTemporalLayer={
|
||||
consumer ? consumer.currentTemporalLayer : null
|
||||
}
|
||||
consumerPreferredSpatialLayer={
|
||||
consumer ? consumer.preferredSpatialLayer : null
|
||||
}
|
||||
consumerPreferredTemporalLayer={
|
||||
consumer ? consumer.preferredTemporalLayer : null
|
||||
}
|
||||
videoMultiLayer={consumer && consumer.type !== 'simple'}
|
||||
videoTrack={consumer && consumer.track}
|
||||
videoVisible={consumerVisible}
|
||||
videoProfile={consumerProfile}
|
||||
videoCodec={consumer && consumer.codec}
|
||||
videoScore={consumer ? consumer.score : null}
|
||||
/>
|
||||
</NewWindow>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ export const Me = PropTypes.shape(
|
|||
export const Producer = PropTypes.shape(
|
||||
{
|
||||
id : PropTypes.string.isRequired,
|
||||
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired,
|
||||
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen', 'extravideo' ]).isRequired,
|
||||
deviceLabel : PropTypes.string,
|
||||
type : PropTypes.oneOf([ 'front', 'back', 'screen' ]),
|
||||
type : PropTypes.oneOf([ 'front', 'back', 'screen', 'extravideo' ]),
|
||||
paused : PropTypes.bool.isRequired,
|
||||
track : PropTypes.any,
|
||||
codec : PropTypes.string.isRequired
|
||||
|
|
@ -37,7 +37,7 @@ export const Consumer = PropTypes.shape(
|
|||
{
|
||||
id : PropTypes.string.isRequired,
|
||||
peerId : PropTypes.string.isRequired,
|
||||
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired,
|
||||
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen', 'extravideo' ]).isRequired,
|
||||
locallyPaused : PropTypes.bool.isRequired,
|
||||
remotelyPaused : PropTypes.bool.isRequired,
|
||||
profile : PropTypes.oneOf([ 'none', 'default', 'low', 'medium', 'high' ]),
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 96 96"
|
||||
style="enable-background:new 0 0 96 96;"
|
||||
xml:space="preserve">
|
||||
<metadata
|
||||
id="metadata11"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata>
|
||||
<defs
|
||||
id="defs9" />
|
||||
<path
|
||||
style="fill:#000000;stroke-width:0.40677965"
|
||||
d="m 33.894283,77.837288 c -1.428534,-1.845763 -3.909722,-5.220659 -5.513751,-7.499764 -1.60403,-2.279109 -4.323663,-5.940126 -6.043631,-8.135593 -5.698554,-7.273973 -6.224902,-8.044795 -6.226676,-9.118803 -0.0034,-2.075799 2.81181,-4.035355 4.9813,-3.467247 0.50339,0.131819 2.562712,1.72771 4.576272,3.546423 4.238418,3.828283 6.617166,5.658035 7.355654,5.658035 0.82497,0 1.045415,-1.364294 0.567453,-3.511881 C 33.348583,54.219654 31.1088,48.20339 28.613609,41.938983 23.524682,29.162764 23.215312,27.731034 25.178629,26.04226 c 2.443255,-2.101599 4.670178,-1.796504 6.362271,0.87165 0.639176,1.007875 2.666245,5.291978 4.504599,9.520229 1.838354,4.228251 3.773553,8.092718 4.300442,8.587705 l 0.957981,0.899977 0.419226,-1.102646 c 0.255274,-0.671424 0.419225,-6.068014 0.419225,-13.799213 0,-13.896836 -0.0078,-13.84873 2.44517,-15.1172 1.970941,-1.019214 4.2259,-0.789449 5.584354,0.569005 l 1.176852,1.176852 0.483523,11.738402 c 0.490017,11.896027 0.826095,14.522982 1.911266,14.939402 1.906224,0.731486 2.21601,-0.184677 4.465407,-13.206045 1.239206,-7.173539 1.968244,-10.420721 2.462128,-10.966454 1.391158,-1.537215 4.742705,-1.519809 6.295208,0.03269 1.147387,1.147388 1.05469,3.124973 -0.669503,14.283063 -0.818745,5.298489 -1.36667,10.090163 -1.220432,10.67282 0.14596,0.581557 0.724796,1.358395 1.286298,1.726306 0.957759,0.627548 1.073422,0.621575 1.86971,-0.09655 0.466837,-0.421011 1.761787,-2.595985 2.877665,-4.833273 2.564176,-5.141059 3.988466,-6.711864 6.085822,-6.711864 2.769954,0 3.610947,2.927256 2.139316,7.446329 C 78.799497,44.318351 66.752066,77.28024 65.51653,80.481356 65.262041,81.140709 64.18139,81.19322 50.866695,81.19322 H 36.491617 Z"
|
||||
id="path3710"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
|
|
@ -1,26 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 96 96"
|
||||
style="enable-background:new 0 0 96 96;"
|
||||
xml:space="preserve">
|
||||
<metadata
|
||||
id="metadata11"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata>
|
||||
<defs
|
||||
id="defs9" />
|
||||
<path
|
||||
style="fill:#ffffff;stroke-width:0.40677965"
|
||||
d="m 33.894283,77.837288 c -1.428534,-1.845763 -3.909722,-5.220659 -5.513751,-7.499764 -1.60403,-2.279109 -4.323663,-5.940126 -6.043631,-8.135593 -5.698554,-7.273973 -6.224902,-8.044795 -6.226676,-9.118803 -0.0034,-2.075799 2.81181,-4.035355 4.9813,-3.467247 0.50339,0.131819 2.562712,1.72771 4.576272,3.546423 4.238418,3.828283 6.617166,5.658035 7.355654,5.658035 0.82497,0 1.045415,-1.364294 0.567453,-3.511881 C 33.348583,54.219654 31.1088,48.20339 28.613609,41.938983 23.524682,29.162764 23.215312,27.731034 25.178629,26.04226 c 2.443255,-2.101599 4.670178,-1.796504 6.362271,0.87165 0.639176,1.007875 2.666245,5.291978 4.504599,9.520229 1.838354,4.228251 3.773553,8.092718 4.300442,8.587705 l 0.957981,0.899977 0.419226,-1.102646 c 0.255274,-0.671424 0.419225,-6.068014 0.419225,-13.799213 0,-13.896836 -0.0078,-13.84873 2.44517,-15.1172 1.970941,-1.019214 4.2259,-0.789449 5.584354,0.569005 l 1.176852,1.176852 0.483523,11.738402 c 0.490017,11.896027 0.826095,14.522982 1.911266,14.939402 1.906224,0.731486 2.21601,-0.184677 4.465407,-13.206045 1.239206,-7.173539 1.968244,-10.420721 2.462128,-10.966454 1.391158,-1.537215 4.742705,-1.519809 6.295208,0.03269 1.147387,1.147388 1.05469,3.124973 -0.669503,14.283063 -0.818745,5.298489 -1.36667,10.090163 -1.220432,10.67282 0.14596,0.581557 0.724796,1.358395 1.286298,1.726306 0.957759,0.627548 1.073422,0.621575 1.86971,-0.09655 0.466837,-0.421011 1.761787,-2.595985 2.877665,-4.833273 2.564176,-5.141059 3.988466,-6.711864 6.085822,-6.711864 2.769954,0 3.610947,2.927256 2.139316,7.446329 C 78.799497,44.318351 66.752066,77.28024 65.51653,80.481356 65.262041,81.140709 64.18139,81.19322 50.866695,81.19322 H 36.491617 Z"
|
||||
id="path3710"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
|
|
@ -15,8 +15,8 @@ const initialState =
|
|||
screenShareInProgress : false,
|
||||
displayNameInProgress : false,
|
||||
loginEnabled : false,
|
||||
raiseHand : false,
|
||||
raiseHandInProgress : false,
|
||||
raisedHand : false,
|
||||
raisedHandInProgress : false,
|
||||
loggedIn : false,
|
||||
isSpeaking : false
|
||||
};
|
||||
|
|
@ -134,18 +134,18 @@ const me = (state = initialState, action) =>
|
|||
return { ...state, screenShareInProgress: flag };
|
||||
}
|
||||
|
||||
case 'SET_MY_RAISE_HAND_STATE':
|
||||
case 'SET_RAISED_HAND':
|
||||
{
|
||||
const { flag } = action.payload;
|
||||
|
||||
return { ...state, raiseHand: flag };
|
||||
return { ...state, raisedHand: flag };
|
||||
}
|
||||
|
||||
case 'SET_MY_RAISE_HAND_STATE_IN_PROGRESS':
|
||||
case 'SET_RAISED_HAND_IN_PROGRESS':
|
||||
{
|
||||
const { flag } = action.payload;
|
||||
|
||||
return { ...state, raiseHandInProgress: flag };
|
||||
return { ...state, raisedHandInProgress: flag };
|
||||
}
|
||||
|
||||
case 'SET_DISPLAY_NAME_IN_PROGRESS':
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ const peer = (state = {}, action) =>
|
|||
case 'SET_PEER_KICK_IN_PROGRESS':
|
||||
return { ...state, peerKickInProgress: action.payload.flag };
|
||||
|
||||
case 'SET_PEER_RAISE_HAND_STATE':
|
||||
return { ...state, raiseHandState: action.payload.raiseHandState };
|
||||
case 'SET_PEER_RAISED_HAND':
|
||||
return { ...state, raisedHand: action.payload.raisedHand };
|
||||
|
||||
case 'ADD_CONSUMER':
|
||||
{
|
||||
|
|
@ -86,7 +86,7 @@ const peers = (state = {}, action) =>
|
|||
case 'SET_PEER_VIDEO_IN_PROGRESS':
|
||||
case 'SET_PEER_AUDIO_IN_PROGRESS':
|
||||
case 'SET_PEER_SCREEN_IN_PROGRESS':
|
||||
case 'SET_PEER_RAISE_HAND_STATE':
|
||||
case 'SET_PEER_RAISED_HAND':
|
||||
case 'SET_PEER_PICTURE':
|
||||
case 'ADD_CONSUMER':
|
||||
case 'ADD_PEER_ROLE':
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ const initialState =
|
|||
selectedPeerId : null,
|
||||
spotlights : [],
|
||||
settingsOpen : false,
|
||||
extraVideoOpen : false,
|
||||
currentSettingsTab : 'media', // media, appearence, advanced
|
||||
lockDialogOpen : false,
|
||||
joined : false,
|
||||
muteAllInProgress : false,
|
||||
|
|
@ -34,6 +36,7 @@ const initialState =
|
|||
SEND_CHAT : [],
|
||||
MODERATE_CHAT : [],
|
||||
SHARE_SCREEN : [],
|
||||
EXTRA_VIDEO : [],
|
||||
SHARE_FILE : [],
|
||||
MODERATE_FILES : [],
|
||||
MODERATE_ROOM : []
|
||||
|
|
@ -113,6 +116,20 @@ const room = (state = initialState, action) =>
|
|||
return { ...state, settingsOpen };
|
||||
}
|
||||
|
||||
case 'SET_EXTRA_VIDEO_OPEN':
|
||||
{
|
||||
const { extraVideoOpen } = action.payload;
|
||||
|
||||
return { ...state, extraVideoOpen };
|
||||
}
|
||||
|
||||
case 'SET_SETTINGS_TAB':
|
||||
{
|
||||
const { tab } = action.payload;
|
||||
|
||||
return { ...state, currentSettingsTab: tab };
|
||||
}
|
||||
|
||||
case 'SET_ROOM_ACTIVE_SPEAKER':
|
||||
{
|
||||
const { peerId } = action.payload;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ const initialState =
|
|||
// low, medium, high, veryhigh, ultra
|
||||
resolution : window.config.defaultResolution || 'medium',
|
||||
lastN : 4,
|
||||
permanentTopBar : true
|
||||
permanentTopBar : true,
|
||||
hiddenControls : false,
|
||||
notificationSounds : true
|
||||
};
|
||||
|
||||
const settings = (state = initialState, action) =>
|
||||
|
|
@ -57,6 +59,20 @@ const settings = (state = initialState, action) =>
|
|||
return { ...state, permanentTopBar };
|
||||
}
|
||||
|
||||
case 'TOGGLE_HIDDEN_CONTROLS':
|
||||
{
|
||||
const hiddenControls = !state.hiddenControls;
|
||||
|
||||
return { ...state, hiddenControls };
|
||||
}
|
||||
|
||||
case 'TOGGLE_NOTIFICATION_SOUNDS':
|
||||
{
|
||||
const notificationSounds = !state.notificationSounds;
|
||||
|
||||
return { ...state, notificationSounds };
|
||||
}
|
||||
|
||||
case 'SET_VIDEO_RESOLUTION':
|
||||
{
|
||||
const { resolution } = action.payload;
|
||||
|
|
|
|||
|
|
@ -56,9 +56,15 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "登录",
|
||||
"tooltip.logout": "注销",
|
||||
"tooltip.admitFromLobby": "从大厅允许",
|
||||
|
|
@ -72,6 +78,7 @@
|
|||
"tooltip.kickParticipant": null,
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "房间名称",
|
||||
"label.chooseRoomButton": "继续",
|
||||
|
|
@ -95,6 +102,10 @@
|
|||
"label.veryHigh": "非常高 (FHD)",
|
||||
"label.ultra": "超高 (UHD)",
|
||||
"label.close": "关闭",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "设置",
|
||||
"settings.camera": "视频设备",
|
||||
|
|
@ -112,6 +123,8 @@
|
|||
"settings.advancedMode": "高级模式",
|
||||
"settings.permanentTopBar": "永久顶吧",
|
||||
"settings.lastn": "可见视频数量",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "无法保存文件",
|
||||
"filesharing.startingFileShare": "正在尝试共享文件",
|
||||
|
|
|
|||
|
|
@ -55,9 +55,15 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "Přihlášení",
|
||||
"tooltip.logout": "Odhlášení",
|
||||
"tooltip.admitFromLobby": "Povolit uživatele z Přijímací místnosti",
|
||||
|
|
@ -71,6 +77,7 @@
|
|||
"tooltip.kickParticipant": null,
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "Jméno místnosti",
|
||||
"label.chooseRoomButton": "Pokračovat",
|
||||
|
|
@ -94,6 +101,10 @@
|
|||
"label.veryHigh": "Velmi vysoké (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Zavřít",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Nastavení",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -109,6 +120,10 @@
|
|||
"settings.layout": "Rozvržení místnosti",
|
||||
"settings.selectRoomLayout": "Vyberte rozvržení místnosti",
|
||||
"settings.advancedMode": "Pokočilý mód",
|
||||
"settings.permanentTopBar": null,
|
||||
"settings.lastn": null,
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "Není možné uložit soubor",
|
||||
"filesharing.startingFileShare": "Pokouším se sdílet soubor",
|
||||
|
|
|
|||
|
|
@ -56,8 +56,14 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": "Dein Browser unterstützt keine Spracherkennung",
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen",
|
||||
"me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen",
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "Anmelden",
|
||||
"tooltip.logout": "Abmelden",
|
||||
|
|
@ -72,6 +78,7 @@
|
|||
"tooltip.kickParticipant": "Teilnehmer rauswerfen",
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "Name des Raums",
|
||||
"label.chooseRoomButton": "Weiter",
|
||||
|
|
@ -95,6 +102,10 @@
|
|||
"label.veryHigh": "Sehr hoch (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Schließen",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Einstellungen",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -112,6 +123,8 @@
|
|||
"settings.advancedMode": "Erweiterter Modus",
|
||||
"settings.permanentTopBar": "Permanente obere Leiste",
|
||||
"settings.lastn": "Anzahl der sichtbaren Videos",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "Fehler beim Speichern der Datei",
|
||||
"filesharing.startingFileShare": "Starte Teilen der Datei",
|
||||
|
|
|
|||
|
|
@ -56,9 +56,15 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "Log ind",
|
||||
"tooltip.logout": "Log ud",
|
||||
"tooltip.admitFromLobby": "Giv adgang fra lobbyen",
|
||||
|
|
@ -72,6 +78,7 @@
|
|||
"tooltip.kickParticipant": null,
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "Værelsesnavn",
|
||||
"label.chooseRoomButton": "Fortsæt",
|
||||
|
|
@ -95,6 +102,10 @@
|
|||
"label.veryHigh": "Meget høj (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Luk",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Indstillinger",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -106,13 +117,14 @@
|
|||
"settings.audioOutput": "Audio output enhed",
|
||||
"settings.selectAudioOutput": "Vælg lydudgangsenhed",
|
||||
"settings.cantSelectAudioOutput": "Kan ikke vælge lydoutputenhed",
|
||||
|
||||
"settings.resolution": "Vælg din videoopløsning",
|
||||
"settings.layout": "Møde visning",
|
||||
"settings.selectRoomLayout": "Vælg møde visning",
|
||||
"settings.advancedMode": "Avanceret tilstand",
|
||||
"settings.permanentTopBar": "Permanent øverste linje",
|
||||
"settings.lastn": "Antal synlige videoer",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "Kan ikke gemme fil",
|
||||
"filesharing.startingFileShare": "Forsøger at dele filen",
|
||||
|
|
|
|||
|
|
@ -56,9 +56,15 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "Σύνδεση",
|
||||
"tooltip.logout": "Αποσύνδεση",
|
||||
"tooltip.admitFromLobby": "Admit from lobby",
|
||||
|
|
@ -72,6 +78,7 @@
|
|||
"tooltip.kickParticipant": null,
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "Όνομα δωματίου",
|
||||
"label.chooseRoomButton": "Συνέχεια",
|
||||
|
|
@ -95,6 +102,10 @@
|
|||
"label.veryHigh": "Πολύ υψηλή (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Κλείσιμο",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Ρυθμίσεις",
|
||||
"settings.camera": "Κάμερα",
|
||||
|
|
@ -112,6 +123,8 @@
|
|||
"settings.advancedMode": "Προηγμένη λειτουργία",
|
||||
"settings.permanentTopBar": "Μόνιμη μπάρα κορυφής",
|
||||
"settings.lastn": "Αριθμός ορατών βίντεο",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "Αδυναμία αποθήκευσης του αρχείου",
|
||||
"filesharing.startingFileShare": "Προσπάθεια διαμοιρασμού αρχείου",
|
||||
|
|
|
|||
|
|
@ -56,9 +56,15 @@
|
|||
"room.clearFileSharing": "Clear files",
|
||||
"room.speechUnsupported": "Your browser does not support speech recognition",
|
||||
"room.moderatoractions": "Moderator actions",
|
||||
"room.raisedHand": "{displayName} raised their hand",
|
||||
"room.loweredHand": "{displayName} put their hand down",
|
||||
"room.extraVideo": "Extra video",
|
||||
|
||||
"me.mutedPTT": "You are muted, hold down SPACE-BAR to talk",
|
||||
|
||||
"roles.gotRole": "You got the role: {role}",
|
||||
"roles.lostRole": "You lost the role: {role}",
|
||||
|
||||
"tooltip.login": "Log in",
|
||||
"tooltip.logout": "Log out",
|
||||
"tooltip.admitFromLobby": "Admit from lobby",
|
||||
|
|
@ -72,6 +78,7 @@
|
|||
"tooltip.kickParticipant": "Kick out participant",
|
||||
"tooltip.muteParticipant": "Mute participant",
|
||||
"tooltip.muteParticipantVideo": "Mute participant video",
|
||||
"tooltip.raisedHand": "Raise hand",
|
||||
|
||||
"label.roomName": "Room name",
|
||||
"label.chooseRoomButton": "Continue",
|
||||
|
|
@ -95,6 +102,10 @@
|
|||
"label.veryHigh": "Very high (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Close",
|
||||
"label.media": "Media",
|
||||
"label.appearence": "Appearence",
|
||||
"label.advanced": "Advanced",
|
||||
"label.addVideo": "Add video",
|
||||
|
||||
"settings.settings": "Settings",
|
||||
"settings.camera": "Camera",
|
||||
|
|
@ -112,6 +123,8 @@
|
|||
"settings.advancedMode": "Advanced mode",
|
||||
"settings.permanentTopBar": "Permanent top bar",
|
||||
"settings.lastn": "Number of visible videos",
|
||||
"settings.hiddenControls": "Hidden media controls",
|
||||
"settings.notificationSounds": "Notification sounds",
|
||||
|
||||
"filesharing.saveFileError": "Unable to save file",
|
||||
"filesharing.startingFileShare": "Attempting to share file",
|
||||
|
|
|
|||
|
|
@ -56,9 +56,15 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "Entrar",
|
||||
"tooltip.logout": "Salir",
|
||||
"tooltip.admitFromLobby": "Admitir desde la sala de espera",
|
||||
|
|
@ -72,6 +78,7 @@
|
|||
"tooltip.kickParticipant": null,
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "Nombre de la sala",
|
||||
"label.chooseRoomButton": "Continuar",
|
||||
|
|
@ -95,6 +102,10 @@
|
|||
"label.veryHigh": "Muy alta (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Cerrar",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Ajustes",
|
||||
"settings.camera": "Cámara",
|
||||
|
|
@ -112,6 +123,8 @@
|
|||
"settings.advancedMode": "Modo avanzado",
|
||||
"settings.permanentTopBar": "Barra superior permanente",
|
||||
"settings.lastn": "Cantidad de videos visibles",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "No ha sido posible guardar el fichero",
|
||||
"filesharing.startingFileShare": "Intentando compartir el fichero",
|
||||
|
|
|
|||
|
|
@ -56,9 +56,15 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "Connexion",
|
||||
"tooltip.logout": "Déconnexion",
|
||||
"tooltip.admitFromLobby": "Autorisé depuis la salle d'attente",
|
||||
|
|
@ -72,6 +78,7 @@
|
|||
"tooltip.kickParticipant": null,
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "Nom de la salle",
|
||||
"label.chooseRoomButton": "Continuer",
|
||||
|
|
@ -95,6 +102,10 @@
|
|||
"label.veryHigh": "Très Haute Définition (FHD)",
|
||||
"label.ultra": "Ultra Haute Définition",
|
||||
"label.close": "Fermer",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Paramètres",
|
||||
"settings.camera": "Caméra",
|
||||
|
|
@ -112,6 +123,8 @@
|
|||
"settings.advancedMode": "Mode avancé",
|
||||
"settings.permanentTopBar": "Barre supérieure permanente",
|
||||
"settings.lastn": "Nombre de vidéos visibles",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "Impossible d'enregistrer le fichier",
|
||||
"filesharing.startingFileShare": "Début du transfert de fichier",
|
||||
|
|
|
|||
|
|
@ -56,9 +56,15 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": "Vaš preglednik ne podržava prepoznavanje govora",
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor",
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "Prijava",
|
||||
"tooltip.logout": "Odjava",
|
||||
"tooltip.admitFromLobby": "Pusti iz predvorja",
|
||||
|
|
@ -72,6 +78,7 @@
|
|||
"tooltip.kickParticipant": "Izbaci sudionika",
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "Naziv sobe",
|
||||
"label.chooseRoomButton": "Nastavi",
|
||||
|
|
@ -95,6 +102,10 @@
|
|||
"label.veryHigh": "Vrlo visoka (FHD)",
|
||||
"label.ultra": "Ultra visoka (UHD)",
|
||||
"label.close": "Zatvori",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Postavke",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -112,6 +123,8 @@
|
|||
"settings.advancedMode": "Napredne mogućnosti",
|
||||
"settings.permanentTopBar": "Stalna gornja šipka",
|
||||
"settings.lastn": "Broj vidljivih videozapisa",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "Nije moguće spremiti datoteku",
|
||||
"filesharing.startingFileShare": "Pokušaj dijeljenja datoteke",
|
||||
|
|
|
|||
|
|
@ -56,9 +56,15 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "Belépés",
|
||||
"tooltip.logout": "Kilépés",
|
||||
"tooltip.admitFromLobby": "Beenegdem az előszobából",
|
||||
|
|
@ -72,6 +78,7 @@
|
|||
"tooltip.kickParticipant": null,
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "Konferencia",
|
||||
"label.chooseRoomButton": "Tovább",
|
||||
|
|
@ -95,6 +102,10 @@
|
|||
"label.veryHigh": "Nagyon magas (FHD)",
|
||||
"label.ultra": "Ultra magas (UHD)",
|
||||
"label.close": "Bezár",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Beállítások",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -112,6 +123,8 @@
|
|||
"settings.advancedMode": "Részletes információk",
|
||||
"settings.permanentTopBar": "Állandó felső sáv",
|
||||
"settings.lastn": "A látható videók száma",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "A file-t nem sikerült elmenteni",
|
||||
"filesharing.startingFileShare": "Fájl megosztása",
|
||||
|
|
|
|||
|
|
@ -56,9 +56,15 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "Log in",
|
||||
"tooltip.logout": "Log out",
|
||||
"tooltip.admitFromLobby": "Ammetti dalla lobby",
|
||||
|
|
@ -71,6 +77,7 @@
|
|||
"tooltip.participants": "Mostra partecipanti",
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "Nome della stanza",
|
||||
"label.chooseRoomButton": "Continua",
|
||||
|
|
@ -94,6 +101,10 @@
|
|||
"label.veryHigh": "Molto alta (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Chiudi",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Impostazioni",
|
||||
"settings.camera": "Videocamera",
|
||||
|
|
@ -111,6 +122,8 @@
|
|||
"settings.advancedMode": "Modalità avanzata",
|
||||
"settings.permanentTopBar": "Barra superiore permanente",
|
||||
"settings.lastn": "Numero di video visibili",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "Impossibile salvare file",
|
||||
"filesharing.startingFileShare": "Tentativo di condivisione file",
|
||||
|
|
|
|||
|
|
@ -56,9 +56,15 @@
|
|||
"room.clearFileSharing": "Fjern filer",
|
||||
"room.speechUnsupported": "Din nettleser støtter ikke stemmegjenkjenning",
|
||||
"room.moderatoractions": "Moderatorhandlinger",
|
||||
"room.raisedHand": "{displayName} rakk opp hånden",
|
||||
"room.loweredHand": "{displayName} tok ned hånden",
|
||||
"room.extraVideo": "Ekstra video",
|
||||
|
||||
"me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke",
|
||||
|
||||
"roles.gotRole": "Du fikk rollen: {role}",
|
||||
"roles.lostRole": "Du mistet rollen: {role}",
|
||||
|
||||
"tooltip.login": "Logg in",
|
||||
"tooltip.logout": "Logg ut",
|
||||
"tooltip.admitFromLobby": "Slipp inn fra lobby",
|
||||
|
|
@ -72,6 +78,7 @@
|
|||
"tooltip.kickParticipant": "Spark ut deltaker",
|
||||
"tooltip.muteParticipant": "Demp deltaker",
|
||||
"tooltip.muteParticipantVideo": "Demp deltakervideo",
|
||||
"tooltip.raisedHand": "Rekk opp hånden",
|
||||
|
||||
"label.roomName": "Møtenavn",
|
||||
"label.chooseRoomButton": "Fortsett",
|
||||
|
|
@ -95,6 +102,10 @@
|
|||
"label.veryHigh": "Veldig høy (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Lukk",
|
||||
"label.media": "Media",
|
||||
"label.appearence": "Utseende",
|
||||
"label.advanced": "Avansert",
|
||||
"label.addVideo": "Legg til video",
|
||||
|
||||
"settings.settings": "Innstillinger",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -112,6 +123,8 @@
|
|||
"settings.advancedMode": "Avansert modus",
|
||||
"settings.permanentTopBar": "Permanent topplinje",
|
||||
"settings.lastn": "Antall videoer synlig",
|
||||
"settings.hiddenControls": "Skjul media knapper",
|
||||
"settings.notificationSounds": "Varslingslyder",
|
||||
|
||||
"filesharing.saveFileError": "Klarte ikke å lagre fil",
|
||||
"filesharing.startingFileShare": "Starter fildeling",
|
||||
|
|
|
|||
|
|
@ -56,9 +56,15 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "Zaloguj",
|
||||
"tooltip.logout": "Wyloguj",
|
||||
"tooltip.admitFromLobby": "Przejście z poczekalni",
|
||||
|
|
@ -72,6 +78,7 @@
|
|||
"tooltip.kickParticipant": null,
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "Nazwa konferencji",
|
||||
"label.chooseRoomButton": "Kontynuuj",
|
||||
|
|
@ -95,6 +102,10 @@
|
|||
"label.veryHigh": "Bardzo wysoka (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Zamknij",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Ustawienia",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -112,6 +123,8 @@
|
|||
"settings.advancedMode": "Tryb zaawansowany",
|
||||
"settings.permanentTopBar": "Stały górny pasek",
|
||||
"settings.lastn": "Liczba widocznych uczestników (zdalnych)",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "Nie można zapisać pliku",
|
||||
"filesharing.startingFileShare": "Próba udostępnienia pliku",
|
||||
|
|
|
|||
|
|
@ -56,9 +56,15 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "Entrar",
|
||||
"tooltip.logout": "Sair",
|
||||
"tooltip.admitFromLobby": "Admitir da sala de espera",
|
||||
|
|
@ -72,6 +78,7 @@
|
|||
"tooltip.kickParticipant": null,
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "Nome da sala",
|
||||
"label.chooseRoomButton": "Continuar",
|
||||
|
|
@ -95,6 +102,10 @@
|
|||
"label.veryHigh": "Muito alta (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Fechar",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Definições",
|
||||
"settings.camera": "Camera",
|
||||
|
|
@ -112,6 +123,8 @@
|
|||
"settings.advancedMode": "Modo avançado",
|
||||
"settings.permanentTopBar": "Barra superior permanente",
|
||||
"settings.lastn": "Número de vídeos visíveis",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "Impossível de gravar o ficheiro",
|
||||
"filesharing.startingFileShare": "Tentando partilha de ficheiro",
|
||||
|
|
|
|||
|
|
@ -56,9 +56,15 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "Intră în cont",
|
||||
"tooltip.logout": "Deconectare",
|
||||
"tooltip.admitFromLobby": "Admite din hol",
|
||||
|
|
@ -72,6 +78,7 @@
|
|||
"tooltip.kickParticipant": null,
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "Numele camerei",
|
||||
"label.chooseRoomButton": "Continuare",
|
||||
|
|
@ -95,6 +102,10 @@
|
|||
"label.veryHigh": "Rezoluție foarte înaltă (FHD)",
|
||||
"label.ultra": "Rezoluție ultra înaltă (UHD)",
|
||||
"label.close": "Închide",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Setări",
|
||||
"settings.camera": "Cameră video",
|
||||
|
|
@ -112,6 +123,8 @@
|
|||
"settings.advancedMode": "Mod avansat",
|
||||
"settings.permanentTopBar": "Bara de sus permanentă",
|
||||
"settings.lastn": "Numărul de videoclipuri vizibile",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "Încercarea de a salva fișierul a eșuat",
|
||||
"filesharing.startingFileShare": "Partajarea fișierului",
|
||||
|
|
|
|||
|
|
@ -56,6 +56,14 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "Giriş",
|
||||
"tooltip.logout": "Çıkış",
|
||||
|
|
@ -70,6 +78,7 @@
|
|||
"tooltip.kickParticipant": null,
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "Oda adı",
|
||||
"label.chooseRoomButton": "Devam",
|
||||
|
|
@ -93,6 +102,10 @@
|
|||
"label.veryHigh": "Çok Yüksek (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Kapat",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Ayarlar",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -107,6 +120,8 @@
|
|||
"settings.advancedMode": "Detaylı mod",
|
||||
"settings.permanentTopBar": "Üst barı kalıcı yap",
|
||||
"settings.lastn": "İzlenebilir video sayısı",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "Dosya kaydedilemiyor",
|
||||
"filesharing.startingFileShare": "Paylaşılan dosyaya erişiliyor",
|
||||
|
|
|
|||
|
|
@ -56,6 +56,14 @@
|
|||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
|
||||
"tooltip.login": "Увійти",
|
||||
"tooltip.logout": "Вихід",
|
||||
|
|
@ -70,6 +78,7 @@
|
|||
"tooltip.kickParticipant": null,
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
|
||||
"label.roomName": "Назва кімнати",
|
||||
"label.chooseRoomButton": "Продовжити",
|
||||
|
|
@ -93,6 +102,10 @@
|
|||
"label.veryHigh": "Дуже високий (FHD)",
|
||||
"label.ultra": "Ультра (UHD)",
|
||||
"label.close": "Закрити",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Налаштування",
|
||||
"settings.camera": "Камера",
|
||||
|
|
@ -110,6 +123,8 @@
|
|||
"settings.advancedMode": "Розширений режим",
|
||||
"settings.permanentTopBar": "Постійний верхній рядок",
|
||||
"settings.lastn": "Кількість видимих відео",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
|
||||
"filesharing.saveFileError": "Неможливо зберегти файл",
|
||||
"filesharing.startingFileShare": "Спроба поділитися файлом",
|
||||
|
|
|
|||
|
|
@ -235,6 +235,8 @@ module.exports =
|
|||
MODERATE_CHAT : [ userRoles.MODERATOR ],
|
||||
// The role(s) have permission to share screen
|
||||
SHARE_SCREEN : [ userRoles.NORMAL ],
|
||||
// The role(s) have permission to produce extra video
|
||||
EXTRA_VIDEO : [ userRoles.NORMAL ],
|
||||
// The role(s) have permission to share files
|
||||
SHARE_FILE : [ userRoles.NORMAL ],
|
||||
// The role(s) have permission to moderate files
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ class Lobby extends EventEmitter
|
|||
return Object.values(this._peers).map((peer) =>
|
||||
({
|
||||
peerId : peer.id,
|
||||
displayName : peer.displayName
|
||||
displayName : peer.displayName,
|
||||
picture : peer.picture
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -336,7 +336,8 @@ class Peer extends EventEmitter
|
|||
id : this.id,
|
||||
displayName : this.displayName,
|
||||
picture : this.picture,
|
||||
roles : this.roles
|
||||
roles : this.roles,
|
||||
raisedHand : this.raisedHand
|
||||
};
|
||||
|
||||
return peerInfo;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ const permissionsFromRoles =
|
|||
SEND_CHAT : [ userRoles.NORMAL ],
|
||||
MODERATE_CHAT : [ userRoles.MODERATOR ],
|
||||
SHARE_SCREEN : [ userRoles.NORMAL ],
|
||||
EXTRA_VIDEO : [ userRoles.NORMAL ],
|
||||
SHARE_FILE : [ userRoles.NORMAL ],
|
||||
MODERATE_FILES : [ userRoles.MODERATOR ],
|
||||
MODERATE_ROOM : [ userRoles.MODERATOR ],
|
||||
|
|
@ -530,6 +531,17 @@ class Room extends EventEmitter
|
|||
peerId : peer.id,
|
||||
role : newRole
|
||||
}, true, true);
|
||||
|
||||
// Got permission to promote peers, notify peer of
|
||||
// peers in lobby
|
||||
if (permissionsFromRoles.PROMOTE_PEER.includes(newRole))
|
||||
{
|
||||
const lobbyPeers = this._lobby.peerList();
|
||||
|
||||
lobbyPeers.length > 0 && this._notification(peer.socket, 'parkedPeers', {
|
||||
lobbyPeers
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
peer.on('lostRole', ({ oldRole }) =>
|
||||
|
|
@ -586,13 +598,21 @@ class Room extends EventEmitter
|
|||
.filter((joinedPeer) => joinedPeer.id !== peer.id)
|
||||
.map((joinedPeer) => (joinedPeer.peerInfo));
|
||||
|
||||
const lobbyPeers = this._lobby.peerList();
|
||||
|
||||
cb(null, {
|
||||
roles : peer.roles,
|
||||
peers : peerInfos,
|
||||
tracker : config.fileTracker,
|
||||
authenticated : peer.authenticated,
|
||||
permissionsFromRoles : permissionsFromRoles,
|
||||
userRoles : userRoles
|
||||
userRoles : userRoles,
|
||||
chatHistory : this._chatHistory,
|
||||
fileHistory : this._fileHistory,
|
||||
lastNHistory : this._lastN,
|
||||
locked : this._locked,
|
||||
lobbyPeers : lobbyPeers,
|
||||
accessCode : this._accessCode
|
||||
});
|
||||
|
||||
// Mark the new Peer as joined.
|
||||
|
|
@ -728,6 +748,13 @@ class Room extends EventEmitter
|
|||
)
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
if (
|
||||
appData.source === 'extravideo' &&
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.EXTRA_VIDEO.includes(role))
|
||||
)
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
// Ensure the Peer is joined.
|
||||
if (!peer.joined)
|
||||
throw new Error('Peer not yet joined');
|
||||
|
|
@ -1067,26 +1094,6 @@ class Room extends EventEmitter
|
|||
break;
|
||||
}
|
||||
|
||||
case 'serverHistory':
|
||||
{
|
||||
// Return to sender
|
||||
const lobbyPeers = this._lobby.peerList();
|
||||
|
||||
cb(
|
||||
null,
|
||||
{
|
||||
chatHistory : this._chatHistory,
|
||||
fileHistory : this._fileHistory,
|
||||
lastNHistory : this._lastN,
|
||||
locked : this._locked,
|
||||
lobbyPeers : lobbyPeers,
|
||||
accessCode : this._accessCode
|
||||
}
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'lockRoom':
|
||||
{
|
||||
if (
|
||||
|
|
@ -1252,14 +1259,14 @@ class Room extends EventEmitter
|
|||
break;
|
||||
}
|
||||
|
||||
case 'raiseHand':
|
||||
case 'raisedHand':
|
||||
{
|
||||
const { raisedHand } = request.data;
|
||||
|
||||
peer.raisedHand = raisedHand;
|
||||
|
||||
// Spread to others
|
||||
this._notification(peer.socket, 'raiseHand', {
|
||||
this._notification(peer.socket, 'raisedHand', {
|
||||
peerId : peer.id,
|
||||
raisedHand : raisedHand
|
||||
}, true);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "multiparty-meeting-server",
|
||||
"version": "3.2.0",
|
||||
"version": "3.3.0",
|
||||
"private": true,
|
||||
"description": "multiparty meeting server",
|
||||
"author": "Håvar Aambø Fosstveit <h@fosstveit.net>",
|
||||
|
|
|
|||
Loading…
Reference in New Issue