Merge remote-tracking branch 'upstream/develop' into browse_passive_users

auto_join_3.3
Astagor 2020-05-13 07:59:09 +02:00
commit 122c21a632
66 changed files with 4320 additions and 1373 deletions

View File

@ -27,6 +27,7 @@
"react": "^16.10.2", "react": "^16.10.2",
"react-cookie-consent": "^2.5.0", "react-cookie-consent": "^2.5.0",
"react-dom": "^16.10.2", "react-dom": "^16.10.2",
"react-flip-toolkit": "^7.0.9",
"react-intl": "^3.4.0", "react-intl": "^3.4.0",
"react-redux": "^7.1.1", "react-redux": "^7.1.1",
"react-router-dom": "^5.1.2", "react-router-dom": "^5.1.2",

View File

@ -33,7 +33,7 @@ var config =
*/ */
audioOutputSupportedBrowsers : audioOutputSupportedBrowsers :
[ [
'chrome', 'chrome',
'opera' 'opera'
], ],
// Socket.io request timeout // Socket.io request timeout
@ -42,18 +42,36 @@ var config =
{ {
tcp : true tcp : true
}, },
defaultLayout : 'democratic', // democratic, filmstrip defaultAudio :
lastN : 4, {
mobileLastN : 1, sampleRate : 48000,
channelCount : 1,
volume : 1.0,
autoGainControl : true,
echoCancellation : true,
noiseSuppression : true,
sampleSize : 16
},
background : 'images/background.jpg',
defaultLayout : 'democratic', // democratic, filmstrip
// If true, will show media control buttons in separate
// control bar, not in the ME container.
buttonControlBar : false,
// If false, will push videos away to make room for side
// drawer. If true, will overlay side drawer over videos
drawerOverlayed : true,
// Timeout for autohiding topbar and button control bar
hideTimeout : 3000,
lastN : 4,
mobileLastN : 1,
// Highest number of speakers user can select // Highest number of speakers user can select
maxLastN : 5, maxLastN : 5,
// If truthy, users can NOT change number of speakers visible // If truthy, users can NOT change number of speakers visible
lockLastN : false, lockLastN : false,
background : 'images/background.jpg',
// Add file and uncomment for adding logo to appbar // Add file and uncomment for adding logo to appbar
// logo : 'images/logo.svg', // logo : 'images/logo.svg',
title : 'Multiparty meeting', title : 'Multiparty meeting',
theme : theme :
{ {
palette : palette :
{ {

View File

@ -129,7 +129,8 @@ export default class RoomClient
produce, produce,
forceTcp, forceTcp,
displayName, displayName,
muted muted,
basePath
} = {}) } = {})
{ {
if (!peerId) if (!peerId)
@ -152,6 +153,9 @@ export default class RoomClient
// Whether we force TCP // Whether we force TCP
this._forceTcp = forceTcp; this._forceTcp = forceTcp;
// URL basepath
this._basePath = basePath;
// Use displayName // Use displayName
if (displayName) if (displayName)
store.dispatch(settingsActions.setDisplayName(displayName)); store.dispatch(settingsActions.setDisplayName(displayName));
@ -414,6 +418,13 @@ export default class RoomClient
break; break;
} }
case 'H': // Open help dialog
{
store.dispatch(roomActions.setHelpOpen(true));
break;
}
default: default:
{ {
break; break;
@ -485,9 +496,9 @@ export default class RoomClient
window.open(url, 'loginWindow'); window.open(url, 'loginWindow');
} }
logout() logout(roomId = this._roomId)
{ {
window.open('/auth/logout', 'logoutWindow'); window.open(`/auth/logout?peerId=${this._peerId}&roomId=${roomId}`, 'logoutWindow');
} }
receiveLoginChildWindow(data) receiveLoginChildWindow(data)
@ -946,14 +957,10 @@ export default class RoomClient
{ {
if (consumer.kind === 'video') if (consumer.kind === 'video')
{ {
if (spotlights.indexOf(consumer.appData.peerId) > -1) if (spotlights.includes(consumer.appData.peerId))
{
await this._resumeConsumer(consumer); await this._resumeConsumer(consumer);
}
else else
{
await this._pauseConsumer(consumer); await this._pauseConsumer(consumer);
}
} }
} }
} }
@ -963,20 +970,65 @@ export default class RoomClient
} }
} }
async getAudioTrack() disconnectLocalHark()
{ {
await navigator.mediaDevices.getUserMedia( logger.debug('disconnectLocalHark() | Stopping harkStream.');
{ if (this._harkStream != null)
audio : true, video : false {
}); this._harkStream.getAudioTracks()[0].stop();
this._harkStream = null;
}
if (this._hark != null)
{
logger.debug('disconnectLocalHark() Stopping hark.');
this._hark.stop();
}
} }
async getVideoTrack() connectLocalHark(track)
{ {
await navigator.mediaDevices.getUserMedia( logger.debug('connectLocalHark() | Track:%o', track);
this._harkStream = new MediaStream();
this._harkStream.addTrack(track.clone());
this._harkStream.getAudioTracks()[0].enabled = true;
if (!this._harkStream.getAudioTracks()[0])
throw new Error('getMicStream():something went wrong with hark');
this._hark = hark(this._harkStream, { play: false });
// eslint-disable-next-line no-unused-vars
this._hark.on('volume_change', (dBs, threshold) =>
{
// The exact formula to convert from dBs (-100..0) to linear (0..1) is:
// Math.pow(10, dBs / 20)
// However it does not produce a visually useful output, so let exagerate
// it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to
// minimize component renderings.
let volume = Math.round(Math.pow(10, dBs / 85) * 10);
if (volume === 1)
volume = 0;
volume = Math.round(volume);
if (this._micProducer && volume !== this._micProducer.volume)
{ {
audio : false, video : true this._micProducer.volume = volume;
});
store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume));
}
});
this._hark.on('speaking', function()
{
store.dispatch(meActions.setIsSpeaking(true));
});
this._hark.on('stopped_speaking', function()
{
store.dispatch(meActions.setIsSpeaking(false));
});
} }
async changeAudioDevice(deviceId) async changeAudioDevice(deviceId)
@ -987,7 +1039,7 @@ export default class RoomClient
meActions.setAudioInProgress(true)); meActions.setAudioInProgress(true));
try try
{ {
const device = this._audioDevices[deviceId]; const device = this._audioDevices[deviceId];
if (!device) if (!device)
@ -997,29 +1049,30 @@ export default class RoomClient
'changeAudioDevice() | new selected webcam [device:%o]', 'changeAudioDevice() | new selected webcam [device:%o]',
device); device);
if (this._hark != null) this.disconnectLocalHark();
this._hark.stop();
if (this._harkStream != null)
{
logger.debug('Stopping hark.');
this._harkStream.getAudioTracks()[0].stop();
this._harkStream = null;
}
if (this._micProducer && this._micProducer.track) if (this._micProducer && this._micProducer.track)
this._micProducer.track.stop(); this._micProducer.track.stop();
logger.debug('changeAudioDevice() | calling getUserMedia()'); logger.debug('changeAudioDevice() | calling getUserMedia() %o', store.getState().settings);
const stream = await navigator.mediaDevices.getUserMedia( const stream = await navigator.mediaDevices.getUserMedia(
{ {
audio : audio :
{ {
deviceId : { exact: device.deviceId } deviceId : { ideal: device.deviceId },
sampleRate : store.getState().settings.sampleRate,
channelCount : store.getState().settings.channelCount,
volume : store.getState().settings.volume,
autoGainControl : store.getState().settings.autoGainControl,
echoCancellation : store.getState().settings.echoCancellation,
noiseSuppression : store.getState().settings.noiseSuppression,
sampleSize : store.getState().settings.sampleSize
} }
}); }
);
logger.debug('Constraints: %o', stream.getAudioTracks()[0].getConstraints());
const track = stream.getAudioTracks()[0]; const track = stream.getAudioTracks()[0];
if (this._micProducer) if (this._micProducer)
@ -1027,47 +1080,8 @@ export default class RoomClient
if (this._micProducer) if (this._micProducer)
this._micProducer.volume = 0; this._micProducer.volume = 0;
this.connectLocalHark(track);
this._harkStream = new MediaStream();
this._harkStream.addTrack(track.clone());
this._harkStream.getAudioTracks()[0].enabled = true;
if (!this._harkStream.getAudioTracks()[0])
throw new Error('changeAudioDevice(): given stream has no audio track');
this._hark = hark(this._harkStream, { play: false });
// eslint-disable-next-line no-unused-vars
this._hark.on('volume_change', (dBs, threshold) =>
{
// The exact formula to convert from dBs (-100..0) to linear (0..1) is:
// Math.pow(10, dBs / 20)
// However it does not produce a visually useful output, so let exaggerate
// it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to
// minimize component renderings.
let volume = Math.round(Math.pow(10, dBs / 85) * 10);
if (volume === 1)
volume = 0;
volume = Math.round(volume);
if (this._micProducer && volume !== this._micProducer.volume)
{
this._micProducer.volume = volume;
store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume));
}
});
this._hark.on('speaking', function()
{
store.dispatch(meActions.setIsSpeaking(true));
});
this._hark.on('stopped_speaking', function()
{
store.dispatch(meActions.setIsSpeaking(false));
});
if (this._micProducer && this._micProducer.id) if (this._micProducer && this._micProducer.id)
store.dispatch( store.dispatch(
producerActions.setProducerTrack(this._micProducer.id, track)); producerActions.setProducerTrack(this._micProducer.id, track));
@ -1388,6 +1402,46 @@ export default class RoomClient
peerActions.setPeerKickInProgress(peerId, false)); peerActions.setPeerKickInProgress(peerId, false));
} }
async mutePeer(peerId)
{
logger.debug('mutePeer() [peerId:"%s"]', peerId);
store.dispatch(
peerActions.setMutePeerInProgress(peerId, true));
try
{
await this.sendRequest('moderator:mute', { peerId });
}
catch (error)
{
logger.error('mutePeer() failed: %o', error);
}
store.dispatch(
peerActions.setMutePeerInProgress(peerId, false));
}
async stopPeerVideo(peerId)
{
logger.debug('stopPeerVideo() [peerId:"%s"]', peerId);
store.dispatch(
peerActions.setStopPeerVideoInProgress(peerId, true));
try
{
await this.sendRequest('moderator:stopVideo', { peerId });
}
catch (error)
{
logger.error('stopPeerVideo() failed: %o', error);
}
store.dispatch(
peerActions.setStopPeerVideoInProgress(peerId, false));
}
async muteAllPeers() async muteAllPeers()
{ {
logger.debug('muteAllPeers()'); logger.debug('muteAllPeers()');
@ -1475,9 +1529,7 @@ export default class RoomClient
if (consumer.appData.peerId === peerId && consumer.appData.source === type) if (consumer.appData.peerId === peerId && consumer.appData.source === type)
{ {
if (mute) if (mute)
{
await this._pauseConsumer(consumer); await this._pauseConsumer(consumer);
}
else else
await this._resumeConsumer(consumer); await this._resumeConsumer(consumer);
} }
@ -1543,6 +1595,26 @@ export default class RoomClient
} }
} }
async lowerPeerHand(peerId)
{
logger.debug('lowerPeerHand() [peerId:"%s"]', peerId);
store.dispatch(
peerActions.setPeerRaisedHandInProgress(peerId, true));
try
{
await this.sendRequest('moderator:lowerHand', { peerId });
}
catch (error)
{
logger.error('lowerPeerHand() | [error:"%o"]', error);
}
store.dispatch(
peerActions.setPeerRaisedHandInProgress(peerId, false));
}
async setRaisedHand(raisedHand) async setRaisedHand(raisedHand)
{ {
logger.debug('setRaisedHand: ', raisedHand); logger.debug('setRaisedHand: ', raisedHand);
@ -1785,6 +1857,11 @@ export default class RoomClient
this._recvTransport = null; this._recvTransport = null;
} }
this._spotlights.clearSpotlights();
store.dispatch(peerActions.clearPeers());
store.dispatch(consumerActions.clearConsumers());
store.dispatch(roomActions.clearSpotlights());
store.dispatch(roomActions.setRoomState('connecting')); store.dispatch(roomActions.setRoomState('connecting'));
}); });
@ -1973,6 +2050,13 @@ export default class RoomClient
break; break;
} }
case 'overRoomLimit':
{
store.dispatch(roomActions.setOverRoomLimit(true));
break;
}
case 'roomReady': case 'roomReady':
{ {
const { turnServers } = notification.data; const { turnServers } = notification.data;
@ -2057,15 +2141,21 @@ export default class RoomClient
lobbyPeers.forEach((peer) => lobbyPeers.forEach((peer) =>
{ {
store.dispatch( store.dispatch(
lobbyPeerActions.addLobbyPeer(peer.peerId)); lobbyPeerActions.addLobbyPeer(peer.id));
store.dispatch( store.dispatch(
lobbyPeerActions.setLobbyPeerDisplayName( lobbyPeerActions.setLobbyPeerDisplayName(
peer.displayName, peer.displayName,
peer.peerId peer.id
) )
); );
store.dispatch( store.dispatch(
lobbyPeerActions.setLobbyPeerPicture(peer.picture)); lobbyPeerActions.setLobbyPeerPicture(
peer.picture,
peer.id
)
);
}); });
store.dispatch( store.dispatch(
@ -2493,8 +2583,6 @@ export default class RoomClient
case 'moderator:mute': case 'moderator:mute':
{ {
// const { peerId } = notification.data;
if (this._micProducer && !this._micProducer.paused) if (this._micProducer && !this._micProducer.paused)
{ {
this.muteMic(); this.muteMic();
@ -2513,8 +2601,6 @@ export default class RoomClient
case 'moderator:stopVideo': case 'moderator:stopVideo':
{ {
// const { peerId } = notification.data;
this.disableWebcam(); this.disableWebcam();
this.disableScreenSharing(); this.disableScreenSharing();
@ -2537,6 +2623,13 @@ export default class RoomClient
break; break;
} }
case 'moderator:lowerHand':
{
this.setRaisedHand(false);
break;
}
case 'gotRole': case 'gotRole':
{ {
const { peerId, role } = notification.data; const { peerId, role } = notification.data;
@ -2772,8 +2865,8 @@ export default class RoomClient
roles, roles,
peers, peers,
tracker, tracker,
permissionsFromRoles, roomPermissions,
userRoles, allowWhenRoleMissing,
chatHistory, chatHistory,
fileHistory, fileHistory,
lastNHistory, lastNHistory,
@ -2799,8 +2892,10 @@ export default class RoomClient
store.dispatch(meActions.loggedIn(authenticated)); store.dispatch(meActions.loggedIn(authenticated));
store.dispatch(roomActions.setUserRoles(userRoles)); store.dispatch(roomActions.setRoomPermissions(roomPermissions));
store.dispatch(roomActions.setPermissionsFromRoles(permissionsFromRoles));
if (allowWhenRoleMissing)
store.dispatch(roomActions.setAllowWhenRoleMissing(allowWhenRoleMissing));
const myRoles = store.getState().me.roles; const myRoles = store.getState().me.roles;
@ -2814,7 +2909,9 @@ export default class RoomClient
{ {
text : intl.formatMessage({ text : intl.formatMessage({
id : 'roles.gotRole', id : 'roles.gotRole',
defaultMessage : `You got the role: ${role}` defaultMessage : 'You got the role: {role}'
}, {
role
}) })
})); }));
} }
@ -2856,11 +2953,11 @@ export default class RoomClient
(lobbyPeers.length > 0) && lobbyPeers.forEach((peer) => (lobbyPeers.length > 0) && lobbyPeers.forEach((peer) =>
{ {
store.dispatch( store.dispatch(
lobbyPeerActions.addLobbyPeer(peer.peerId)); lobbyPeerActions.addLobbyPeer(peer.id));
store.dispatch( store.dispatch(
lobbyPeerActions.setLobbyPeerDisplayName(peer.displayName, peer.peerId)); lobbyPeerActions.setLobbyPeerDisplayName(peer.displayName, peer.id));
store.dispatch( store.dispatch(
lobbyPeerActions.setLobbyPeerPicture(peer.picture)); lobbyPeerActions.setLobbyPeerPicture(peer.picture, peer.id));
}); });
(accessCode != null) && store.dispatch( (accessCode != null) && store.dispatch(
@ -3236,11 +3333,20 @@ export default class RoomClient
const stream = await navigator.mediaDevices.getUserMedia( const stream = await navigator.mediaDevices.getUserMedia(
{ {
audio : { audio : {
deviceId : { ideal: deviceId } deviceId : { ideal: device.deviceId },
sampleRate : store.getState().settings.sampleRate,
channelCount : store.getState().settings.channelCount,
volume : store.getState().settings.volume,
autoGainControl : store.getState().settings.autoGainControl,
echoCancellation : store.getState().settings.echoCancellation,
noiseSuppression : store.getState().settings.noiseSuppression,
sampleSize : store.getState().settings.sampleSize
} }
} }
); );
logger.debug('Constraints: %o', stream.getAudioTracks()[0].getConstraints());
track = stream.getAudioTracks()[0]; track = stream.getAudioTracks()[0];
this._micProducer = await this._sendTransport.produce( this._micProducer = await this._sendTransport.produce(
@ -3294,51 +3400,8 @@ export default class RoomClient
this._micProducer.volume = 0; this._micProducer.volume = 0;
if (this._hark != null) this.connectLocalHark(track);
this._hark.stop();
if (this._harkStream != null)
this._harkStream.getAudioTracks()[0].stop();
this._harkStream = new MediaStream();
this._harkStream.addTrack(track.clone());
if (!this._harkStream.getAudioTracks()[0])
throw new Error('enableMic(): given stream has no audio track');
this._hark = hark(this._harkStream, { play: false });
// eslint-disable-next-line no-unused-vars
this._hark.on('volume_change', (dBs, threshold) =>
{
// The exact formula to convert from dBs (-100..0) to linear (0..1) is:
// Math.pow(10, dBs / 20)
// However it does not produce a visually useful output, so let exaggerate
// it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to
// minimize component renderings.
let volume = Math.round(Math.pow(10, dBs / 85) * 10);
if (volume === 1)
volume = 0;
volume = Math.round(volume);
if (this._micProducer && volume !== this._micProducer.volume)
{
this._micProducer.volume = volume;
store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume));
}
});
this._hark.on('speaking', function()
{
store.dispatch(meActions.setIsSpeaking(true));
});
this._hark.on('stopped_speaking', function()
{
store.dispatch(meActions.setIsSpeaking(false));
});
} }
catch (error) catch (error)
{ {

View File

@ -225,10 +225,7 @@ export default class ScreenShare
return new DisplayMediaScreenShare(); return new DisplayMediaScreenShare();
} }
case 'chrome': case 'chrome':
{ case 'edge':
return new DisplayMediaScreenShare();
}
case 'msedge':
{ {
return new DisplayMediaScreenShare(); return new DisplayMediaScreenShare();
} }

View File

@ -156,6 +156,15 @@ export default class Spotlights extends EventEmitter
}); });
} }
clearSpotlights()
{
this._started = false;
this._peerList = [];
this._selectedSpotlights = [];
this._currentSpotlights = [];
}
_newPeer(id) _newPeer(id)
{ {
logger.debug( logger.debug(

View File

@ -10,6 +10,11 @@ export const removeConsumer = (consumerId, peerId) =>
payload : { consumerId, peerId } payload : { consumerId, peerId }
}); });
export const clearConsumers = () =>
({
type : 'CLEAR_CONSUMERS'
});
export const setConsumerPaused = (consumerId, originator) => export const setConsumerPaused = (consumerId, originator) =>
({ ({
type : 'SET_CONSUMER_PAUSED', type : 'SET_CONSUMER_PAUSED',

View File

@ -10,6 +10,11 @@ export const removePeer = (peerId) =>
payload : { peerId } payload : { peerId }
}); });
export const clearPeers = () =>
({
type : 'CLEAR_PEERS'
});
export const setPeerDisplayName = (displayName, peerId) => export const setPeerDisplayName = (displayName, peerId) =>
({ ({
type : 'SET_PEER_DISPLAY_NAME', type : 'SET_PEER_DISPLAY_NAME',
@ -40,6 +45,12 @@ export const setPeerRaisedHand = (peerId, raisedHand, raisedHandTimestamp) =>
payload : { peerId, raisedHand, raisedHandTimestamp } payload : { peerId, raisedHand, raisedHandTimestamp }
}); });
export const setPeerRaisedHandInProgress = (peerId, flag) =>
({
type : 'SET_PEER_RAISED_HAND_IN_PROGRESS',
payload : { peerId, flag }
});
export const setPeerPicture = (peerId, picture) => export const setPeerPicture = (peerId, picture) =>
({ ({
type : 'SET_PEER_PICTURE', type : 'SET_PEER_PICTURE',
@ -63,3 +74,15 @@ export const setPeerKickInProgress = (peerId, flag) =>
type : 'SET_PEER_KICK_IN_PROGRESS', type : 'SET_PEER_KICK_IN_PROGRESS',
payload : { peerId, flag } payload : { peerId, flag }
}); });
export const setMutePeerInProgress = (peerId, flag) =>
({
type : 'STOP_PEER_AUDIO_IN_PROGRESS',
payload : { peerId, flag }
});
export const setStopPeerVideoInProgress = (peerId, flag) =>
({
type : 'STOP_PEER_VIDEO_IN_PROGRESS',
payload : { peerId, flag }
});

View File

@ -40,6 +40,12 @@ export const setSignInRequired = (signInRequired) =>
payload : { signInRequired } payload : { signInRequired }
}); });
export const setOverRoomLimit = (overRoomLimit) =>
({
type : 'SET_OVER_ROOM_LIMIT',
payload : { overRoomLimit }
});
export const setAccessCode = (accessCode) => export const setAccessCode = (accessCode) =>
({ ({
type : 'SET_ACCESS_CODE', type : 'SET_ACCESS_CODE',
@ -64,6 +70,18 @@ export const setExtraVideoOpen = (extraVideoOpen) =>
payload : { extraVideoOpen } payload : { extraVideoOpen }
}); });
export const setHelpOpen = (helpOpen) =>
({
type : 'SET_HELP_OPEN',
payload : { helpOpen }
});
export const setAboutOpen = (aboutOpen) =>
({
type : 'SET_ABOUT_OPEN',
payload : { aboutOpen }
});
export const setSettingsTab = (tab) => export const setSettingsTab = (tab) =>
({ ({
type : 'SET_SETTINGS_TAB', type : 'SET_SETTINGS_TAB',
@ -112,6 +130,11 @@ export const setSpotlights = (spotlights) =>
payload : { spotlights } payload : { spotlights }
}); });
export const clearSpotlights = () =>
({
type : 'CLEAR_SPOTLIGHTS'
});
export const toggleJoined = () => export const toggleJoined = () =>
({ ({
type : 'TOGGLE_JOINED' type : 'TOGGLE_JOINED'
@ -159,14 +182,14 @@ export const setClearFileSharingInProgress = (flag) =>
payload : { flag } payload : { flag }
}); });
export const setUserRoles = (userRoles) => export const setRoomPermissions = (roomPermissions) =>
({ ({
type : 'SET_USER_ROLES', type : 'SET_ROOM_PERMISSIONS',
payload : { userRoles } payload : { roomPermissions }
}); });
export const setPermissionsFromRoles = (permissionsFromRoles) => export const setAllowWhenRoleMissing = (allowWhenRoleMissing) =>
({ ({
type : 'SET_PERMISSIONS_FROM_ROLES', type : 'SET_ALLOW_WHEN_ROLE_MISSING',
payload : { permissionsFromRoles } payload : { allowWhenRoleMissing }
}); });

View File

@ -38,6 +38,60 @@ export const togglePermanentTopBar = () =>
type : 'TOGGLE_PERMANENT_TOPBAR' type : 'TOGGLE_PERMANENT_TOPBAR'
}); });
export const toggleButtonControlBar = () =>
({
type : 'TOGGLE_BUTTON_CONTROL_BAR'
});
export const toggleDrawerOverlayed = () =>
({
type : 'TOGGLE_DRAWER_OVERLAYED'
});
export const toggleShowNotifications = () =>
({
type : 'TOGGLE_SHOW_NOTIFICATIONS'
});
export const setEchoCancellation = (echoCancellation) =>
({
type : 'SET_ECHO_CANCELLATION',
payload : { echoCancellation }
});
export const setAutoGainControl = (autoGainControl) =>
({
type : 'SET_AUTO_GAIN_CONTROL',
payload : { autoGainControl }
});
export const setNoiseSuppression = (noiseSuppression) =>
({
type : 'SET_NOISE_SUPPRESSION',
payload : { noiseSuppression }
});
export const setDefaultAudio = (audio) =>
({
type : 'SET_DEFAULT_AUDIO',
payload : { audio }
});
export const toggleEchoCancellation = () =>
({
type : 'TOGGLE_ECHO_CANCELLATION'
});
export const toggleAutoGainControl = () =>
({
type : 'TOGGLE_AUTO_GAIN_CONTROL'
});
export const toggleNoiseSuppression = () =>
({
type : 'TOGGLE_NOISE_SUPPRESSION'
});
export const toggleHiddenControls = () => export const toggleHiddenControls = () =>
({ ({
type : 'TOGGLE_HIDDEN_CONTROLS' type : 'TOGGLE_HIDDEN_CONTROLS'

View File

@ -5,6 +5,8 @@ import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { permissions } from '../../../permissions';
import { makePermissionSelector } from '../../Selectors';
import ListItem from '@material-ui/core/ListItem'; import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText'; import ListItemText from '@material-ui/core/ListItemText';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
@ -61,6 +63,7 @@ const ListLobbyPeer = (props) =>
peer.promotionInProgress || peer.promotionInProgress ||
promotionInProgress promotionInProgress
} }
color='primary'
onClick={(e) => onClick={(e) =>
{ {
e.stopPropagation(); e.stopPropagation();
@ -84,28 +87,32 @@ ListLobbyPeer.propTypes =
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const mapStateToProps = (state, { id }) => const makeMapStateToProps = (initialState, { id }) =>
{ {
return { const hasPermission = makePermissionSelector(permissions.PROMOTE_PEER);
peer : state.lobbyPeers[id],
promotionInProgress : state.room.lobbyPeersPromotionInProgress, const mapStateToProps = (state) =>
canPromote : {
state.me.roles.some((role) => return {
state.room.permissionsFromRoles.PROMOTE_PEER.includes(role)) peer : state.lobbyPeers[id],
promotionInProgress : state.room.lobbyPeersPromotionInProgress,
canPromote : hasPermission(state)
};
}; };
return mapStateToProps;
}; };
export default withRoomContext(connect( export default withRoomContext(connect(
mapStateToProps, makeMapStateToProps,
null, null,
null, null,
{ {
areStatesEqual : (next, prev) => areStatesEqual : (next, prev) =>
{ {
return ( return (
prev.room.permissionsFromRoles === next.room.permissionsFromRoles && prev.room === next.room &&
prev.room.lobbyPeersPromotionInProgress === prev.peers === next.peers && // For checking permissions
next.room.lobbyPeersPromotionInProgress &&
prev.me.roles === next.me.roles && prev.me.roles === next.me.roles &&
prev.lobbyPeers === next.lobbyPeers prev.lobbyPeers === next.lobbyPeers
); );

View File

@ -1,8 +1,10 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
lobbyPeersKeySelector lobbyPeersKeySelector,
makePermissionSelector
} from '../../Selectors'; } from '../../Selectors';
import { permissions } from '../../../permissions';
import * as appPropTypes from '../../appPropTypes'; import * as appPropTypes from '../../appPropTypes';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
@ -140,15 +142,20 @@ LockDialog.propTypes =
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => const makeMapStateToProps = () =>
{ {
return { const hasPermission = makePermissionSelector(permissions.PROMOTE_PEER);
room : state.room,
lobbyPeers : lobbyPeersKeySelector(state), const mapStateToProps = (state) =>
canPromote : {
state.me.roles.some((role) => return {
state.room.permissionsFromRoles.PROMOTE_PEER.includes(role)) room : state.room,
lobbyPeers : lobbyPeersKeySelector(state),
canPromote : hasPermission(state)
};
}; };
return mapStateToProps;
}; };
const mapDispatchToProps = { const mapDispatchToProps = {
@ -157,7 +164,7 @@ const mapDispatchToProps = {
}; };
export default withRoomContext(connect( export default withRoomContext(connect(
mapStateToProps, makeMapStateToProps,
mapDispatchToProps, mapDispatchToProps,
null, null,
{ {
@ -166,6 +173,7 @@ export default withRoomContext(connect(
return ( return (
prev.room === next.room && prev.room === next.room &&
prev.me.roles === next.me.roles && prev.me.roles === next.me.roles &&
prev.peers === next.peers &&
prev.lobbyPeers === next.lobbyPeers prev.lobbyPeers === next.lobbyPeers
); );
} }

View File

@ -1,6 +1,10 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { meProducersSelector } from '../Selectors'; import {
meProducersSelector,
makePermissionSelector
} from '../Selectors';
import { permissions } from '../../permissions';
import { withRoomContext } from '../../RoomContext'; import { withRoomContext } from '../../RoomContext';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -10,6 +14,7 @@ import { useIntl, FormattedMessage } from 'react-intl';
import VideoView from '../VideoContainers/VideoView'; import VideoView from '../VideoContainers/VideoView';
import Volume from './Volume'; import Volume from './Volume';
import Fab from '@material-ui/core/Fab'; import Fab from '@material-ui/core/Fab';
import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip'; import Tooltip from '@material-ui/core/Tooltip';
import MicIcon from '@material-ui/icons/Mic'; import MicIcon from '@material-ui/icons/Mic';
import MicOffIcon from '@material-ui/icons/MicOff'; import MicOffIcon from '@material-ui/icons/MicOff';
@ -59,12 +64,47 @@ const styles = (theme) =>
margin : theme.spacing(1), margin : theme.spacing(1),
pointerEvents : 'auto' pointerEvents : 'auto'
}, },
smallContainer :
{
backgroundColor : 'rgba(255, 255, 255, 0.9)',
margin : '0.5vmin',
padding : '0.5vmin',
boxShadow : '0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12)',
pointerEvents : 'auto',
transition : 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
'&:hover' :
{
backgroundColor : 'rgba(213, 213, 213, 1)'
}
},
viewContainer : viewContainer :
{ {
position : 'relative', position : 'relative',
width : '100%', width : '100%',
height : '100%' height : '100%'
}, },
meTag :
{
position : 'absolute',
float : 'left',
top : '50%',
left : '50%',
transform : 'translate(-50%, -50%)',
color : 'rgba(255, 255, 255, 0.5)',
fontSize : '7em',
zIndex : 30,
margin : 0,
opacity : 0,
transition : 'opacity 0.1s ease-in-out',
'&.hover' :
{
opacity : 1
},
'&.smallContainer' :
{
fontSize : '3em'
}
},
controls : controls :
{ {
position : 'absolute', position : 'absolute',
@ -86,35 +126,18 @@ const styles = (theme) =>
'&.hover' : '&.hover' :
{ {
opacity : 1 opacity : 1
},
'& p' :
{
position : 'absolute',
float : 'left',
top : '50%',
left : '50%',
transform : 'translate(-50%, -50%)',
color : 'rgba(255, 255, 255, 0.5)',
fontSize : '7em',
margin : 0,
opacity : 0,
transition : 'opacity 0.1s ease-in-out',
'&.hover' :
{
opacity : 1
}
} }
}, },
ptt : ptt :
{ {
position : 'absolute', position : 'absolute',
float : 'left', float : 'left',
top : '10%', top : '25%',
left : '50%', left : '50%',
transform : 'translate(-50%, 0%)', transform : 'translate(-50%, 0%)',
color : 'rgba(255, 255, 255, 0.7)', color : 'rgba(255, 255, 255, 0.7)',
fontSize : '2vs', fontSize : '1.3em',
backgroundColor : 'rgba(255, 0, 0, 0.5)', backgroundColor : 'rgba(255, 0, 0, 0.9)',
margin : '4px', margin : '4px',
padding : theme.spacing(2), padding : theme.spacing(2),
zIndex : 31, zIndex : 31,
@ -145,7 +168,7 @@ const Me = (props) =>
activeSpeaker, activeSpeaker,
spacing, spacing,
style, style,
smallButtons, smallContainer,
advancedMode, advancedMode,
micProducer, micProducer,
webcamProducer, webcamProducer,
@ -267,6 +290,28 @@ const Me = (props) =>
'margin' : spacing 'margin' : spacing
}; };
let audioScore = null;
if (micProducer && micProducer.score)
{
audioScore =
micProducer.score.reduce(
(prev, curr) =>
(prev.score < curr.score ? prev : curr)
);
}
let videoScore = null;
if (webcamProducer && webcamProducer.score)
{
videoScore =
webcamProducer.score.reduce(
(prev, curr) =>
(prev.score < curr.score ? prev : curr)
);
}
return ( return (
<React.Fragment> <React.Fragment>
<div <div
@ -299,158 +344,273 @@ const Me = (props) =>
}} }}
style={spacingStyle} style={spacingStyle}
> >
{ me.browser.platform !== 'mobile' &&
<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.viewContainer} style={style}> <div className={classes.viewContainer} style={style}>
<div className={classnames( <p className={
classes.ptt, classnames(
(micState === 'muted' && me.isSpeaking) ? 'enabled' : null classes.meTag,
)} hover ? 'hover' : null,
smallContainer ? 'smallContainer' : null
)}
> >
<FormattedMessage <FormattedMessage
id='me.mutedPTT' id='room.me'
defaultMessage='You are muted, hold down SPACE-BAR to talk' defaultMessage='ME'
/> />
</div> </p>
<div { !settings.buttonControlBar &&
className={classnames( <div
classes.controls, className={classnames(
settings.hiddenControls ? 'hide' : null, classes.controls,
hover ? 'hover' : null settings.hiddenControls ? 'hide' : null,
)} hover ? 'hover' : null
onMouseOver={() => setHover(true)} )}
onMouseOut={() => setHover(false)} onMouseOver={() => setHover(true)}
onTouchStart={() => onMouseOut={() => setHover(false)}
{ onTouchStart={() =>
if (touchTimeout)
clearTimeout(touchTimeout);
setHover(true);
}}
onTouchEnd={() =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
touchTimeout = setTimeout(() =>
{ {
setHover(false); if (touchTimeout)
}, 2000); clearTimeout(touchTimeout);
}}
>
<p className={hover ? 'hover' : null}>
<FormattedMessage
id='room.me'
defaultMessage='ME'
/>
</p>
<React.Fragment> setHover(true);
<Tooltip title={micTip} placement='left'> }}
<div> onTouchEnd={() =>
<Fab {
aria-label={intl.formatMessage({ if (touchTimeout)
id : 'device.muteAudio', clearTimeout(touchTimeout);
defaultMessage : 'Mute audio'
})} touchTimeout = setTimeout(() =>
className={classes.fab} {
disabled={!me.canSendMic || me.audioInProgress} setHover(false);
color={micState === 'on' ? 'default' : 'secondary'} }, 2000);
size={smallButtons ? 'small' : 'large'} }}
onClick={() => >
{ <React.Fragment>
if (micState === 'off') <Tooltip title={micTip} placement='left'>
roomClient.enableMic(); { smallContainer ?
else if (micState === 'on') <div>
roomClient.muteMic(); <IconButton
else aria-label={intl.formatMessage({
roomClient.unmuteMic(); id : 'device.muteAudio',
}} defaultMessage : 'Mute audio'
> })}
{ micState === 'on' ? className={classes.smallContainer}
<MicIcon /> disabled={!me.canSendMic || me.audioInProgress}
: color={micState === 'on' ? 'primary' : 'secondary'}
<MicOffIcon /> size='small'
} onClick={() =>
</Fab>
</div>
</Tooltip>
<Tooltip title={webcamTip} placement='left'>
<div>
<Fab
aria-label={intl.formatMessage({
id : 'device.startVideo',
defaultMessage : 'Start video'
})}
className={classes.fab}
disabled={!me.canSendWebcam || me.webcamInProgress}
color={webcamState === 'on' ? 'default' : 'secondary'}
size={smallButtons ? 'small' : 'large'}
onClick={() =>
{
webcamState === 'on' ?
roomClient.disableWebcam() :
roomClient.enableWebcam();
}}
>
{ webcamState === 'on' ?
<VideoIcon />
:
<VideoOffIcon />
}
</Fab>
</div>
</Tooltip>
{ me.browser.platform !== 'mobile' &&
<Tooltip title={screenTip} placement='left'>
<div>
<Fab
aria-label={intl.formatMessage({
id : 'device.startScreenSharing',
defaultMessage : 'Start screen sharing'
})}
className={classes.fab}
disabled={
!canShareScreen ||
!me.canShareScreen ||
me.screenShareInProgress
}
color={screenState === 'on' ? 'primary' : 'default'}
size={smallButtons ? 'small' : 'large'}
onClick={() =>
{
switch (screenState)
{ {
case 'on': if (micState === 'off')
{ roomClient.enableMic();
roomClient.disableScreenSharing(); else if (micState === 'on')
break; roomClient.muteMic();
} else
case 'off': roomClient.unmuteMic();
{ }}
roomClient.enableScreenSharing(); >
break; { micState === 'on' ?
} <MicIcon />
default: :
{ <MicOffIcon />
break;
}
} }
}} </IconButton>
> </div>
{ (screenState === 'on' || screenState === 'unsupported') && :
<ScreenOffIcon/> <div>
} <Fab
{ screenState === 'off' && aria-label={intl.formatMessage({
<ScreenIcon/> id : 'device.muteAudio',
} defaultMessage : 'Mute audio'
</Fab> })}
</div> className={classes.fab}
disabled={!me.canSendMic || me.audioInProgress}
color={micState === 'on' ? 'default' : 'secondary'}
size='large'
onClick={() =>
{
if (micState === 'off')
roomClient.enableMic();
else if (micState === 'on')
roomClient.muteMic();
else
roomClient.unmuteMic();
}}
>
{ micState === 'on' ?
<MicIcon />
:
<MicOffIcon />
}
</Fab>
</div>
}
</Tooltip> </Tooltip>
} <Tooltip title={webcamTip} placement='left'>
</React.Fragment> { smallContainer ?
</div> <div>
<IconButton
aria-label={intl.formatMessage({
id : 'device.startVideo',
defaultMessage : 'Start video'
})}
className={classes.smallContainer}
disabled={!me.canSendWebcam || me.webcamInProgress}
color={webcamState === 'on' ? 'primary' : 'secondary'}
size='small'
onClick={() =>
{
webcamState === 'on' ?
roomClient.disableWebcam() :
roomClient.enableWebcam();
}}
>
{ webcamState === 'on' ?
<VideoIcon />
:
<VideoOffIcon />
}
</IconButton>
</div>
:
<div>
<Fab
aria-label={intl.formatMessage({
id : 'device.startVideo',
defaultMessage : 'Start video'
})}
className={classes.fab}
disabled={!me.canSendWebcam || me.webcamInProgress}
color={webcamState === 'on' ? 'default' : 'secondary'}
size='large'
onClick={() =>
{
webcamState === 'on' ?
roomClient.disableWebcam() :
roomClient.enableWebcam();
}}
>
{ webcamState === 'on' ?
<VideoIcon />
:
<VideoOffIcon />
}
</Fab>
</div>
}
</Tooltip>
{ me.browser.platform !== 'mobile' &&
<Tooltip title={screenTip} placement='left'>
{ smallContainer ?
<div>
<IconButton
aria-label={intl.formatMessage({
id : 'device.startScreenSharing',
defaultMessage : 'Start screen sharing'
})}
className={classes.smallContainer}
disabled={
!canShareScreen ||
!me.canShareScreen ||
me.screenShareInProgress
}
color='primary'
size='small'
onClick={() =>
{
switch (screenState)
{
case 'on':
{
roomClient.disableScreenSharing();
break;
}
case 'off':
{
roomClient.enableScreenSharing();
break;
}
default:
{
break;
}
}
}}
>
{ (screenState === 'on' || screenState === 'unsupported') &&
<ScreenOffIcon/>
}
{ screenState === 'off' &&
<ScreenIcon/>
}
</IconButton>
</div>
:
<div>
<Fab
aria-label={intl.formatMessage({
id : 'device.startScreenSharing',
defaultMessage : 'Start screen sharing'
})}
className={classes.fab}
disabled={
!canShareScreen ||
!me.canShareScreen ||
me.screenShareInProgress
}
color={screenState === 'on' ? 'primary' : 'default'}
size='large'
onClick={() =>
{
switch (screenState)
{
case 'on':
{
roomClient.disableScreenSharing();
break;
}
case 'off':
{
roomClient.enableScreenSharing();
break;
}
default:
{
break;
}
}
}}
>
{ (screenState === 'on' || screenState === 'unsupported') &&
<ScreenOffIcon/>
}
{ screenState === 'off' &&
<ScreenIcon/>
}
</Fab>
</div>
}
</Tooltip>
}
</React.Fragment>
</div>
}
<VideoView <VideoView
isMe isMe
VideoView
advancedMode={advancedMode} advancedMode={advancedMode}
peer={me} peer={me}
displayName={settings.displayName} displayName={settings.displayName}
@ -459,6 +619,8 @@ const Me = (props) =>
videoVisible={videoVisible} videoVisible={videoVisible}
audioCodec={micProducer && micProducer.codec} audioCodec={micProducer && micProducer.codec}
videoCodec={webcamProducer && webcamProducer.codec} videoCodec={webcamProducer && webcamProducer.codec}
audioScore={audioScore}
videoScore={videoScore}
onChangeDisplayName={(displayName) => onChangeDisplayName={(displayName) =>
{ {
roomClient.changeDisplayName(displayName); roomClient.changeDisplayName(displayName);
@ -502,6 +664,18 @@ const Me = (props) =>
style={spacingStyle} style={spacingStyle}
> >
<div className={classes.viewContainer} style={style}> <div className={classes.viewContainer} style={style}>
<p className={
classnames(
classes.meTag,
hover ? 'hover' : null,
smallContainer ? 'smallContainer' : null
)}
>
<FormattedMessage
id='room.me'
defaultMessage='ME'
/>
</p>
<div <div
className={classnames( className={classnames(
classes.controls, classes.controls,
@ -528,31 +702,46 @@ const Me = (props) =>
}, 2000); }, 2000);
}} }}
> >
<p className={hover ? 'hover' : null}>
<FormattedMessage
id='room.me'
defaultMessage='ME'
/>
</p>
<Tooltip title={webcamTip} placement='left'> <Tooltip title={webcamTip} placement='left'>
<div> { smallContainer ?
<Fab <div>
aria-label={intl.formatMessage({ <IconButton
id : 'device.stopVideo', aria-label={intl.formatMessage({
defaultMessage : 'Stop video' id : 'device.stopVideo',
})} defaultMessage : 'Stop video'
className={classes.fab} })}
disabled={!me.canSendWebcam || me.webcamInProgress} className={classes.smallContainer}
size={smallButtons ? 'small' : 'large'} disabled={!me.canSendWebcam || me.webcamInProgress}
onClick={() => size='small'
{ color='primary'
roomClient.disableExtraVideo(producer.id); onClick={() =>
}} {
> roomClient.disableExtraVideo(producer.id);
<VideoIcon /> }}
</Fab> >
</div> <VideoIcon />
</IconButton>
</div>
:
<div>
<Fab
aria-label={intl.formatMessage({
id : 'device.stopVideo',
defaultMessage : 'Stop video'
})}
className={classes.fab}
disabled={!me.canSendWebcam || me.webcamInProgress}
size={smallContainer ? 'small' : 'large'}
onClick={() =>
{
roomClient.disableExtraVideo(producer.id);
}}
>
<VideoIcon />
</Fab>
</div>
}
</Tooltip> </Tooltip>
</div> </div>
@ -599,40 +788,18 @@ const Me = (props) =>
style={spacingStyle} style={spacingStyle}
> >
<div className={classes.viewContainer} style={style}> <div className={classes.viewContainer} style={style}>
<div <p className={
className={classnames( classnames(
classes.controls, classes.meTag,
settings.hiddenControls ? 'hide' : null, hover ? 'hover' : null,
hover ? 'hover' : null smallContainer ? 'smallContainer' : 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
<FormattedMessage id='room.me'
id='room.me' defaultMessage='ME'
defaultMessage='ME' />
/> </p>
</p>
</div>
<VideoView <VideoView
isMe isMe
@ -663,38 +830,43 @@ Me.propTypes =
extraVideoProducers : PropTypes.arrayOf(appPropTypes.Producer), extraVideoProducers : PropTypes.arrayOf(appPropTypes.Producer),
spacing : PropTypes.number, spacing : PropTypes.number,
style : PropTypes.object, style : PropTypes.object,
smallButtons : PropTypes.bool, smallContainer : PropTypes.bool,
canShareScreen : PropTypes.bool.isRequired, canShareScreen : PropTypes.bool.isRequired,
classes : PropTypes.object.isRequired, classes : PropTypes.object.isRequired,
theme : PropTypes.object.isRequired theme : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => const makeMapStateToProps = () =>
{ {
return { const hasPermission = makePermissionSelector(permissions.SHARE_SCREEN);
me : state.me,
...meProducersSelector(state), const mapStateToProps = (state) =>
settings : state.settings, {
activeSpeaker : state.me.id === state.room.activeSpeakerId, return {
canShareScreen : me : state.me,
state.me.roles.some((role) => ...meProducersSelector(state),
state.room.permissionsFromRoles.SHARE_SCREEN.includes(role)) settings : state.settings,
activeSpeaker : state.me.id === state.room.activeSpeakerId,
canShareScreen : hasPermission(state)
};
}; };
return mapStateToProps;
}; };
export default withRoomContext(connect( export default withRoomContext(connect(
mapStateToProps, makeMapStateToProps,
null, null,
null, null,
{ {
areStatesEqual : (next, prev) => areStatesEqual : (next, prev) =>
{ {
return ( return (
prev.room.permissionsFromRoles === next.room.permissionsFromRoles && prev.room === next.room &&
prev.me === next.me && prev.me === next.me &&
prev.peers === next.peers &&
prev.producers === next.producers && prev.producers === next.producers &&
prev.settings === next.settings && prev.settings === next.settings
prev.room.activeSpeakerId === next.room.activeSpeakerId
); );
} }
} }

View File

@ -12,6 +12,7 @@ import { useIntl, FormattedMessage } from 'react-intl';
import VideoView from '../VideoContainers/VideoView'; import VideoView from '../VideoContainers/VideoView';
import Tooltip from '@material-ui/core/Tooltip'; import Tooltip from '@material-ui/core/Tooltip';
import Fab from '@material-ui/core/Fab'; import Fab from '@material-ui/core/Fab';
import IconButton from '@material-ui/core/IconButton';
import VolumeUpIcon from '@material-ui/icons/VolumeUp'; import VolumeUpIcon from '@material-ui/icons/VolumeUp';
import VolumeOffIcon from '@material-ui/icons/VolumeOff'; import VolumeOffIcon from '@material-ui/icons/VolumeOff';
import NewWindowIcon from '@material-ui/icons/OpenInNew'; import NewWindowIcon from '@material-ui/icons/OpenInNew';
@ -59,6 +60,19 @@ const styles = (theme) =>
{ {
margin : theme.spacing(1) margin : theme.spacing(1)
}, },
smallContainer :
{
backgroundColor : 'rgba(255, 255, 255, 0.9)',
margin : '0.5vmin',
padding : '0.5vmin',
boxShadow : '0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12)',
pointerEvents : 'auto',
transition : 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
'&:hover' :
{
backgroundColor : 'rgba(213, 213, 213, 1)'
}
},
viewContainer : viewContainer :
{ {
position : 'relative', position : 'relative',
@ -130,7 +144,7 @@ const Peer = (props) =>
toggleConsumerWindow, toggleConsumerWindow,
spacing, spacing,
style, style,
smallButtons, smallContainer,
windowConsumer, windowConsumer,
classes, classes,
theme theme
@ -235,7 +249,30 @@ const Peer = (props) =>
})} })}
placement={smallScreen ? 'top' : 'left'} placement={smallScreen ? 'top' : 'left'}
> >
<div> { smallContainer ?
<IconButton
aria-label={intl.formatMessage({
id : 'device.muteAudio',
defaultMessage : 'Mute audio'
})}
className={classes.smallContainer}
disabled={!micConsumer}
color='primary'
size='small'
onClick={() =>
{
micEnabled ?
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
}}
>
{ micEnabled ?
<VolumeUpIcon />
:
<VolumeOffIcon />
}
</IconButton>
:
<Fab <Fab
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'device.muteAudio', id : 'device.muteAudio',
@ -244,7 +281,7 @@ const Peer = (props) =>
className={classes.fab} className={classes.fab}
disabled={!micConsumer} disabled={!micConsumer}
color={micEnabled ? 'default' : 'secondary'} color={micEnabled ? 'default' : 'secondary'}
size={smallButtons ? 'small' : 'large'} size='large'
onClick={() => onClick={() =>
{ {
micEnabled ? micEnabled ?
@ -258,7 +295,7 @@ const Peer = (props) =>
<VolumeOffIcon /> <VolumeOffIcon />
} }
</Fab> </Fab>
</div> }
</Tooltip> </Tooltip>
{ browser.platform !== 'mobile' && { browser.platform !== 'mobile' &&
@ -269,7 +306,27 @@ const Peer = (props) =>
})} })}
placement={smallScreen ? 'top' : 'left'} placement={smallScreen ? 'top' : 'left'}
> >
<div> { smallContainer ?
<IconButton
aria-label={intl.formatMessage({
id : 'label.newWindow',
defaultMessage : 'New window'
})}
className={classes.smallContainer}
disabled={
!videoVisible ||
(windowConsumer === webcamConsumer.id)
}
size='small'
color='primary'
onClick={() =>
{
toggleConsumerWindow(webcamConsumer);
}}
>
<NewWindowIcon />
</IconButton>
:
<Fab <Fab
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'label.newWindow', id : 'label.newWindow',
@ -280,7 +337,7 @@ const Peer = (props) =>
!videoVisible || !videoVisible ||
(windowConsumer === webcamConsumer.id) (windowConsumer === webcamConsumer.id)
} }
size={smallButtons ? 'small' : 'large'} size='large'
onClick={() => onClick={() =>
{ {
toggleConsumerWindow(webcamConsumer); toggleConsumerWindow(webcamConsumer);
@ -288,7 +345,7 @@ const Peer = (props) =>
> >
<NewWindowIcon /> <NewWindowIcon />
</Fab> </Fab>
</div> }
</Tooltip> </Tooltip>
} }
@ -299,7 +356,24 @@ const Peer = (props) =>
})} })}
placement={smallScreen ? 'top' : 'left'} placement={smallScreen ? 'top' : 'left'}
> >
<div> { smallContainer ?
<IconButton
aria-label={intl.formatMessage({
id : 'label.fullscreen',
defaultMessage : 'Fullscreen'
})}
className={classes.smallContainer}
disabled={!videoVisible}
size='small'
color='primary'
onClick={() =>
{
toggleConsumerFullscreen(webcamConsumer);
}}
>
<FullScreenIcon />
</IconButton>
:
<Fab <Fab
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'label.fullscreen', id : 'label.fullscreen',
@ -307,7 +381,7 @@ const Peer = (props) =>
})} })}
className={classes.fab} className={classes.fab}
disabled={!videoVisible} disabled={!videoVisible}
size={smallButtons ? 'small' : 'large'} size='large'
onClick={() => onClick={() =>
{ {
toggleConsumerFullscreen(webcamConsumer); toggleConsumerFullscreen(webcamConsumer);
@ -315,11 +389,12 @@ const Peer = (props) =>
> >
<FullScreenIcon /> <FullScreenIcon />
</Fab> </Fab>
</div> }
</Tooltip> </Tooltip>
</div> </div>
<VideoView <VideoView
showQuality
advancedMode={advancedMode} advancedMode={advancedMode}
peer={peer} peer={peer}
displayName={peer.displayName} displayName={peer.displayName}
@ -427,7 +502,27 @@ const Peer = (props) =>
})} })}
placement={smallScreen ? 'top' : 'left'} placement={smallScreen ? 'top' : 'left'}
> >
<div> { smallContainer ?
<IconButton
aria-label={intl.formatMessage({
id : 'label.newWindow',
defaultMessage : 'New window'
})}
className={classes.smallContainer}
disabled={
!videoVisible ||
(windowConsumer === consumer.id)
}
size='small'
color='primary'
onClick={() =>
{
toggleConsumerWindow(consumer);
}}
>
<NewWindowIcon />
</IconButton>
:
<Fab <Fab
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'label.newWindow', id : 'label.newWindow',
@ -438,7 +533,7 @@ const Peer = (props) =>
!videoVisible || !videoVisible ||
(windowConsumer === consumer.id) (windowConsumer === consumer.id)
} }
size={smallButtons ? 'small' : 'large'} size='large'
onClick={() => onClick={() =>
{ {
toggleConsumerWindow(consumer); toggleConsumerWindow(consumer);
@ -446,7 +541,7 @@ const Peer = (props) =>
> >
<NewWindowIcon /> <NewWindowIcon />
</Fab> </Fab>
</div> }
</Tooltip> </Tooltip>
} }
@ -457,7 +552,24 @@ const Peer = (props) =>
})} })}
placement={smallScreen ? 'top' : 'left'} placement={smallScreen ? 'top' : 'left'}
> >
<div> { smallContainer ?
<IconButton
aria-label={intl.formatMessage({
id : 'label.fullscreen',
defaultMessage : 'Fullscreen'
})}
className={classes.smallContainer}
disabled={!videoVisible}
size='small'
color='primary'
onClick={() =>
{
toggleConsumerFullscreen(consumer);
}}
>
<FullScreenIcon />
</IconButton>
:
<Fab <Fab
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'label.fullscreen', id : 'label.fullscreen',
@ -465,7 +577,7 @@ const Peer = (props) =>
})} })}
className={classes.fab} className={classes.fab}
disabled={!videoVisible} disabled={!videoVisible}
size={smallButtons ? 'small' : 'large'} size='large'
onClick={() => onClick={() =>
{ {
toggleConsumerFullscreen(consumer); toggleConsumerFullscreen(consumer);
@ -473,11 +585,12 @@ const Peer = (props) =>
> >
<FullScreenIcon /> <FullScreenIcon />
</Fab> </Fab>
</div> }
</Tooltip> </Tooltip>
</div> </div>
<VideoView <VideoView
showQuality
advancedMode={advancedMode} advancedMode={advancedMode}
peer={peer} peer={peer}
displayName={peer.displayName} displayName={peer.displayName}
@ -573,26 +686,24 @@ const Peer = (props) =>
})} })}
placement={smallScreen ? 'top' : 'left'} placement={smallScreen ? 'top' : 'left'}
> >
<div> <Fab
<Fab aria-label={intl.formatMessage({
aria-label={intl.formatMessage({ id : 'label.newWindow',
id : 'label.newWindow', defaultMessage : 'New window'
defaultMessage : 'New window' })}
})} className={classes.fab}
className={classes.fab} disabled={
disabled={ !screenVisible ||
!screenVisible || (windowConsumer === screenConsumer.id)
(windowConsumer === screenConsumer.id) }
} size={smallContainer ? 'small' : 'large'}
size={smallButtons ? 'small' : 'large'} onClick={() =>
onClick={() => {
{ toggleConsumerWindow(screenConsumer);
toggleConsumerWindow(screenConsumer); }}
}} >
> <NewWindowIcon />
<NewWindowIcon /> </Fab>
</Fab>
</div>
</Tooltip> </Tooltip>
} }
@ -603,26 +714,25 @@ const Peer = (props) =>
})} })}
placement={smallScreen ? 'top' : 'left'} placement={smallScreen ? 'top' : 'left'}
> >
<div> <Fab
<Fab aria-label={intl.formatMessage({
aria-label={intl.formatMessage({ id : 'label.fullscreen',
id : 'label.fullscreen', defaultMessage : 'Fullscreen'
defaultMessage : 'Fullscreen' })}
})} className={classes.fab}
className={classes.fab} disabled={!screenVisible}
disabled={!screenVisible} size={smallContainer ? 'small' : 'large'}
size={smallButtons ? 'small' : 'large'} onClick={() =>
onClick={() => {
{ toggleConsumerFullscreen(screenConsumer);
toggleConsumerFullscreen(screenConsumer); }}
}} >
> <FullScreenIcon />
<FullScreenIcon /> </Fab>
</Fab>
</div>
</Tooltip> </Tooltip>
</div> </div>
<VideoView <VideoView
showQuality
advancedMode={advancedMode} advancedMode={advancedMode}
videoContain videoContain
consumerSpatialLayers={ consumerSpatialLayers={
@ -670,7 +780,7 @@ Peer.propTypes =
browser : PropTypes.object.isRequired, browser : PropTypes.object.isRequired,
spacing : PropTypes.number, spacing : PropTypes.number,
style : PropTypes.object, style : PropTypes.object,
smallButtons : PropTypes.bool, smallContainer : PropTypes.bool,
toggleConsumerFullscreen : PropTypes.func.isRequired, toggleConsumerFullscreen : PropTypes.func.isRequired,
toggleConsumerWindow : PropTypes.func.isRequired, toggleConsumerWindow : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired, classes : PropTypes.object.isRequired,

View File

@ -0,0 +1,133 @@
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 { FormattedMessage } from 'react-intl';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import Link from '@material-ui/core/Link';
import Button from '@material-ui/core/Button';
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'
}
},
logo :
{
marginRight : 'auto'
},
link :
{
display : 'block',
textAlign : 'center'
}
});
const About = ({
aboutOpen,
handleCloseAbout,
classes
}) =>
{
return (
<Dialog
open={aboutOpen}
onClose={() => handleCloseAbout(false)}
classes={{
paper : classes.dialogPaper
}}
>
<DialogTitle id='form-dialog-title'>
<FormattedMessage
id='room.about'
defaultMessage='About'
/>
</DialogTitle>
<DialogContent dividers='true'>
<DialogContentText>
Contributions to this work were made on behalf of the GÉANT
project, a project that has received funding from the
European Unions 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.<br />
<br />
GÉANT Vereniging (Association) is registered with the
Chamber of Commerce in Amsterdam with registration number
40535155 and operates in the UK as a branch of GÉANT
Vereniging. Registered office: Hoekenrode 3, 1102BR
Amsterdam, The Netherlands. UK branch address: City House,
126-130 Hills Road, Cambridge CB2 1PQ, UK.
</DialogContentText>
<Link href='https://edumeet.org' target='_blank' rel='noreferrer' color='secondary' variant='h6' className={classes.link}>
https://edumeet.org
</Link>
</DialogContent>
<DialogActions>
{ window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
<Button onClick={() => { handleCloseAbout(false); }} color='primary'>
<FormattedMessage
id='label.close'
defaultMessage='Close'
/>
</Button>
</DialogActions>
</Dialog>
);
};
About.propTypes =
{
roomClient : PropTypes.object.isRequired,
aboutOpen : PropTypes.bool.isRequired,
handleCloseAbout : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
({
aboutOpen : state.room.aboutOpen
});
const mapDispatchToProps = {
handleCloseAbout : roomActions.setAboutOpen
};
export default withRoomContext(connect(
mapStateToProps,
mapDispatchToProps,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.room.aboutOpen === next.room.aboutOpen
);
}
}
)(withStyles(styles)(About)));

View File

@ -0,0 +1,311 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { meProducersSelector } from '../Selectors';
import { withStyles } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import classnames from 'classnames';
import * as appPropTypes from '../appPropTypes';
import { withRoomContext } from '../../RoomContext';
import { useIntl } from 'react-intl';
import Fab from '@material-ui/core/Fab';
import Tooltip from '@material-ui/core/Tooltip';
import MicIcon from '@material-ui/icons/Mic';
import MicOffIcon from '@material-ui/icons/MicOff';
import VideoIcon from '@material-ui/icons/Videocam';
import VideoOffIcon from '@material-ui/icons/VideocamOff';
import ScreenIcon from '@material-ui/icons/ScreenShare';
import ScreenOffIcon from '@material-ui/icons/StopScreenShare';
const styles = (theme) =>
({
root :
{
position : 'fixed',
display : 'flex',
[theme.breakpoints.up('md')] :
{
top : '50%',
transform : 'translate(0%, -50%)',
flexDirection : 'column',
justifyContent : 'center',
alignItems : 'center',
left : theme.spacing(1)
},
[theme.breakpoints.down('sm')] :
{
flexDirection : 'row',
bottom : theme.spacing(1),
left : '50%',
transform : 'translate(-50%, -0%)'
}
},
fab :
{
margin : theme.spacing(1)
},
show :
{
opacity : 1,
transition : 'opacity .5s'
},
hide :
{
opacity : 0,
transition : 'opacity .5s'
}
});
const ButtonControlBar = (props) =>
{
const intl = useIntl();
const {
roomClient,
toolbarsVisible,
hiddenControls,
me,
micProducer,
webcamProducer,
screenProducer,
classes,
theme
} = props;
let micState;
let micTip;
if (!me.canSendMic)
{
micState = 'unsupported';
micTip = intl.formatMessage({
id : 'device.audioUnsupported',
defaultMessage : 'Audio unsupported'
});
}
else if (!micProducer)
{
micState = 'off';
micTip = intl.formatMessage({
id : 'device.activateAudio',
defaultMessage : 'Activate audio'
});
}
else if (!micProducer.locallyPaused && !micProducer.remotelyPaused)
{
micState = 'on';
micTip = intl.formatMessage({
id : 'device.muteAudio',
defaultMessage : 'Mute audio'
});
}
else
{
micState = 'muted';
micTip = intl.formatMessage({
id : 'device.unMuteAudio',
defaultMessage : 'Unmute audio'
});
}
let webcamState;
let webcamTip;
if (!me.canSendWebcam)
{
webcamState = 'unsupported';
webcamTip = intl.formatMessage({
id : 'device.videoUnsupported',
defaultMessage : 'Video unsupported'
});
}
else if (webcamProducer)
{
webcamState = 'on';
webcamTip = intl.formatMessage({
id : 'device.stopVideo',
defaultMessage : 'Stop video'
});
}
else
{
webcamState = 'off';
webcamTip = intl.formatMessage({
id : 'device.startVideo',
defaultMessage : 'Start video'
});
}
let screenState;
let screenTip;
if (!me.canShareScreen)
{
screenState = 'unsupported';
screenTip = intl.formatMessage({
id : 'device.screenSharingUnsupported',
defaultMessage : 'Screen sharing not supported'
});
}
else if (screenProducer)
{
screenState = 'on';
screenTip = intl.formatMessage({
id : 'device.stopScreenSharing',
defaultMessage : 'Stop screen sharing'
});
}
else
{
screenState = 'off';
screenTip = intl.formatMessage({
id : 'device.startScreenSharing',
defaultMessage : 'Start screen sharing'
});
}
const smallScreen = useMediaQuery(theme.breakpoints.down('sm'));
return (
<div
className={
classnames(
classes.root,
hiddenControls ?
(toolbarsVisible ? classes.show : classes.hide) :
classes.show)
}
>
<Tooltip title={micTip} placement={smallScreen ? 'top' : 'right'}>
<Fab
aria-label={intl.formatMessage({
id : 'device.muteAudio',
defaultMessage : 'Mute audio'
})}
className={classes.fab}
disabled={!me.canSendMic || me.audioInProgress}
color={micState === 'on' ? 'default' : 'secondary'}
size={smallScreen ? 'large' : 'medium'}
onClick={() =>
{
micState === 'on' ?
roomClient.muteMic() :
roomClient.unmuteMic();
}}
>
{ micState === 'on' ?
<MicIcon />
:
<MicOffIcon />
}
</Fab>
</Tooltip>
<Tooltip title={webcamTip} placement={smallScreen ? 'top' : 'right'}>
<Fab
aria-label={intl.formatMessage({
id : 'device.startVideo',
defaultMessage : 'Start video'
})}
className={classes.fab}
disabled={!me.canSendWebcam || me.webcamInProgress}
color={webcamState === 'on' ? 'default' : 'secondary'}
size={smallScreen ? 'large' : 'medium'}
onClick={() =>
{
webcamState === 'on' ?
roomClient.disableWebcam() :
roomClient.enableWebcam();
}}
>
{ webcamState === 'on' ?
<VideoIcon />
:
<VideoOffIcon />
}
</Fab>
</Tooltip>
<Tooltip title={screenTip} placement={smallScreen ? 'top' : 'right'}>
<Fab
aria-label={intl.formatMessage({
id : 'device.startScreenSharing',
defaultMessage : 'Start screen sharing'
})}
className={classes.fab}
disabled={!me.canShareScreen || me.screenShareInProgress}
color={screenState === 'on' ? 'primary' : 'default'}
size={smallScreen ? 'large' : 'medium'}
onClick={() =>
{
switch (screenState)
{
case 'on':
{
roomClient.disableScreenSharing();
break;
}
case 'off':
{
roomClient.enableScreenSharing();
break;
}
default:
{
break;
}
}
}}
>
{ screenState === 'on' || screenState === 'unsupported' ?
<ScreenOffIcon/>
:null
}
{ screenState === 'off' ?
<ScreenIcon/>
:null
}
</Fab>
</Tooltip>
</div>
);
};
ButtonControlBar.propTypes =
{
roomClient : PropTypes.any.isRequired,
toolbarsVisible : PropTypes.bool.isRequired,
hiddenControls : PropTypes.bool.isRequired,
me : appPropTypes.Me.isRequired,
micProducer : appPropTypes.Producer,
webcamProducer : appPropTypes.Producer,
screenProducer : appPropTypes.Producer,
classes : PropTypes.object.isRequired,
theme : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
({
toolbarsVisible : state.room.toolbarsVisible,
hiddenControls : state.settings.hiddenControls,
...meProducersSelector(state),
me : state.me
});
export default withRoomContext(connect(
mapStateToProps,
null,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.room.toolbarsVisible === next.room.toolbarsVisible &&
prev.settings.hiddenControls === next.settings.hiddenControls &&
prev.producers === next.producers &&
prev.me === next.me
);
}
}
)(withStyles(styles, { withTheme: true })(ButtonControlBar)));

View File

@ -0,0 +1,169 @@
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 DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import Button from '@material-ui/core/Button';
import Paper from '@material-ui/core/Paper';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
const shortcuts=[
{ key: 'h', label: 'room.help', defaultMessage: 'Help' },
{ key: 'm', label: 'device.muteAudio', defaultMessage: 'Mute Audio' },
{ key: 'v', label: 'device.stopVideo', defaultMessage: 'Mute Video' },
{ key: '1', label: 'label.democratic', defaultMessage: 'Democratic View' },
{ key: '2', label: 'label.filmstrip', defaultMessage: 'Filmstrip View' },
{ key: 'space', label: 'me.mutedPTT', defaultMessage: 'Push SPACE to talk' },
{ key: 'a', label: 'label.advanced', defaultMessage: 'Show advanced information' }
];
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'
},
display : 'flex',
flexDirection : 'column'
},
paper : {
padding : theme.spacing(1),
textAlign : 'center',
color : theme.palette.text.secondary,
whiteSpace : 'nowrap',
marginRight : theme.spacing(3),
marginBottom : theme.spacing(1),
minWidth : theme.spacing(8)
},
shortcuts : {
display : 'flex',
flexDirection : 'row',
alignItems : 'center'
},
tabsHeader :
{
flexGrow : 1
}
});
const Help = ({
helpOpen,
handleCloseHelp,
classes
}) =>
{
const intl = useIntl();
return (
<Dialog
open={helpOpen}
onClose={() => { handleCloseHelp(false); }}
classes={{
paper : classes.dialogPaper
}}
>
<DialogTitle id='form-dialog-title'>
<FormattedMessage
id='room.help'
defaultMessage='Help'
/>
</DialogTitle>
<Tabs
className={classes.tabsHeader}
indicatorColor='primary'
textColor='primary'
variant='fullWidth'
>
<Tab
label={
intl.formatMessage({
id : 'room.shortcutKeys',
defaultMessage : 'Shortcut keys'
})
}
/>
</Tabs>
<DialogContent dividers='true'>
<DialogContentText>
{shortcuts.map((value, index) =>
{
return (
<div key={index} className={classes.shortcuts}>
<Paper className={classes.paper}>
{value.key}
</Paper>
<FormattedMessage
id={value.label}
defaultMessage={value.defaultMessage}
/>
</div>
);
})}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => { handleCloseHelp(false); }} color='primary'>
<FormattedMessage
id='label.close'
defaultMessage='Close'
/>
</Button>
</DialogActions>
</Dialog>
);
};
Help.propTypes =
{
roomClient : PropTypes.object.isRequired,
helpOpen : PropTypes.bool.isRequired,
handleCloseHelp : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
({
helpOpen : state.room.helpOpen
});
const mapDispatchToProps = {
handleCloseHelp : roomActions.setHelpOpen
};
export default withRoomContext(connect(
mapStateToProps,
mapDispatchToProps,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.room.helpOpen === next.room.helpOpen
);
}
}
)(withStyles(styles)(Help)));

View File

@ -4,23 +4,28 @@ import PropTypes from 'prop-types';
import { import {
lobbyPeersKeySelector, lobbyPeersKeySelector,
peersLengthSelector, peersLengthSelector,
raisedHandsSelector raisedHandsSelector,
makePermissionSelector
} from '../Selectors'; } from '../Selectors';
import { permissions } from '../../permissions';
import * as appPropTypes from '../appPropTypes'; import * as appPropTypes from '../appPropTypes';
import { withRoomContext } from '../../RoomContext'; import { withRoomContext } from '../../RoomContext';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import * as roomActions from '../../actions/roomActions'; import * as roomActions from '../../actions/roomActions';
import * as toolareaActions from '../../actions/toolareaActions'; import * as toolareaActions from '../../actions/toolareaActions';
import { useIntl, FormattedMessage } from 'react-intl'; import { useIntl, FormattedMessage } from 'react-intl';
import classnames from 'classnames';
import AppBar from '@material-ui/core/AppBar'; import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar'; import Toolbar from '@material-ui/core/Toolbar';
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from '@material-ui/core/MenuItem';
import Menu from '@material-ui/core/Menu'; import Menu from '@material-ui/core/Menu';
import Popover from '@material-ui/core/Popover';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu'; import MenuIcon from '@material-ui/icons/Menu';
import Avatar from '@material-ui/core/Avatar'; import Avatar from '@material-ui/core/Avatar';
import Badge from '@material-ui/core/Badge'; import Badge from '@material-ui/core/Badge';
import Paper from '@material-ui/core/Paper';
import ExtensionIcon from '@material-ui/icons/Extension'; import ExtensionIcon from '@material-ui/icons/Extension';
import AccountCircle from '@material-ui/icons/AccountCircle'; import AccountCircle from '@material-ui/icons/AccountCircle';
import FullScreenIcon from '@material-ui/icons/Fullscreen'; import FullScreenIcon from '@material-ui/icons/Fullscreen';
@ -33,9 +38,37 @@ import LockOpenIcon from '@material-ui/icons/LockOpen';
import VideoCallIcon from '@material-ui/icons/VideoCall'; import VideoCallIcon from '@material-ui/icons/VideoCall';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import Tooltip from '@material-ui/core/Tooltip'; import Tooltip from '@material-ui/core/Tooltip';
import MoreIcon from '@material-ui/icons/MoreVert';
import HelpIcon from '@material-ui/icons/Help';
import InfoIcon from '@material-ui/icons/Info';
const styles = (theme) => const styles = (theme) =>
({ ({
persistentDrawerOpen :
{
width : 'calc(100% - 30vw)',
marginLeft : '30vw',
[theme.breakpoints.down('lg')] :
{
width : 'calc(100% - 40vw)',
marginLeft : '40vw'
},
[theme.breakpoints.down('md')] :
{
width : 'calc(100% - 50vw)',
marginLeft : '50vw'
},
[theme.breakpoints.down('sm')] :
{
width : 'calc(100% - 70vw)',
marginLeft : '70vw'
},
[theme.breakpoints.down('xs')] :
{
width : 'calc(100% - 90vw)',
marginLeft : '90vw'
}
},
menuButton : menuButton :
{ {
margin : 0, margin : 0,
@ -77,9 +110,17 @@ const styles = (theme) =>
display : 'block' display : 'block'
} }
}, },
actionButtons : sectionDesktop : {
{ display : 'none',
display : 'flex' [theme.breakpoints.up('md')] : {
display : 'flex'
}
},
sectionMobile : {
display : 'flex',
[theme.breakpoints.up('md')] : {
display : 'none'
}
}, },
actionButton : actionButton :
{ {
@ -96,7 +137,7 @@ const styles = (theme) =>
}, },
moreAction : moreAction :
{ {
margin : theme.spacing(0, 0, 0, 1) margin : theme.spacing(0.5, 0, 0.5, 1.5)
} }
}); });
@ -135,16 +176,36 @@ const TopBar = (props) =>
{ {
const intl = useIntl(); const intl = useIntl();
const [ moreActionsElement, setMoreActionsElement ] = useState(null); const [ mobileMoreAnchorEl, setMobileMoreAnchorEl ] = useState(null);
const [ anchorEl, setAnchorEl ] = useState(null);
const [ currentMenu, setCurrentMenu ] = useState(null);
const handleMoreActionsOpen = (event) => const handleExited = () =>
{ {
setMoreActionsElement(event.currentTarget); setCurrentMenu(null);
}; };
const handleMoreActionsClose = () => const handleMobileMenuOpen = (event) =>
{ {
setMoreActionsElement(null); setMobileMoreAnchorEl(event.currentTarget);
};
const handleMobileMenuClose = () =>
{
setMobileMoreAnchorEl(null);
};
const handleMenuOpen = (event, menu) =>
{
setAnchorEl(event.currentTarget);
setCurrentMenu(menu);
};
const handleMenuClose = () =>
{
setAnchorEl(null);
handleMobileMenuClose();
}; };
const { const {
@ -153,6 +214,9 @@ const TopBar = (props) =>
peersLength, peersLength,
lobbyPeers, lobbyPeers,
permanentTopBar, permanentTopBar,
drawerOverlayed,
toolAreaOpen,
isMobile,
myPicture, myPicture,
loggedIn, loggedIn,
loginEnabled, loginEnabled,
@ -161,6 +225,8 @@ const TopBar = (props) =>
onFullscreen, onFullscreen,
setSettingsOpen, setSettingsOpen,
setExtraVideoOpen, setExtraVideoOpen,
setHelpOpen,
setAboutOpen,
setLockDialogOpen, setLockDialogOpen,
toggleToolArea, toggleToolArea,
openUsersTab, openUsersTab,
@ -171,7 +237,8 @@ const TopBar = (props) =>
classes classes
} = props; } = props;
const isMoreActionsMenuOpen = Boolean(moreActionsElement); const isMenuOpen = Boolean(anchorEl);
const isMobileMenuOpen = Boolean(mobileMoreAnchorEl);
const lockTooltip = room.locked ? const lockTooltip = room.locked ?
intl.formatMessage({ intl.formatMessage({
@ -210,7 +277,12 @@ const TopBar = (props) =>
<React.Fragment> <React.Fragment>
<AppBar <AppBar
position='fixed' position='fixed'
className={room.toolbarsVisible || permanentTopBar ? classes.show : classes.hide} className={classnames(
room.toolbarsVisible || permanentTopBar ?
classes.show : classes.hide,
!(isMobile || drawerOverlayed) && toolAreaOpen ?
classes.persistentDrawerOpen : null
)}
> >
<Toolbar> <Toolbar>
<PulsingBadge <PulsingBadge
@ -239,14 +311,26 @@ const TopBar = (props) =>
{ window.config.title ? window.config.title : 'Multiparty meeting' } { window.config.title ? window.config.title : 'Multiparty meeting' }
</Typography> </Typography>
<div className={classes.grow} /> <div className={classes.grow} />
<div className={classes.actionButtons}> <div className={classes.sectionDesktop}>
<IconButton <Tooltip
aria-haspopup='true' title={intl.formatMessage({
onClick={handleMoreActionsOpen} id : 'label.moreActions',
color='inherit' defaultMessage : 'More actions'
})}
> >
<ExtensionIcon /> <IconButton
</IconButton> aria-owns={
isMenuOpen &&
currentMenu === 'moreActions' ?
'material-appbar' : undefined
}
aria-haspopup='true'
onClick={(event) => handleMenuOpen(event, 'moreActions')}
color='inherit'
>
<ExtensionIcon />
</IconButton>
</Tooltip>
{ fullscreenEnabled && { fullscreenEnabled &&
<Tooltip title={fullscreenTooltip}> <Tooltip title={fullscreenTooltip}>
<IconButton <IconButton
@ -386,52 +470,293 @@ const TopBar = (props) =>
</IconButton> </IconButton>
</Tooltip> </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> </div>
<div className={classes.sectionMobile}>
<IconButton
aria-haspopup='true'
onClick={handleMobileMenuOpen}
color='inherit'
>
<MoreIcon />
</IconButton>
</div>
<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>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<Menu <Popover
anchorEl={moreActionsElement} anchorEl={anchorEl}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
transformOrigin={{ vertical: 'top', horizontal: 'left' }} transformOrigin={{ vertical: 'top', horizontal: 'left' }}
open={isMoreActionsMenuOpen} open={isMenuOpen}
onClose={handleMoreActionsClose} onClose={handleMenuClose}
onExited={handleExited}
getContentAnchorEl={null} getContentAnchorEl={null}
> >
{ currentMenu === 'moreActions' &&
<Paper>
<MenuItem
disabled={!canProduceExtraVideo}
onClick={() =>
{
handleMenuClose();
setExtraVideoOpen(!room.extraVideoOpen);
}}
>
<VideoCallIcon
aria-label={intl.formatMessage({
id : 'label.addVideo',
defaultMessage : 'Add video'
})}
/>
<p className={classes.moreAction}>
<FormattedMessage
id='label.addVideo'
defaultMessage='Add video'
/>
</p>
</MenuItem>
<MenuItem
onClick={() =>
{
handleMenuClose();
setHelpOpen(!room.helpOpen);
}}
>
<HelpIcon
aria-label={intl.formatMessage({
id : 'room.help',
defaultMessage : 'Help'
})}
/>
<p className={classes.moreAction}>
<FormattedMessage
id='room.help'
defaultMessage='Help'
/>
</p>
</MenuItem>
<MenuItem
onClick={() =>
{
handleMenuClose();
setAboutOpen(!room.aboutOpen);
}}
>
<InfoIcon
aria-label={intl.formatMessage({
id : 'room.about',
defaultMessage : 'About'
})}
/>
<p className={classes.moreAction}>
<FormattedMessage
id='room.about'
defaultMessage='About'
/>
</p>
</MenuItem>
</Paper>
}
</Popover>
<Menu
anchorEl={mobileMoreAnchorEl}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
transformOrigin={{ vertical: 'bottom', horizontal: 'right' }}
open={isMobileMenuOpen}
onClose={handleMenuClose}
getContentAnchorEl={null}
>
{ loginEnabled &&
<MenuItem
aria-label={loginTooltip}
onClick={() =>
{
handleMenuClose();
loggedIn ? roomClient.logout() : roomClient.login();
}}
>
{ myPicture ?
<Avatar src={myPicture} />
:
<AccountCircle className={loggedIn ? classes.green : null} />
}
{ loggedIn ?
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.logout'
defaultMessage='Log out'
/>
</p>
:
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.login'
defaultMessage='Log in'
/>
</p>
}
</MenuItem>
}
<MenuItem <MenuItem
dense aria-label={lockTooltip}
disabled={!canProduceExtraVideo} disabled={!canLock}
onClick={() => onClick={() =>
{ {
handleMoreActionsClose(); handleMenuClose();
setExtraVideoOpen(!room.extraVideoOpen);
if (room.locked)
{
roomClient.unlockRoom();
}
else
{
roomClient.lockRoom();
}
}} }}
> >
<VideoCallIcon { room.locked ?
aria-label={intl.formatMessage({ <LockIcon />
id : 'label.addVideo', :
defaultMessage : 'Add video' <LockOpenIcon />
})} }
/> { room.locked ?
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.unLockRoom'
defaultMessage='Unlock room'
/>
</p>
:
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.lockRoom'
defaultMessage='Lock room'
/>
</p>
}
</MenuItem>
<MenuItem
aria-label={intl.formatMessage({
id : 'tooltip.settings',
defaultMessage : 'Show settings'
})}
onClick={() =>
{
handleMenuClose();
setSettingsOpen(!room.settingsOpen);
}}
>
<SettingsIcon />
<p className={classes.moreAction}> <p className={classes.moreAction}>
<FormattedMessage <FormattedMessage
id='label.addVideo' id='tooltip.settings'
defaultMessage='Add video' defaultMessage='Show settings'
/>
</p>
</MenuItem>
{ lobbyPeers.length > 0 &&
<MenuItem
aria-label={intl.formatMessage({
id : 'tooltip.lobby',
defaultMessage : 'Show lobby'
})}
disabled={!canPromote}
onClick={() =>
{
handleMenuClose();
setLockDialogOpen(!room.lockDialogOpen);
}}
>
<PulsingBadge
color='secondary'
badgeContent={lobbyPeers.length}
>
<SecurityIcon />
</PulsingBadge>
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.lobby'
defaultMessage='Show lobby'
/>
</p>
</MenuItem>
}
<MenuItem
aria-label={intl.formatMessage({
id : 'tooltip.participants',
defaultMessage : 'Show participants'
})}
onClick={() =>
{
handleMenuClose();
openUsersTab();
}}
>
<Badge
color='primary'
badgeContent={peersLength + 1}
>
<PeopleIcon />
</Badge>
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.participants'
defaultMessage='Show participants'
/>
</p>
</MenuItem>
{ fullscreenEnabled &&
<MenuItem
aria-label={intl.formatMessage({
id : 'tooltip.enterFullscreen',
defaultMessage : 'Enter fullscreen'
})}
onClick={() =>
{
handleMenuClose();
onFullscreen();
}}
>
{ fullscreen ?
<FullScreenExitIcon />
:
<FullScreenIcon />
}
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.enterFullscreen'
defaultMessage='Enter fullscreen'
/>
</p>
</MenuItem>
}
<MenuItem
aria-label={intl.formatMessage({
id : 'label.moreActions',
defaultMessage : 'Add video'
})}
onClick={(event) => handleMenuOpen(event, 'moreActions')}
>
<ExtensionIcon />
<p className={classes.moreAction}>
<FormattedMessage
id='label.moreActions'
defaultMessage='More actions'
/> />
</p> </p>
</MenuItem> </MenuItem>
@ -444,9 +769,12 @@ TopBar.propTypes =
{ {
roomClient : PropTypes.object.isRequired, roomClient : PropTypes.object.isRequired,
room : appPropTypes.Room.isRequired, room : appPropTypes.Room.isRequired,
isMobile : PropTypes.bool.isRequired,
peersLength : PropTypes.number, peersLength : PropTypes.number,
lobbyPeers : PropTypes.array, lobbyPeers : PropTypes.array,
permanentTopBar : PropTypes.bool, permanentTopBar : PropTypes.bool.isRequired,
drawerOverlayed : PropTypes.bool.isRequired,
toolAreaOpen : PropTypes.bool.isRequired,
myPicture : PropTypes.string, myPicture : PropTypes.string,
loggedIn : PropTypes.bool.isRequired, loggedIn : PropTypes.bool.isRequired,
loginEnabled : PropTypes.bool.isRequired, loginEnabled : PropTypes.bool.isRequired,
@ -456,6 +784,8 @@ TopBar.propTypes =
setToolbarsVisible : PropTypes.func.isRequired, setToolbarsVisible : PropTypes.func.isRequired,
setSettingsOpen : PropTypes.func.isRequired, setSettingsOpen : PropTypes.func.isRequired,
setExtraVideoOpen : PropTypes.func.isRequired, setExtraVideoOpen : PropTypes.func.isRequired,
setHelpOpen : PropTypes.func.isRequired,
setAboutOpen : PropTypes.func.isRequired,
setLockDialogOpen : PropTypes.func.isRequired, setLockDialogOpen : PropTypes.func.isRequired,
toggleToolArea : PropTypes.func.isRequired, toggleToolArea : PropTypes.func.isRequired,
openUsersTab : PropTypes.func.isRequired, openUsersTab : PropTypes.func.isRequired,
@ -467,27 +797,38 @@ TopBar.propTypes =
theme : PropTypes.object.isRequired theme : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => const makeMapStateToProps = () =>
({ {
room : state.room, const hasExtraVideoPermission =
peersLength : peersLengthSelector(state), makePermissionSelector(permissions.EXTRA_VIDEO);
lobbyPeers : lobbyPeersKeySelector(state),
permanentTopBar : state.settings.permanentTopBar, const hasLockPermission =
loggedIn : state.me.loggedIn, makePermissionSelector(permissions.CHANGE_ROOM_LOCK);
loginEnabled : state.me.loginEnabled,
myPicture : state.me.picture, const hasPromotionPermission =
unread : state.toolarea.unreadMessages + makePermissionSelector(permissions.PROMOTE_PEER);
state.toolarea.unreadFiles + raisedHandsSelector(state),
canProduceExtraVideo : const mapStateToProps = (state) =>
state.me.roles.some((role) => ({
state.room.permissionsFromRoles.EXTRA_VIDEO.includes(role)), room : state.room,
canLock : isMobile : state.me.browser.platform === 'mobile',
state.me.roles.some((role) => peersLength : peersLengthSelector(state),
state.room.permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role)), lobbyPeers : lobbyPeersKeySelector(state),
canPromote : permanentTopBar : state.settings.permanentTopBar,
state.me.roles.some((role) => drawerOverlayed : state.settings.drawerOverlayed,
state.room.permissionsFromRoles.PROMOTE_PEER.includes(role)) toolAreaOpen : state.toolarea.toolAreaOpen,
}); loggedIn : state.me.loggedIn,
loginEnabled : state.me.loginEnabled,
myPicture : state.me.picture,
unread : state.toolarea.unreadMessages +
state.toolarea.unreadFiles + raisedHandsSelector(state),
canProduceExtraVideo : hasExtraVideoPermission(state),
canLock : hasLockPermission(state),
canPromote : hasPromotionPermission(state)
});
return mapStateToProps;
};
const mapDispatchToProps = (dispatch) => const mapDispatchToProps = (dispatch) =>
({ ({
@ -503,6 +844,14 @@ const mapDispatchToProps = (dispatch) =>
{ {
dispatch(roomActions.setExtraVideoOpen(extraVideoOpen)); dispatch(roomActions.setExtraVideoOpen(extraVideoOpen));
}, },
setHelpOpen : (helpOpen) =>
{
dispatch(roomActions.setHelpOpen(helpOpen));
},
setAboutOpen : (aboutOpen) =>
{
dispatch(roomActions.setAboutOpen(aboutOpen));
},
setLockDialogOpen : (lockDialogOpen) => setLockDialogOpen : (lockDialogOpen) =>
{ {
dispatch(roomActions.setLockDialogOpen(lockDialogOpen)); dispatch(roomActions.setLockDialogOpen(lockDialogOpen));
@ -519,7 +868,7 @@ const mapDispatchToProps = (dispatch) =>
}); });
export default withRoomContext(connect( export default withRoomContext(connect(
mapStateToProps, makeMapStateToProps,
mapDispatchToProps, mapDispatchToProps,
null, null,
{ {
@ -530,12 +879,15 @@ export default withRoomContext(connect(
prev.peers === next.peers && prev.peers === next.peers &&
prev.lobbyPeers === next.lobbyPeers && prev.lobbyPeers === next.lobbyPeers &&
prev.settings.permanentTopBar === next.settings.permanentTopBar && prev.settings.permanentTopBar === next.settings.permanentTopBar &&
prev.settings.drawerOverlayed === next.settings.drawerOverlayed &&
prev.me.loggedIn === next.me.loggedIn && prev.me.loggedIn === next.me.loggedIn &&
prev.me.browser === next.me.browser &&
prev.me.loginEnabled === next.me.loginEnabled && prev.me.loginEnabled === next.me.loginEnabled &&
prev.me.picture === next.me.picture && prev.me.picture === next.me.picture &&
prev.me.roles === next.me.roles && prev.me.roles === next.me.roles &&
prev.toolarea.unreadMessages === next.toolarea.unreadMessages && prev.toolarea.unreadMessages === next.toolarea.unreadMessages &&
prev.toolarea.unreadFiles === next.toolarea.unreadFiles prev.toolarea.unreadFiles === next.toolarea.unreadFiles &&
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen
); );
} }
} }

View File

@ -83,6 +83,10 @@ const styles = (theme) =>
green : green :
{ {
color : 'rgba(0, 153, 0, 1)' color : 'rgba(0, 153, 0, 1)'
},
red :
{
color : 'rgba(153, 0, 0, 1)'
} }
}); });
@ -220,7 +224,7 @@ const JoinDialog = ({
myPicture={myPicture} myPicture={myPicture}
onLogin={() => onLogin={() =>
{ {
loggedIn ? roomClient.logout() : roomClient.login(roomId); loggedIn ? roomClient.logout(roomId) : roomClient.login(roomId);
}} }}
loggedIn={loggedIn} loggedIn={loggedIn}
> >
@ -281,6 +285,16 @@ const JoinDialog = ({
}} }}
fullWidth fullWidth
/> />
{!room.inLobby && room.overRoomLimit &&
<DialogContentText className={classes.red} variant='h6' gutterBottom>
<FormattedMessage
id='room.overRoomLimit'
defaultMessage={
'The room is full, retry after some time.'
}
/>
</DialogContentText>
}
</DialogContent> </DialogContent>
@ -419,6 +433,7 @@ export default withRoomContext(connect(
return ( return (
prev.room.inLobby === next.room.inLobby && prev.room.inLobby === next.room.inLobby &&
prev.room.signInRequired === next.room.signInRequired && prev.room.signInRequired === next.room.signInRequired &&
prev.room.overRoomLimit === next.room.overRoomLimit &&
prev.settings.displayName === next.settings.displayName && prev.settings.displayName === next.settings.displayName &&
prev.me.displayNameInProgress === next.me.displayNameInProgress && prev.me.displayNameInProgress === next.me.displayNameInProgress &&
prev.me.loginEnabled === next.me.loginEnabled && prev.me.loginEnabled === next.me.loginEnabled &&

View File

@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { permissions } from '../../../permissions';
import { makePermissionSelector } from '../../Selectors';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import InputBase from '@material-ui/core/InputBase'; import InputBase from '@material-ui/core/InputBase';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
@ -119,26 +121,32 @@ ChatInput.propTypes =
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => const makeMapStateToProps = () =>
({ {
displayName : state.settings.displayName, const hasPermission = makePermissionSelector(permissions.SEND_CHAT);
picture : state.me.picture,
canChat : const mapStateToProps = (state) =>
state.me.roles.some((role) => ({
state.room.permissionsFromRoles.SEND_CHAT.includes(role)) displayName : state.settings.displayName,
}); picture : state.me.picture,
canChat : hasPermission(state)
});
return mapStateToProps;
};
export default withRoomContext( export default withRoomContext(
connect( connect(
mapStateToProps, makeMapStateToProps,
null, null,
null, null,
{ {
areStatesEqual : (next, prev) => areStatesEqual : (next, prev) =>
{ {
return ( return (
prev.room.permissionsFromRoles === next.room.permissionsFromRoles && prev.room === next.room &&
prev.me.roles === next.me.roles && prev.me.roles === next.me.roles &&
prev.peers === next.peers &&
prev.settings.displayName === next.settings.displayName && prev.settings.displayName === next.settings.displayName &&
prev.me.picture === next.me.picture prev.me.picture === next.me.picture
); );

View File

@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { useIntl, FormattedMessage } from 'react-intl'; import { useIntl, FormattedMessage } from 'react-intl';
import { permissions } from '../../../permissions';
import { makePermissionSelector } from '../../Selectors';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
const styles = (theme) => const styles = (theme) =>
@ -76,16 +78,21 @@ ChatModerator.propTypes =
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => const makeMapStateToProps = () =>
({ {
isChatModerator : const hasPermission = makePermissionSelector(permissions.MODERATE_CHAT);
state.me.roles.some((role) =>
state.room.permissionsFromRoles.MODERATE_CHAT.includes(role)), const mapStateToProps = (state) =>
room : state.room ({
}); isChatModerator : hasPermission(state),
room : state.room
});
return mapStateToProps;
};
export default withRoomContext(connect( export default withRoomContext(connect(
mapStateToProps, makeMapStateToProps,
null, null,
null, null,
{ {
@ -93,7 +100,8 @@ export default withRoomContext(connect(
{ {
return ( return (
prev.room === next.room && prev.room === next.room &&
prev.me === next.me prev.me === next.me &&
prev.peers === next.peers
); );
} }
} }

View File

@ -4,6 +4,8 @@ import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { permissions } from '../../../permissions';
import { makePermissionSelector } from '../../Selectors';
import FileList from './FileList'; import FileList from './FileList';
import FileSharingModerator from './FileSharingModerator'; import FileSharingModerator from './FileSharingModerator';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
@ -25,6 +27,10 @@ const styles = (theme) =>
button : button :
{ {
margin : theme.spacing(1) margin : theme.spacing(1)
},
shareButtonsWrapper :
{
display : 'flex'
} }
}); });
@ -42,6 +48,7 @@ const FileSharing = (props) =>
const { const {
canShareFiles, canShareFiles,
browser,
canShare, canShare,
classes classes
} = props; } = props;
@ -57,29 +64,61 @@ const FileSharing = (props) =>
defaultMessage : 'File sharing not supported' defaultMessage : 'File sharing not supported'
}); });
const buttonGalleryDescription = canShareFiles ?
intl.formatMessage({
id : 'label.shareGalleryFile',
defaultMessage : 'Share image'
})
:
intl.formatMessage({
id : 'label.fileSharingUnsupported',
defaultMessage : 'File sharing not supported'
});
return ( return (
<Paper className={classes.root}> <Paper className={classes.root}>
<FileSharingModerator /> <FileSharingModerator />
<input <div className={classes.shareButtonsWrapper} >
className={classes.input} <input
type='file' className={classes.input}
disabled={!canShare} type='file'
onChange={handleFileChange} disabled={!canShare}
// Need to reset to be able to share same file twice onChange={handleFileChange}
onClick={(e) => (e.target.value = null)} // Need to reset to be able to share same file twice
id='share-files-button' onClick={(e) => (e.target.value = null)}
/> id='share-files-button'
<label htmlFor='share-files-button'> />
<Button <input
variant='contained' className={classes.input}
component='span' type='file'
className={classes.button} disabled={!canShare}
disabled={!canShareFiles || !canShare} onChange={handleFileChange}
> accept='image/*'
{buttonDescription} id='share-files-gallery-button'
</Button> />
</label> <label htmlFor='share-files-button'>
<Button
variant='contained'
component='span'
className={classes.button}
disabled={!canShareFiles || !canShare}
>
{buttonDescription}
</Button>
</label>
{
(browser.platform === 'mobile') && canShareFiles && canShare && <label htmlFor='share-files-gallery-button'>
<Button
variant='contained'
component='span'
className={classes.button}
disabled={!canShareFiles || !canShare}
>
{buttonGalleryDescription}
</Button>
</label>
}
</div>
<FileList /> <FileList />
</Paper> </Paper>
); );
@ -87,34 +126,43 @@ const FileSharing = (props) =>
FileSharing.propTypes = { FileSharing.propTypes = {
roomClient : PropTypes.any.isRequired, roomClient : PropTypes.any.isRequired,
browser : PropTypes.object.isRequired,
canShareFiles : PropTypes.bool.isRequired, canShareFiles : PropTypes.bool.isRequired,
tabOpen : PropTypes.bool.isRequired, tabOpen : PropTypes.bool.isRequired,
canShare : PropTypes.bool.isRequired, canShare : PropTypes.bool.isRequired,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => const makeMapStateToProps = () =>
{ {
return { const hasPermission = makePermissionSelector(permissions.SHARE_FILE);
canShareFiles : state.me.canShareFiles,
tabOpen : state.toolarea.currentToolTab === 'files', const mapStateToProps = (state) =>
canShare : {
state.me.roles.some((role) => return {
state.room.permissionsFromRoles.SHARE_FILE.includes(role)) canShareFiles : state.me.canShareFiles,
browser : state.me.browser,
tabOpen : state.toolarea.currentToolTab === 'files',
canShare : hasPermission(state)
};
}; };
return mapStateToProps;
}; };
export default withRoomContext(connect( export default withRoomContext(connect(
mapStateToProps, makeMapStateToProps,
null, null,
null, null,
{ {
areStatesEqual : (next, prev) => areStatesEqual : (next, prev) =>
{ {
return ( return (
prev.room.permissionsFromRoles === next.room.permissionsFromRoles && prev.room === next.room &&
prev.me.browser === next.me.browser &&
prev.me.roles === next.me.roles && prev.me.roles === next.me.roles &&
prev.me.canShareFiles === next.me.canShareFiles && prev.me.canShareFiles === next.me.canShareFiles &&
prev.peers === next.peers &&
prev.toolarea.currentToolTab === next.toolarea.currentToolTab prev.toolarea.currentToolTab === next.toolarea.currentToolTab
); );
} }

View File

@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { useIntl, FormattedMessage } from 'react-intl'; import { useIntl, FormattedMessage } from 'react-intl';
import { permissions } from '../../../permissions';
import { makePermissionSelector } from '../../Selectors';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
const styles = (theme) => const styles = (theme) =>
@ -76,16 +78,21 @@ FileSharingModerator.propTypes =
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => const makeMapStateToProps = () =>
({ {
isFileSharingModerator : const hasPermission = makePermissionSelector(permissions.MODERATE_FILES);
state.me.roles.some((role) =>
state.room.permissionsFromRoles.MODERATE_FILES.includes(role)), const mapStateToProps = (state) =>
room : state.room ({
}); isFileSharingModerator : hasPermission(state),
room : state.room
});
return mapStateToProps;
};
export default withRoomContext(connect( export default withRoomContext(connect(
mapStateToProps, makeMapStateToProps,
null, null,
null, null,
{ {
@ -93,7 +100,8 @@ export default withRoomContext(connect(
{ {
return ( return (
prev.room === next.room && prev.room === next.room &&
prev.me === next.me prev.me === next.me &&
prev.peers === next.peers
); );
} }
} }

View File

@ -2,10 +2,12 @@ import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import classnames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as appPropTypes from '../../appPropTypes'; import * as appPropTypes from '../../appPropTypes';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import PanIcon from '@material-ui/icons/PanTool'; import PanIcon from '@material-ui/icons/PanTool';
import EmptyAvatar from '../../../images/avatar-empty.jpeg'; import EmptyAvatar from '../../../images/avatar-empty.jpeg';
@ -22,7 +24,7 @@ const styles = (theme) =>
{ {
borderRadius : '50%', borderRadius : '50%',
height : '2rem', height : '2rem',
marginTop : theme.spacing(1) marginTop : theme.spacing(0.5)
}, },
peerInfo : peerInfo :
{ {
@ -32,6 +34,10 @@ const styles = (theme) =>
flexGrow : 1, flexGrow : 1,
alignItems : 'center' alignItems : 'center'
}, },
buttons :
{
padding : theme.spacing(1)
},
green : green :
{ {
color : 'rgba(0, 153, 0, 1)' color : 'rgba(0, 153, 0, 1)'
@ -58,22 +64,33 @@ const ListMe = (props) =>
<div className={classes.peerInfo}> <div className={classes.peerInfo}>
{settings.displayName} {settings.displayName}
</div> </div>
<IconButton <Tooltip
aria-label={intl.formatMessage({ title={intl.formatMessage({
id : 'tooltip.raisedHand', id : 'tooltip.raisedHand',
defaultMessage : 'Raise hand' defaultMessage : 'Raise hand'
})} })}
className={me.raisedHand ? classes.green : null} placement='bottom'
disabled={me.raisedHandInProgress}
onClick={(e) =>
{
e.stopPropagation();
roomClient.setRaisedHand(!me.raisedHand);
}}
> >
<PanIcon /> <IconButton
</IconButton> aria-label={intl.formatMessage({
id : 'tooltip.raisedHand',
defaultMessage : 'Raise hand'
})}
className={
classnames(me.raisedHand ? classes.green : null, classes.buttons)
}
disabled={me.raisedHandInProgress}
color='primary'
onClick={(e) =>
{
e.stopPropagation();
roomClient.setRaisedHand(!me.raisedHand);
}}
>
<PanIcon />
</IconButton>
</Tooltip>
</div> </div>
); );
}; };

View File

@ -6,9 +6,13 @@ import PropTypes from 'prop-types';
import * as appPropTypes from '../../appPropTypes'; import * as appPropTypes from '../../appPropTypes';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { green } from '@material-ui/core/colors';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import VideocamIcon from '@material-ui/icons/Videocam'; import VideocamIcon from '@material-ui/icons/Videocam';
import VideocamOffIcon from '@material-ui/icons/VideocamOff'; import VideocamOffIcon from '@material-ui/icons/VideocamOff';
import MicIcon from '@material-ui/icons/Mic';
import MicOffIcon from '@material-ui/icons/MicOff';
import VolumeUpIcon from '@material-ui/icons/VolumeUp'; import VolumeUpIcon from '@material-ui/icons/VolumeUp';
import VolumeOffIcon from '@material-ui/icons/VolumeOff'; import VolumeOffIcon from '@material-ui/icons/VolumeOff';
import ScreenIcon from '@material-ui/icons/ScreenShare'; import ScreenIcon from '@material-ui/icons/ScreenShare';
@ -16,6 +20,7 @@ import ScreenOffIcon from '@material-ui/icons/StopScreenShare';
import ExitIcon from '@material-ui/icons/ExitToApp'; import ExitIcon from '@material-ui/icons/ExitToApp';
import EmptyAvatar from '../../../images/avatar-empty.jpeg'; import EmptyAvatar from '../../../images/avatar-empty.jpeg';
import PanIcon from '@material-ui/icons/PanTool'; import PanIcon from '@material-ui/icons/PanTool';
import RecordVoiceOverIcon from '@material-ui/icons/RecordVoiceOver';
const styles = (theme) => const styles = (theme) =>
({ ({
@ -30,7 +35,7 @@ const styles = (theme) =>
{ {
borderRadius : '50%', borderRadius : '50%',
height : '2rem', height : '2rem',
marginTop : theme.spacing(1) marginTop : theme.spacing(0.5)
}, },
peerInfo : peerInfo :
{ {
@ -43,11 +48,16 @@ const styles = (theme) =>
indicators : indicators :
{ {
display : 'flex', display : 'flex',
padding : theme.spacing(1.5) padding : theme.spacing(1)
},
buttons :
{
padding : theme.spacing(1)
}, },
green : green :
{ {
color : 'rgba(0, 153, 0, 1)' color : 'rgba(0, 153, 0, 1)',
marginLeft : theme.spacing(2)
} }
}); });
@ -58,6 +68,7 @@ const ListPeer = (props) =>
const { const {
roomClient, roomClient,
isModerator, isModerator,
spotlight,
peer, peer,
micConsumer, micConsumer,
webcamConsumer, webcamConsumer,
@ -93,95 +104,207 @@ const ListPeer = (props) =>
<div className={classes.peerInfo}> <div className={classes.peerInfo}>
{peer.displayName} {peer.displayName}
</div> </div>
<div className={classes.indicators}> { peer.raisedHand &&
{ peer.raisedHand &&
<PanIcon className={classes.green} />
}
</div>
{ screenConsumer &&
<IconButton <IconButton
aria-label={intl.formatMessage({ className={classes.buttons}
style={{ color: green[500] }}
disabled={!isModerator || peer.raisedHandInProgress}
onClick={(e) =>
{
e.stopPropagation();
roomClient.lowerPeerHand(peer.id);
}}
>
<PanIcon />
</IconButton>
}
{ spotlight &&
<IconButton
className={classes.buttons}
style={{ color: green[500] }}
disabled
>
<RecordVoiceOverIcon />
</IconButton>
}
{ screenConsumer && spotlight &&
<Tooltip
title={intl.formatMessage({
id : 'tooltip.muteScreenSharing', id : 'tooltip.muteScreenSharing',
defaultMessage : 'Mute participant share' defaultMessage : 'Mute participant share'
})} })}
color={screenVisible ? 'primary' : 'secondary'} placement='bottom'
disabled={peer.peerScreenInProgress}
onClick={(e) =>
{
e.stopPropagation();
screenVisible ?
roomClient.modifyPeerConsumer(peer.id, 'screen', true) :
roomClient.modifyPeerConsumer(peer.id, 'screen', false);
}}
> >
{ screenVisible ? <IconButton
<ScreenIcon /> aria-label={intl.formatMessage({
: id : 'tooltip.muteScreenSharing',
<ScreenOffIcon /> defaultMessage : 'Mute participant share'
} })}
</IconButton> color={screenVisible ? 'primary' : 'secondary'}
} disabled={peer.peerScreenInProgress}
<IconButton className={classes.buttons}
aria-label={intl.formatMessage({ onClick={(e) =>
id : 'tooltip.muteParticipantVideo', {
defaultMessage : 'Mute participant video' e.stopPropagation();
})}
color={webcamEnabled ? 'primary' : 'secondary'}
disabled={peer.peerVideoInProgress}
onClick={(e) =>
{
e.stopPropagation();
webcamEnabled ? screenVisible ?
roomClient.modifyPeerConsumer(peer.id, 'webcam', true) : roomClient.modifyPeerConsumer(peer.id, 'screen', true) :
roomClient.modifyPeerConsumer(peer.id, 'webcam', false); roomClient.modifyPeerConsumer(peer.id, 'screen', false);
}} }}
> >
{ webcamEnabled ? { screenVisible ?
<VideocamIcon /> <ScreenIcon />
: :
<VideocamOffIcon /> <ScreenOffIcon />
} }
</IconButton> </IconButton>
<IconButton </Tooltip>
aria-label={intl.formatMessage({ }
{ spotlight &&
<Tooltip
title={intl.formatMessage({
id : 'tooltip.muteParticipantVideo',
defaultMessage : 'Mute participant video'
})}
placement='bottom'
>
<IconButton
aria-label={intl.formatMessage({
id : 'tooltip.muteParticipantVideo',
defaultMessage : 'Mute participant video'
})}
color={webcamEnabled ? 'primary' : 'secondary'}
disabled={peer.peerVideoInProgress}
className={classes.buttons}
onClick={(e) =>
{
e.stopPropagation();
webcamEnabled ?
roomClient.modifyPeerConsumer(peer.id, 'webcam', true) :
roomClient.modifyPeerConsumer(peer.id, 'webcam', false);
}}
>
{ webcamEnabled ?
<VideocamIcon />
:
<VideocamOffIcon />
}
</IconButton>
</Tooltip>
}
<Tooltip
title={intl.formatMessage({
id : 'tooltip.muteParticipant', id : 'tooltip.muteParticipant',
defaultMessage : 'Mute participant' defaultMessage : 'Mute participant'
})} })}
color={micEnabled ? 'primary' : 'secondary'} placement='bottom'
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 <IconButton
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'tooltip.kickParticipant', id : 'tooltip.muteParticipant',
defaultMessage : 'Kick out participant' defaultMessage : 'Mute participant'
})} })}
disabled={peer.peerKickInProgress} color={micEnabled ? 'primary' : 'secondary'}
disabled={peer.peerAudioInProgress}
className={classes.buttons}
onClick={(e) => onClick={(e) =>
{ {
e.stopPropagation(); e.stopPropagation();
roomClient.kickPeer(peer.id); micEnabled ?
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
}} }}
> >
<ExitIcon /> { micEnabled ?
<VolumeUpIcon />
:
<VolumeOffIcon />
}
</IconButton> </IconButton>
</Tooltip>
{ isModerator &&
<Tooltip
title={intl.formatMessage({
id : 'tooltip.kickParticipant',
defaultMessage : 'Kick out participant'
})}
placement='bottom'
>
<IconButton
aria-label={intl.formatMessage({
id : 'tooltip.kickParticipant',
defaultMessage : 'Kick out participant'
})}
disabled={peer.peerKickInProgress}
className={classes.buttons}
color='secondary'
onClick={(e) =>
{
e.stopPropagation();
roomClient.kickPeer(peer.id);
}}
>
<ExitIcon />
</IconButton>
</Tooltip>
}
{ isModerator && micConsumer &&
<Tooltip
title={intl.formatMessage({
id : 'tooltip.muteParticipantAudioModerator',
defaultMessage : 'Mute participant audio globally'
})}
placement='bottom'
>
<IconButton
className={classes.buttons}
style={{ color: green[500] }}
disabled={!isModerator || peer.stopPeerAudioInProgress}
onClick={(e) =>
{
e.stopPropagation();
roomClient.mutePeer(peer.id);
}}
>
{ !micConsumer.remotelyPaused ?
<MicIcon />
:
<MicOffIcon />
}
</IconButton>
</Tooltip>
}
{ isModerator && webcamConsumer &&
<Tooltip
title={intl.formatMessage({
id : 'tooltip.muteParticipantVideoModerator',
defaultMessage : 'Mute participant video globally'
})}
placement='bottom'
>
<IconButton
className={classes.buttons}
style={{ color: green[500] }}
disabled={!isModerator || peer.stopPeerVideoInProgress}
onClick={(e) =>
{
e.stopPropagation();
roomClient.stopPeerVideo(peer.id);
}}
>
{ !webcamConsumer.remotelyPaused ?
<VideocamIcon />
:
<VideocamOffIcon />
}
</IconButton>
</Tooltip>
} }
{children} {children}
</div> </div>
@ -193,6 +316,7 @@ ListPeer.propTypes =
roomClient : PropTypes.any.isRequired, roomClient : PropTypes.any.isRequired,
advancedMode : PropTypes.bool, advancedMode : PropTypes.bool,
isModerator : PropTypes.bool, isModerator : PropTypes.bool,
spotlight : PropTypes.bool,
peer : appPropTypes.Peer.isRequired, peer : appPropTypes.Peer.isRequired,
micConsumer : appPropTypes.Consumer, micConsumer : appPropTypes.Consumer,
webcamConsumer : appPropTypes.Consumer, webcamConsumer : appPropTypes.Consumer,

View File

@ -1,13 +1,15 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
passivePeersSelector, participantListSelector,
spotlightSortedPeersSelector makePermissionSelector
} from '../../Selectors'; } from '../../Selectors';
import classNames from 'classnames'; import { permissions } from '../../../permissions';
import classnames from 'classnames';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Flipper, Flipped } from 'react-flip-toolkit';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ListPeer from './ListPeer'; import ListPeer from './ListPeer';
import ListMe from './ListMe'; import ListMe from './ListMe';
@ -76,9 +78,9 @@ class ParticipantList extends React.PureComponent
roomClient, roomClient,
advancedMode, advancedMode,
isModerator, isModerator,
passivePeers, participants,
spotlights,
selectedPeerId, selectedPeerId,
spotlightPeers,
classes classes
} = this.props; } = this.props;
@ -107,50 +109,42 @@ class ParticipantList extends React.PureComponent
<ul className={classes.list}> <ul className={classes.list}>
<li className={classes.listheader}> <li className={classes.listheader}>
<FormattedMessage <FormattedMessage
id='room.spotlights' id='label.participants'
defaultMessage='Participants in Spotlight' defaultMessage='Participants'
/> />
</li> </li>
{ spotlightPeers.map((peer) => ( <Flipper
<li flipKey={participants}
key={peer.id} >
className={classNames(classes.listItem, { { participants.map((peer) => (
selected : peer.id === selectedPeerId <Flipped key={peer.id} flipId={peer.id}>
})} <li
onClick={() => roomClient.setSelectedPeer(peer.id)} key={peer.id}
> className={classnames(classes.listItem, {
<ListPeer selected : peer.id === selectedPeerId
id={peer.id} })}
advancedMode={advancedMode} onClick={() => roomClient.setSelectedPeer(peer.id)}
isModerator={isModerator} >
> { spotlights.includes(peer.id) ?
<Volume small id={peer.id} /> <ListPeer
</ListPeer> id={peer.id}
</li> advancedMode={advancedMode}
))} isModerator={isModerator}
</ul> spotlight
<ul className={classes.list}> >
<li className={classes.listheader}> <Volume small id={peer.id} />
<FormattedMessage </ListPeer>
id='room.passive' :
defaultMessage='Passive Participants' <ListPeer
/> id={peer.id}
</li> advancedMode={advancedMode}
{ passivePeers.map((peer) => ( isModerator={isModerator}
<li />
key={peer.id} }
className={classNames(classes.listItem, { </li>
selected : peer.id === selectedPeerId </Flipped>
})} ))}
onClick={() => roomClient.setSelectedPeer(peer.id)} </Flipper>
>
<ListPeer
id={peer.id}
advancedMode={advancedMode}
isModerator={isModerator}
/>
</li>
))}
</ul> </ul>
</div> </div>
); );
@ -162,37 +156,40 @@ ParticipantList.propTypes =
roomClient : PropTypes.any.isRequired, roomClient : PropTypes.any.isRequired,
advancedMode : PropTypes.bool, advancedMode : PropTypes.bool,
isModerator : PropTypes.bool, isModerator : PropTypes.bool,
passivePeers : PropTypes.array, participants : PropTypes.array,
spotlights : PropTypes.array,
selectedPeerId : PropTypes.string, selectedPeerId : PropTypes.string,
spotlightPeers : PropTypes.array,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => const makeMapStateToProps = () =>
{ {
return { const hasPermission = makePermissionSelector(permissions.MODERATE_ROOM);
isModerator :
state.me.roles.some((role) => const mapStateToProps = (state) =>
state.room.permissionsFromRoles.MODERATE_ROOM.includes(role)), {
passivePeers : passivePeersSelector(state), return {
selectedPeerId : state.room.selectedPeerId, isModerator : hasPermission(state),
spotlightPeers : spotlightSortedPeersSelector(state) participants : participantListSelector(state),
spotlights : state.room.spotlights,
selectedPeerId : state.room.selectedPeerId
};
}; };
return mapStateToProps;
}; };
const ParticipantListContainer = withRoomContext(connect( const ParticipantListContainer = withRoomContext(connect(
mapStateToProps, makeMapStateToProps,
null, null,
null, null,
{ {
areStatesEqual : (next, prev) => areStatesEqual : (next, prev) =>
{ {
return ( return (
prev.room.permissionsFromRoles === next.room.permissionsFromRoles && prev.room === next.room &&
prev.me.roles === next.me.roles && prev.me.roles === next.me.roles &&
prev.peers === next.peers && prev.peers === next.peers
prev.room.spotlights === next.room.spotlights &&
prev.room.selectedPeerId === next.room.selectedPeerId
); );
} }
} }

View File

@ -11,10 +11,9 @@ import Peer from '../Containers/Peer';
import Me from '../Containers/Me'; import Me from '../Containers/Me';
const RATIO = 1.334; const RATIO = 1.334;
const PADDING_V = 50; const PADDING = 60;
const PADDING_H = 0;
const styles = () => const styles = (theme) =>
({ ({
root : root :
{ {
@ -23,6 +22,7 @@ const styles = () =>
display : 'flex', display : 'flex',
flexDirection : 'row', flexDirection : 'row',
flexWrap : 'wrap', flexWrap : 'wrap',
overflow : 'hidden',
justifyContent : 'center', justifyContent : 'center',
alignItems : 'center', alignItems : 'center',
alignContent : 'center' alignContent : 'center'
@ -36,6 +36,14 @@ const styles = () =>
{ {
paddingTop : 60, paddingTop : 60,
transition : 'padding .5s' transition : 'padding .5s'
},
buttonControlBar :
{
paddingLeft : 60,
[theme.breakpoints.down('sm')] :
{
paddingLeft : 0
}
} }
}); });
@ -66,9 +74,11 @@ class Democratic extends React.PureComponent
return; return;
} }
const width = this.peersRef.current.clientWidth - PADDING_H; const width =
const height = this.peersRef.current.clientHeight - this.peersRef.current.clientWidth - (this.props.buttonControlBar ? PADDING : 0);
(this.props.toolbarsVisible || this.props.permanentTopBar ? PADDING_V : PADDING_H); const height =
this.peersRef.current.clientHeight -
(this.props.toolbarsVisible || this.props.permanentTopBar ? PADDING : 0);
let x, y, space; let x, y, space;
@ -130,6 +140,7 @@ class Democratic extends React.PureComponent
spotlightsPeers, spotlightsPeers,
toolbarsVisible, toolbarsVisible,
permanentTopBar, permanentTopBar,
buttonControlBar,
classes classes
} = this.props; } = this.props;
@ -144,7 +155,8 @@ class Democratic extends React.PureComponent
className={classnames( className={classnames(
classes.root, classes.root,
toolbarsVisible || permanentTopBar ? toolbarsVisible || permanentTopBar ?
classes.showingToolBar : classes.hiddenToolBar classes.showingToolBar : classes.hiddenToolBar,
buttonControlBar ? classes.buttonControlBar : null
)} )}
ref={this.peersRef} ref={this.peersRef}
> >
@ -172,21 +184,25 @@ class Democratic extends React.PureComponent
Democratic.propTypes = Democratic.propTypes =
{ {
advancedMode : PropTypes.bool, advancedMode : PropTypes.bool,
boxes : PropTypes.number, boxes : PropTypes.number,
spotlightsPeers : PropTypes.array.isRequired, spotlightsPeers : PropTypes.array.isRequired,
toolbarsVisible : PropTypes.bool.isRequired, toolbarsVisible : PropTypes.bool.isRequired,
permanentTopBar : PropTypes.bool, permanentTopBar : PropTypes.bool.isRequired,
classes : PropTypes.object.isRequired buttonControlBar : PropTypes.bool.isRequired,
toolAreaOpen : PropTypes.bool.isRequired,
classes : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => const mapStateToProps = (state) =>
{ {
return { return {
boxes : videoBoxesSelector(state), boxes : videoBoxesSelector(state),
spotlightsPeers : spotlightPeersSelector(state), spotlightsPeers : spotlightPeersSelector(state),
toolbarsVisible : state.room.toolbarsVisible, toolbarsVisible : state.room.toolbarsVisible,
permanentTopBar : state.settings.permanentTopBar permanentTopBar : state.settings.permanentTopBar,
buttonControlBar : state.settings.buttonControlBar,
toolAreaOpen : state.toolarea.toolAreaOpen
}; };
}; };
@ -203,8 +219,10 @@ export default connect(
prev.consumers === next.consumers && prev.consumers === next.consumers &&
prev.room.spotlights === next.room.spotlights && prev.room.spotlights === next.room.spotlights &&
prev.room.toolbarsVisible === next.room.toolbarsVisible && prev.room.toolbarsVisible === next.room.toolbarsVisible &&
prev.settings.permanentTopBar === next.settings.permanentTopBar prev.settings.permanentTopBar === next.settings.permanentTopBar &&
prev.settings.buttonControlBar === next.settings.buttonControlBar &&
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen
); );
} }
} }
)(withStyles(styles)(Democratic)); )(withStyles(styles, { withTheme: true })(Democratic));

View File

@ -25,6 +25,7 @@ const styles = () =>
height : '100%', height : '100%',
width : '100%', width : '100%',
display : 'grid', display : 'grid',
overflow : 'hidden',
gridTemplateColumns : '1fr', gridTemplateColumns : '1fr',
gridTemplateRows : '1fr 0.25fr' gridTemplateRows : '1fr 0.25fr'
}, },
@ -49,7 +50,7 @@ const styles = () =>
}, },
'&.active' : '&.active' :
{ {
opacity : '0.6' borderColor : 'var(--selected-peer-border-color)'
} }
}, },
hiddenToolBar : hiddenToolBar :
@ -123,6 +124,9 @@ class Filmstrip extends React.PureComponent
const root = this.rootContainer.current; const root = this.rootContainer.current;
if (!root)
return;
const availableWidth = root.clientWidth; const availableWidth = root.clientWidth;
// Grid is: // Grid is:
// 4/5 speaker // 4/5 speaker
@ -279,7 +283,7 @@ class Filmstrip extends React.PureComponent
<Me <Me
advancedMode={advancedMode} advancedMode={advancedMode}
style={peerStyle} style={peerStyle}
smallButtons smallContainer
/> />
</div> </div>
</Grid> </Grid>
@ -302,7 +306,7 @@ class Filmstrip extends React.PureComponent
advancedMode={advancedMode} advancedMode={advancedMode}
id={peerId} id={peerId}
style={peerStyle} style={peerStyle}
smallButtons smallContainer
/> />
</div> </div>
</Grid> </Grid>
@ -331,6 +335,7 @@ Filmstrip.propTypes = {
spotlights : PropTypes.array.isRequired, spotlights : PropTypes.array.isRequired,
boxes : PropTypes.number, boxes : PropTypes.number,
toolbarsVisible : PropTypes.bool.isRequired, toolbarsVisible : PropTypes.bool.isRequired,
toolAreaOpen : PropTypes.bool.isRequired,
permanentTopBar : PropTypes.bool, permanentTopBar : PropTypes.bool,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
@ -346,6 +351,7 @@ const mapStateToProps = (state) =>
spotlights : state.room.spotlights, spotlights : state.room.spotlights,
boxes : videoBoxesSelector(state), boxes : videoBoxesSelector(state),
toolbarsVisible : state.room.toolbarsVisible, toolbarsVisible : state.room.toolbarsVisible,
toolAreaOpen : state.toolarea.toolAreaOpen,
permanentTopBar : state.settings.permanentTopBar permanentTopBar : state.settings.permanentTopBar
}; };
}; };
@ -361,6 +367,7 @@ export default withRoomContext(connect(
prev.room.activeSpeakerId === next.room.activeSpeakerId && prev.room.activeSpeakerId === next.room.activeSpeakerId &&
prev.room.selectedPeerId === next.room.selectedPeerId && prev.room.selectedPeerId === next.room.selectedPeerId &&
prev.room.toolbarsVisible === next.room.toolbarsVisible && prev.room.toolbarsVisible === next.room.toolbarsVisible &&
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen &&
prev.settings.permanentTopBar === next.settings.permanentTopBar && prev.settings.permanentTopBar === next.settings.permanentTopBar &&
prev.peers === next.peers && prev.peers === next.peers &&
prev.consumers === next.consumers && prev.consumers === next.consumers &&

View File

@ -12,6 +12,7 @@ import { FormattedMessage } from 'react-intl';
import CookieConsent from 'react-cookie-consent'; import CookieConsent from 'react-cookie-consent';
import CssBaseline from '@material-ui/core/CssBaseline'; import CssBaseline from '@material-ui/core/CssBaseline';
import SwipeableDrawer from '@material-ui/core/SwipeableDrawer'; import SwipeableDrawer from '@material-ui/core/SwipeableDrawer';
import Drawer from '@material-ui/core/Drawer';
import Hidden from '@material-ui/core/Hidden'; import Hidden from '@material-ui/core/Hidden';
import Notifications from './Notifications/Notifications'; import Notifications from './Notifications/Notifications';
import MeetingDrawer from './MeetingDrawer/MeetingDrawer'; import MeetingDrawer from './MeetingDrawer/MeetingDrawer';
@ -25,8 +26,11 @@ import Settings from './Settings/Settings';
import TopBar from './Controls/TopBar'; import TopBar from './Controls/TopBar';
import WakeLock from 'react-wakelock-react16'; import WakeLock from 'react-wakelock-react16';
import ExtraVideo from './Controls/ExtraVideo'; import ExtraVideo from './Controls/ExtraVideo';
import ButtonControlBar from './Controls/ButtonControlBar';
import Help from './Controls/Help';
import About from './Controls/About';
const TIMEOUT = 5 * 1000; const TIMEOUT = window.config.hideTimeout || 5000;
const styles = (theme) => const styles = (theme) =>
({ ({
@ -42,6 +46,27 @@ const styles = (theme) =>
backgroundSize : 'cover', backgroundSize : 'cover',
backgroundRepeat : 'no-repeat' backgroundRepeat : 'no-repeat'
}, },
drawer :
{
width : '30vw',
flexShrink : 0,
[theme.breakpoints.down('lg')] :
{
width : '40vw'
},
[theme.breakpoints.down('md')] :
{
width : '50vw'
},
[theme.breakpoints.down('sm')] :
{
width : '70vw'
},
[theme.breakpoints.down('xs')] :
{
width : '90vw'
}
},
drawerPaper : drawerPaper :
{ {
width : '30vw', width : '30vw',
@ -142,6 +167,9 @@ class Room extends React.PureComponent
room, room,
browser, browser,
advancedMode, advancedMode,
showNotifications,
buttonControlBar,
drawerOverlayed,
toolAreaOpen, toolAreaOpen,
toggleToolArea, toggleToolArea,
classes, classes,
@ -154,6 +182,8 @@ class Room extends React.PureComponent
democratic : Democratic democratic : Democratic
}[room.mode]; }[room.mode];
const container = window !== undefined ? window.document.body : undefined;
return ( return (
<div className={classes.root}> <div className={classes.root}>
{ !isElectron() && { !isElectron() &&
@ -178,7 +208,9 @@ class Room extends React.PureComponent
<AudioPeers /> <AudioPeers />
<Notifications /> { showNotifications &&
<Notifications />
}
<CssBaseline /> <CssBaseline />
@ -188,22 +220,44 @@ class Room extends React.PureComponent
onFullscreen={this.handleToggleFullscreen} onFullscreen={this.handleToggleFullscreen}
/> />
<nav> { (browser.platform === 'mobile' || drawerOverlayed) ?
<Hidden implementation='css'> <nav>
<SwipeableDrawer <Hidden implementation='css'>
variant='temporary' <SwipeableDrawer
anchor={theme.direction === 'rtl' ? 'right' : 'left'} container={container}
open={toolAreaOpen} variant='temporary'
onClose={() => toggleToolArea()} anchor={theme.direction === 'rtl' ? 'right' : 'left'}
onOpen={() => toggleToolArea()} open={toolAreaOpen}
classes={{ onClose={() => toggleToolArea()}
paper : classes.drawerPaper onOpen={() => toggleToolArea()}
}} classes={{
> paper : classes.drawerPaper
<MeetingDrawer closeDrawer={toggleToolArea} /> }}
</SwipeableDrawer> ModalProps={{
</Hidden> keepMounted : true // Better open performance on mobile.
</nav> }}
>
<MeetingDrawer closeDrawer={toggleToolArea} />
</SwipeableDrawer>
</Hidden>
</nav>
:
<nav className={toolAreaOpen ? classes.drawer : null}>
<Hidden implementation='css'>
<Drawer
variant='persistent'
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
open={toolAreaOpen}
onClose={() => toggleToolArea()}
classes={{
paper : classes.drawerPaper
}}
>
<MeetingDrawer closeDrawer={toggleToolArea} />
</Drawer>
</Hidden>
</nav>
}
{ browser.platform === 'mobile' && browser.os !== 'ios' && { browser.platform === 'mobile' && browser.os !== 'ios' &&
<WakeLock /> <WakeLock />
@ -211,6 +265,10 @@ class Room extends React.PureComponent
<View advancedMode={advancedMode} /> <View advancedMode={advancedMode} />
{ buttonControlBar &&
<ButtonControlBar />
}
{ room.lockDialogOpen && { room.lockDialogOpen &&
<LockDialog /> <LockDialog />
} }
@ -222,6 +280,13 @@ class Room extends React.PureComponent
{ room.extraVideoOpen && { room.extraVideoOpen &&
<ExtraVideo /> <ExtraVideo />
} }
{ room.helpOpen &&
<Help />
}
{ room.aboutOpen &&
<About />
}
</div> </div>
); );
} }
@ -232,6 +297,9 @@ Room.propTypes =
room : appPropTypes.Room.isRequired, room : appPropTypes.Room.isRequired,
browser : PropTypes.object.isRequired, browser : PropTypes.object.isRequired,
advancedMode : PropTypes.bool.isRequired, advancedMode : PropTypes.bool.isRequired,
showNotifications : PropTypes.bool.isRequired,
buttonControlBar : PropTypes.bool.isRequired,
drawerOverlayed : PropTypes.bool.isRequired,
toolAreaOpen : PropTypes.bool.isRequired, toolAreaOpen : PropTypes.bool.isRequired,
setToolbarsVisible : PropTypes.func.isRequired, setToolbarsVisible : PropTypes.func.isRequired,
toggleToolArea : PropTypes.func.isRequired, toggleToolArea : PropTypes.func.isRequired,
@ -241,10 +309,13 @@ Room.propTypes =
const mapStateToProps = (state) => const mapStateToProps = (state) =>
({ ({
room : state.room, room : state.room,
browser : state.me.browser, browser : state.me.browser,
advancedMode : state.settings.advancedMode, advancedMode : state.settings.advancedMode,
toolAreaOpen : state.toolarea.toolAreaOpen showNotifications : state.settings.showNotifications,
buttonControlBar : state.settings.buttonControlBar,
drawerOverlayed : state.settings.drawerOverlayed,
toolAreaOpen : state.toolarea.toolAreaOpen
}); });
const mapDispatchToProps = (dispatch) => const mapDispatchToProps = (dispatch) =>
@ -270,6 +341,9 @@ export default connect(
prev.room === next.room && prev.room === next.room &&
prev.me.browser === next.me.browser && prev.me.browser === next.me.browser &&
prev.settings.advancedMode === next.settings.advancedMode && prev.settings.advancedMode === next.settings.advancedMode &&
prev.settings.showNotifications === next.settings.showNotifications &&
prev.settings.buttonControlBar === next.settings.buttonControlBar &&
prev.settings.drawerOverlayed === next.settings.drawerOverlayed &&
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen
); );
} }

View File

@ -1,5 +1,8 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
const meRolesSelect = (state) => state.me.roles;
const roomPermissionsSelect = (state) => state.room.roomPermissions;
const roomAllowWhenRoleMissing = (state) => state.room.allowWhenRoleMissing;
const producersSelect = (state) => state.producers; const producersSelect = (state) => state.producers;
const consumersSelect = (state) => state.consumers; const consumersSelect = (state) => state.consumers;
const spotlightsSelector = (state) => state.room.spotlights; const spotlightsSelector = (state) => state.room.spotlights;
@ -12,7 +15,8 @@ const peersKeySelector = createSelector(
peersSelector, peersSelector,
(peers) => Object.keys(peers) (peers) => Object.keys(peers)
); );
const peersValueSelector = createSelector(
export const peersValueSelector = createSelector(
peersSelector, peersSelector,
(peers) => Object.values(peers) (peers) => Object.values(peers)
); );
@ -113,8 +117,31 @@ export const spotlightPeersSelector = createSelector(
export const spotlightSortedPeersSelector = createSelector( export const spotlightSortedPeersSelector = createSelector(
spotlightsSelector, spotlightsSelector,
peersValueSelector, peersValueSelector,
(spotlights, peers) => peers.filter((peer) => spotlights.includes(peer.id)) (spotlights, peers) =>
.sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || ''))) peers.filter((peer) => spotlights.includes(peer.id) && !peer.raisedHand)
.sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || '')))
);
const raisedHandSortedPeers = createSelector(
peersValueSelector,
(peers) => peers.filter((peer) => peer.raisedHand)
.sort((a, b) => a.raisedHandTimestamp - b.raisedHandTimestamp)
);
const peersSortedSelector = createSelector(
spotlightsSelector,
peersValueSelector,
(spotlights, peers) =>
peers.filter((peer) => !spotlights.includes(peer.id) && !peer.raisedHand)
.sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || '')))
);
export const participantListSelector = createSelector(
raisedHandSortedPeers,
spotlightSortedPeersSelector,
peersSortedSelector,
(raisedHands, spotlights, peers) =>
[ ...raisedHands, ...spotlights, ...peers ]
); );
export const peersLengthSelector = createSelector( export const peersLengthSelector = createSelector(
@ -193,3 +220,53 @@ export const makePeerConsumerSelector = () =>
} }
); );
}; };
// Very important that the Components that use this
// selector need to check at least these state changes:
//
// areStatesEqual : (next, prev) =>
// {
// return (
// prev.room.roomPermissions === next.room.roomPermissions &&
// prev.room.allowWhenRoleMissing === next.room.allowWhenRoleMissing &&
// prev.peers === next.peers &&
// prev.me.roles === next.me.roles
// );
// }
export const makePermissionSelector = (permission) =>
{
return createSelector(
meRolesSelect,
roomPermissionsSelect,
roomAllowWhenRoleMissing,
peersValueSelector,
(roles, roomPermissions, allowWhenRoleMissing, peers) =>
{
if (!roomPermissions)
return false;
const permitted = roles.some((role) =>
roomPermissions[permission].includes(role)
);
if (permitted)
return true;
if (!allowWhenRoleMissing)
return false;
// Allow if config is set, and no one is present
if (allowWhenRoleMissing.includes(permission) &&
peers.filter(
(peer) =>
peer.roles.some(
(role) => roomPermissions[permission].includes(role)
)
).length === 0
)
return true;
return false;
}
);
};

View File

@ -26,10 +26,14 @@ const styles = (theme) =>
}); });
const AppearenceSettings = ({ const AppearenceSettings = ({
isMobile,
room, room,
settings, settings,
onTogglePermanentTopBar, onTogglePermanentTopBar,
onToggleHiddenControls, onToggleHiddenControls,
onToggleButtonControlBar,
onToggleShowNotifications,
onToggleDrawerOverlayed,
handleChangeMode, handleChangeMode,
classes classes
}) => }) =>
@ -101,30 +105,64 @@ const AppearenceSettings = ({
defaultMessage : 'Hidden media controls' defaultMessage : 'Hidden media controls'
})} })}
/> />
<FormControlLabel
className={classes.setting}
control={<Checkbox checked={settings.buttonControlBar} onChange={onToggleButtonControlBar} value='buttonControlBar' />}
label={intl.formatMessage({
id : 'settings.buttonControlBar',
defaultMessage : 'Separate media controls'
})}
/>
{ !isMobile &&
<FormControlLabel
className={classes.setting}
control={<Checkbox checked={settings.drawerOverlayed} onChange={onToggleDrawerOverlayed} value='drawerOverlayed' />}
label={intl.formatMessage({
id : 'settings.drawerOverlayed',
defaultMessage : 'Side drawer over content'
})}
/>
}
<FormControlLabel
className={classes.setting}
control={<Checkbox checked={settings.showNotifications} onChange={onToggleShowNotifications} value='showNotifications' />}
label={intl.formatMessage({
id : 'settings.showNotifications',
defaultMessage : 'Show notifications'
})}
/>
</React.Fragment> </React.Fragment>
); );
}; };
AppearenceSettings.propTypes = AppearenceSettings.propTypes =
{ {
room : appPropTypes.Room.isRequired, isMobile : PropTypes.bool.isRequired,
settings : PropTypes.object.isRequired, room : appPropTypes.Room.isRequired,
onTogglePermanentTopBar : PropTypes.func.isRequired, settings : PropTypes.object.isRequired,
onToggleHiddenControls : PropTypes.func.isRequired, onTogglePermanentTopBar : PropTypes.func.isRequired,
handleChangeMode : PropTypes.func.isRequired, onToggleHiddenControls : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired onToggleButtonControlBar : PropTypes.func.isRequired,
onToggleShowNotifications : PropTypes.func.isRequired,
onToggleDrawerOverlayed : PropTypes.func.isRequired,
handleChangeMode : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => const mapStateToProps = (state) =>
({ ({
isMobile : state.me.browser.platform === 'mobile',
room : state.room, room : state.room,
settings : state.settings settings : state.settings
}); });
const mapDispatchToProps = { const mapDispatchToProps = {
onTogglePermanentTopBar : settingsActions.togglePermanentTopBar, onTogglePermanentTopBar : settingsActions.togglePermanentTopBar,
onToggleHiddenControls : settingsActions.toggleHiddenControls, onToggleHiddenControls : settingsActions.toggleHiddenControls,
handleChangeMode : roomActions.setDisplayMode onToggleShowNotifications : settingsActions.toggleShowNotifications,
onToggleButtonControlBar : settingsActions.toggleButtonControlBar,
onToggleDrawerOverlayed : settingsActions.toggleDrawerOverlayed,
handleChangeMode : roomActions.setDisplayMode
}; };
export default connect( export default connect(
@ -135,6 +173,7 @@ export default connect(
areStatesEqual : (next, prev) => areStatesEqual : (next, prev) =>
{ {
return ( return (
prev.me.browser === next.me.browser &&
prev.room === next.room && prev.room === next.room &&
prev.settings === next.settings prev.settings === next.settings
); );

View File

@ -3,12 +3,15 @@ import { connect } from 'react-redux';
import * as appPropTypes from '../appPropTypes'; import * as appPropTypes from '../appPropTypes';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../RoomContext'; import { withRoomContext } from '../../RoomContext';
import * as settingsActions from '../../actions/settingsActions';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useIntl, FormattedMessage } from 'react-intl'; import { useIntl, FormattedMessage } from 'react-intl';
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText'; import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl'; import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Select from '@material-ui/core/Select'; import Select from '@material-ui/core/Select';
import Checkbox from '@material-ui/core/Checkbox';
const styles = (theme) => const styles = (theme) =>
({ ({
@ -23,6 +26,9 @@ const styles = (theme) =>
}); });
const MediaSettings = ({ const MediaSettings = ({
setEchoCancellation,
setAutoGainControl,
setNoiseSuppression,
roomClient, roomClient,
me, me,
settings, settings,
@ -247,6 +253,51 @@ const MediaSettings = ({
/> />
</FormHelperText> </FormHelperText>
</FormControl> </FormControl>
<FormControlLabel
className={classes.setting}
control={
<Checkbox checked={settings.echoCancellation} onChange={
(event) =>
{
setEchoCancellation(event.target.checked);
roomClient.changeAudioDevice(settings.selectedAudioDevice);
}}
/>}
label={intl.formatMessage({
id : 'settings.echoCancellation',
defaultMessage : 'Echo cancellation'
})}
/>
<FormControlLabel
className={classes.setting}
control={
<Checkbox checked={settings.autoGainControl} onChange={
(event) =>
{
setAutoGainControl(event.target.checked);
roomClient.changeAudioDevice(settings.selectedAudioDevice);
}}
/>}
label={intl.formatMessage({
id : 'settings.autoGainControl',
defaultMessage : 'Auto gain control'
})}
/>
<FormControlLabel
className={classes.setting}
control={
<Checkbox checked={settings.noiseSuppression} onChange={
(event) =>
{
setNoiseSuppression(event.target.checked);
roomClient.changeAudioDevice(settings.selectedAudioDevice);
}}
/>}
label={intl.formatMessage({
id : 'settings.noiseSuppression',
defaultMessage : 'Noise suppression'
})}
/>
</form> </form>
</React.Fragment> </React.Fragment>
); );
@ -254,10 +305,13 @@ const MediaSettings = ({
MediaSettings.propTypes = MediaSettings.propTypes =
{ {
roomClient : PropTypes.any.isRequired, roomClient : PropTypes.any.isRequired,
me : appPropTypes.Me.isRequired, setEchoCancellation : PropTypes.func.isRequired,
settings : PropTypes.object.isRequired, setAutoGainControl : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired setNoiseSuppression : PropTypes.func.isRequired,
me : appPropTypes.Me.isRequired,
settings : PropTypes.object.isRequired,
classes : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => const mapStateToProps = (state) =>
@ -268,9 +322,15 @@ const mapStateToProps = (state) =>
}; };
}; };
const mapDispatchToProps = {
setEchoCancellation : settingsActions.setEchoCancellation,
setAutoGainControl : settingsActions.toggleAutoGainControl,
setNoiseSuppression : settingsActions.toggleNoiseSuppression
};
export default withRoomContext(connect( export default withRoomContext(connect(
mapStateToProps, mapStateToProps,
null, mapDispatchToProps,
null, null,
{ {
areStatesEqual : (next, prev) => areStatesEqual : (next, prev) =>

View File

@ -95,7 +95,7 @@ const Settings = ({
/> />
<Tab <Tab
label={intl.formatMessage({ label={intl.formatMessage({
id : 'label.appearence', id : 'label.appearance',
defaultMessage : 'Appearence' defaultMessage : 'Appearence'
})} })}
/> />

View File

@ -4,13 +4,12 @@ import classnames from 'classnames';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import EditableInput from '../Controls/EditableInput'; import EditableInput from '../Controls/EditableInput';
import Logger from '../../Logger'; import Logger from '../../Logger';
import { green, yellow, orange, red } from '@material-ui/core/colors'; import { yellow, orange, red } from '@material-ui/core/colors';
import SignalCellularOffIcon from '@material-ui/icons/SignalCellularOff'; import SignalCellularOffIcon from '@material-ui/icons/SignalCellularOff';
import SignalCellular0BarIcon from '@material-ui/icons/SignalCellular0Bar'; import SignalCellular0BarIcon from '@material-ui/icons/SignalCellular0Bar';
import SignalCellular1BarIcon from '@material-ui/icons/SignalCellular1Bar'; import SignalCellular1BarIcon from '@material-ui/icons/SignalCellular1Bar';
import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar'; import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar';
import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar'; import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar';
import SignalCellularAltIcon from '@material-ui/icons/SignalCellularAlt';
const logger = new Logger('VideoView'); const logger = new Logger('VideoView');
@ -153,6 +152,7 @@ class VideoView extends React.PureComponent
{ {
const { const {
isMe, isMe,
showQuality,
isScreen, isScreen,
displayName, displayName,
showPeerInfo, showPeerInfo,
@ -162,8 +162,6 @@ class VideoView extends React.PureComponent
videoMultiLayer, videoMultiLayer,
audioScore, audioScore,
videoScore, videoScore,
// consumerSpatialLayers,
// consumerTemporalLayers,
consumerCurrentSpatialLayer, consumerCurrentSpatialLayer,
consumerCurrentTemporalLayer, consumerCurrentTemporalLayer,
consumerPreferredSpatialLayer, consumerPreferredSpatialLayer,
@ -180,58 +178,63 @@ class VideoView extends React.PureComponent
videoHeight videoHeight
} = this.state; } = this.state;
let quality = <SignalCellularOffIcon style={{ color: red[500] }}/>; let quality = null;
if (videoScore || audioScore) if (showQuality)
{ {
const score = videoScore ? videoScore : audioScore; quality = <SignalCellularOffIcon style={{ color: red[500] }}/>;
switch (score.producerScore) if (videoScore || audioScore)
{ {
case 0: const score = videoScore ? videoScore : audioScore;
case 1:
switch (isMe ? score.score : score.producerScore)
{ {
quality = <SignalCellular0BarIcon style={{ color: red[500] }}/>; case 0:
case 1:
break; {
} quality = <SignalCellular0BarIcon style={{ color: red[500] }}/>;
case 2: break;
case 3: }
{
quality = <SignalCellular1BarIcon style={{ color: red[500] }}/>; case 2:
case 3:
break; {
} quality = <SignalCellular1BarIcon style={{ color: red[500] }}/>;
case 4: break;
case 5: }
case 6:
{ case 4:
quality = <SignalCellular2BarIcon style={{ color: orange[500] }}/>; case 5:
case 6:
break; {
} quality = <SignalCellular2BarIcon style={{ color: orange[500] }}/>;
case 7: break;
case 8: }
{
quality = <SignalCellular3BarIcon style={{ color: yellow[500] }}/>; case 7:
case 8:
break; case 9:
} {
quality = <SignalCellular3BarIcon style={{ color: yellow[500] }}/>;
case 9:
case 10: break;
{ }
quality = <SignalCellularAltIcon style={{ color: green[500] }}/>;
case 10:
break; {
} quality = null;
default: break;
{ }
break;
default:
{
break;
}
} }
} }
} }
@ -261,7 +264,7 @@ class VideoView extends React.PureComponent
<p>{videoWidth}x{videoHeight}</p> <p>{videoWidth}x{videoHeight}</p>
} }
</div> </div>
{ !isMe && { showQuality &&
<div className={classnames(classes.box, 'right')}> <div className={classnames(classes.box, 'right')}>
{ {
quality quality
@ -441,6 +444,7 @@ class VideoView extends React.PureComponent
VideoView.propTypes = VideoView.propTypes =
{ {
isMe : PropTypes.bool, isMe : PropTypes.bool,
showQuality : PropTypes.bool,
isScreen : PropTypes.bool, isScreen : PropTypes.bool,
displayName : PropTypes.string, displayName : PropTypes.string,
showPeerInfo : PropTypes.bool, showPeerInfo : PropTypes.bool,

View File

@ -38,6 +38,7 @@ import messagesCzech from './translations/cs';
import messagesItalian from './translations/it'; import messagesItalian from './translations/it';
import messagesUkrainian from './translations/uk'; import messagesUkrainian from './translations/uk';
import messagesTurkish from './translations/tr'; import messagesTurkish from './translations/tr';
import messagesLatvian from './translations/lv';
import './index.css'; import './index.css';
@ -63,7 +64,8 @@ const messages =
'cs' : messagesCzech, 'cs' : messagesCzech,
'it' : messagesItalian, 'it' : messagesItalian,
'uk' : messagesUkrainian, 'uk' : messagesUkrainian,
'tr' : messagesTurkish 'tr' : messagesTurkish,
'lv' : messagesLatvian
}; };
const locale = navigator.language.split(/[-_]/)[0]; // language without region code const locale = navigator.language.split(/[-_]/)[0]; // language without region code
@ -113,6 +115,13 @@ function run()
const forceTcp = parameters.get('forceTcp') === 'true'; const forceTcp = parameters.get('forceTcp') === 'true';
const displayName = parameters.get('displayName'); const displayName = parameters.get('displayName');
const muted = parameters.get('muted') === 'true'; const muted = parameters.get('muted') === 'true';
const { pathname } = window.location;
let basePath = pathname.substring(0, pathname.lastIndexOf('/'));
if (!basePath)
basePath = '/';
// Get current device. // Get current device.
const device = deviceInfo(); const device = deviceInfo();
@ -132,7 +141,8 @@ function run()
produce, produce,
forceTcp, forceTcp,
displayName, displayName,
muted muted,
basePath
}); });
global.CLIENT = roomClient; global.CLIENT = roomClient;
@ -144,7 +154,7 @@ function run()
<PersistGate loading={<LoadingView />} persistor={persistor}> <PersistGate loading={<LoadingView />} persistor={persistor}>
<RoomContext.Provider value={roomClient}> <RoomContext.Provider value={roomClient}>
<SnackbarProvider> <SnackbarProvider>
<Router> <Router basename={basePath}>
<Suspense fallback={<LoadingView />}> <Suspense fallback={<LoadingView />}>
<React.Fragment> <React.Fragment>
<Route exact path='/' component={ChooseRoom} /> <Route exact path='/' component={ChooseRoom} />

View File

@ -0,0 +1,20 @@
export const permissions = {
// The role(s) have permission to lock/unlock a room
CHANGE_ROOM_LOCK : 'CHANGE_ROOM_LOCK',
// The role(s) have permission to promote a peer from the lobby
PROMOTE_PEER : 'PROMOTE_PEER',
// The role(s) have permission to send chat messages
SEND_CHAT : 'SEND_CHAT',
// The role(s) have permission to moderate chat
MODERATE_CHAT : 'MODERATE_CHAT',
// The role(s) have permission to share screen
SHARE_SCREEN : 'SHARE_SCREEN',
// The role(s) have permission to produce extra video
EXTRA_VIDEO : 'EXTRA_VIDEO',
// The role(s) have permission to share files
SHARE_FILE : 'SHARE_FILE',
// The role(s) have permission to moderate files
MODERATE_FILES : 'MODERATE_FILES',
// The role(s) have permission to moderate room (e.g. kick user)
MODERATE_ROOM : 'MODERATE_ROOM'
};

View File

@ -110,6 +110,11 @@ const consumers = (state = initialState, action) =>
return { ...state, [consumerId]: newConsumer }; return { ...state, [consumerId]: newConsumer };
} }
case 'CLEAR_CONSUMERS':
{
return initialState;
}
default: default:
return state; return state;
} }

View File

@ -1,4 +1,6 @@
const peer = (state = {}, action) => const initialState = {};
const peer = (state = initialState, action) =>
{ {
switch (action.type) switch (action.type)
{ {
@ -26,6 +28,12 @@ const peer = (state = {}, action) =>
raisedHand : action.payload.raisedHand, raisedHand : action.payload.raisedHand,
raisedHandTimestamp : action.payload.raisedHandTimestamp raisedHandTimestamp : action.payload.raisedHandTimestamp
}; };
case 'SET_PEER_RAISED_HAND_IN_PROGRESS':
return {
...state,
raisedHandInProgress : action.payload.flag
};
case 'ADD_CONSUMER': case 'ADD_CONSUMER':
{ {
@ -62,12 +70,24 @@ const peer = (state = {}, action) =>
return { ...state, roles }; return { ...state, roles };
} }
case 'STOP_PEER_AUDIO_IN_PROGRESS':
return {
...state,
stopPeerAudioInProgress : action.payload.flag
};
case 'STOP_PEER_VIDEO_IN_PROGRESS':
return {
...state,
stopPeerVideoInProgress : action.payload.flag
};
default: default:
return state; return state;
} }
}; };
const peers = (state = {}, action) => const peers = (state = initialState, action) =>
{ {
switch (action.type) switch (action.type)
{ {
@ -91,10 +111,13 @@ const peers = (state = {}, action) =>
case 'SET_PEER_AUDIO_IN_PROGRESS': case 'SET_PEER_AUDIO_IN_PROGRESS':
case 'SET_PEER_SCREEN_IN_PROGRESS': case 'SET_PEER_SCREEN_IN_PROGRESS':
case 'SET_PEER_RAISED_HAND': case 'SET_PEER_RAISED_HAND':
case 'SET_PEER_RAISED_HAND_IN_PROGRESS':
case 'SET_PEER_PICTURE': case 'SET_PEER_PICTURE':
case 'ADD_CONSUMER': case 'ADD_CONSUMER':
case 'ADD_PEER_ROLE': case 'ADD_PEER_ROLE':
case 'REMOVE_PEER_ROLE': case 'REMOVE_PEER_ROLE':
case 'STOP_PEER_AUDIO_IN_PROGRESS':
case 'STOP_PEER_VIDEO_IN_PROGRESS':
{ {
const oldPeer = state[action.payload.peerId]; const oldPeer = state[action.payload.peerId];
@ -118,6 +141,11 @@ const peers = (state = {}, action) =>
return { ...state, [oldPeer.id]: peer(oldPeer, action) }; return { ...state, [oldPeer.id]: peer(oldPeer, action) };
} }
case 'CLEAR_PEERS':
{
return initialState;
}
default: default:
return state; return state;
} }

View File

@ -60,6 +60,17 @@ const producers = (state = initialState, action) =>
return { ...state, [producerId]: newProducer }; return { ...state, [producerId]: newProducer };
} }
case 'SET_PRODUCER_SCORE':
{
const { producerId, score } = action.payload;
const producer = state[producerId];
const newProducer = { ...producer, score };
return { ...state, [producerId]: newProducer };
}
default: default:
return state; return state;
} }

View File

@ -6,6 +6,7 @@ const initialState =
locked : false, locked : false,
inLobby : false, inLobby : false,
signInRequired : false, signInRequired : false,
overRoomLimit : false,
// access code to the room if locked and joinByAccessCode == true // access code to the room if locked and joinByAccessCode == true
accessCode : '', accessCode : '',
// if true: accessCode is a possibility to open the room // if true: accessCode is a possibility to open the room
@ -21,6 +22,8 @@ const initialState =
spotlights : [], spotlights : [],
settingsOpen : false, settingsOpen : false,
extraVideoOpen : false, extraVideoOpen : false,
helpOpen : false,
aboutOpen : false,
currentSettingsTab : 'media', // media, appearence, advanced currentSettingsTab : 'media', // media, appearence, advanced
lockDialogOpen : false, lockDialogOpen : false,
joined : false, joined : false,
@ -30,18 +33,8 @@ const initialState =
closeMeetingInProgress : false, closeMeetingInProgress : false,
clearChatInProgress : false, clearChatInProgress : false,
clearFileSharingInProgress : false, clearFileSharingInProgress : false,
userRoles : { NORMAL: 'normal' }, // Default role roomPermissions : null,
permissionsFromRoles : { allowWhenRoleMissing : null
CHANGE_ROOM_LOCK : [],
PROMOTE_PEER : [],
SEND_CHAT : [],
MODERATE_CHAT : [],
SHARE_SCREEN : [],
EXTRA_VIDEO : [],
SHARE_FILE : [],
MODERATE_FILES : [],
MODERATE_ROOM : []
}
}; };
const room = (state = initialState, action) => const room = (state = initialState, action) =>
@ -88,7 +81,12 @@ const room = (state = initialState, action) =>
return { ...state, signInRequired }; return { ...state, signInRequired };
} }
case 'SET_OVER_ROOM_LIMIT':
{
const { overRoomLimit } = action.payload;
return { ...state, overRoomLimit };
}
case 'SET_ACCESS_CODE': case 'SET_ACCESS_CODE':
{ {
const { accessCode } = action.payload; const { accessCode } = action.payload;
@ -124,6 +122,20 @@ const room = (state = initialState, action) =>
return { ...state, extraVideoOpen }; return { ...state, extraVideoOpen };
} }
case 'SET_HELP_OPEN':
{
const { helpOpen } = action.payload;
return { ...state, helpOpen };
}
case 'SET_ABOUT_OPEN':
{
const { aboutOpen } = action.payload;
return { ...state, aboutOpen };
}
case 'SET_SETTINGS_TAB': case 'SET_SETTINGS_TAB':
{ {
const { tab } = action.payload; const { tab } = action.payload;
@ -200,6 +212,11 @@ const room = (state = initialState, action) =>
return { ...state, spotlights }; return { ...state, spotlights };
} }
case 'CLEAR_SPOTLIGHTS':
{
return { ...state, spotlights: [] };
}
case 'SET_LOBBY_PEERS_PROMOTION_IN_PROGRESS': case 'SET_LOBBY_PEERS_PROMOTION_IN_PROGRESS':
return { ...state, lobbyPeersPromotionInProgress: action.payload.flag }; return { ...state, lobbyPeersPromotionInProgress: action.payload.flag };
@ -218,18 +235,18 @@ const room = (state = initialState, action) =>
case 'CLEAR_FILE_SHARING_IN_PROGRESS': case 'CLEAR_FILE_SHARING_IN_PROGRESS':
return { ...state, clearFileSharingInProgress: action.payload.flag }; return { ...state, clearFileSharingInProgress: action.payload.flag };
case 'SET_USER_ROLES': case 'SET_ROOM_PERMISSIONS':
{ {
const { userRoles } = action.payload; const { roomPermissions } = action.payload;
return { ...state, userRoles }; return { ...state, roomPermissions };
} }
case 'SET_PERMISSIONS_FROM_ROLES': case 'SET_ALLOW_WHEN_ROLE_MISSING':
{ {
const { permissionsFromRoles } = action.payload; const { allowWhenRoleMissing } = action.payload;
return { ...state, permissionsFromRoles }; return { ...state, allowWhenRoleMissing };
} }
default: default:

View File

@ -4,12 +4,23 @@ const initialState =
selectedWebcam : null, selectedWebcam : null,
selectedAudioDevice : null, selectedAudioDevice : null,
advancedMode : false, advancedMode : false,
sampleRate : 48000,
channelCount : 1,
volume : 1.0,
autoGainControl : true,
echoCancellation : true,
noiseSuppression : true,
sampleSize : 16,
// low, medium, high, veryhigh, ultra // low, medium, high, veryhigh, ultra
resolution : window.config.defaultResolution || 'medium', resolution : window.config.defaultResolution || 'medium',
lastN : 4, lastN : 4,
permanentTopBar : true, permanentTopBar : true,
hiddenControls : false, hiddenControls : false,
notificationSounds : true showNotifications : true,
notificationSounds : true,
buttonControlBar : window.config.buttonControlBar || false,
drawerOverlayed : window.config.drawerOverlayed || true,
...window.config.defaultAudio
}; };
const settings = (state = initialState, action) => const settings = (state = initialState, action) =>
@ -45,6 +56,83 @@ const settings = (state = initialState, action) =>
return { ...state, advancedMode }; return { ...state, advancedMode };
} }
case 'SET_SAMPLE_RATE':
{
const { sampleRate } = action.payload;
return { ...state, sampleRate };
}
case 'SET_CHANNEL_COUNT':
{
const { channelCount } = action.payload;
return { ...state, channelCount };
}
case 'SET_VOLUME':
{
const { volume } = action.payload;
return { ...state, volume };
}
case 'SET_AUTO_GAIN_CONTROL':
{
const { autoGainControl } = action.payload;
return { ...state, autoGainControl };
}
case 'SET_ECHO_CANCELLATION':
{
const { echoCancellation } = action.payload;
return { ...state, echoCancellation };
}
case 'SET_NOISE_SUPPRESSION':
{
const { noiseSuppression } = action.payload;
return { ...state, noiseSuppression };
}
case 'SET_DEFAULT_AUDIO':
{
const { audio } = action.payload;
return { ...state, audio };
}
case 'TOGGLE_AUTO_GAIN_CONTROL':
{
const autoGainControl = !state.autoGainControl;
return { ...state, autoGainControl };
}
case 'TOGGLE_ECHO_CANCELLATION':
{
const echoCancellation = !state.echoCancellation;
return { ...state, echoCancellation };
}
case 'TOGGLE_NOISE_SUPPRESSION':
{
const noiseSuppression = !state.noiseSuppression;
return { ...state, noiseSuppression };
}
case 'SET_SAMPLE_SIZE':
{
const { sampleSize } = action.payload;
return { ...state, sampleSize };
}
case 'SET_LAST_N': case 'SET_LAST_N':
{ {
const { lastN } = action.payload; const { lastN } = action.payload;
@ -59,6 +147,20 @@ const settings = (state = initialState, action) =>
return { ...state, permanentTopBar }; return { ...state, permanentTopBar };
} }
case 'TOGGLE_BUTTON_CONTROL_BAR':
{
const buttonControlBar = !state.buttonControlBar;
return { ...state, buttonControlBar };
}
case 'TOGGLE_DRAWER_OVERLAYED':
{
const drawerOverlayed = !state.drawerOverlayed;
return { ...state, drawerOverlayed };
}
case 'TOGGLE_HIDDEN_CONTROLS': case 'TOGGLE_HIDDEN_CONTROLS':
{ {
const hiddenControls = !state.hiddenControls; const hiddenControls = !state.hiddenControls;
@ -73,6 +175,13 @@ const settings = (state = initialState, action) =>
return { ...state, notificationSounds }; return { ...state, notificationSounds };
} }
case 'TOGGLE_SHOW_NOTIFICATIONS':
{
const showNotifications = !state.showNotifications;
return { ...state, showNotifications };
}
case 'SET_VIDEO_RESOLUTION': case 'SET_VIDEO_RESOLUTION':
{ {
const { resolution } = action.payload; const { resolution } = action.payload;

View File

@ -59,7 +59,11 @@
"room.raisedHand": null, "room.raisedHand": null,
"room.loweredHand": null, "room.loweredHand": null,
"room.extraVideo": null, "room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"me.mutedPTT": null, "me.mutedPTT": null,
"roles.gotRole": null, "roles.gotRole": null,
@ -79,6 +83,10 @@
"tooltip.muteParticipant": null, "tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null, "tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "房间名称", "label.roomName": "房间名称",
"label.chooseRoomButton": "继续", "label.chooseRoomButton": "继续",
@ -92,6 +100,7 @@
"label.filesharing": "文件共享", "label.filesharing": "文件共享",
"label.participants": "参与者", "label.participants": "参与者",
"label.shareFile": "共享文件", "label.shareFile": "共享文件",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "不支持文件共享", "label.fileSharingUnsupported": "不支持文件共享",
"label.unknown": "未知", "label.unknown": "未知",
"label.democratic": "民主视图", "label.democratic": "民主视图",
@ -103,10 +112,11 @@
"label.ultra": "超高 (UHD)", "label.ultra": "超高 (UHD)",
"label.close": "关闭", "label.close": "关闭",
"label.media": null, "label.media": null,
"label.appearence": null, "label.appearance": null,
"label.advanced": null, "label.advanced": null,
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null,
"settings.settings": "设置", "settings.settings": "设置",
"settings.camera": "视频设备", "settings.camera": "视频设备",
@ -126,6 +136,12 @@
"settings.lastn": "可见视频数量", "settings.lastn": "可见视频数量",
"settings.hiddenControls": null, "settings.hiddenControls": null,
"settings.notificationSounds": null, "settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"filesharing.saveFileError": "无法保存文件", "filesharing.saveFileError": "无法保存文件",
"filesharing.startingFileShare": "正在尝试共享文件", "filesharing.startingFileShare": "正在尝试共享文件",

View File

@ -58,6 +58,10 @@
"room.raisedHand": null, "room.raisedHand": null,
"room.loweredHand": null, "room.loweredHand": null,
"room.extraVideo": null, "room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -78,6 +82,10 @@
"tooltip.muteParticipant": null, "tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null, "tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Jméno místnosti", "label.roomName": "Jméno místnosti",
"label.chooseRoomButton": "Pokračovat", "label.chooseRoomButton": "Pokračovat",
@ -91,6 +99,7 @@
"label.filesharing": "Sdílení souborů", "label.filesharing": "Sdílení souborů",
"label.participants": "Účastníci", "label.participants": "Účastníci",
"label.shareFile": "Sdílet soubor", "label.shareFile": "Sdílet soubor",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Sdílení souborů není podporováno", "label.fileSharingUnsupported": "Sdílení souborů není podporováno",
"label.unknown": "Neznámý", "label.unknown": "Neznámý",
"label.democratic": "Rozvržení: Demokratické", "label.democratic": "Rozvržení: Demokratické",
@ -102,10 +111,11 @@
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Zavřít", "label.close": "Zavřít",
"label.media": null, "label.media": null,
"label.appearence": null, "label.appearance": null,
"label.advanced": null, "label.advanced": null,
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null,
"settings.settings": "Nastavení", "settings.settings": "Nastavení",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -125,6 +135,12 @@
"settings.lastn": null, "settings.lastn": null,
"settings.hiddenControls": null, "settings.hiddenControls": null,
"settings.notificationSounds": null, "settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"filesharing.saveFileError": "Není možné uložit soubor", "filesharing.saveFileError": "Není možné uložit soubor",
"filesharing.startingFileShare": "Pokouším se sdílet soubor", "filesharing.startingFileShare": "Pokouším se sdílet soubor",

View File

@ -51,19 +51,23 @@
"room.videoPaused": "Video gestoppt", "room.videoPaused": "Video gestoppt",
"room.muteAll": "Alle stummschalten", "room.muteAll": "Alle stummschalten",
"room.stopAllVideo": "Alle Videos stoppen", "room.stopAllVideo": "Alle Videos stoppen",
"room.closeMeeting": "Meeting schließen", "room.closeMeeting": "Meeting beenden",
"room.clearChat": null, "room.clearChat": "Liste löschen",
"room.clearFileSharing": null, "room.clearFileSharing": "Liste löschen",
"room.speechUnsupported": "Dein Browser unterstützt keine Spracherkennung", "room.speechUnsupported": "Dein Browser unterstützt keine Spracherkennung",
"room.moderatoractions": null, "room.moderatoractions": "Moderator Aktionen",
"room.raisedHand": null, "room.raisedHand": "{displayName} hebt die Hand",
"room.loweredHand": null, "room.loweredHand": "{displayName} senkt die Hand",
"room.extraVideo": null, "room.extraVideo": "Video hinzufügen",
"room.overRoomLimit": "Der Raum ist voll, probiere es später nochmal",
"room.help": "Hilfe",
"room.about": "Über",
"room.shortcutKeys": "Tastaturkürzel",
"me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen", "me.mutedPTT": "Du bist stummgeschaltet. Halte die SPACE-Taste um zu sprechen",
"roles.gotRole": null, "roles.gotRole": "Rolle erhalten: {role}",
"roles.lostRole": null, "roles.lostRole": "Rolle entzogen: {role}",
"tooltip.login": "Anmelden", "tooltip.login": "Anmelden",
"tooltip.logout": "Abmelden", "tooltip.logout": "Abmelden",
@ -75,10 +79,14 @@
"tooltip.lobby": "Warteraum", "tooltip.lobby": "Warteraum",
"tooltip.settings": "Einstellungen", "tooltip.settings": "Einstellungen",
"tooltip.participants": "Teilnehmer", "tooltip.participants": "Teilnehmer",
"tooltip.kickParticipant": "Teilnehmer rauswerfen", "tooltip.kickParticipant": "Rauswerfen",
"tooltip.muteParticipant": null, "tooltip.muteParticipant": "Stummschalten",
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipantVideo": "Video stoppen",
"tooltip.raisedHand": null, "tooltip.raisedHand": "Hand heben",
"tooltip.muteScreenSharing": "Stoppe Bildschirmfreigabe",
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Name des Raums", "label.roomName": "Name des Raums",
"label.chooseRoomButton": "Weiter", "label.chooseRoomButton": "Weiter",
@ -92,21 +100,23 @@
"label.filesharing": "Dateien", "label.filesharing": "Dateien",
"label.participants": "Teilnehmer", "label.participants": "Teilnehmer",
"label.shareFile": "Datei hochladen", "label.shareFile": "Datei hochladen",
"label.shareGalleryFile": "Bild teilen",
"label.fileSharingUnsupported": "Dateifreigabe nicht unterstützt", "label.fileSharingUnsupported": "Dateifreigabe nicht unterstützt",
"label.unknown": "Unbekannt", "label.unknown": "Unbekannt",
"label.democratic": "Demokratisch", "label.democratic": "Demokratisch",
"label.filmstrip": "Filmstreifen", "label.filmstrip": "Filmstreifen",
"label.low": "Niedrig", "label.low": "Niedrig",
"label.medium": "Medium", "label.medium": "Mittel",
"label.high": "Hoch (HD)", "label.high": "Hoch (HD)",
"label.veryHigh": "Sehr hoch (FHD)", "label.veryHigh": "Sehr hoch (FHD)",
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Schließen", "label.close": "Schließen",
"label.media": null, "label.media": "Audio / Video",
"label.appearence": null, "label.appearance": "Ansicht",
"label.advanced": null, "label.advanced": "Erweitert",
"label.addVideo": null, "label.addVideo": "Video hinzufügen",
"label.promoteAllPeers": null, "label.promoteAllPeers": "Alle Teilnehmer reinlassen",
"label.moreActions": "Weitere Aktionen",
"settings.settings": "Einstellungen", "settings.settings": "Einstellungen",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -124,8 +134,14 @@
"settings.advancedMode": "Erweiterter Modus", "settings.advancedMode": "Erweiterter Modus",
"settings.permanentTopBar": "Permanente obere Leiste", "settings.permanentTopBar": "Permanente obere Leiste",
"settings.lastn": "Anzahl der sichtbaren Videos", "settings.lastn": "Anzahl der sichtbaren Videos",
"settings.hiddenControls": null, "settings.hiddenControls": "Medienwerkzeugleiste automatisch ausblenden",
"settings.notificationSounds": null, "settings.notificationSounds": "Audiosignal bei Benachrichtigungen",
"settings.showNotifications": "Zeige Benachrichtigungen",
"settings.buttonControlBar": "Separate seitliche Medienwerkzeugleiste",
"settings.echoCancellation": "Echounterdrückung",
"settings.autoGainControl": "Automatische Pegelregelung (Audioeingang)",
"settings.noiseSuppression": "Rauschunterdrückung",
"settings.drawerOverlayed": "Seitenpanel verdeckt Hauptinhalt",
"filesharing.saveFileError": "Fehler beim Speichern der Datei", "filesharing.saveFileError": "Fehler beim Speichern der Datei",
"filesharing.startingFileShare": "Starte Teilen der Datei", "filesharing.startingFileShare": "Starte Teilen der Datei",
@ -167,8 +183,8 @@
"devices.cameraDisconnected": "Kamera getrennt", "devices.cameraDisconnected": "Kamera getrennt",
"devices.cameraError": "Fehler mit deiner Kamera", "devices.cameraError": "Fehler mit deiner Kamera",
"moderator.clearChat": null, "moderator.clearChat": "Moderator hat Chat gelöscht",
"moderator.clearFiles": null, "moderator.clearFiles": "Moderator hat geteilte Dateiliste gelöscht",
"moderator.muteAudio": null, "moderator.muteAudio": "Moderator hat dich stummgeschaltet",
"moderator.muteVideo": null "moderator.muteVideo": "Moderator hat dein Video gestoppt"
} }

View File

@ -59,6 +59,10 @@
"room.raisedHand": null, "room.raisedHand": null,
"room.loweredHand": null, "room.loweredHand": null,
"room.extraVideo": null, "room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -79,6 +83,10 @@
"tooltip.muteParticipant": null, "tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null, "tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Værelsesnavn", "label.roomName": "Værelsesnavn",
"label.chooseRoomButton": "Fortsæt", "label.chooseRoomButton": "Fortsæt",
@ -92,6 +100,7 @@
"label.filesharing": "Fildeling", "label.filesharing": "Fildeling",
"label.participants": "Deltagere", "label.participants": "Deltagere",
"label.shareFile": "Del fil", "label.shareFile": "Del fil",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Fildeling er ikke understøttet", "label.fileSharingUnsupported": "Fildeling er ikke understøttet",
"label.unknown": "Ukendt", "label.unknown": "Ukendt",
"label.democracy": "Galleri visning", "label.democracy": "Galleri visning",
@ -103,10 +112,11 @@
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Luk", "label.close": "Luk",
"label.media": null, "label.media": null,
"label.appearence": null, "label.appearance": null,
"label.advanced": null, "label.advanced": null,
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null,
"settings.settings": "Indstillinger", "settings.settings": "Indstillinger",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -126,6 +136,12 @@
"settings.lastn": "Antal synlige videoer", "settings.lastn": "Antal synlige videoer",
"settings.hiddenControls": null, "settings.hiddenControls": null,
"settings.notificationSounds": null, "settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"filesharing.saveFileError": "Kan ikke gemme fil", "filesharing.saveFileError": "Kan ikke gemme fil",
"filesharing.startingFileShare": "Forsøger at dele filen", "filesharing.startingFileShare": "Forsøger at dele filen",

View File

@ -59,6 +59,10 @@
"room.raisedHand": null, "room.raisedHand": null,
"room.loweredHand": null, "room.loweredHand": null,
"room.extraVideo": null, "room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -79,6 +83,10 @@
"tooltip.muteParticipant": null, "tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null, "tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Όνομα δωματίου", "label.roomName": "Όνομα δωματίου",
"label.chooseRoomButton": "Συνέχεια", "label.chooseRoomButton": "Συνέχεια",
@ -92,6 +100,7 @@
"label.filesharing": "Διαμοιρασμοός αρχείου", "label.filesharing": "Διαμοιρασμοός αρχείου",
"label.participants": "Συμμετέχοντες", "label.participants": "Συμμετέχοντες",
"label.shareFile": "Διαμοιραστείτε ένα αρχείο", "label.shareFile": "Διαμοιραστείτε ένα αρχείο",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Ο διαμοιρασμός αρχείων δεν υποστηρίζεται", "label.fileSharingUnsupported": "Ο διαμοιρασμός αρχείων δεν υποστηρίζεται",
"label.unknown": "Άγνωστο", "label.unknown": "Άγνωστο",
"label.democratic": null, "label.democratic": null,
@ -103,10 +112,11 @@
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Κλείσιμο", "label.close": "Κλείσιμο",
"label.media": null, "label.media": null,
"label.appearence": null, "label.appearance": null,
"label.advanced": null, "label.advanced": null,
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null,
"settings.settings": "Ρυθμίσεις", "settings.settings": "Ρυθμίσεις",
"settings.camera": "Κάμερα", "settings.camera": "Κάμερα",
@ -126,6 +136,12 @@
"settings.lastn": "Αριθμός ορατών βίντεο", "settings.lastn": "Αριθμός ορατών βίντεο",
"settings.hiddenControls": null, "settings.hiddenControls": null,
"settings.notificationSounds": null, "settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"filesharing.saveFileError": "Αδυναμία αποθήκευσης του αρχείου", "filesharing.saveFileError": "Αδυναμία αποθήκευσης του αρχείου",
"filesharing.startingFileShare": "Προσπάθεια διαμοιρασμού αρχείου", "filesharing.startingFileShare": "Προσπάθεια διαμοιρασμού αρχείου",

View File

@ -59,6 +59,10 @@
"room.raisedHand": "{displayName} raised their hand", "room.raisedHand": "{displayName} raised their hand",
"room.loweredHand": "{displayName} put their hand down", "room.loweredHand": "{displayName} put their hand down",
"room.extraVideo": "Extra video", "room.extraVideo": "Extra video",
"room.overRoomLimit": "The room is full, retry after some time.",
"room.help": "Help",
"room.about": "About",
"room.shortcutKeys": "Shortcut Keys",
"me.mutedPTT": "You are muted, hold down SPACE-BAR to talk", "me.mutedPTT": "You are muted, hold down SPACE-BAR to talk",
@ -79,6 +83,10 @@
"tooltip.muteParticipant": "Mute participant", "tooltip.muteParticipant": "Mute participant",
"tooltip.muteParticipantVideo": "Mute participant video", "tooltip.muteParticipantVideo": "Mute participant video",
"tooltip.raisedHand": "Raise hand", "tooltip.raisedHand": "Raise hand",
"tooltip.muteScreenSharing": "Mute participant share",
"tooltip.muteParticipantAudioModerator": "Mute participant audio globally",
"tooltip.muteParticipantVideoModerator": "Mute participant video globally",
"tooltip.muteScreenSharingModerator": "Mute participant screen share globally",
"label.roomName": "Room name", "label.roomName": "Room name",
"label.chooseRoomButton": "Continue", "label.chooseRoomButton": "Continue",
@ -92,6 +100,7 @@
"label.filesharing": "File sharing", "label.filesharing": "File sharing",
"label.participants": "Participants", "label.participants": "Participants",
"label.shareFile": "Share file", "label.shareFile": "Share file",
"label.shareGalleryFile": "Share image",
"label.fileSharingUnsupported": "File sharing not supported", "label.fileSharingUnsupported": "File sharing not supported",
"label.unknown": "Unknown", "label.unknown": "Unknown",
"label.democratic": "Democratic view", "label.democratic": "Democratic view",
@ -103,10 +112,11 @@
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Close", "label.close": "Close",
"label.media": "Media", "label.media": "Media",
"label.appearence": "Appearence", "label.appearance": "Appearence",
"label.advanced": "Advanced", "label.advanced": "Advanced",
"label.addVideo": "Add video", "label.addVideo": "Add video",
"label.promoteAllPeers": "Promote all", "label.promoteAllPeers": "Promote all",
"label.moreActions": "More actions",
"settings.settings": "Settings", "settings.settings": "Settings",
"settings.camera": "Camera", "settings.camera": "Camera",
@ -126,6 +136,12 @@
"settings.lastn": "Number of visible videos", "settings.lastn": "Number of visible videos",
"settings.hiddenControls": "Hidden media controls", "settings.hiddenControls": "Hidden media controls",
"settings.notificationSounds": "Notification sounds", "settings.notificationSounds": "Notification sounds",
"settings.showNotifications": "Show notifications",
"settings.buttonControlBar": "Separate media controls",
"settings.echoCancellation": "Echo cancellation",
"settings.autoGainControl": "Auto gain control",
"settings.noiseSuppression": "Noise suppression",
"settings.drawerOverlayed": "Side drawer over content",
"filesharing.saveFileError": "Unable to save file", "filesharing.saveFileError": "Unable to save file",
"filesharing.startingFileShare": "Attempting to share file", "filesharing.startingFileShare": "Attempting to share file",

View File

@ -59,6 +59,10 @@
"room.raisedHand": null, "room.raisedHand": null,
"room.loweredHand": null, "room.loweredHand": null,
"room.extraVideo": null, "room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -79,6 +83,10 @@
"tooltip.muteParticipant": null, "tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null, "tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Nombre de la sala", "label.roomName": "Nombre de la sala",
"label.chooseRoomButton": "Continuar", "label.chooseRoomButton": "Continuar",
@ -92,6 +100,7 @@
"label.filesharing": "Compartir ficheros", "label.filesharing": "Compartir ficheros",
"label.participants": "Participantes", "label.participants": "Participantes",
"label.shareFile": "Compartir fichero", "label.shareFile": "Compartir fichero",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Compartir ficheros no está disponible", "label.fileSharingUnsupported": "Compartir ficheros no está disponible",
"label.unknown": "Desconocido", "label.unknown": "Desconocido",
"label.democratic": "Vista democrática", "label.democratic": "Vista democrática",
@ -103,10 +112,11 @@
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Cerrar", "label.close": "Cerrar",
"label.media": null, "label.media": null,
"label.appearence": null, "label.appearance": null,
"label.advanced": null, "label.advanced": null,
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null,
"settings.settings": "Ajustes", "settings.settings": "Ajustes",
"settings.camera": "Cámara", "settings.camera": "Cámara",
@ -126,6 +136,12 @@
"settings.lastn": "Cantidad de videos visibles", "settings.lastn": "Cantidad de videos visibles",
"settings.hiddenControls": null, "settings.hiddenControls": null,
"settings.notificationSounds": null, "settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"filesharing.saveFileError": "No ha sido posible guardar el fichero", "filesharing.saveFileError": "No ha sido posible guardar el fichero",
"filesharing.startingFileShare": "Intentando compartir el fichero", "filesharing.startingFileShare": "Intentando compartir el fichero",

View File

@ -59,6 +59,10 @@
"room.raisedHand": null, "room.raisedHand": null,
"room.loweredHand": null, "room.loweredHand": null,
"room.extraVideo": null, "room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -79,6 +83,10 @@
"tooltip.muteParticipant": null, "tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null, "tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Nom de la salle", "label.roomName": "Nom de la salle",
"label.chooseRoomButton": "Continuer", "label.chooseRoomButton": "Continuer",
@ -92,6 +100,7 @@
"label.filesharing": "Partage de fichier", "label.filesharing": "Partage de fichier",
"label.participants": "Participants", "label.participants": "Participants",
"label.shareFile": "Partager un fichier", "label.shareFile": "Partager un fichier",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Partage de fichier non supporté", "label.fileSharingUnsupported": "Partage de fichier non supporté",
"label.unknown": "Inconnu", "label.unknown": "Inconnu",
"label.democratic": "Vue démocratique", "label.democratic": "Vue démocratique",
@ -103,10 +112,11 @@
"label.ultra": "Ultra Haute Définition", "label.ultra": "Ultra Haute Définition",
"label.close": "Fermer", "label.close": "Fermer",
"label.media": null, "label.media": null,
"label.appearence": null, "label.appearance": null,
"label.advanced": null, "label.advanced": null,
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null,
"settings.settings": "Paramètres", "settings.settings": "Paramètres",
"settings.camera": "Caméra", "settings.camera": "Caméra",
@ -126,6 +136,12 @@
"settings.lastn": "Nombre de vidéos visibles", "settings.lastn": "Nombre de vidéos visibles",
"settings.hiddenControls": null, "settings.hiddenControls": null,
"settings.notificationSounds": null, "settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"filesharing.saveFileError": "Impossible d'enregistrer le fichier", "filesharing.saveFileError": "Impossible d'enregistrer le fichier",
"filesharing.startingFileShare": "Début du transfert de fichier", "filesharing.startingFileShare": "Début du transfert de fichier",

View File

@ -52,18 +52,22 @@
"room.muteAll": "Utišaj sve", "room.muteAll": "Utišaj sve",
"room.stopAllVideo": "Ugasi sve kamere", "room.stopAllVideo": "Ugasi sve kamere",
"room.closeMeeting": "Završi sastanak", "room.closeMeeting": "Završi sastanak",
"room.clearChat": null, "room.clearChat": "Izbriši razgovor",
"room.clearFileSharing": null, "room.clearFileSharing": "Izbriši dijeljene datoteke",
"room.speechUnsupported": "Vaš preglednik ne podržava prepoznavanje govora", "room.speechUnsupported": "Vaš preglednik ne podržava prepoznavanje govora",
"room.moderatoractions": null, "room.moderatoractions": "Akcije moderatora",
"room.raisedHand": null, "room.raisedHand": "{displayName} je podigao ruku",
"room.loweredHand": null, "room.loweredHand": "{displayName} je spustio ruku",
"room.extraVideo": null, "room.extraVideo": "Dodatni video",
"room.overRoomLimit": "Soba je popunjena, pokušajte ponovno kasnije.",
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor", "me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor",
"roles.gotRole": null, "roles.gotRole": "Dodijeljena vam je uloga: {role}",
"roles.lostRole": null, "roles.lostRole": "Uloga: {role} je povučena",
"tooltip.login": "Prijava", "tooltip.login": "Prijava",
"tooltip.logout": "Odjava", "tooltip.logout": "Odjava",
@ -74,11 +78,15 @@
"tooltip.leaveFullscreen": "Izađi iz punog ekrana", "tooltip.leaveFullscreen": "Izađi iz punog ekrana",
"tooltip.lobby": "Prikaži predvorje", "tooltip.lobby": "Prikaži predvorje",
"tooltip.settings": "Prikaži postavke", "tooltip.settings": "Prikaži postavke",
"tooltip.participants": "Pokažite sudionike", "tooltip.participants": "Prikaži sudionike",
"tooltip.kickParticipant": "Izbaci sudionika", "tooltip.kickParticipant": "Izbaci sudionika",
"tooltip.muteParticipant": null, "tooltip.muteParticipant": "Utišaj sudionika",
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipantVideo": "Ne primaj video sudionika",
"tooltip.raisedHand": null, "tooltip.raisedHand": "Podigni ruku",
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Naziv sobe", "label.roomName": "Naziv sobe",
"label.chooseRoomButton": "Nastavi", "label.chooseRoomButton": "Nastavi",
@ -92,6 +100,7 @@
"label.filesharing": "Dijeljenje datoteka", "label.filesharing": "Dijeljenje datoteka",
"label.participants": "Sudionici", "label.participants": "Sudionici",
"label.shareFile": "Dijeli datoteku", "label.shareFile": "Dijeli datoteku",
"label.shareGalleryFile": "Dijeli sliku",
"label.fileSharingUnsupported": "Dijeljenje datoteka nije podržano", "label.fileSharingUnsupported": "Dijeljenje datoteka nije podržano",
"label.unknown": "Nepoznato", "label.unknown": "Nepoznato",
"label.democratic":"Demokratski prikaz", "label.democratic":"Demokratski prikaz",
@ -102,11 +111,12 @@
"label.veryHigh": "Vrlo visoka (FHD)", "label.veryHigh": "Vrlo visoka (FHD)",
"label.ultra": "Ultra visoka (UHD)", "label.ultra": "Ultra visoka (UHD)",
"label.close": "Zatvori", "label.close": "Zatvori",
"label.media": null, "label.media": "Medij",
"label.appearence": null, "label.appearance": "Prikaz",
"label.advanced": null, "label.advanced": "Napredno",
"label.addVideo": null, "label.addVideo": "Dodaj video",
"label.promoteAllPeers": null, "label.promoteAllPeers": "Promoviraj sve",
"label.moreActions": null,
"settings.settings": "Postavke", "settings.settings": "Postavke",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -116,16 +126,22 @@
"settings.selectAudio": "Odaberi uređaj za zvuk", "settings.selectAudio": "Odaberi uređaj za zvuk",
"settings.cantSelectAudio": "Nije moguće odabrati uređaj za zvuk", "settings.cantSelectAudio": "Nije moguće odabrati uređaj za zvuk",
"settings.audioOutput": "Uređaj za izlaz zvuka", "settings.audioOutput": "Uređaj za izlaz zvuka",
"settings.selectAudioOutput": "Odaberite audio izlazni uređaj", "settings.selectAudioOutput": "Odaberite izlazni uređaj za zvuk",
"settings.cantSelectAudioOutput": "Nije moguće odabrati audio izlazni uređaj", "settings.cantSelectAudioOutput": "Nije moguće odabrati izlazni uređaj za zvuk",
"settings.resolution": "Odaberi video rezoluciju", "settings.resolution": "Odaberi video rezoluciju",
"settings.layout": "Način prikaza", "settings.layout": "Način prikaza",
"settings.selectRoomLayout": "Odaberi način prikaza", "settings.selectRoomLayout": "Odaberi način prikaza",
"settings.advancedMode": "Napredne mogućnosti", "settings.advancedMode": "Napredne mogućnosti",
"settings.permanentTopBar": "Stalna gornja šipka", "settings.permanentTopBar": "Stalna gornja šipka",
"settings.lastn": "Broj vidljivih videozapisa", "settings.lastn": "Broj vidljivih videozapisa",
"settings.hiddenControls": null, "settings.hiddenControls": "Skrivene kontrole medija",
"settings.notificationSounds": null, "settings.notificationSounds": "Zvuk obavijesti",
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"filesharing.saveFileError": "Nije moguće spremiti datoteku", "filesharing.saveFileError": "Nije moguće spremiti datoteku",
"filesharing.startingFileShare": "Pokušaj dijeljenja datoteke", "filesharing.startingFileShare": "Pokušaj dijeljenja datoteke",
@ -167,8 +183,8 @@
"devices.cameraDisconnected": "Kamera odspojena", "devices.cameraDisconnected": "Kamera odspojena",
"devices.cameraError": "Greška prilikom pristupa kameri", "devices.cameraError": "Greška prilikom pristupa kameri",
"moderator.clearChat": null, "moderator.clearChat": "Moderator je izbrisao razgovor",
"moderator.clearFiles": null, "moderator.clearFiles": "Moderator je izbrisao datoteke",
"moderator.muteAudio": null, "moderator.muteAudio": "Moderator je utišao tvoj zvuk",
"moderator.muteVideo": null "moderator.muteVideo": "Moderator je zaustavio tvoj video"
} }

View File

@ -1,24 +1,24 @@
{ {
"socket.disconnected": "A kapcsolat lebomlott", "socket.disconnected": "A kapcsolat lebomlott",
"socket.reconnecting": "A kapcsolat lebomlott, újrapróbálkozás", "socket.reconnecting": "A kapcsolat lebomlott, újrapróbálkozás",
"socket.reconnected": "Sikeres újarkapcsolódás", "socket.reconnected": "Sikeres újrakapcsolódás",
"socket.requestError": "Sikertelen szerver lekérés", "socket.requestError": "Sikertelen szerver lekérés",
"room.chooseRoom": null, "room.chooseRoom": "Válaszd ki a konferenciaszobát",
"room.cookieConsent": "Ez a weblap a felhasználói élmény fokozása miatt sütiket használ", "room.cookieConsent": "Ez a weblap a felhasználói élmény fokozása miatt sütiket használ",
"room.consentUnderstand": "I understand", "room.consentUnderstand": "Megértettem",
"room.joined": "Csatlakozátál a konferenciához", "room.joined": "Csatlakoztál a konferenciához",
"room.cantJoin": "Sikertelen csatlakozás a konferenciához", "room.cantJoin": "Sikertelen csatlakozás a konferenciához",
"room.youLocked": "A konferenciába való belépés letiltva", "room.youLocked": "A konferenciába való belépés letiltva",
"room.cantLock": "Sikertelen a konferenciaba való belépés letiltása", "room.cantLock": "Sikertelen a konferenciába való belépés letiltása",
"room.youUnLocked": "A konferenciába való belépés engedélyezve", "room.youUnLocked": "A konferenciába való belépés engedélyezve",
"room.cantUnLock": "Sikertelen a konferenciába való belépés engedélyezése", "room.cantUnLock": "Sikertelen a konferenciába való belépés engedélyezése",
"room.locked": "A konferenciába való belépés letiltva", "room.locked": "A konferenciába való belépés letiltva",
"room.unlocked": "A konferenciába való belépés engedélyezve", "room.unlocked": "A konferenciába való belépés engedélyezve",
"room.newLobbyPeer": "Új részvevő lépett be a konferencia előszobájába", "room.newLobbyPeer": "Új részvevő lépett be a konferencia előszobájába",
"room.lobbyPeerLeft": "A konferencia előszobájából a részvevő távozott", "room.lobbyPeerLeft": "A konferencia előszobájából a részvevő távozott",
"room.lobbyPeerChangedDisplayName": "Az előszobai résztvevő meváltoztatta a nevét: {displayName}", "room.lobbyPeerChangedDisplayName": "Az előszobai résztvevő megváltoztatta a nevét: {displayName}",
"room.lobbyPeerChangedPicture": "Az előszobai résztvevő meváltoztatta a képét", "room.lobbyPeerChangedPicture": "Az előszobai résztvevő megváltoztatta a képét",
"room.setAccessCode": "A konferencia hozzáférési kódja megváltozott", "room.setAccessCode": "A konferencia hozzáférési kódja megváltozott",
"room.accessCodeOn": "A konferencia hozzáférési kódja aktiválva", "room.accessCodeOn": "A konferencia hozzáférési kódja aktiválva",
"room.accessCodeOff": "A konferencia hozzáférési kódka deaktiválva", "room.accessCodeOff": "A konferencia hozzáférési kódka deaktiválva",
@ -39,8 +39,8 @@
"room.audioOnly": "csak Hang", "room.audioOnly": "csak Hang",
"room.audioVideo": "Hang és Videó", "room.audioVideo": "Hang és Videó",
"room.youAreReady": "Ok, kész vagy", "room.youAreReady": "Ok, kész vagy",
"room.emptyRequireLogin": "A konferencia üres! Be kell lépned a konferecnia elkezdéséhez, vagy várnod kell amíg a házigazda becsatlakozik.", "room.emptyRequireLogin": "A konferencia üres! Be kell lépned a konferencia elkezdéséhez, vagy várnod kell amíg a házigazda becsatlakozik.",
"room.locketWait": "A konferencia szobába a a belépés tilos - Várj amíg valaki be nem enged ...", "room.locketWait": "Az automatikus belépés tiltva van - Várj amíg valaki beenged ...",
"room.lobbyAdministration": "Előszoba adminisztráció", "room.lobbyAdministration": "Előszoba adminisztráció",
"room.peersInLobby": "Résztvevők az előszobában", "room.peersInLobby": "Résztvevők az előszobában",
"room.lobbyEmpty": "Épp senki sincs a konferencia előszobájában", "room.lobbyEmpty": "Épp senki sincs a konferencia előszobájában",
@ -49,25 +49,29 @@
"room.spotlights": "Látható résztvevők", "room.spotlights": "Látható résztvevők",
"room.passive": "Passzív résztvevők", "room.passive": "Passzív résztvevők",
"room.videoPaused": "Ez a videóstream szünetel", "room.videoPaused": "Ez a videóstream szünetel",
"room.muteAll": null, "room.muteAll": "Mindenki némítása",
"room.stopAllVideo": null, "room.stopAllVideo": "Mindenki video némítása",
"room.closeMeeting": null, "room.closeMeeting": "Konferencia lebontása",
"room.clearChat": null, "room.clearChat": "Chat történelem kiürítése",
"room.clearFileSharing": null, "room.clearFileSharing": "File megosztás kiürítése",
"room.speechUnsupported": null, "room.speechUnsupported": "A böngésződ nem támogatja a hangfelismerést",
"room.moderatoractions": null, "room.moderatoractions": "Moderátor funkciók",
"room.raisedHand": null, "room.raisedHand": "{displayName} jelentkezik",
"room.loweredHand": null, "room.loweredHand": "{displayName} leeresztette a kezét",
"room.extraVideo": null, "room.extraVideo": "Kiegészítő videó",
"room.overRoomLimit": "A konferenciaszoba betelt..",
"room.help": "Segítség",
"room.about": "Névjegy",
"room.shortcutKeys": "Billentyűparancsok",
"me.mutedPTT": null, "me.mutedPTT": "Némítva vagy, ha beszélnél nyomd le a szóköz billentyűt",
"roles.gotRole": null, "roles.gotRole": "{role} szerepet kaptál",
"roles.lostRole": null, "roles.lostRole": "Elvesztetted a {role} szerepet",
"tooltip.login": "Belépés", "tooltip.login": "Belépés",
"tooltip.logout": "Kilépés", "tooltip.logout": "Kilépés",
"tooltip.admitFromLobby": "Beenegdem az előszobából", "tooltip.admitFromLobby": "Beengedem az előszobából",
"tooltip.lockRoom": "A konferenciába való belépés letiltása", "tooltip.lockRoom": "A konferenciába való belépés letiltása",
"tooltip.unLockRoom": "konferenciába való belépés engedélyezése", "tooltip.unLockRoom": "konferenciába való belépés engedélyezése",
"tooltip.enterFullscreen": "Teljes képernyős mód", "tooltip.enterFullscreen": "Teljes képernyős mód",
@ -75,10 +79,14 @@
"tooltip.lobby": "Az előszobában várakozók listája", "tooltip.lobby": "Az előszobában várakozók listája",
"tooltip.settings": "Beállítások", "tooltip.settings": "Beállítások",
"tooltip.participants": "Résztvevők", "tooltip.participants": "Résztvevők",
"tooltip.kickParticipant": null, "tooltip.kickParticipant": "Résztvevő kirúgása",
"tooltip.muteParticipant": null, "tooltip.muteParticipant": "Résztvevő némítása",
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipantVideo": "Résztvevő videóstreamének némítása",
"tooltip.raisedHand": null, "tooltip.raisedHand": "Jelentkezés",
"tooltip.muteScreenSharing": "Képernyőmegosztás szüneteltetése",
"tooltip.muteParticipantAudioModerator": "Résztvevő hangjának általános némítása",
"tooltip.muteParticipantVideoModerator": "Résztvevő videójának általános némítása",
"tooltip.muteScreenSharingModerator": "Résztvevő képernyőmegosztásának általános némítása",
"label.roomName": "Konferencia", "label.roomName": "Konferencia",
"label.chooseRoomButton": "Tovább", "label.chooseRoomButton": "Tovább",
@ -92,6 +100,7 @@
"label.filesharing": "Fájl megosztás", "label.filesharing": "Fájl megosztás",
"label.participants": "Résztvevők", "label.participants": "Résztvevők",
"label.shareFile": "Fájl megosztása", "label.shareFile": "Fájl megosztása",
"label.shareGalleryFile": "Fájl megosztás galériából",
"label.fileSharingUnsupported": "Fájl megosztás nem támogatott", "label.fileSharingUnsupported": "Fájl megosztás nem támogatott",
"label.unknown": "Ismeretlen", "label.unknown": "Ismeretlen",
"label.democratic": "Egyforma képméretű képkiosztás", "label.democratic": "Egyforma képméretű képkiosztás",
@ -102,11 +111,12 @@
"label.veryHigh": "Nagyon magas (FHD)", "label.veryHigh": "Nagyon magas (FHD)",
"label.ultra": "Ultra magas (UHD)", "label.ultra": "Ultra magas (UHD)",
"label.close": "Bezár", "label.close": "Bezár",
"label.media": null, "label.media": "Média",
"label.appearence": null, "label.appearance": "Megjelenés",
"label.advanced": null, "label.advanced": "Részletek",
"label.addVideo": null, "label.addVideo": "Videó hozzáadása",
"label.promoteAllPeers": null, "label.promoteAllPeers": "Mindenkit beengedek",
"label.moreActions": "További műveletek",
"settings.settings": "Beállítások", "settings.settings": "Beállítások",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -124,13 +134,19 @@
"settings.advancedMode": "Részletes információk", "settings.advancedMode": "Részletes információk",
"settings.permanentTopBar": "Állandó felső sáv", "settings.permanentTopBar": "Állandó felső sáv",
"settings.lastn": "A látható videók száma", "settings.lastn": "A látható videók száma",
"settings.hiddenControls": null, "settings.hiddenControls": "Média Gombok automatikus elrejtése",
"settings.notificationSounds": null, "settings.notificationSounds": "Értesítések hangjelzéssel",
"settings.showNotifications": "Értesítések megjelenítése",
"settings.buttonControlBar": "Médiavezérlő gombok leválasztása",
"settings.echoCancellation": "Visszhangelnyomás",
"settings.autoGainControl": "Automatikus hangerő",
"settings.noiseSuppression": "Zajelnyomás",
"settings.drawerOverlayed": "Oldalsáv a tartalom felett",
"filesharing.saveFileError": "A file-t nem sikerült elmenteni", "filesharing.saveFileError": "A file-t nem sikerült elmenteni",
"filesharing.startingFileShare": "Fájl megosztása", "filesharing.startingFileShare": "Fájl megosztása",
"filesharing.successfulFileShare": "A fájl sikeresen megosztva", "filesharing.successfulFileShare": "A fájl sikeresen megosztva",
"filesharing.unableToShare": "Sikereteln fájl megosztás", "filesharing.unableToShare": "Sikertelen fájl megosztás",
"filesharing.error": "Hiba a fájlmegosztás során", "filesharing.error": "Hiba a fájlmegosztás során",
"filesharing.finished": "A fájl letöltés befejeződött", "filesharing.finished": "A fájl letöltés befejeződött",
"filesharing.save": "Mentés", "filesharing.save": "Mentés",
@ -140,7 +156,7 @@
"devices.devicesChanged": "Az eszközei megváltoztak, konfiguráld őket be a beállítások menüben", "devices.devicesChanged": "Az eszközei megváltoztak, konfiguráld őket be a beállítások menüben",
"device.audioUnsupported": "A hnag nem támogatott", "device.audioUnsupported": "A hang nem támogatott",
"device.activateAudio": "Hang aktiválása", "device.activateAudio": "Hang aktiválása",
"device.muteAudio": "Hang némítása", "device.muteAudio": "Hang némítása",
"device.unMuteAudio": "Hang némítás kikapcsolása", "device.unMuteAudio": "Hang némítás kikapcsolása",
@ -151,9 +167,9 @@
"device.screenSharingUnsupported": "A képernyő megosztás nem támogatott", "device.screenSharingUnsupported": "A képernyő megosztás nem támogatott",
"device.startScreenSharing": "Képernyőmegosztás indítása", "device.startScreenSharing": "Képernyőmegosztás indítása",
"device.stopScreenSharing": "Képernyőmegosztás leáłłítása", "device.stopScreenSharing": "Képernyőmegosztás leállítása",
"devices.microphoneDisconnected": "Microphone kapcsolat bontva", "devices.microphoneDisconnected": "Mikrofon kapcsolat bontva",
"devices.microphoneError": "Hiba történt a mikrofon hangeszköz elérése közben", "devices.microphoneError": "Hiba történt a mikrofon hangeszköz elérése közben",
"devices.microphoneMute": "A mikrofon némítva lett", "devices.microphoneMute": "A mikrofon némítva lett",
"devices.microphoneUnMute": "A mikrofon némítása ki lett kapocsolva", "devices.microphoneUnMute": "A mikrofon némítása ki lett kapocsolva",
@ -167,8 +183,8 @@
"devices.cameraDisconnected": "A kamera kapcsolata lebomlott", "devices.cameraDisconnected": "A kamera kapcsolata lebomlott",
"devices.cameraError": "Hiba történt a kamera elérése során", "devices.cameraError": "Hiba történt a kamera elérése során",
"moderator.clearChat": null, "moderator.clearChat": "A moderátor kiürítette a chat történelmet",
"moderator.clearFiles": null, "moderator.clearFiles": "A moderátor kiürítette a file megosztás történelmet",
"moderator.muteAudio": null, "moderator.muteAudio": "A moderátor elnémította a hangod",
"moderator.muteVideo": null "moderator.muteVideo": "A moderátor elnémította a videód"
} }

View File

@ -49,25 +49,29 @@
"room.spotlights": "Partecipanti in Evidenza", "room.spotlights": "Partecipanti in Evidenza",
"room.passive": "Participanti Passivi", "room.passive": "Participanti Passivi",
"room.videoPaused": "Il video è in pausa", "room.videoPaused": "Il video è in pausa",
"room.muteAll": null, "room.muteAll": "Muta tutti",
"room.stopAllVideo": null, "room.stopAllVideo": "Ferma tutti i video",
"room.closeMeeting": null, "room.closeMeeting": "Termina meeting",
"room.clearChat": null, "room.clearChat": "Pulisci chat",
"room.clearFileSharing": null, "room.clearFileSharing": "Pulisci file sharing",
"room.speechUnsupported": null, "room.speechUnsupported": "Il tuo browser non supporta il riconoscimento vocale",
"room.moderatoractions": null, "room.moderatoractions": "Azioni moderatore",
"room.raisedHand": null, "room.raisedHand": "{displayName} ha alzato la mano",
"room.loweredHand": null, "room.loweredHand": "{displayName} ha abbassato la mano",
"room.extraVideo": null, "room.extraVideo": "Video extra",
"room.overRoomLimit": "La stanza è piena, riprova più tardi.",
"me.mutedPTT": null, "room.help": "Aiuto",
"room.about": "Informazioni su",
"room.shortcutKeys": "Scorciatoie da tastiera",
"roles.gotRole": null, "me.mutedPTT": "Sei mutato, tieni premuto SPAZIO per parlare",
"roles.lostRole": null,
"roles.gotRole": "Hai ottenuto il ruolo: {role}",
"roles.lostRole": "Hai perso il ruolo: {role}",
"tooltip.login": "Log in", "tooltip.login": "Log in",
"tooltip.logout": "Log out", "tooltip.logout": "Log out",
"tooltip.admitFromLobby": "Ammetti dalla lobby", "tooltip.admitFromLobby": "Accetta partecipante dalla lobby",
"tooltip.lockRoom": "Blocca stanza", "tooltip.lockRoom": "Blocca stanza",
"tooltip.unLockRoom": "Sblocca stanza", "tooltip.unLockRoom": "Sblocca stanza",
"tooltip.enterFullscreen": "Modalità schermo intero", "tooltip.enterFullscreen": "Modalità schermo intero",
@ -75,9 +79,14 @@
"tooltip.lobby": "Mostra lobby", "tooltip.lobby": "Mostra lobby",
"tooltip.settings": "Mostra impostazioni", "tooltip.settings": "Mostra impostazioni",
"tooltip.participants": "Mostra partecipanti", "tooltip.participants": "Mostra partecipanti",
"tooltip.muteParticipant": null, "tooltip.kickParticipant": "Espelli partecipante",
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipant": "Muta partecipante",
"tooltip.raisedHand": null, "tooltip.muteParticipantVideo": "Ferma video partecipante",
"tooltip.raisedHand": "Mano alzata",
"tooltip.muteScreenSharing": "Ferma condivisione schermo partecipante",
"tooltip.muteParticipantAudioModerator": "Sospendi audio globale",
"tooltip.muteParticipantVideoModerator": "Sospendi video globale",
"tooltip.muteScreenSharingModerator": "Sospendi condivisione schermo globale",
"label.roomName": "Nome della stanza", "label.roomName": "Nome della stanza",
"label.chooseRoomButton": "Continua", "label.chooseRoomButton": "Continua",
@ -91,6 +100,7 @@
"label.filesharing": "Condivisione file", "label.filesharing": "Condivisione file",
"label.participants": "Partecipanti", "label.participants": "Partecipanti",
"label.shareFile": "Condividi file", "label.shareFile": "Condividi file",
"label.shareGalleryFile": "Condividi immagine",
"label.fileSharingUnsupported": "Condivisione file non supportata", "label.fileSharingUnsupported": "Condivisione file non supportata",
"label.unknown": "Sconosciuto", "label.unknown": "Sconosciuto",
"label.democratic": "Vista Democratica", "label.democratic": "Vista Democratica",
@ -101,11 +111,12 @@
"label.veryHigh": "Molto alta (FHD)", "label.veryHigh": "Molto alta (FHD)",
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Chiudi", "label.close": "Chiudi",
"label.media": null, "label.media": "Media",
"label.appearence": null, "label.appearance": "Aspetto",
"label.advanced": null, "label.advanced": "Avanzate",
"label.addVideo": null, "label.addVideo": "Aggiungi video",
"label.promoteAllPeers": null, "label.promoteAllPeers": "Promuovi tutti",
"label.moreActions": "Altre azioni",
"settings.settings": "Impostazioni", "settings.settings": "Impostazioni",
"settings.camera": "Videocamera", "settings.camera": "Videocamera",
@ -123,8 +134,14 @@
"settings.advancedMode": "Modalità avanzata", "settings.advancedMode": "Modalità avanzata",
"settings.permanentTopBar": "Barra superiore permanente", "settings.permanentTopBar": "Barra superiore permanente",
"settings.lastn": "Numero di video visibili", "settings.lastn": "Numero di video visibili",
"settings.hiddenControls": null, "settings.hiddenControls": "Controlli media nascosti",
"settings.notificationSounds": null, "settings.notificationSounds": "Suoni di notifica",
"settings.showNotifications": "Mostra notifiche",
"settings.buttonControlBar": "Controlli media separati",
"settings.echoCancellation": "Cancellazione echo",
"settings.autoGainControl": "Controllo guadagno automatico",
"settings.noiseSuppression": "Riduzione del rumore",
"settings.drawerOverlayed": "Barra laterale sovrapposta",
"filesharing.saveFileError": "Impossibile salvare file", "filesharing.saveFileError": "Impossibile salvare file",
"filesharing.startingFileShare": "Tentativo di condivisione file", "filesharing.startingFileShare": "Tentativo di condivisione file",
@ -140,7 +157,7 @@
"devices.devicesChanged": "Il tuo dispositivo è cambiato, configura i dispositivi nel menù di impostazioni", "devices.devicesChanged": "Il tuo dispositivo è cambiato, configura i dispositivi nel menù di impostazioni",
"device.audioUnsupported": "Dispositivo audio non supportato", "device.audioUnsupported": "Dispositivo audio non supportato",
"device.activateAudio": "Attiva audio", "device.activateAudio": "Attiva audio",
"device.muteAudio": "Silenzia audio", "device.muteAudio": "Silenzia audio",
"device.unMuteAudio": "Riattiva audio", "device.unMuteAudio": "Riattiva audio",
@ -166,8 +183,8 @@
"devices.cameraDisconnected": "Videocamera scollegata", "devices.cameraDisconnected": "Videocamera scollegata",
"devices.cameraError": "Errore con l'accesso alla videocamera", "devices.cameraError": "Errore con l'accesso alla videocamera",
"moderator.clearChat": null, "moderator.clearChat": "Il moderatore ha pulito la chat",
"moderator.clearFiles": null, "moderator.clearFiles": "Il moderatore ha pulito i file",
"moderator.muteAudio": null, "moderator.muteAudio": "Il moderatore ha mutato il tuo audio",
"moderator.muteVideo": null "moderator.muteVideo": "Il moderatore ha fermato il tuo video"
} }

View File

@ -0,0 +1,184 @@
{
"socket.disconnected": "Esat bezsaistē",
"socket.reconnecting": "Esat bezsaistē, tiek mēģināts pievienoties",
"socket.reconnected": "Esat atkārtoti pievienojies",
"socket.requestError": "Kļūme servera pieprasījumā",
"room.chooseRoom": "Ievadiet sapulces telpas nosaukumu (ID), kurai vēlaties pievienoties",
"room.cookieConsent": "Lai uzlabotu lietotāja pieredzi, šī vietne izmanto sīkfailus",
"room.consentUnderstand": "Es saprotu un piekrītu",
"room.joined": "Jūs esiet pievienojies sapulces telpai",
"room.cantJoin": "Nav iespējams pievienoties sapulces telpai",
"room.youLocked": "Jūs aizslēdzāt sapulces telpu",
"room.cantLock": "Nav iespējams aizslēgt sapulces telpu",
"room.youUnLocked": "Jūs atslēdzāt sapulces telpu",
"room.cantUnLock": "Nav iespējams atslēgt sapulces telpu",
"room.locked": "Sapulces telpa tagad ir AIZSLĒGTA",
"room.unlocked": "Sapulces telpa tagad ir ATSLĒGTA",
"room.newLobbyPeer": "Jauns dalībnieks ienācis uzgaidāmajā telpā",
"room.lobbyPeerLeft": "Dalībnieks uzgaidāmo telpu pameta",
"room.lobbyPeerChangedDisplayName": "Dalībnieks uzgaidāmajā telpā nomainīja vārdu uz {displayName}",
"room.lobbyPeerChangedPicture": "Dalībnieks uzgaidāmajā telpā nomainīja pašattēlu",
"room.setAccessCode": "Pieejas kods sapulces telpai aktualizēts",
"room.accessCodeOn": "Pieejas kods sapulces telpai tagad ir aktivēts",
"room.accessCodeOff": "Pieejas kods sapulces telpai tagad ir deaktivēts (atslēgts)",
"room.peerChangedDisplayName": "{oldDisplayName} pārsaucās par {displayName}",
"room.newPeer": "{displayName} pievienojās sapulces telpai",
"room.newFile": "Pieejams jauns fails",
"room.toggleAdvancedMode": "Pārslēgt uz advancēto režīmu",
"room.setDemocraticView": "Nomainīts izkārtojums uz demokrātisko skatu",
"room.setFilmStripView": "Nomainīts izkārtojums uz diapozitīvu (filmstrip) skatu",
"room.loggedIn": "Jūs esat ierakstījies (sistēmā)",
"room.loggedOut": "Jūs esat izrakstījies (no sistēmas)",
"room.changedDisplayName": "Jūsu vārds mainīts uz {displayName}",
"room.changeDisplayNameError": "Gadījās ķibele ar Jūsu vārda nomaiņu",
"room.chatError": "Nav iespējams nosūtīt tērziņa ziņu",
"room.aboutToJoin": "Jūs grasāties pievienoties sapulcei",
"room.roomId": "Sapulces telpas nosaukums (ID): {roomName}",
"room.setYourName": "Norādiet savu dalības vārdu un izvēlieties kā vēlaties pievienoties sapulcei:",
"room.audioOnly": "Vienīgi audio",
"room.audioVideo": "Audio & video",
"room.youAreReady": "Ok, Jūs esiet gatavi!",
"room.emptyRequireLogin": "Sapulces telpa ir tukša! Jūs varat Ierakstīties sistēmā, lai uzsāktu vadīt sapulci vai pagaidīt kamēr pievienojas sapulces rīkotājs/vadītājs",
"room.locketWait": "Sapulce telpa ir slēgta. Jūs atrodaties tās uzgaidāmajā telpā. Uzkavējieties, kamēr kāds Jūs sapulcē ielaiž ...",
"room.lobbyAdministration": "Uzgaidāmās telpas administrēšana",
"room.peersInLobby": "Dalībnieki uzgaidāmajā telpā",
"room.lobbyEmpty": "Pašreiz uzgaidāmajā telpā neviena nav",
"room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}",
"room.me": "Es",
"room.spotlights": "Aktīvie (referējošie) dalībnieki",
"room.passive": "Pasīvie dalībnieki",
"room.videoPaused": "Šis video ir pauzēts",
"room.muteAll": "Noklusināt visus dalībnieku mikrofonus",
"room.stopAllVideo": "Izslēgt visu dalībnieku kameras",
"room.closeMeeting": "Beigt sapulci",
"room.clearChat": "Nodzēst visus tērziņus",
"room.clearFileSharing": "Notīrīt visus kopīgotos failus",
"room.speechUnsupported": "Jūsu pārlūks neatbalsta balss atpazīšanu",
"room.moderatoractions": "Moderatora rīcība",
"room.raisedHand": "{displayName} pacēla roku",
"room.loweredHand": "{displayName} nolaida roku",
"room.extraVideo": "Papildus video",
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"me.mutedPTT": "Jūs esat noklusināts. Turiet taustiņu SPACE-BAR, lai runātu",
"roles.gotRole": "Jūs ieguvāt lomu: {role}",
"roles.lostRole": "Jūs zaudējāt lomu: {role}",
"tooltip.login": "Ierakstīties",
"tooltip.logout": "Izrakstīties",
"tooltip.admitFromLobby": "Ielaist no uzgaidāmās telpas",
"tooltip.lockRoom": "Aizslēgt sapulces telpu",
"tooltip.unLockRoom": "Atlēgt sapulces telpu",
"tooltip.enterFullscreen": "Aktivēt pilnekrāna režīmu",
"tooltip.leaveFullscreen": "Pamest pilnekrānu",
"tooltip.lobby": "Parādīt uzgaidāmo telpu",
"tooltip.settings": "Parādīt iestatījumus",
"tooltip.participants": "Parādīt dalībniekus",
"tooltip.kickParticipant": "Izvadīt (izspert) dalībnieku",
"tooltip.muteParticipant": "Noklusināt dalībnieku",
"tooltip.muteParticipantVideo": "Atslēgt dalībnieka video",
"tooltip.raisedHand": "Pacelt roku",
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Sapulces telpas nosaukums (ID)",
"label.chooseRoomButton": "Turpināt",
"label.yourName": "Jūu vārds",
"label.newWindow": "Jauns logs",
"label.fullscreen": "Pilnekrāns",
"label.openDrawer": "Atvērt atvilkni",
"label.leave": "Pamest",
"label.chatInput": "Rakstiet tērziņa ziņu...",
"label.chat": "Tērzētava",
"label.filesharing": "Failu koplietošana",
"label.participants": "Dalībnieki",
"label.shareFile": "Koplietot failu",
"label.fileSharingUnsupported": "Failu koplietošana netiek atbalstīta",
"label.unknown": "Nezināms",
"label.democratic": "Demokrātisks skats",
"label.filmstrip": "Diapozitīvu (filmstrip) skats",
"label.low": "Zema",
"label.medium": "Vidēja",
"label.high": "Augsta (HD)",
"label.veryHigh": "Ļoti augsta (FHD)",
"label.ultra": "Ultra (UHD)",
"label.close": "Aizvērt",
"label.media": "Mediji",
"label.appearance": "Izskats",
"label.advanced": "Advancēts",
"label.addVideo": "Pievienot video",
"label.moreActions": null,
"settings.settings": "Iestatījumi",
"settings.camera": "Kamera",
"settings.selectCamera": "Izvēlieties kameru (video ierīci)",
"settings.cantSelectCamera": "Nav iespējams lietot šo kameru (video ierīci)",
"settings.audio": "Skaņas ierīce",
"settings.selectAudio": "Izvēlieties skaņas ierīci",
"settings.cantSelectAudio": "Nav iespējams lietot šo skaņas (audio) ierīci",
"settings.resolution": "Iestatiet jūsu video izšķirtspēju",
"settings.layout": "Sapulces telpas izkārtojums",
"settings.selectRoomLayout": "Iestatiet sapulces telpas izkārtojumu",
"settings.advancedMode": "Advancētais režīms",
"settings.permanentTopBar": "Pastāvīga augšējā (ekrānaugšas) josla",
"settings.lastn": "Jums redzamo video/kameru skaits",
"settings.hiddenControls": "Slēpto mediju vadība",
"settings.notificationSounds": "Paziņojumu skaņas",
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"filesharing.saveFileError": "Nav iespējams saglabāt failu",
"filesharing.startingFileShare": "Tiek mēģināts kopīgot failu",
"filesharing.successfulFileShare": "Fails sekmīgi kopīgots",
"filesharing.unableToShare": "Nav iespējams kopīgot failu",
"filesharing.error": "Atgadījās faila kopīgošanas kļūme",
"filesharing.finished": "Fails ir lejupielādēts",
"filesharing.save": "Saglabāt",
"filesharing.sharedFile": "{displayName} kopīgoja failu",
"filesharing.download": "Lejuplādēt",
"filesharing.missingSeeds": "Ja šis process aizņem ilgu laiku, iespējams nav neviena, kas sēklo (seed) šo torentu. Mēģiniet palūgt kādu atkārtoti augšuplādēt Jūsu gribēto failu.",
"devices.devicesChanged": "Jūsu ierīces pamainījās. Iestatījumu izvēlnē (dialogā) iestatiet jaunās ierīces.",
"device.audioUnsupported": "Skaņa (audio) netiek atbalstīta",
"device.activateAudio": "Iespējot/aktivēt mikrofonu (izejošo skaņu)",
"device.muteAudio": "Atslēgt/noklusināt mikrofonu (izejošo skaņu) ",
"device.unMuteAudio": "Ieslēgt mikrofonu (izejošo skaņu)",
"device.videoUnsupported": "Kamera (izejošais video) netiek atbalstīta",
"device.startVideo": "Ieslēgt kameru (izejošo video)",
"device.stopVideo": "Izslēgt kameru (izejošo video)",
"device.screenSharingUnsupported": "Ekrāna kopīgošana netiek atbalstīta",
"device.startScreenSharing": "Sākt ekrāna kopīgošanu",
"device.stopScreenSharing": "Beigt ekrāna kopīgošanu",
"devices.microphoneDisconnected": "Mikrofons atvienots",
"devices.microphoneError": "Atgadījās kļūme, piekļūstot jūsu mikrofonam",
"devices.microPhoneMute": "Mikrofons izslēgts/noklusināts",
"devices.micophoneUnMute": "Mikrofons ieslēgts",
"devices.microphoneEnable": "Mikrofons iespējots",
"devices.microphoneMuteError": "Nav iespējams izslēgt Jūsu mikrofonu",
"devices.microphoneUnMuteError": "Nav iespējams ieslēgt Jūsu mikrofonu",
"devices.screenSharingDisconnected" : "Ekrāna kopīgošana nenotiek (atvienota)",
"devices.screenSharingError": "Atgadījās kļūme, piekļūstot Jūsu ekrānam",
"devices.cameraDisconnected": "Kamera atvienota",
"devices.cameraError": "Atgadījās kļūme, piekļūstot Jūsu kamerai",
"moderator.clearChat": "Moderators nodzēsa tērziņus",
"moderator.clearFiles": "Moderators notīrīja failus",
"moderator.muteAudio": "Moderators noklusināja jūsu mikrofonu",
"moderator.muteVideo": "Moderators atslēdza jūsu kameru"
}

View File

@ -59,6 +59,10 @@
"room.raisedHand": "{displayName} rakk opp hånden", "room.raisedHand": "{displayName} rakk opp hånden",
"room.loweredHand": "{displayName} tok ned hånden", "room.loweredHand": "{displayName} tok ned hånden",
"room.extraVideo": "Ekstra video", "room.extraVideo": "Ekstra video",
"room.overRoomLimit": "Rommet er fullt, prøv igjen om litt.",
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke", "me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke",
@ -79,6 +83,10 @@
"tooltip.muteParticipant": "Demp deltaker", "tooltip.muteParticipant": "Demp deltaker",
"tooltip.muteParticipantVideo": "Demp deltakervideo", "tooltip.muteParticipantVideo": "Demp deltakervideo",
"tooltip.raisedHand": "Rekk opp hånden", "tooltip.raisedHand": "Rekk opp hånden",
"tooltip.muteScreenSharing": "Demp deltaker skjermdeling",
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Møtenavn", "label.roomName": "Møtenavn",
"label.chooseRoomButton": "Fortsett", "label.chooseRoomButton": "Fortsett",
@ -92,6 +100,7 @@
"label.filesharing": "Fildeling", "label.filesharing": "Fildeling",
"label.participants": "Deltakere", "label.participants": "Deltakere",
"label.shareFile": "Del fil", "label.shareFile": "Del fil",
"label.shareGalleryFile": "Del bilde",
"label.fileSharingUnsupported": "Fildeling ikke støttet", "label.fileSharingUnsupported": "Fildeling ikke støttet",
"label.unknown": "Ukjent", "label.unknown": "Ukjent",
"label.democratic": "Demokratisk", "label.democratic": "Demokratisk",
@ -103,10 +112,11 @@
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Lukk", "label.close": "Lukk",
"label.media": "Media", "label.media": "Media",
"label.appearence": "Utseende", "label.appearance": "Utseende",
"label.advanced": "Avansert", "label.advanced": "Avansert",
"label.addVideo": "Legg til video", "label.addVideo": "Legg til video",
"label.promoteAllPeers": "Slipp inn alle", "label.promoteAllPeers": "Slipp inn alle",
"label.moreActions": "Flere handlinger",
"settings.settings": "Innstillinger", "settings.settings": "Innstillinger",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -126,6 +136,12 @@
"settings.lastn": "Antall videoer synlig", "settings.lastn": "Antall videoer synlig",
"settings.hiddenControls": "Skjul media knapper", "settings.hiddenControls": "Skjul media knapper",
"settings.notificationSounds": "Varslingslyder", "settings.notificationSounds": "Varslingslyder",
"settings.showNotifications": "Vis varslinger",
"settings.buttonControlBar": "Separate media knapper",
"settings.echoCancellation": "Echokansellering",
"settings.autoGainControl": "Auto gain kontroll",
"settings.noiseSuppression": "Støy reduksjon",
"settings.drawerOverlayed": "Sidemeny over innhold",
"filesharing.saveFileError": "Klarte ikke å lagre fil", "filesharing.saveFileError": "Klarte ikke å lagre fil",
"filesharing.startingFileShare": "Starter fildeling", "filesharing.startingFileShare": "Starter fildeling",

View File

@ -49,21 +49,25 @@
"room.spotlights": "Aktywni uczestnicy", "room.spotlights": "Aktywni uczestnicy",
"room.passive": "Pasywni uczestnicy", "room.passive": "Pasywni uczestnicy",
"room.videoPaused": "To wideo jest wstrzymane.", "room.videoPaused": "To wideo jest wstrzymane.",
"room.muteAll": null, "room.muteAll": "Wycisz wszystkich",
"room.stopAllVideo": null, "room.stopAllVideo": "Zatrzymaj wszystkie Video",
"room.closeMeeting": null, "room.closeMeeting": "Zamknij spotkanie",
"room.clearChat": null, "room.clearChat": "Wyczyść Chat",
"room.clearFileSharing": null, "room.clearFileSharing": "Wyczyść pliki",
"room.speechUnsupported": null, "room.speechUnsupported": "Twoja przeglądarka nie rozpoznaje mowy",
"room.moderatoractions": null, "room.moderatoractions": "Akcje moderatora",
"room.raisedHand": null, "room.raisedHand": "{displayName} podniósł rękę",
"room.loweredHand": null, "room.loweredHand": "{displayName} opuścił rękę",
"room.extraVideo": null, "room.extraVideo": "Dodatkowe Video",
"room.overRoomLimit": "Pokój jest pełny, spróbuj za jakiś czas.",
"room.help": "Pomoc",
"room.about": "O pogramie",
"room.shortcutKeys": "Skróty klawiaturowe",
"me.mutedPTT": null, "me.mutedPTT": "Masz wyciszony mikrofon, przytrzymaj spację aby mówić",
"roles.gotRole": null, "roles.gotRole": "Masz rolę {role}",
"roles.lostRole": null, "roles.lostRole": "Nie masz już roli {role}",
"tooltip.login": "Zaloguj", "tooltip.login": "Zaloguj",
"tooltip.logout": "Wyloguj", "tooltip.logout": "Wyloguj",
@ -75,10 +79,14 @@
"tooltip.lobby": "Pokaż poczekalnię", "tooltip.lobby": "Pokaż poczekalnię",
"tooltip.settings": "Pokaż ustawienia", "tooltip.settings": "Pokaż ustawienia",
"tooltip.participants": "Pokaż uczestników", "tooltip.participants": "Pokaż uczestników",
"tooltip.kickParticipant": null, "tooltip.kickParticipant": "Wyrzuć użytkownika",
"tooltip.muteParticipant": null, "tooltip.muteParticipant": "Wycisz użytkownika",
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipantVideo": "Wyłącz wideo użytkownika",
"tooltip.raisedHand": null, "tooltip.raisedHand": "Podnieś rękę",
"tooltip.muteScreenSharing": "Anuluj udostępniania pulpitu przez użytkownika",
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Nazwa konferencji", "label.roomName": "Nazwa konferencji",
"label.chooseRoomButton": "Kontynuuj", "label.chooseRoomButton": "Kontynuuj",
@ -92,6 +100,7 @@
"label.filesharing": "Udostępnianie plików", "label.filesharing": "Udostępnianie plików",
"label.participants": "Uczestnicy", "label.participants": "Uczestnicy",
"label.shareFile": "Udostępnij plik", "label.shareFile": "Udostępnij plik",
"label.shareGalleryFile": "Udostępnij obraz",
"label.fileSharingUnsupported": "Udostępnianie plików nie jest obsługiwane", "label.fileSharingUnsupported": "Udostępnianie plików nie jest obsługiwane",
"label.unknown": "Nieznane", "label.unknown": "Nieznane",
"label.democratic": "Układ demokratyczny", "label.democratic": "Układ demokratyczny",
@ -102,11 +111,12 @@
"label.veryHigh": "Bardzo wysoka (FHD)", "label.veryHigh": "Bardzo wysoka (FHD)",
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Zamknij", "label.close": "Zamknij",
"label.media": null, "label.media": "Media",
"label.appearence": null, "label.appearance": "Wygląd",
"label.advanced": null, "label.advanced": "Zaawansowane",
"label.addVideo": null, "label.addVideo": "Dodaj wideo",
"label.promoteAllPeers": null, "label.promoteAllPeers": "Wpuść wszystkich",
"label.moreActions": "Więcej akcji",
"settings.settings": "Ustawienia", "settings.settings": "Ustawienia",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -124,8 +134,14 @@
"settings.advancedMode": "Tryb zaawansowany", "settings.advancedMode": "Tryb zaawansowany",
"settings.permanentTopBar": "Stały górny pasek", "settings.permanentTopBar": "Stały górny pasek",
"settings.lastn": "Liczba widocznych uczestników (zdalnych)", "settings.lastn": "Liczba widocznych uczestników (zdalnych)",
"settings.hiddenControls": null, "settings.hiddenControls": "Ukryte kontrolki mediów",
"settings.notificationSounds": null, "settings.notificationSounds": "Powiadomienia dźwiękiem",
"settings.showNotifications": "Pokaż powiadomienia",
"settings.buttonControlBar": "Rozdziel kontrolki mediów",
"settings.echoCancellation": "Usuwanie echa",
"settings.autoGainControl": "Auto korekta wzmocnienia",
"settings.noiseSuppression": "Wyciszenie szumów",
"settings.drawerOverlayed": "Szuflada nad zawartością",
"filesharing.saveFileError": "Nie można zapisać pliku", "filesharing.saveFileError": "Nie można zapisać pliku",
"filesharing.startingFileShare": "Próba udostępnienia pliku", "filesharing.startingFileShare": "Próba udostępnienia pliku",
@ -167,8 +183,8 @@
"devices.cameraDisconnected": "Kamera odłączona", "devices.cameraDisconnected": "Kamera odłączona",
"devices.cameraError": "Wystąpił błąd podczas uzyskiwania dostępu do kamery", "devices.cameraError": "Wystąpił błąd podczas uzyskiwania dostępu do kamery",
"moderator.clearChat": null, "moderator.clearChat": "Moderator wyczyścił chat",
"moderator.clearFiles": null, "moderator.clearFiles": "Moderator wyczyścił pliki",
"moderator.muteAudio": null, "moderator.muteAudio": "Moderator wyciszył audio",
"moderator.muteVideo": null "moderator.muteVideo": "Moderator wyciszył twoje video"
} }

View File

@ -59,6 +59,10 @@
"room.raisedHand": null, "room.raisedHand": null,
"room.loweredHand": null, "room.loweredHand": null,
"room.extraVideo": null, "room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -79,6 +83,10 @@
"tooltip.muteParticipant": null, "tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null, "tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Nome da sala", "label.roomName": "Nome da sala",
"label.chooseRoomButton": "Continuar", "label.chooseRoomButton": "Continuar",
@ -92,6 +100,7 @@
"label.filesharing": "Partilha de ficheiro", "label.filesharing": "Partilha de ficheiro",
"label.participants": "Participantes", "label.participants": "Participantes",
"label.shareFile": "Partilhar ficheiro", "label.shareFile": "Partilhar ficheiro",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Partilha de ficheiro não disponível", "label.fileSharingUnsupported": "Partilha de ficheiro não disponível",
"label.unknown": "Desconhecido", "label.unknown": "Desconhecido",
"label.democratic": "Vista democrática", "label.democratic": "Vista democrática",
@ -103,10 +112,11 @@
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Fechar", "label.close": "Fechar",
"label.media": null, "label.media": null,
"label.appearence": null, "label.appearance": null,
"label.advanced": null, "label.advanced": null,
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null,
"settings.settings": "Definições", "settings.settings": "Definições",
"settings.camera": "Camera", "settings.camera": "Camera",
@ -126,6 +136,12 @@
"settings.lastn": "Número de vídeos visíveis", "settings.lastn": "Número de vídeos visíveis",
"settings.hiddenControls": null, "settings.hiddenControls": null,
"settings.notificationSounds": null, "settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"filesharing.saveFileError": "Impossível de gravar o ficheiro", "filesharing.saveFileError": "Impossível de gravar o ficheiro",
"filesharing.startingFileShare": "Tentando partilha de ficheiro", "filesharing.startingFileShare": "Tentando partilha de ficheiro",

View File

@ -59,6 +59,10 @@
"room.raisedHand": null, "room.raisedHand": null,
"room.loweredHand": null, "room.loweredHand": null,
"room.extraVideo": null, "room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -79,6 +83,10 @@
"tooltip.muteParticipant": null, "tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null, "tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Numele camerei", "label.roomName": "Numele camerei",
"label.chooseRoomButton": "Continuare", "label.chooseRoomButton": "Continuare",
@ -92,6 +100,7 @@
"label.filesharing": "Partajarea fișierelor", "label.filesharing": "Partajarea fișierelor",
"label.participants": "Participanți", "label.participants": "Participanți",
"label.shareFile": "Partajează fișierul", "label.shareFile": "Partajează fișierul",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Partajarea fișierelor nu este acceptată", "label.fileSharingUnsupported": "Partajarea fișierelor nu este acceptată",
"label.unknown": "Necunoscut", "label.unknown": "Necunoscut",
"label.democratic": "Distribuție egală a dimensiunii imaginii", "label.democratic": "Distribuție egală a dimensiunii imaginii",
@ -103,10 +112,11 @@
"label.ultra": "Rezoluție ultra înaltă (UHD)", "label.ultra": "Rezoluție ultra înaltă (UHD)",
"label.close": "Închide", "label.close": "Închide",
"label.media": null, "label.media": null,
"label.appearence": null, "label.appearance": null,
"label.advanced": null, "label.advanced": null,
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null,
"settings.settings": "Setări", "settings.settings": "Setări",
"settings.camera": "Cameră video", "settings.camera": "Cameră video",
@ -126,6 +136,12 @@
"settings.lastn": "Numărul de videoclipuri vizibile", "settings.lastn": "Numărul de videoclipuri vizibile",
"settings.hiddenControls": null, "settings.hiddenControls": null,
"settings.notificationSounds": null, "settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"filesharing.saveFileError": "Încercarea de a salva fișierul a eșuat", "filesharing.saveFileError": "Încercarea de a salva fișierul a eșuat",
"filesharing.startingFileShare": "Partajarea fișierului", "filesharing.startingFileShare": "Partajarea fișierului",

View File

@ -59,6 +59,10 @@
"room.raisedHand": null, "room.raisedHand": null,
"room.loweredHand": null, "room.loweredHand": null,
"room.extraVideo": null, "room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -79,6 +83,10 @@
"tooltip.muteParticipant": null, "tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null, "tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Oda adı", "label.roomName": "Oda adı",
"label.chooseRoomButton": "Devam", "label.chooseRoomButton": "Devam",
@ -92,6 +100,7 @@
"label.filesharing": "Dosya paylaşım", "label.filesharing": "Dosya paylaşım",
"label.participants": "Katılımcı", "label.participants": "Katılımcı",
"label.shareFile": "Dosya paylaş", "label.shareFile": "Dosya paylaş",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Dosya paylaşımı desteklenmiyor", "label.fileSharingUnsupported": "Dosya paylaşımı desteklenmiyor",
"label.unknown": "Bilinmeyen", "label.unknown": "Bilinmeyen",
"label.democratic": "Demokratik görünüm", "label.democratic": "Demokratik görünüm",
@ -103,10 +112,11 @@
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Kapat", "label.close": "Kapat",
"label.media": null, "label.media": null,
"label.appearence": null, "label.appearance": null,
"label.advanced": null, "label.advanced": null,
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null,
"settings.settings": "Ayarlar", "settings.settings": "Ayarlar",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -123,6 +133,12 @@
"settings.lastn": "İzlenebilir video sayısı", "settings.lastn": "İzlenebilir video sayısı",
"settings.hiddenControls": null, "settings.hiddenControls": null,
"settings.notificationSounds": null, "settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"filesharing.saveFileError": "Dosya kaydedilemiyor", "filesharing.saveFileError": "Dosya kaydedilemiyor",
"filesharing.startingFileShare": "Paylaşılan dosyaya erişiliyor", "filesharing.startingFileShare": "Paylaşılan dosyaya erişiliyor",

View File

@ -59,6 +59,10 @@
"room.raisedHand": null, "room.raisedHand": null,
"room.loweredHand": null, "room.loweredHand": null,
"room.extraVideo": null, "room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -79,6 +83,10 @@
"tooltip.muteParticipant": null, "tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null, "tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null, "tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Назва кімнати", "label.roomName": "Назва кімнати",
"label.chooseRoomButton": "Продовжити", "label.chooseRoomButton": "Продовжити",
@ -92,6 +100,7 @@
"label.filesharing": "Обмін файлами", "label.filesharing": "Обмін файлами",
"label.participants": "Учасники", "label.participants": "Учасники",
"label.shareFile": "Надіслати файл", "label.shareFile": "Надіслати файл",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Обмін файлами не підтримується", "label.fileSharingUnsupported": "Обмін файлами не підтримується",
"label.unknown": "Невідомо", "label.unknown": "Невідомо",
"label.democrat": "Демократичний вигляд", "label.democrat": "Демократичний вигляд",
@ -103,10 +112,11 @@
"label.ultra": "Ультра (UHD)", "label.ultra": "Ультра (UHD)",
"label.close": "Закрити", "label.close": "Закрити",
"label.media": null, "label.media": null,
"label.appearence": null, "label.appearance": null,
"label.advanced": null, "label.advanced": null,
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null,
"settings.settings": "Налаштування", "settings.settings": "Налаштування",
"settings.camera": "Камера", "settings.camera": "Камера",
@ -126,6 +136,12 @@
"settings.lastn": "Кількість видимих ​​відео", "settings.lastn": "Кількість видимих ​​відео",
"settings.hiddenControls": null, "settings.hiddenControls": null,
"settings.notificationSounds": null, "settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"filesharing.saveFileError": "Неможливо зберегти файл", "filesharing.saveFileError": "Неможливо зберегти файл",
"filesharing.startingFileShare": "Спроба поділитися файлом", "filesharing.startingFileShare": "Спроба поділитися файлом",

11
server/access.js 100644
View File

@ -0,0 +1,11 @@
module.exports = {
// The role(s) will gain access to the room
// even if it is locked (!)
BYPASS_ROOM_LOCK : 'BYPASS_ROOM_LOCK',
// The role(s) will gain access to the room without
// going into the lobby. If you want to restrict access to your
// server to only directly allow authenticated users, you could
// add the userRoles.AUTHENTICATED to the user in the userMapping
// function, and change to BYPASS_LOBBY : [ userRoles.AUTHENTICATED ]
BYPASS_LOBBY : 'BYPASS_LOBBY'
};

View File

@ -1,5 +1,23 @@
const os = require('os'); const os = require('os');
const userRoles = require('../userRoles'); const userRoles = require('../userRoles');
const {
BYPASS_ROOM_LOCK,
BYPASS_LOBBY
} = require('../access');
const {
CHANGE_ROOM_LOCK,
PROMOTE_PEER,
SEND_CHAT,
MODERATE_CHAT,
SHARE_SCREEN,
EXTRA_VIDEO,
SHARE_FILE,
MODERATE_FILES,
MODERATE_ROOM
} = require('../permissions');
// const AwaitQueue = require('awaitqueue'); // const AwaitQueue = require('awaitqueue');
// const axios = require('axios'); // const axios = require('axios');
@ -96,8 +114,8 @@ module.exports =
this._queue = new AwaitQueue(); this._queue = new AwaitQueue();
} }
// rooms: number of rooms // rooms: rooms object
// peers: number of peers // peers: peers object
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
async log({ rooms, peers }) async log({ rooms, peers })
{ {
@ -106,9 +124,9 @@ module.exports =
// Do your logging in here, use queue to keep correct order // Do your logging in here, use queue to keep correct order
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('Number of rooms: ', rooms); console.log('Number of rooms: ', rooms.size);
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('Number of peers: ', peers); console.log('Number of peers: ', peers.size);
}) })
.catch((error) => .catch((error) =>
{ {
@ -216,39 +234,50 @@ module.exports =
accessFromRoles : { accessFromRoles : {
// The role(s) will gain access to the room // The role(s) will gain access to the room
// even if it is locked (!) // even if it is locked (!)
BYPASS_ROOM_LOCK : [ userRoles.ADMIN ], [BYPASS_ROOM_LOCK] : [ userRoles.ADMIN ],
// The role(s) will gain access to the room without // The role(s) will gain access to the room without
// going into the lobby. If you want to restrict access to your // going into the lobby. If you want to restrict access to your
// server to only directly allow authenticated users, you could // server to only directly allow authenticated users, you could
// add the userRoles.AUTHENTICATED to the user in the userMapping // add the userRoles.AUTHENTICATED to the user in the userMapping
// function, and change to BYPASS_LOBBY : [ userRoles.AUTHENTICATED ] // function, and change to BYPASS_LOBBY : [ userRoles.AUTHENTICATED ]
BYPASS_LOBBY : [ userRoles.NORMAL ] [BYPASS_LOBBY] : [ userRoles.NORMAL ]
}, },
permissionsFromRoles : { permissionsFromRoles : {
// The role(s) have permission to lock/unlock a room // The role(s) have permission to lock/unlock a room
CHANGE_ROOM_LOCK : [ userRoles.NORMAL ], [CHANGE_ROOM_LOCK] : [ userRoles.MODERATOR ],
// The role(s) have permission to promote a peer from the lobby // The role(s) have permission to promote a peer from the lobby
PROMOTE_PEER : [ userRoles.NORMAL ], [PROMOTE_PEER] : [ userRoles.NORMAL ],
// The role(s) have permission to send chat messages // The role(s) have permission to send chat messages
SEND_CHAT : [ userRoles.NORMAL ], [SEND_CHAT] : [ userRoles.NORMAL ],
// The role(s) have permission to moderate chat // The role(s) have permission to moderate chat
MODERATE_CHAT : [ userRoles.MODERATOR ], [MODERATE_CHAT] : [ userRoles.MODERATOR ],
// The role(s) have permission to share screen // The role(s) have permission to share screen
SHARE_SCREEN : [ userRoles.NORMAL ], [SHARE_SCREEN] : [ userRoles.NORMAL ],
// The role(s) have permission to produce extra video // The role(s) have permission to produce extra video
EXTRA_VIDEO : [ userRoles.NORMAL ], [EXTRA_VIDEO] : [ userRoles.NORMAL ],
// The role(s) have permission to share files // The role(s) have permission to share files
SHARE_FILE : [ userRoles.NORMAL ], [SHARE_FILE] : [ userRoles.NORMAL ],
// The role(s) have permission to moderate files // The role(s) have permission to moderate files
MODERATE_FILES : [ userRoles.MODERATOR ], [MODERATE_FILES] : [ userRoles.MODERATOR ],
// The role(s) have permission to moderate room (e.g. kick user) // The role(s) have permission to moderate room (e.g. kick user)
MODERATE_ROOM : [ userRoles.MODERATOR ] [MODERATE_ROOM] : [ userRoles.MODERATOR ]
}, },
// Array of permissions. If no peer with the permission in question
// is in the room, all peers are permitted to do the action. The peers
// that are allowed because of this rule will not be able to do this
// action as soon as a peer with the permission joins. In this example
// everyone will be able to lock/unlock room until a MODERATOR joins.
allowWhenRoleMissing : [ CHANGE_ROOM_LOCK ],
// When truthy, the room will be open to all users when as long as there // When truthy, the room will be open to all users when as long as there
// are allready users in the room // are allready users in the room
activateOnHostJoin : true, activateOnHostJoin : true,
// When set, maxUsersPerRoom defines how many users can join
// a single room. If not set, there is no limit.
// maxUsersPerRoom : 20,
// Room size before spreading to new router
routerScaleSize : 40,
// Mediasoup settings // Mediasoup settings
mediasoup : mediasoup :
{ {
numWorkers : Object.keys(os.cpus()).length, numWorkers : Object.keys(os.cpus()).length,
// mediasoup Worker settings. // mediasoup Worker settings.

View File

@ -46,7 +46,7 @@ class Lobby extends EventEmitter
return Object.values(this._peers).map((peer) => return Object.values(this._peers).map((peer) =>
({ ({
peerId : peer.id, id : peer.id,
displayName : peer.displayName, displayName : peer.displayName,
picture : peer.picture picture : peer.picture
})); }));
@ -154,8 +154,6 @@ class Lobby extends EventEmitter
this.emit('lobbyEmpty'); this.emit('lobbyEmpty');
}; };
this._notification(peer.socket, 'enteredLobby');
this._peers[peer.id] = peer; this._peers[peer.id] = peer;
peer.on('gotRole', peer.gotRoleHandler); peer.on('gotRole', peer.gotRoleHandler);
@ -165,6 +163,8 @@ class Lobby extends EventEmitter
peer.socket.on('request', peer.socketRequestHandler); peer.socket.on('request', peer.socketRequestHandler);
peer.on('close', peer.closeHandler); peer.on('close', peer.closeHandler);
this._notification(peer.socket, 'enteredLobby');
} }
async _handleSocketRequest(peer, request, cb) async _handleSocketRequest(peer, request, cb)
@ -189,7 +189,8 @@ class Lobby extends EventEmitter
cb(); cb();
break; break;
} }
case 'changePicture': case 'changePicture':
{ {
const { picture } = request.data; const { picture } = request.data;

View File

@ -39,6 +39,8 @@ class Peer extends EventEmitter
this._email = null; this._email = null;
this._routerId = null;
this._rtpCapabilities = null; this._rtpCapabilities = null;
this._raisedHand = false; this._raisedHand = false;
@ -62,10 +64,10 @@ class Peer extends EventEmitter
// Iterate and close all mediasoup Transport associated to this Peer, so all // Iterate and close all mediasoup Transport associated to this Peer, so all
// its Producers and Consumers will also be closed. // its Producers and Consumers will also be closed.
this.transports.forEach((transport) => for (const transport of this.transports.values())
{ {
transport.close(); transport.close();
}); }
if (this.socket) if (this.socket)
this.socket.disconnect(true); this.socket.disconnect(true);
@ -238,6 +240,16 @@ class Peer extends EventEmitter
this._email = email; this._email = email;
} }
get routerId()
{
return this._routerId;
}
set routerId(routerId)
{
this._routerId = routerId;
}
get rtpCapabilities() get rtpCapabilities()
{ {
return this._rtpCapabilities; return this._rtpCapabilities;

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
"license": "MIT", "license": "MIT",
"main": "lib/index.js", "main": "lib/index.js",
"scripts": { "scripts": {
"start": "DEBUG=${DEBUG:='*mediasoup* *INFO* *WARN* *ERROR*'} INTERACTIVE=${INTERACTIVE:='true'} node server.js", "start": "node server.js",
"connect": "node connect.js", "connect": "node connect.js",
"lint": "eslint -c .eslintrc.json --ext .js *.js lib/" "lint": "eslint -c .eslintrc.json --ext .js *.js lib/"
}, },

View File

@ -0,0 +1,20 @@
module.exports = {
// The role(s) have permission to lock/unlock a room
CHANGE_ROOM_LOCK : 'CHANGE_ROOM_LOCK',
// The role(s) have permission to promote a peer from the lobby
PROMOTE_PEER : 'PROMOTE_PEER',
// The role(s) have permission to send chat messages
SEND_CHAT : 'SEND_CHAT',
// The role(s) have permission to moderate chat
MODERATE_CHAT : 'MODERATE_CHAT',
// The role(s) have permission to share screen
SHARE_SCREEN : 'SHARE_SCREEN',
// The role(s) have permission to produce extra video
EXTRA_VIDEO : 'EXTRA_VIDEO',
// The role(s) have permission to share files
SHARE_FILE : 'SHARE_FILE',
// The role(s) have permission to moderate files
MODERATE_FILES : 'MODERATE_FILES',
// The role(s) have permission to moderate room (e.g. kick user)
MODERATE_ROOM : 'MODERATE_ROOM'
};

View File

@ -35,6 +35,7 @@ const RedisStore = require('connect-redis')(expressSession);
const sharedSession = require('express-socket.io-session'); const sharedSession = require('express-socket.io-session');
const interactiveServer = require('./lib/interactiveServer'); const interactiveServer = require('./lib/interactiveServer');
const promExporter = require('./lib/promExporter'); const promExporter = require('./lib/promExporter');
const { v4: uuidv4 } = require('uuid');
/* eslint-disable no-console */ /* eslint-disable no-console */
console.log('- process.env.DEBUG:', process.env.DEBUG); console.log('- process.env.DEBUG:', process.env.DEBUG);
@ -55,10 +56,6 @@ if ('StatusLogger' in config)
// @type {Array<mediasoup.Worker>} // @type {Array<mediasoup.Worker>}
const mediasoupWorkers = []; const mediasoupWorkers = [];
// Index of next mediasoup Worker to use.
// @type {Number}
let nextMediasoupWorkerIdx = 0;
// Map of Room instances indexed by roomId. // Map of Room instances indexed by roomId.
const rooms = new Map(); const rooms = new Map();
@ -130,50 +127,58 @@ let oidcStrategy;
async function run() async function run()
{ {
// Open the interactive server. try
await interactiveServer(rooms, peers);
// start Prometheus exporter
if (config.prometheus)
{ {
await promExporter(rooms, peers, config.prometheus); // Open the interactive server.
} await interactiveServer(rooms, peers);
if (typeof(config.auth) === 'undefined') // start Prometheus exporter
{ if (config.prometheus)
logger.warn('Auth is not configured properly!');
}
else
{
await setupAuth();
}
// Run a mediasoup Worker.
await runMediasoupWorkers();
// Run HTTPS server.
await runHttpsServer();
// Run WebSocketServer.
await runWebSocketServer();
// Log rooms status every 30 seconds.
setInterval(() =>
{
for (const room of rooms.values())
{ {
room.logStatus(); await promExporter(rooms, peers, config.prometheus);
} }
}, 120000);
// check for deserted rooms if (typeof(config.auth) === 'undefined')
setInterval(() =>
{
for (const room of rooms.values())
{ {
room.checkEmpty(); logger.warn('Auth is not configured properly!');
} }
}, 10000); else
{
await setupAuth();
}
// Run a mediasoup Worker.
await runMediasoupWorkers();
// Run HTTPS server.
await runHttpsServer();
// Run WebSocketServer.
await runWebSocketServer();
const errorHandler = (err, req, res, next) =>
{
const trackingId = uuidv4();
res.status(500).send(
`<h1>Internal Server Error</h1>
<p>If you report this error, please also report this
<i>tracking ID</i> which makes it possible to locate your session
in the logs which are available to the system administrator:
<b>${trackingId}</b></p>`
);
logger.error(
'Express error handler dump with tracking ID: %s, error dump: %o',
trackingId, err);
};
// eslint-disable-next-line no-unused-vars
app.use(errorHandler);
}
catch (error)
{
logger.error('run() [error:"%o"]', error);
}
} }
function statusLog() function statusLog()
@ -181,8 +186,8 @@ function statusLog()
if (statusLogger) if (statusLogger)
{ {
statusLogger.log({ statusLogger.log({
rooms : rooms.size, rooms : rooms,
peers : peers.size peers : peers
}); });
} }
} }
@ -363,38 +368,45 @@ async function setupAuth()
app.get( app.get(
'/auth/callback', '/auth/callback',
passport.authenticate('oidc', { failureRedirect: '/auth/login' }), passport.authenticate('oidc', { failureRedirect: '/auth/login' }),
async (req, res) => async (req, res, next) =>
{ {
const state = JSON.parse(base64.decode(req.query.state)); try
const { peerId, roomId } = state;
req.session.peerId = peerId;
req.session.roomId = roomId;
let peer = peers.get(peerId);
if (!peer) // User has no socket session yet, make temporary
peer = new Peer({ id: peerId, roomId });
if (peer.roomId !== roomId) // The peer is mischievous
throw new Error('peer authenticated with wrong room');
if (typeof config.userMapping === 'function')
{ {
await config.userMapping({ const state = JSON.parse(base64.decode(req.query.state));
peer,
roomId, const { peerId, roomId } = state;
userinfo : req.user._userinfo
}); req.session.peerId = peerId;
req.session.roomId = roomId;
let peer = peers.get(peerId);
if (!peer) // User has no socket session yet, make temporary
peer = new Peer({ id: peerId, roomId });
if (peer.roomId !== roomId) // The peer is mischievous
throw new Error('peer authenticated with wrong room');
if (typeof config.userMapping === 'function')
{
await config.userMapping({
peer,
roomId,
userinfo : req.user._userinfo
});
}
peer.authenticated = true;
res.send(loginHelper({
displayName : peer.displayName,
picture : peer.picture
}));
}
catch (error)
{
return next(error);
} }
peer.authenticated = true;
res.send(loginHelper({
displayName : peer.displayName,
picture : peer.picture
}));
} }
); );
} }
@ -581,7 +593,8 @@ async function runWebSocketServer()
{ {
logger.error('room creation or room joining failed [error:"%o"]', error); logger.error('room creation or room joining failed [error:"%o"]', error);
socket.disconnect(true); if (socket)
socket.disconnect(true);
return; return;
}); });
@ -619,19 +632,6 @@ async function runMediasoupWorkers()
} }
} }
/**
* Get next mediasoup Worker.
*/
function getMediasoupWorker()
{
const worker = mediasoupWorkers[nextMediasoupWorkerIdx];
if (++nextMediasoupWorkerIdx === mediasoupWorkers.length)
nextMediasoupWorkerIdx = 0;
return worker;
}
/** /**
* Get a Room instance (or create one if it does not exist). * Get a Room instance (or create one if it does not exist).
*/ */
@ -644,9 +644,9 @@ async function getOrCreateRoom({ roomId })
{ {
logger.info('creating a new Room [roomId:"%s"]', roomId); logger.info('creating a new Room [roomId:"%s"]', roomId);
const mediasoupWorker = getMediasoupWorker(); // const mediasoupWorker = getMediasoupWorker();
room = await Room.create({ mediasoupWorker, roomId }); room = await Room.create({ mediasoupWorkers, roomId });
rooms.set(roomId, room); rooms.set(roomId, room);