Merge remote-tracking branch 'upstream/develop' into browse_passive_users
commit
122c21a632
|
|
@ -27,6 +27,7 @@
|
|||
"react": "^16.10.2",
|
||||
"react-cookie-consent": "^2.5.0",
|
||||
"react-dom": "^16.10.2",
|
||||
"react-flip-toolkit": "^7.0.9",
|
||||
"react-intl": "^3.4.0",
|
||||
"react-redux": "^7.1.1",
|
||||
"react-router-dom": "^5.1.2",
|
||||
|
|
|
|||
|
|
@ -42,14 +42,32 @@ var config =
|
|||
{
|
||||
tcp : true
|
||||
},
|
||||
defaultAudio :
|
||||
{
|
||||
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
|
||||
maxLastN : 5,
|
||||
// If truthy, users can NOT change number of speakers visible
|
||||
lockLastN : false,
|
||||
background : 'images/background.jpg',
|
||||
// Add file and uncomment for adding logo to appbar
|
||||
// logo : 'images/logo.svg',
|
||||
title : 'Multiparty meeting',
|
||||
|
|
|
|||
|
|
@ -129,7 +129,8 @@ export default class RoomClient
|
|||
produce,
|
||||
forceTcp,
|
||||
displayName,
|
||||
muted
|
||||
muted,
|
||||
basePath
|
||||
} = {})
|
||||
{
|
||||
if (!peerId)
|
||||
|
|
@ -152,6 +153,9 @@ export default class RoomClient
|
|||
// Whether we force TCP
|
||||
this._forceTcp = forceTcp;
|
||||
|
||||
// URL basepath
|
||||
this._basePath = basePath;
|
||||
|
||||
// Use displayName
|
||||
if (displayName)
|
||||
store.dispatch(settingsActions.setDisplayName(displayName));
|
||||
|
|
@ -414,6 +418,13 @@ export default class RoomClient
|
|||
break;
|
||||
}
|
||||
|
||||
case 'H': // Open help dialog
|
||||
{
|
||||
store.dispatch(roomActions.setHelpOpen(true));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
break;
|
||||
|
|
@ -485,9 +496,9 @@ export default class RoomClient
|
|||
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)
|
||||
|
|
@ -946,95 +957,45 @@ export default class RoomClient
|
|||
{
|
||||
if (consumer.kind === 'video')
|
||||
{
|
||||
if (spotlights.indexOf(consumer.appData.peerId) > -1)
|
||||
{
|
||||
if (spotlights.includes(consumer.appData.peerId))
|
||||
await this._resumeConsumer(consumer);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this._pauseConsumer(consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('updateSpotlights() failed: %o', error);
|
||||
}
|
||||
}
|
||||
|
||||
async getAudioTrack()
|
||||
disconnectLocalHark()
|
||||
{
|
||||
await navigator.mediaDevices.getUserMedia(
|
||||
{
|
||||
audio : true, video : false
|
||||
});
|
||||
}
|
||||
|
||||
async getVideoTrack()
|
||||
{
|
||||
await navigator.mediaDevices.getUserMedia(
|
||||
{
|
||||
audio : false, video : true
|
||||
});
|
||||
}
|
||||
|
||||
async changeAudioDevice(deviceId)
|
||||
{
|
||||
logger.debug('changeAudioDevice() [deviceId: %s]', deviceId);
|
||||
|
||||
store.dispatch(
|
||||
meActions.setAudioInProgress(true));
|
||||
|
||||
try
|
||||
{
|
||||
const device = this._audioDevices[deviceId];
|
||||
|
||||
if (!device)
|
||||
throw new Error('no audio devices');
|
||||
|
||||
logger.debug(
|
||||
'changeAudioDevice() | new selected webcam [device:%o]',
|
||||
device);
|
||||
|
||||
if (this._hark != null)
|
||||
this._hark.stop();
|
||||
|
||||
logger.debug('disconnectLocalHark() | Stopping harkStream.');
|
||||
if (this._harkStream != null)
|
||||
{
|
||||
logger.debug('Stopping hark.');
|
||||
this._harkStream.getAudioTracks()[0].stop();
|
||||
this._harkStream = null;
|
||||
}
|
||||
|
||||
if (this._micProducer && this._micProducer.track)
|
||||
this._micProducer.track.stop();
|
||||
|
||||
logger.debug('changeAudioDevice() | calling getUserMedia()');
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia(
|
||||
if (this._hark != null)
|
||||
{
|
||||
audio :
|
||||
{
|
||||
deviceId : { exact: device.deviceId }
|
||||
logger.debug('disconnectLocalHark() Stopping hark.');
|
||||
this._hark.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const track = stream.getAudioTracks()[0];
|
||||
|
||||
if (this._micProducer)
|
||||
await this._micProducer.replaceTrack({ track });
|
||||
|
||||
if (this._micProducer)
|
||||
this._micProducer.volume = 0;
|
||||
|
||||
connectLocalHark(track)
|
||||
{
|
||||
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('changeAudioDevice(): given stream has no audio track');
|
||||
throw new Error('getMicStream():something went wrong with hark');
|
||||
|
||||
this._hark = hark(this._harkStream, { play: false });
|
||||
|
||||
|
|
@ -1043,7 +1004,7 @@ export default class RoomClient
|
|||
{
|
||||
// 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
|
||||
// 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);
|
||||
|
|
@ -1068,6 +1029,59 @@ export default class RoomClient
|
|||
{
|
||||
store.dispatch(meActions.setIsSpeaking(false));
|
||||
});
|
||||
}
|
||||
|
||||
async changeAudioDevice(deviceId)
|
||||
{
|
||||
logger.debug('changeAudioDevice() [deviceId: %s]', deviceId);
|
||||
|
||||
store.dispatch(
|
||||
meActions.setAudioInProgress(true));
|
||||
|
||||
try
|
||||
{
|
||||
const device = this._audioDevices[deviceId];
|
||||
|
||||
if (!device)
|
||||
throw new Error('no audio devices');
|
||||
|
||||
logger.debug(
|
||||
'changeAudioDevice() | new selected webcam [device:%o]',
|
||||
device);
|
||||
|
||||
this.disconnectLocalHark();
|
||||
|
||||
if (this._micProducer && this._micProducer.track)
|
||||
this._micProducer.track.stop();
|
||||
|
||||
logger.debug('changeAudioDevice() | calling getUserMedia() %o', store.getState().settings);
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia(
|
||||
{
|
||||
audio :
|
||||
{
|
||||
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];
|
||||
|
||||
if (this._micProducer)
|
||||
await this._micProducer.replaceTrack({ track });
|
||||
|
||||
if (this._micProducer)
|
||||
this._micProducer.volume = 0;
|
||||
this.connectLocalHark(track);
|
||||
|
||||
if (this._micProducer && this._micProducer.id)
|
||||
store.dispatch(
|
||||
producerActions.setProducerTrack(this._micProducer.id, track));
|
||||
|
|
@ -1388,6 +1402,46 @@ export default class RoomClient
|
|||
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()
|
||||
{
|
||||
logger.debug('muteAllPeers()');
|
||||
|
|
@ -1475,9 +1529,7 @@ export default class RoomClient
|
|||
if (consumer.appData.peerId === peerId && consumer.appData.source === type)
|
||||
{
|
||||
if (mute)
|
||||
{
|
||||
await this._pauseConsumer(consumer);
|
||||
}
|
||||
else
|
||||
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)
|
||||
{
|
||||
logger.debug('setRaisedHand: ', raisedHand);
|
||||
|
|
@ -1785,6 +1857,11 @@ export default class RoomClient
|
|||
this._recvTransport = null;
|
||||
}
|
||||
|
||||
this._spotlights.clearSpotlights();
|
||||
|
||||
store.dispatch(peerActions.clearPeers());
|
||||
store.dispatch(consumerActions.clearConsumers());
|
||||
store.dispatch(roomActions.clearSpotlights());
|
||||
store.dispatch(roomActions.setRoomState('connecting'));
|
||||
});
|
||||
|
||||
|
|
@ -1973,6 +2050,13 @@ export default class RoomClient
|
|||
break;
|
||||
}
|
||||
|
||||
case 'overRoomLimit':
|
||||
{
|
||||
store.dispatch(roomActions.setOverRoomLimit(true));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'roomReady':
|
||||
{
|
||||
const { turnServers } = notification.data;
|
||||
|
|
@ -2057,15 +2141,21 @@ export default class RoomClient
|
|||
lobbyPeers.forEach((peer) =>
|
||||
{
|
||||
store.dispatch(
|
||||
lobbyPeerActions.addLobbyPeer(peer.peerId));
|
||||
lobbyPeerActions.addLobbyPeer(peer.id));
|
||||
|
||||
store.dispatch(
|
||||
lobbyPeerActions.setLobbyPeerDisplayName(
|
||||
peer.displayName,
|
||||
peer.peerId
|
||||
peer.id
|
||||
)
|
||||
);
|
||||
|
||||
store.dispatch(
|
||||
lobbyPeerActions.setLobbyPeerPicture(peer.picture));
|
||||
lobbyPeerActions.setLobbyPeerPicture(
|
||||
peer.picture,
|
||||
peer.id
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
store.dispatch(
|
||||
|
|
@ -2493,8 +2583,6 @@ export default class RoomClient
|
|||
|
||||
case 'moderator:mute':
|
||||
{
|
||||
// const { peerId } = notification.data;
|
||||
|
||||
if (this._micProducer && !this._micProducer.paused)
|
||||
{
|
||||
this.muteMic();
|
||||
|
|
@ -2513,8 +2601,6 @@ export default class RoomClient
|
|||
|
||||
case 'moderator:stopVideo':
|
||||
{
|
||||
// const { peerId } = notification.data;
|
||||
|
||||
this.disableWebcam();
|
||||
this.disableScreenSharing();
|
||||
|
||||
|
|
@ -2537,6 +2623,13 @@ export default class RoomClient
|
|||
break;
|
||||
}
|
||||
|
||||
case 'moderator:lowerHand':
|
||||
{
|
||||
this.setRaisedHand(false);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'gotRole':
|
||||
{
|
||||
const { peerId, role } = notification.data;
|
||||
|
|
@ -2772,8 +2865,8 @@ export default class RoomClient
|
|||
roles,
|
||||
peers,
|
||||
tracker,
|
||||
permissionsFromRoles,
|
||||
userRoles,
|
||||
roomPermissions,
|
||||
allowWhenRoleMissing,
|
||||
chatHistory,
|
||||
fileHistory,
|
||||
lastNHistory,
|
||||
|
|
@ -2799,8 +2892,10 @@ export default class RoomClient
|
|||
|
||||
store.dispatch(meActions.loggedIn(authenticated));
|
||||
|
||||
store.dispatch(roomActions.setUserRoles(userRoles));
|
||||
store.dispatch(roomActions.setPermissionsFromRoles(permissionsFromRoles));
|
||||
store.dispatch(roomActions.setRoomPermissions(roomPermissions));
|
||||
|
||||
if (allowWhenRoleMissing)
|
||||
store.dispatch(roomActions.setAllowWhenRoleMissing(allowWhenRoleMissing));
|
||||
|
||||
const myRoles = store.getState().me.roles;
|
||||
|
||||
|
|
@ -2814,7 +2909,9 @@ export default class RoomClient
|
|||
{
|
||||
text : intl.formatMessage({
|
||||
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) =>
|
||||
{
|
||||
store.dispatch(
|
||||
lobbyPeerActions.addLobbyPeer(peer.peerId));
|
||||
lobbyPeerActions.addLobbyPeer(peer.id));
|
||||
store.dispatch(
|
||||
lobbyPeerActions.setLobbyPeerDisplayName(peer.displayName, peer.peerId));
|
||||
lobbyPeerActions.setLobbyPeerDisplayName(peer.displayName, peer.id));
|
||||
store.dispatch(
|
||||
lobbyPeerActions.setLobbyPeerPicture(peer.picture));
|
||||
lobbyPeerActions.setLobbyPeerPicture(peer.picture, peer.id));
|
||||
});
|
||||
|
||||
(accessCode != null) && store.dispatch(
|
||||
|
|
@ -3236,11 +3333,20 @@ export default class RoomClient
|
|||
const stream = await navigator.mediaDevices.getUserMedia(
|
||||
{
|
||||
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];
|
||||
|
||||
this._micProducer = await this._sendTransport.produce(
|
||||
|
|
@ -3294,51 +3400,8 @@ export default class RoomClient
|
|||
|
||||
this._micProducer.volume = 0;
|
||||
|
||||
if (this._hark != null)
|
||||
this._hark.stop();
|
||||
this.connectLocalHark(track);
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -225,10 +225,7 @@ export default class ScreenShare
|
|||
return new DisplayMediaScreenShare();
|
||||
}
|
||||
case 'chrome':
|
||||
{
|
||||
return new DisplayMediaScreenShare();
|
||||
}
|
||||
case 'msedge':
|
||||
case 'edge':
|
||||
{
|
||||
return new DisplayMediaScreenShare();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,6 +156,15 @@ export default class Spotlights extends EventEmitter
|
|||
});
|
||||
}
|
||||
|
||||
clearSpotlights()
|
||||
{
|
||||
this._started = false;
|
||||
|
||||
this._peerList = [];
|
||||
this._selectedSpotlights = [];
|
||||
this._currentSpotlights = [];
|
||||
}
|
||||
|
||||
_newPeer(id)
|
||||
{
|
||||
logger.debug(
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ export const removeConsumer = (consumerId, peerId) =>
|
|||
payload : { consumerId, peerId }
|
||||
});
|
||||
|
||||
export const clearConsumers = () =>
|
||||
({
|
||||
type : 'CLEAR_CONSUMERS'
|
||||
});
|
||||
|
||||
export const setConsumerPaused = (consumerId, originator) =>
|
||||
({
|
||||
type : 'SET_CONSUMER_PAUSED',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ export const removePeer = (peerId) =>
|
|||
payload : { peerId }
|
||||
});
|
||||
|
||||
export const clearPeers = () =>
|
||||
({
|
||||
type : 'CLEAR_PEERS'
|
||||
});
|
||||
|
||||
export const setPeerDisplayName = (displayName, peerId) =>
|
||||
({
|
||||
type : 'SET_PEER_DISPLAY_NAME',
|
||||
|
|
@ -40,6 +45,12 @@ export const setPeerRaisedHand = (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) =>
|
||||
({
|
||||
type : 'SET_PEER_PICTURE',
|
||||
|
|
@ -63,3 +74,15 @@ export const setPeerKickInProgress = (peerId, flag) =>
|
|||
type : 'SET_PEER_KICK_IN_PROGRESS',
|
||||
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 }
|
||||
});
|
||||
|
|
|
|||
|
|
@ -40,6 +40,12 @@ export const setSignInRequired = (signInRequired) =>
|
|||
payload : { signInRequired }
|
||||
});
|
||||
|
||||
export const setOverRoomLimit = (overRoomLimit) =>
|
||||
({
|
||||
type : 'SET_OVER_ROOM_LIMIT',
|
||||
payload : { overRoomLimit }
|
||||
});
|
||||
|
||||
export const setAccessCode = (accessCode) =>
|
||||
({
|
||||
type : 'SET_ACCESS_CODE',
|
||||
|
|
@ -64,6 +70,18 @@ export const setExtraVideoOpen = (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) =>
|
||||
({
|
||||
type : 'SET_SETTINGS_TAB',
|
||||
|
|
@ -112,6 +130,11 @@ export const setSpotlights = (spotlights) =>
|
|||
payload : { spotlights }
|
||||
});
|
||||
|
||||
export const clearSpotlights = () =>
|
||||
({
|
||||
type : 'CLEAR_SPOTLIGHTS'
|
||||
});
|
||||
|
||||
export const toggleJoined = () =>
|
||||
({
|
||||
type : 'TOGGLE_JOINED'
|
||||
|
|
@ -159,14 +182,14 @@ export const setClearFileSharingInProgress = (flag) =>
|
|||
payload : { flag }
|
||||
});
|
||||
|
||||
export const setUserRoles = (userRoles) =>
|
||||
export const setRoomPermissions = (roomPermissions) =>
|
||||
({
|
||||
type : 'SET_USER_ROLES',
|
||||
payload : { userRoles }
|
||||
type : 'SET_ROOM_PERMISSIONS',
|
||||
payload : { roomPermissions }
|
||||
});
|
||||
|
||||
export const setPermissionsFromRoles = (permissionsFromRoles) =>
|
||||
export const setAllowWhenRoleMissing = (allowWhenRoleMissing) =>
|
||||
({
|
||||
type : 'SET_PERMISSIONS_FROM_ROLES',
|
||||
payload : { permissionsFromRoles }
|
||||
type : 'SET_ALLOW_WHEN_ROLE_MISSING',
|
||||
payload : { allowWhenRoleMissing }
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,6 +38,60 @@ export const togglePermanentTopBar = () =>
|
|||
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 = () =>
|
||||
({
|
||||
type : 'TOGGLE_HIDDEN_CONTROLS'
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import PropTypes from 'prop-types';
|
|||
import classnames from 'classnames';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { permissions } from '../../../permissions';
|
||||
import { makePermissionSelector } from '../../Selectors';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
|
|
@ -61,6 +63,7 @@ const ListLobbyPeer = (props) =>
|
|||
peer.promotionInProgress ||
|
||||
promotionInProgress
|
||||
}
|
||||
color='primary'
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
|
@ -84,28 +87,32 @@ ListLobbyPeer.propTypes =
|
|||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, { id }) =>
|
||||
const makeMapStateToProps = (initialState, { id }) =>
|
||||
{
|
||||
const hasPermission = makePermissionSelector(permissions.PROMOTE_PEER);
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
peer : state.lobbyPeers[id],
|
||||
promotionInProgress : state.room.lobbyPeersPromotionInProgress,
|
||||
canPromote :
|
||||
state.me.roles.some((role) =>
|
||||
state.room.permissionsFromRoles.PROMOTE_PEER.includes(role))
|
||||
canPromote : hasPermission(state)
|
||||
};
|
||||
};
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
makeMapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.room.permissionsFromRoles === next.room.permissionsFromRoles &&
|
||||
prev.room.lobbyPeersPromotionInProgress ===
|
||||
next.room.lobbyPeersPromotionInProgress &&
|
||||
prev.room === next.room &&
|
||||
prev.peers === next.peers && // For checking permissions
|
||||
prev.me.roles === next.me.roles &&
|
||||
prev.lobbyPeers === next.lobbyPeers
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
lobbyPeersKeySelector
|
||||
lobbyPeersKeySelector,
|
||||
makePermissionSelector
|
||||
} from '../../Selectors';
|
||||
import { permissions } from '../../../permissions';
|
||||
import * as appPropTypes from '../../appPropTypes';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
|
|
@ -140,24 +142,29 @@ LockDialog.propTypes =
|
|||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const makeMapStateToProps = () =>
|
||||
{
|
||||
const hasPermission = makePermissionSelector(permissions.PROMOTE_PEER);
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
room : state.room,
|
||||
lobbyPeers : lobbyPeersKeySelector(state),
|
||||
canPromote :
|
||||
state.me.roles.some((role) =>
|
||||
state.room.permissionsFromRoles.PROMOTE_PEER.includes(role))
|
||||
canPromote : hasPermission(state)
|
||||
};
|
||||
};
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
handleCloseLockDialog : roomActions.setLockDialogOpen,
|
||||
handleAccessCode : roomActions.setAccessCode
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
makeMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
{
|
||||
|
|
@ -166,6 +173,7 @@ export default withRoomContext(connect(
|
|||
return (
|
||||
prev.room === next.room &&
|
||||
prev.me.roles === next.me.roles &&
|
||||
prev.peers === next.peers &&
|
||||
prev.lobbyPeers === next.lobbyPeers
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import React, { useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { meProducersSelector } from '../Selectors';
|
||||
import {
|
||||
meProducersSelector,
|
||||
makePermissionSelector
|
||||
} from '../Selectors';
|
||||
import { permissions } from '../../permissions';
|
||||
import { withRoomContext } from '../../RoomContext';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import PropTypes from 'prop-types';
|
||||
|
|
@ -10,6 +14,7 @@ import { useIntl, FormattedMessage } from 'react-intl';
|
|||
import VideoView from '../VideoContainers/VideoView';
|
||||
import Volume from './Volume';
|
||||
import Fab from '@material-ui/core/Fab';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import MicIcon from '@material-ui/icons/Mic';
|
||||
import MicOffIcon from '@material-ui/icons/MicOff';
|
||||
|
|
@ -59,12 +64,47 @@ const styles = (theme) =>
|
|||
margin : theme.spacing(1),
|
||||
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 :
|
||||
{
|
||||
position : 'relative',
|
||||
width : '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 :
|
||||
{
|
||||
position : 'absolute',
|
||||
|
|
@ -86,35 +126,18 @@ const styles = (theme) =>
|
|||
'&.hover' :
|
||||
{
|
||||
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 :
|
||||
{
|
||||
position : 'absolute',
|
||||
float : 'left',
|
||||
top : '10%',
|
||||
top : '25%',
|
||||
left : '50%',
|
||||
transform : 'translate(-50%, 0%)',
|
||||
color : 'rgba(255, 255, 255, 0.7)',
|
||||
fontSize : '2vs',
|
||||
backgroundColor : 'rgba(255, 0, 0, 0.5)',
|
||||
fontSize : '1.3em',
|
||||
backgroundColor : 'rgba(255, 0, 0, 0.9)',
|
||||
margin : '4px',
|
||||
padding : theme.spacing(2),
|
||||
zIndex : 31,
|
||||
|
|
@ -145,7 +168,7 @@ const Me = (props) =>
|
|||
activeSpeaker,
|
||||
spacing,
|
||||
style,
|
||||
smallButtons,
|
||||
smallContainer,
|
||||
advancedMode,
|
||||
micProducer,
|
||||
webcamProducer,
|
||||
|
|
@ -267,6 +290,28 @@ const Me = (props) =>
|
|||
'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 (
|
||||
<React.Fragment>
|
||||
<div
|
||||
|
|
@ -299,7 +344,8 @@ const Me = (props) =>
|
|||
}}
|
||||
style={spacingStyle}
|
||||
>
|
||||
<div className={classes.viewContainer} style={style}>
|
||||
|
||||
{ me.browser.platform !== 'mobile' &&
|
||||
<div className={classnames(
|
||||
classes.ptt,
|
||||
(micState === 'muted' && me.isSpeaking) ? 'enabled' : null
|
||||
|
|
@ -310,6 +356,21 @@ const Me = (props) =>
|
|||
defaultMessage='You are muted, hold down SPACE-BAR to talk'
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
<div className={classes.viewContainer} style={style}>
|
||||
<p className={
|
||||
classnames(
|
||||
classes.meTag,
|
||||
hover ? 'hover' : null,
|
||||
smallContainer ? 'smallContainer' : null
|
||||
)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='room.me'
|
||||
defaultMessage='ME'
|
||||
/>
|
||||
</p>
|
||||
{ !settings.buttonControlBar &&
|
||||
<div
|
||||
className={classnames(
|
||||
classes.controls,
|
||||
|
|
@ -336,15 +397,37 @@ const Me = (props) =>
|
|||
}, 2000);
|
||||
}}
|
||||
>
|
||||
<p className={hover ? 'hover' : null}>
|
||||
<FormattedMessage
|
||||
id='room.me'
|
||||
defaultMessage='ME'
|
||||
/>
|
||||
</p>
|
||||
|
||||
<React.Fragment>
|
||||
<Tooltip title={micTip} placement='left'>
|
||||
{ smallContainer ?
|
||||
<div>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'device.muteAudio',
|
||||
defaultMessage : 'Mute audio'
|
||||
})}
|
||||
className={classes.smallContainer}
|
||||
disabled={!me.canSendMic || me.audioInProgress}
|
||||
color={micState === 'on' ? 'primary' : 'secondary'}
|
||||
size='small'
|
||||
onClick={() =>
|
||||
{
|
||||
if (micState === 'off')
|
||||
roomClient.enableMic();
|
||||
else if (micState === 'on')
|
||||
roomClient.muteMic();
|
||||
else
|
||||
roomClient.unmuteMic();
|
||||
}}
|
||||
>
|
||||
{ micState === 'on' ?
|
||||
<MicIcon />
|
||||
:
|
||||
<MicOffIcon />
|
||||
}
|
||||
</IconButton>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
|
|
@ -354,7 +437,7 @@ const Me = (props) =>
|
|||
className={classes.fab}
|
||||
disabled={!me.canSendMic || me.audioInProgress}
|
||||
color={micState === 'on' ? 'default' : 'secondary'}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
size='large'
|
||||
onClick={() =>
|
||||
{
|
||||
if (micState === 'off')
|
||||
|
|
@ -372,8 +455,35 @@ const Me = (props) =>
|
|||
}
|
||||
</Fab>
|
||||
</div>
|
||||
}
|
||||
</Tooltip>
|
||||
<Tooltip title={webcamTip} placement='left'>
|
||||
{ smallContainer ?
|
||||
<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({
|
||||
|
|
@ -383,7 +493,7 @@ const Me = (props) =>
|
|||
className={classes.fab}
|
||||
disabled={!me.canSendWebcam || me.webcamInProgress}
|
||||
color={webcamState === 'on' ? 'default' : 'secondary'}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
size='large'
|
||||
onClick={() =>
|
||||
{
|
||||
webcamState === 'on' ?
|
||||
|
|
@ -398,9 +508,56 @@ const Me = (props) =>
|
|||
}
|
||||
</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({
|
||||
|
|
@ -414,7 +571,7 @@ const Me = (props) =>
|
|||
me.screenShareInProgress
|
||||
}
|
||||
color={screenState === 'on' ? 'primary' : 'default'}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
size='large'
|
||||
onClick={() =>
|
||||
{
|
||||
switch (screenState)
|
||||
|
|
@ -444,13 +601,16 @@ const Me = (props) =>
|
|||
}
|
||||
</Fab>
|
||||
</div>
|
||||
}
|
||||
</Tooltip>
|
||||
}
|
||||
</React.Fragment>
|
||||
</div>
|
||||
}
|
||||
|
||||
<VideoView
|
||||
isMe
|
||||
VideoView
|
||||
advancedMode={advancedMode}
|
||||
peer={me}
|
||||
displayName={settings.displayName}
|
||||
|
|
@ -459,6 +619,8 @@ const Me = (props) =>
|
|||
videoVisible={videoVisible}
|
||||
audioCodec={micProducer && micProducer.codec}
|
||||
videoCodec={webcamProducer && webcamProducer.codec}
|
||||
audioScore={audioScore}
|
||||
videoScore={videoScore}
|
||||
onChangeDisplayName={(displayName) =>
|
||||
{
|
||||
roomClient.changeDisplayName(displayName);
|
||||
|
|
@ -502,6 +664,18 @@ const Me = (props) =>
|
|||
style={spacingStyle}
|
||||
>
|
||||
<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
|
||||
className={classnames(
|
||||
classes.controls,
|
||||
|
|
@ -528,14 +702,28 @@ const Me = (props) =>
|
|||
}, 2000);
|
||||
}}
|
||||
>
|
||||
<p className={hover ? 'hover' : null}>
|
||||
<FormattedMessage
|
||||
id='room.me'
|
||||
defaultMessage='ME'
|
||||
/>
|
||||
</p>
|
||||
|
||||
<Tooltip title={webcamTip} placement='left'>
|
||||
{ smallContainer ?
|
||||
<div>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'device.stopVideo',
|
||||
defaultMessage : 'Stop video'
|
||||
})}
|
||||
className={classes.smallContainer}
|
||||
disabled={!me.canSendWebcam || me.webcamInProgress}
|
||||
size='small'
|
||||
color='primary'
|
||||
onClick={() =>
|
||||
{
|
||||
roomClient.disableExtraVideo(producer.id);
|
||||
}}
|
||||
>
|
||||
<VideoIcon />
|
||||
|
||||
</IconButton>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
|
|
@ -544,7 +732,7 @@ const Me = (props) =>
|
|||
})}
|
||||
className={classes.fab}
|
||||
disabled={!me.canSendWebcam || me.webcamInProgress}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
size={smallContainer ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
roomClient.disableExtraVideo(producer.id);
|
||||
|
|
@ -553,6 +741,7 @@ const Me = (props) =>
|
|||
<VideoIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
}
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
|
|
@ -599,40 +788,18 @@ const Me = (props) =>
|
|||
style={spacingStyle}
|
||||
>
|
||||
<div className={classes.viewContainer} style={style}>
|
||||
<div
|
||||
className={classnames(
|
||||
classes.controls,
|
||||
settings.hiddenControls ? 'hide' : null,
|
||||
hover ? 'hover' : null
|
||||
<p className={
|
||||
classnames(
|
||||
classes.meTag,
|
||||
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
|
||||
id='room.me'
|
||||
defaultMessage='ME'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<VideoView
|
||||
isMe
|
||||
|
|
@ -663,12 +830,16 @@ Me.propTypes =
|
|||
extraVideoProducers : PropTypes.arrayOf(appPropTypes.Producer),
|
||||
spacing : PropTypes.number,
|
||||
style : PropTypes.object,
|
||||
smallButtons : PropTypes.bool,
|
||||
smallContainer : PropTypes.bool,
|
||||
canShareScreen : PropTypes.bool.isRequired,
|
||||
classes : PropTypes.object.isRequired,
|
||||
theme : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const makeMapStateToProps = () =>
|
||||
{
|
||||
const hasPermission = makePermissionSelector(permissions.SHARE_SCREEN);
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
|
|
@ -676,25 +847,26 @@ const mapStateToProps = (state) =>
|
|||
...meProducersSelector(state),
|
||||
settings : state.settings,
|
||||
activeSpeaker : state.me.id === state.room.activeSpeakerId,
|
||||
canShareScreen :
|
||||
state.me.roles.some((role) =>
|
||||
state.room.permissionsFromRoles.SHARE_SCREEN.includes(role))
|
||||
canShareScreen : hasPermission(state)
|
||||
};
|
||||
};
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
makeMapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.room.permissionsFromRoles === next.room.permissionsFromRoles &&
|
||||
prev.room === next.room &&
|
||||
prev.me === next.me &&
|
||||
prev.peers === next.peers &&
|
||||
prev.producers === next.producers &&
|
||||
prev.settings === next.settings &&
|
||||
prev.room.activeSpeakerId === next.room.activeSpeakerId
|
||||
prev.settings === next.settings
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { useIntl, FormattedMessage } from 'react-intl';
|
|||
import VideoView from '../VideoContainers/VideoView';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import Fab from '@material-ui/core/Fab';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import VolumeUpIcon from '@material-ui/icons/VolumeUp';
|
||||
import VolumeOffIcon from '@material-ui/icons/VolumeOff';
|
||||
import NewWindowIcon from '@material-ui/icons/OpenInNew';
|
||||
|
|
@ -59,6 +60,19 @@ const styles = (theme) =>
|
|||
{
|
||||
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 :
|
||||
{
|
||||
position : 'relative',
|
||||
|
|
@ -130,7 +144,7 @@ const Peer = (props) =>
|
|||
toggleConsumerWindow,
|
||||
spacing,
|
||||
style,
|
||||
smallButtons,
|
||||
smallContainer,
|
||||
windowConsumer,
|
||||
classes,
|
||||
theme
|
||||
|
|
@ -235,7 +249,30 @@ const Peer = (props) =>
|
|||
})}
|
||||
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
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'device.muteAudio',
|
||||
|
|
@ -244,7 +281,7 @@ const Peer = (props) =>
|
|||
className={classes.fab}
|
||||
disabled={!micConsumer}
|
||||
color={micEnabled ? 'default' : 'secondary'}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
size='large'
|
||||
onClick={() =>
|
||||
{
|
||||
micEnabled ?
|
||||
|
|
@ -258,7 +295,7 @@ const Peer = (props) =>
|
|||
<VolumeOffIcon />
|
||||
}
|
||||
</Fab>
|
||||
</div>
|
||||
}
|
||||
</Tooltip>
|
||||
|
||||
{ browser.platform !== 'mobile' &&
|
||||
|
|
@ -269,7 +306,27 @@ const Peer = (props) =>
|
|||
})}
|
||||
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
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.newWindow',
|
||||
|
|
@ -280,7 +337,7 @@ const Peer = (props) =>
|
|||
!videoVisible ||
|
||||
(windowConsumer === webcamConsumer.id)
|
||||
}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
size='large'
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerWindow(webcamConsumer);
|
||||
|
|
@ -288,7 +345,7 @@ const Peer = (props) =>
|
|||
>
|
||||
<NewWindowIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
}
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
|
|
@ -299,7 +356,24 @@ const Peer = (props) =>
|
|||
})}
|
||||
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
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.fullscreen',
|
||||
|
|
@ -307,7 +381,7 @@ const Peer = (props) =>
|
|||
})}
|
||||
className={classes.fab}
|
||||
disabled={!videoVisible}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
size='large'
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerFullscreen(webcamConsumer);
|
||||
|
|
@ -315,11 +389,12 @@ const Peer = (props) =>
|
|||
>
|
||||
<FullScreenIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
}
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<VideoView
|
||||
showQuality
|
||||
advancedMode={advancedMode}
|
||||
peer={peer}
|
||||
displayName={peer.displayName}
|
||||
|
|
@ -427,7 +502,27 @@ const Peer = (props) =>
|
|||
})}
|
||||
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
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.newWindow',
|
||||
|
|
@ -438,7 +533,7 @@ const Peer = (props) =>
|
|||
!videoVisible ||
|
||||
(windowConsumer === consumer.id)
|
||||
}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
size='large'
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerWindow(consumer);
|
||||
|
|
@ -446,7 +541,7 @@ const Peer = (props) =>
|
|||
>
|
||||
<NewWindowIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
}
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
|
|
@ -457,7 +552,24 @@ const Peer = (props) =>
|
|||
})}
|
||||
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
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.fullscreen',
|
||||
|
|
@ -465,7 +577,7 @@ const Peer = (props) =>
|
|||
})}
|
||||
className={classes.fab}
|
||||
disabled={!videoVisible}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
size='large'
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerFullscreen(consumer);
|
||||
|
|
@ -473,11 +585,12 @@ const Peer = (props) =>
|
|||
>
|
||||
<FullScreenIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
}
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<VideoView
|
||||
showQuality
|
||||
advancedMode={advancedMode}
|
||||
peer={peer}
|
||||
displayName={peer.displayName}
|
||||
|
|
@ -573,7 +686,6 @@ const Peer = (props) =>
|
|||
})}
|
||||
placement={smallScreen ? 'top' : 'left'}
|
||||
>
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.newWindow',
|
||||
|
|
@ -584,7 +696,7 @@ const Peer = (props) =>
|
|||
!screenVisible ||
|
||||
(windowConsumer === screenConsumer.id)
|
||||
}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
size={smallContainer ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerWindow(screenConsumer);
|
||||
|
|
@ -592,7 +704,6 @@ const Peer = (props) =>
|
|||
>
|
||||
<NewWindowIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
|
|
@ -603,7 +714,6 @@ const Peer = (props) =>
|
|||
})}
|
||||
placement={smallScreen ? 'top' : 'left'}
|
||||
>
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.fullscreen',
|
||||
|
|
@ -611,7 +721,7 @@ const Peer = (props) =>
|
|||
})}
|
||||
className={classes.fab}
|
||||
disabled={!screenVisible}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
size={smallContainer ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerFullscreen(screenConsumer);
|
||||
|
|
@ -619,10 +729,10 @@ const Peer = (props) =>
|
|||
>
|
||||
<FullScreenIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<VideoView
|
||||
showQuality
|
||||
advancedMode={advancedMode}
|
||||
videoContain
|
||||
consumerSpatialLayers={
|
||||
|
|
@ -670,7 +780,7 @@ Peer.propTypes =
|
|||
browser : PropTypes.object.isRequired,
|
||||
spacing : PropTypes.number,
|
||||
style : PropTypes.object,
|
||||
smallButtons : PropTypes.bool,
|
||||
smallContainer : PropTypes.bool,
|
||||
toggleConsumerFullscreen : PropTypes.func.isRequired,
|
||||
toggleConsumerWindow : PropTypes.func.isRequired,
|
||||
classes : PropTypes.object.isRequired,
|
||||
|
|
|
|||
|
|
@ -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 Union’s Horizon 2020 research and innovation
|
||||
programme under Grant Agreement No. 731122 (GN4-2).
|
||||
On behalf of GÉANT project, GÉANT Association is the sole
|
||||
owner of the copyright in all material which was developed
|
||||
by a member of the GÉANT project.<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)));
|
||||
|
|
@ -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)));
|
||||
|
|
@ -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)));
|
||||
|
|
@ -4,23 +4,28 @@ import PropTypes from 'prop-types';
|
|||
import {
|
||||
lobbyPeersKeySelector,
|
||||
peersLengthSelector,
|
||||
raisedHandsSelector
|
||||
raisedHandsSelector,
|
||||
makePermissionSelector
|
||||
} from '../Selectors';
|
||||
import { permissions } from '../../permissions';
|
||||
import * as appPropTypes from '../appPropTypes';
|
||||
import { withRoomContext } from '../../RoomContext';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import * as roomActions from '../../actions/roomActions';
|
||||
import * as toolareaActions from '../../actions/toolareaActions';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import classnames from 'classnames';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import Menu from '@material-ui/core/Menu';
|
||||
import Popover from '@material-ui/core/Popover';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Badge from '@material-ui/core/Badge';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import ExtensionIcon from '@material-ui/icons/Extension';
|
||||
import AccountCircle from '@material-ui/icons/AccountCircle';
|
||||
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 Button from '@material-ui/core/Button';
|
||||
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) =>
|
||||
({
|
||||
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 :
|
||||
{
|
||||
margin : 0,
|
||||
|
|
@ -77,9 +110,17 @@ const styles = (theme) =>
|
|||
display : 'block'
|
||||
}
|
||||
},
|
||||
actionButtons :
|
||||
{
|
||||
sectionDesktop : {
|
||||
display : 'none',
|
||||
[theme.breakpoints.up('md')] : {
|
||||
display : 'flex'
|
||||
}
|
||||
},
|
||||
sectionMobile : {
|
||||
display : 'flex',
|
||||
[theme.breakpoints.up('md')] : {
|
||||
display : 'none'
|
||||
}
|
||||
},
|
||||
actionButton :
|
||||
{
|
||||
|
|
@ -96,7 +137,7 @@ const styles = (theme) =>
|
|||
},
|
||||
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 [ 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 {
|
||||
|
|
@ -153,6 +214,9 @@ const TopBar = (props) =>
|
|||
peersLength,
|
||||
lobbyPeers,
|
||||
permanentTopBar,
|
||||
drawerOverlayed,
|
||||
toolAreaOpen,
|
||||
isMobile,
|
||||
myPicture,
|
||||
loggedIn,
|
||||
loginEnabled,
|
||||
|
|
@ -161,6 +225,8 @@ const TopBar = (props) =>
|
|||
onFullscreen,
|
||||
setSettingsOpen,
|
||||
setExtraVideoOpen,
|
||||
setHelpOpen,
|
||||
setAboutOpen,
|
||||
setLockDialogOpen,
|
||||
toggleToolArea,
|
||||
openUsersTab,
|
||||
|
|
@ -171,7 +237,8 @@ const TopBar = (props) =>
|
|||
classes
|
||||
} = props;
|
||||
|
||||
const isMoreActionsMenuOpen = Boolean(moreActionsElement);
|
||||
const isMenuOpen = Boolean(anchorEl);
|
||||
const isMobileMenuOpen = Boolean(mobileMoreAnchorEl);
|
||||
|
||||
const lockTooltip = room.locked ?
|
||||
intl.formatMessage({
|
||||
|
|
@ -210,7 +277,12 @@ const TopBar = (props) =>
|
|||
<React.Fragment>
|
||||
<AppBar
|
||||
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>
|
||||
<PulsingBadge
|
||||
|
|
@ -239,14 +311,26 @@ const TopBar = (props) =>
|
|||
{ window.config.title ? window.config.title : 'Multiparty meeting' }
|
||||
</Typography>
|
||||
<div className={classes.grow} />
|
||||
<div className={classes.actionButtons}>
|
||||
<div className={classes.sectionDesktop}>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'label.moreActions',
|
||||
defaultMessage : 'More actions'
|
||||
})}
|
||||
>
|
||||
<IconButton
|
||||
aria-owns={
|
||||
isMenuOpen &&
|
||||
currentMenu === 'moreActions' ?
|
||||
'material-appbar' : undefined
|
||||
}
|
||||
aria-haspopup='true'
|
||||
onClick={handleMoreActionsOpen}
|
||||
onClick={(event) => handleMenuOpen(event, 'moreActions')}
|
||||
color='inherit'
|
||||
>
|
||||
<ExtensionIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{ fullscreenEnabled &&
|
||||
<Tooltip title={fullscreenTooltip}>
|
||||
<IconButton
|
||||
|
|
@ -386,6 +470,16 @@ const TopBar = (props) =>
|
|||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
</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({
|
||||
|
|
@ -402,23 +496,24 @@ const TopBar = (props) =>
|
|||
defaultMessage='Leave'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Menu
|
||||
anchorEl={moreActionsElement}
|
||||
<Popover
|
||||
anchorEl={anchorEl}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
||||
open={isMoreActionsMenuOpen}
|
||||
onClose={handleMoreActionsClose}
|
||||
open={isMenuOpen}
|
||||
onClose={handleMenuClose}
|
||||
onExited={handleExited}
|
||||
getContentAnchorEl={null}
|
||||
>
|
||||
{ currentMenu === 'moreActions' &&
|
||||
<Paper>
|
||||
<MenuItem
|
||||
dense
|
||||
disabled={!canProduceExtraVideo}
|
||||
onClick={() =>
|
||||
{
|
||||
handleMoreActionsClose();
|
||||
handleMenuClose();
|
||||
setExtraVideoOpen(!room.extraVideoOpen);
|
||||
}}
|
||||
>
|
||||
|
|
@ -435,6 +530,236 @@ const TopBar = (props) =>
|
|||
/>
|
||||
</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
|
||||
aria-label={lockTooltip}
|
||||
disabled={!canLock}
|
||||
onClick={() =>
|
||||
{
|
||||
handleMenuClose();
|
||||
|
||||
if (room.locked)
|
||||
{
|
||||
roomClient.unlockRoom();
|
||||
}
|
||||
else
|
||||
{
|
||||
roomClient.lockRoom();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{ room.locked ?
|
||||
<LockIcon />
|
||||
:
|
||||
<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}>
|
||||
<FormattedMessage
|
||||
id='tooltip.settings'
|
||||
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>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
@ -444,9 +769,12 @@ TopBar.propTypes =
|
|||
{
|
||||
roomClient : PropTypes.object.isRequired,
|
||||
room : appPropTypes.Room.isRequired,
|
||||
isMobile : PropTypes.bool.isRequired,
|
||||
peersLength : PropTypes.number,
|
||||
lobbyPeers : PropTypes.array,
|
||||
permanentTopBar : PropTypes.bool,
|
||||
permanentTopBar : PropTypes.bool.isRequired,
|
||||
drawerOverlayed : PropTypes.bool.isRequired,
|
||||
toolAreaOpen : PropTypes.bool.isRequired,
|
||||
myPicture : PropTypes.string,
|
||||
loggedIn : PropTypes.bool.isRequired,
|
||||
loginEnabled : PropTypes.bool.isRequired,
|
||||
|
|
@ -456,6 +784,8 @@ TopBar.propTypes =
|
|||
setToolbarsVisible : PropTypes.func.isRequired,
|
||||
setSettingsOpen : PropTypes.func.isRequired,
|
||||
setExtraVideoOpen : PropTypes.func.isRequired,
|
||||
setHelpOpen : PropTypes.func.isRequired,
|
||||
setAboutOpen : PropTypes.func.isRequired,
|
||||
setLockDialogOpen : PropTypes.func.isRequired,
|
||||
toggleToolArea : PropTypes.func.isRequired,
|
||||
openUsersTab : PropTypes.func.isRequired,
|
||||
|
|
@ -467,28 +797,39 @@ TopBar.propTypes =
|
|||
theme : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const makeMapStateToProps = () =>
|
||||
{
|
||||
const hasExtraVideoPermission =
|
||||
makePermissionSelector(permissions.EXTRA_VIDEO);
|
||||
|
||||
const hasLockPermission =
|
||||
makePermissionSelector(permissions.CHANGE_ROOM_LOCK);
|
||||
|
||||
const hasPromotionPermission =
|
||||
makePermissionSelector(permissions.PROMOTE_PEER);
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
({
|
||||
room : state.room,
|
||||
isMobile : state.me.browser.platform === 'mobile',
|
||||
peersLength : peersLengthSelector(state),
|
||||
lobbyPeers : lobbyPeersKeySelector(state),
|
||||
permanentTopBar : state.settings.permanentTopBar,
|
||||
drawerOverlayed : state.settings.drawerOverlayed,
|
||||
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 :
|
||||
state.me.roles.some((role) =>
|
||||
state.room.permissionsFromRoles.EXTRA_VIDEO.includes(role)),
|
||||
canLock :
|
||||
state.me.roles.some((role) =>
|
||||
state.room.permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role)),
|
||||
canPromote :
|
||||
state.me.roles.some((role) =>
|
||||
state.room.permissionsFromRoles.PROMOTE_PEER.includes(role))
|
||||
canProduceExtraVideo : hasExtraVideoPermission(state),
|
||||
canLock : hasLockPermission(state),
|
||||
canPromote : hasPromotionPermission(state)
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
({
|
||||
setToolbarsVisible : (visible) =>
|
||||
|
|
@ -503,6 +844,14 @@ const mapDispatchToProps = (dispatch) =>
|
|||
{
|
||||
dispatch(roomActions.setExtraVideoOpen(extraVideoOpen));
|
||||
},
|
||||
setHelpOpen : (helpOpen) =>
|
||||
{
|
||||
dispatch(roomActions.setHelpOpen(helpOpen));
|
||||
},
|
||||
setAboutOpen : (aboutOpen) =>
|
||||
{
|
||||
dispatch(roomActions.setAboutOpen(aboutOpen));
|
||||
},
|
||||
setLockDialogOpen : (lockDialogOpen) =>
|
||||
{
|
||||
dispatch(roomActions.setLockDialogOpen(lockDialogOpen));
|
||||
|
|
@ -519,7 +868,7 @@ const mapDispatchToProps = (dispatch) =>
|
|||
});
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
makeMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
{
|
||||
|
|
@ -530,12 +879,15 @@ export default withRoomContext(connect(
|
|||
prev.peers === next.peers &&
|
||||
prev.lobbyPeers === next.lobbyPeers &&
|
||||
prev.settings.permanentTopBar === next.settings.permanentTopBar &&
|
||||
prev.settings.drawerOverlayed === next.settings.drawerOverlayed &&
|
||||
prev.me.loggedIn === next.me.loggedIn &&
|
||||
prev.me.browser === next.me.browser &&
|
||||
prev.me.loginEnabled === next.me.loginEnabled &&
|
||||
prev.me.picture === next.me.picture &&
|
||||
prev.me.roles === next.me.roles &&
|
||||
prev.toolarea.unreadMessages === next.toolarea.unreadMessages &&
|
||||
prev.toolarea.unreadFiles === next.toolarea.unreadFiles
|
||||
prev.toolarea.unreadFiles === next.toolarea.unreadFiles &&
|
||||
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,10 @@ const styles = (theme) =>
|
|||
green :
|
||||
{
|
||||
color : 'rgba(0, 153, 0, 1)'
|
||||
},
|
||||
red :
|
||||
{
|
||||
color : 'rgba(153, 0, 0, 1)'
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -220,7 +224,7 @@ const JoinDialog = ({
|
|||
myPicture={myPicture}
|
||||
onLogin={() =>
|
||||
{
|
||||
loggedIn ? roomClient.logout() : roomClient.login(roomId);
|
||||
loggedIn ? roomClient.logout(roomId) : roomClient.login(roomId);
|
||||
}}
|
||||
loggedIn={loggedIn}
|
||||
>
|
||||
|
|
@ -281,6 +285,16 @@ const JoinDialog = ({
|
|||
}}
|
||||
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>
|
||||
|
||||
|
|
@ -419,6 +433,7 @@ export default withRoomContext(connect(
|
|||
return (
|
||||
prev.room.inLobby === next.room.inLobby &&
|
||||
prev.room.signInRequired === next.room.signInRequired &&
|
||||
prev.room.overRoomLimit === next.room.overRoomLimit &&
|
||||
prev.settings.displayName === next.settings.displayName &&
|
||||
prev.me.displayNameInProgress === next.me.displayNameInProgress &&
|
||||
prev.me.loginEnabled === next.me.loginEnabled &&
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
|
|||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { permissions } from '../../../permissions';
|
||||
import { makePermissionSelector } from '../../Selectors';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import InputBase from '@material-ui/core/InputBase';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
|
|
@ -119,26 +121,32 @@ ChatInput.propTypes =
|
|||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const makeMapStateToProps = () =>
|
||||
{
|
||||
const hasPermission = makePermissionSelector(permissions.SEND_CHAT);
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
({
|
||||
displayName : state.settings.displayName,
|
||||
picture : state.me.picture,
|
||||
canChat :
|
||||
state.me.roles.some((role) =>
|
||||
state.room.permissionsFromRoles.SEND_CHAT.includes(role))
|
||||
canChat : hasPermission(state)
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export default withRoomContext(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
makeMapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.room.permissionsFromRoles === next.room.permissionsFromRoles &&
|
||||
prev.room === next.room &&
|
||||
prev.me.roles === next.me.roles &&
|
||||
prev.peers === next.peers &&
|
||||
prev.settings.displayName === next.settings.displayName &&
|
||||
prev.me.picture === next.me.picture
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
|
|||
import { withRoomContext } from '../../../RoomContext';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import { permissions } from '../../../permissions';
|
||||
import { makePermissionSelector } from '../../Selectors';
|
||||
import Button from '@material-ui/core/Button';
|
||||
|
||||
const styles = (theme) =>
|
||||
|
|
@ -76,16 +78,21 @@ ChatModerator.propTypes =
|
|||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const makeMapStateToProps = () =>
|
||||
{
|
||||
const hasPermission = makePermissionSelector(permissions.MODERATE_CHAT);
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
({
|
||||
isChatModerator :
|
||||
state.me.roles.some((role) =>
|
||||
state.room.permissionsFromRoles.MODERATE_CHAT.includes(role)),
|
||||
isChatModerator : hasPermission(state),
|
||||
room : state.room
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
makeMapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
|
|
@ -93,7 +100,8 @@ export default withRoomContext(connect(
|
|||
{
|
||||
return (
|
||||
prev.room === next.room &&
|
||||
prev.me === next.me
|
||||
prev.me === next.me &&
|
||||
prev.peers === next.peers
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import { connect } from 'react-redux';
|
|||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { permissions } from '../../../permissions';
|
||||
import { makePermissionSelector } from '../../Selectors';
|
||||
import FileList from './FileList';
|
||||
import FileSharingModerator from './FileSharingModerator';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
|
|
@ -25,6 +27,10 @@ const styles = (theme) =>
|
|||
button :
|
||||
{
|
||||
margin : theme.spacing(1)
|
||||
},
|
||||
shareButtonsWrapper :
|
||||
{
|
||||
display : 'flex'
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -42,6 +48,7 @@ const FileSharing = (props) =>
|
|||
|
||||
const {
|
||||
canShareFiles,
|
||||
browser,
|
||||
canShare,
|
||||
classes
|
||||
} = props;
|
||||
|
|
@ -57,9 +64,21 @@ const FileSharing = (props) =>
|
|||
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 (
|
||||
<Paper className={classes.root}>
|
||||
<FileSharingModerator />
|
||||
<div className={classes.shareButtonsWrapper} >
|
||||
<input
|
||||
className={classes.input}
|
||||
type='file'
|
||||
|
|
@ -69,6 +88,14 @@ const FileSharing = (props) =>
|
|||
onClick={(e) => (e.target.value = null)}
|
||||
id='share-files-button'
|
||||
/>
|
||||
<input
|
||||
className={classes.input}
|
||||
type='file'
|
||||
disabled={!canShare}
|
||||
onChange={handleFileChange}
|
||||
accept='image/*'
|
||||
id='share-files-gallery-button'
|
||||
/>
|
||||
<label htmlFor='share-files-button'>
|
||||
<Button
|
||||
variant='contained'
|
||||
|
|
@ -79,7 +106,19 @@ const FileSharing = (props) =>
|
|||
{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 />
|
||||
</Paper>
|
||||
);
|
||||
|
|
@ -87,34 +126,43 @@ const FileSharing = (props) =>
|
|||
|
||||
FileSharing.propTypes = {
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
browser : PropTypes.object.isRequired,
|
||||
canShareFiles : PropTypes.bool.isRequired,
|
||||
tabOpen : PropTypes.bool.isRequired,
|
||||
canShare : PropTypes.bool.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const makeMapStateToProps = () =>
|
||||
{
|
||||
const hasPermission = makePermissionSelector(permissions.SHARE_FILE);
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
canShareFiles : state.me.canShareFiles,
|
||||
browser : state.me.browser,
|
||||
tabOpen : state.toolarea.currentToolTab === 'files',
|
||||
canShare :
|
||||
state.me.roles.some((role) =>
|
||||
state.room.permissionsFromRoles.SHARE_FILE.includes(role))
|
||||
canShare : hasPermission(state)
|
||||
};
|
||||
};
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
makeMapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
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.canShareFiles === next.me.canShareFiles &&
|
||||
prev.peers === next.peers &&
|
||||
prev.toolarea.currentToolTab === next.toolarea.currentToolTab
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
|
|||
import { withRoomContext } from '../../../RoomContext';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import { permissions } from '../../../permissions';
|
||||
import { makePermissionSelector } from '../../Selectors';
|
||||
import Button from '@material-ui/core/Button';
|
||||
|
||||
const styles = (theme) =>
|
||||
|
|
@ -76,16 +78,21 @@ FileSharingModerator.propTypes =
|
|||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const makeMapStateToProps = () =>
|
||||
{
|
||||
const hasPermission = makePermissionSelector(permissions.MODERATE_FILES);
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
({
|
||||
isFileSharingModerator :
|
||||
state.me.roles.some((role) =>
|
||||
state.room.permissionsFromRoles.MODERATE_FILES.includes(role)),
|
||||
isFileSharingModerator : hasPermission(state),
|
||||
room : state.room
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
makeMapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
|
|
@ -93,7 +100,8 @@ export default withRoomContext(connect(
|
|||
{
|
||||
return (
|
||||
prev.room === next.room &&
|
||||
prev.me === next.me
|
||||
prev.me === next.me &&
|
||||
prev.peers === next.peers
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as appPropTypes from '../../appPropTypes';
|
||||
import { useIntl } from 'react-intl';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import PanIcon from '@material-ui/icons/PanTool';
|
||||
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
||||
|
||||
|
|
@ -22,7 +24,7 @@ const styles = (theme) =>
|
|||
{
|
||||
borderRadius : '50%',
|
||||
height : '2rem',
|
||||
marginTop : theme.spacing(1)
|
||||
marginTop : theme.spacing(0.5)
|
||||
},
|
||||
peerInfo :
|
||||
{
|
||||
|
|
@ -32,6 +34,10 @@ const styles = (theme) =>
|
|||
flexGrow : 1,
|
||||
alignItems : 'center'
|
||||
},
|
||||
buttons :
|
||||
{
|
||||
padding : theme.spacing(1)
|
||||
},
|
||||
green :
|
||||
{
|
||||
color : 'rgba(0, 153, 0, 1)'
|
||||
|
|
@ -58,13 +64,23 @@ const ListMe = (props) =>
|
|||
<div className={classes.peerInfo}>
|
||||
{settings.displayName}
|
||||
</div>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.raisedHand',
|
||||
defaultMessage : 'Raise hand'
|
||||
})}
|
||||
placement='bottom'
|
||||
>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.raisedHand',
|
||||
defaultMessage : 'Raise hand'
|
||||
})}
|
||||
className={me.raisedHand ? classes.green : null}
|
||||
className={
|
||||
classnames(me.raisedHand ? classes.green : null, classes.buttons)
|
||||
}
|
||||
disabled={me.raisedHandInProgress}
|
||||
color='primary'
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
|
@ -74,6 +90,7 @@ const ListMe = (props) =>
|
|||
>
|
||||
<PanIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,9 +6,13 @@ import PropTypes from 'prop-types';
|
|||
import * as appPropTypes from '../../appPropTypes';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { green } from '@material-ui/core/colors';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import VideocamIcon from '@material-ui/icons/Videocam';
|
||||
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 VolumeOffIcon from '@material-ui/icons/VolumeOff';
|
||||
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 EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
||||
import PanIcon from '@material-ui/icons/PanTool';
|
||||
import RecordVoiceOverIcon from '@material-ui/icons/RecordVoiceOver';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
|
|
@ -30,7 +35,7 @@ const styles = (theme) =>
|
|||
{
|
||||
borderRadius : '50%',
|
||||
height : '2rem',
|
||||
marginTop : theme.spacing(1)
|
||||
marginTop : theme.spacing(0.5)
|
||||
},
|
||||
peerInfo :
|
||||
{
|
||||
|
|
@ -43,11 +48,16 @@ const styles = (theme) =>
|
|||
indicators :
|
||||
{
|
||||
display : 'flex',
|
||||
padding : theme.spacing(1.5)
|
||||
padding : theme.spacing(1)
|
||||
},
|
||||
buttons :
|
||||
{
|
||||
padding : theme.spacing(1)
|
||||
},
|
||||
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 {
|
||||
roomClient,
|
||||
isModerator,
|
||||
spotlight,
|
||||
peer,
|
||||
micConsumer,
|
||||
webcamConsumer,
|
||||
|
|
@ -93,12 +104,38 @@ const ListPeer = (props) =>
|
|||
<div className={classes.peerInfo}>
|
||||
{peer.displayName}
|
||||
</div>
|
||||
<div className={classes.indicators}>
|
||||
{ peer.raisedHand &&
|
||||
<PanIcon className={classes.green} />
|
||||
<IconButton
|
||||
className={classes.buttons}
|
||||
style={{ color: green[500] }}
|
||||
disabled={!isModerator || peer.raisedHandInProgress}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
||||
roomClient.lowerPeerHand(peer.id);
|
||||
}}
|
||||
>
|
||||
<PanIcon />
|
||||
</IconButton>
|
||||
}
|
||||
</div>
|
||||
{ screenConsumer &&
|
||||
{ spotlight &&
|
||||
<IconButton
|
||||
className={classes.buttons}
|
||||
style={{ color: green[500] }}
|
||||
disabled
|
||||
>
|
||||
<RecordVoiceOverIcon />
|
||||
</IconButton>
|
||||
}
|
||||
{ screenConsumer && spotlight &&
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.muteScreenSharing',
|
||||
defaultMessage : 'Mute participant share'
|
||||
})}
|
||||
placement='bottom'
|
||||
>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.muteScreenSharing',
|
||||
|
|
@ -106,6 +143,7 @@ const ListPeer = (props) =>
|
|||
})}
|
||||
color={screenVisible ? 'primary' : 'secondary'}
|
||||
disabled={peer.peerScreenInProgress}
|
||||
className={classes.buttons}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
|
@ -121,7 +159,16 @@ const ListPeer = (props) =>
|
|||
<ScreenOffIcon />
|
||||
}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
{ spotlight &&
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.muteParticipantVideo',
|
||||
defaultMessage : 'Mute participant video'
|
||||
})}
|
||||
placement='bottom'
|
||||
>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.muteParticipantVideo',
|
||||
|
|
@ -129,6 +176,7 @@ const ListPeer = (props) =>
|
|||
})}
|
||||
color={webcamEnabled ? 'primary' : 'secondary'}
|
||||
disabled={peer.peerVideoInProgress}
|
||||
className={classes.buttons}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
|
@ -144,6 +192,15 @@ const ListPeer = (props) =>
|
|||
<VideocamOffIcon />
|
||||
}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.muteParticipant',
|
||||
defaultMessage : 'Mute participant'
|
||||
})}
|
||||
placement='bottom'
|
||||
>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.muteParticipant',
|
||||
|
|
@ -151,6 +208,7 @@ const ListPeer = (props) =>
|
|||
})}
|
||||
color={micEnabled ? 'primary' : 'secondary'}
|
||||
disabled={peer.peerAudioInProgress}
|
||||
className={classes.buttons}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
|
|
@ -166,13 +224,23 @@ const ListPeer = (props) =>
|
|||
<VolumeOffIcon />
|
||||
}
|
||||
</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();
|
||||
|
|
@ -182,6 +250,61 @@ const ListPeer = (props) =>
|
|||
>
|
||||
<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}
|
||||
</div>
|
||||
|
|
@ -193,6 +316,7 @@ ListPeer.propTypes =
|
|||
roomClient : PropTypes.any.isRequired,
|
||||
advancedMode : PropTypes.bool,
|
||||
isModerator : PropTypes.bool,
|
||||
spotlight : PropTypes.bool,
|
||||
peer : appPropTypes.Peer.isRequired,
|
||||
micConsumer : appPropTypes.Consumer,
|
||||
webcamConsumer : appPropTypes.Consumer,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
passivePeersSelector,
|
||||
spotlightSortedPeersSelector
|
||||
participantListSelector,
|
||||
makePermissionSelector
|
||||
} from '../../Selectors';
|
||||
import classNames from 'classnames';
|
||||
import { permissions } from '../../../permissions';
|
||||
import classnames from 'classnames';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Flipper, Flipped } from 'react-flip-toolkit';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import ListPeer from './ListPeer';
|
||||
import ListMe from './ListMe';
|
||||
|
|
@ -76,9 +78,9 @@ class ParticipantList extends React.PureComponent
|
|||
roomClient,
|
||||
advancedMode,
|
||||
isModerator,
|
||||
passivePeers,
|
||||
participants,
|
||||
spotlights,
|
||||
selectedPeerId,
|
||||
spotlightPeers,
|
||||
classes
|
||||
} = this.props;
|
||||
|
||||
|
|
@ -107,50 +109,42 @@ class ParticipantList extends React.PureComponent
|
|||
<ul className={classes.list}>
|
||||
<li className={classes.listheader}>
|
||||
<FormattedMessage
|
||||
id='room.spotlights'
|
||||
defaultMessage='Participants in Spotlight'
|
||||
id='label.participants'
|
||||
defaultMessage='Participants'
|
||||
/>
|
||||
</li>
|
||||
{ spotlightPeers.map((peer) => (
|
||||
<Flipper
|
||||
flipKey={participants}
|
||||
>
|
||||
{ participants.map((peer) => (
|
||||
<Flipped key={peer.id} flipId={peer.id}>
|
||||
<li
|
||||
key={peer.id}
|
||||
className={classNames(classes.listItem, {
|
||||
className={classnames(classes.listItem, {
|
||||
selected : peer.id === selectedPeerId
|
||||
})}
|
||||
onClick={() => roomClient.setSelectedPeer(peer.id)}
|
||||
>
|
||||
{ spotlights.includes(peer.id) ?
|
||||
<ListPeer
|
||||
id={peer.id}
|
||||
advancedMode={advancedMode}
|
||||
isModerator={isModerator}
|
||||
spotlight
|
||||
>
|
||||
<Volume small id={peer.id} />
|
||||
</ListPeer>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul className={classes.list}>
|
||||
<li className={classes.listheader}>
|
||||
<FormattedMessage
|
||||
id='room.passive'
|
||||
defaultMessage='Passive Participants'
|
||||
/>
|
||||
</li>
|
||||
{ passivePeers.map((peer) => (
|
||||
<li
|
||||
key={peer.id}
|
||||
className={classNames(classes.listItem, {
|
||||
selected : peer.id === selectedPeerId
|
||||
})}
|
||||
onClick={() => roomClient.setSelectedPeer(peer.id)}
|
||||
>
|
||||
:
|
||||
<ListPeer
|
||||
id={peer.id}
|
||||
advancedMode={advancedMode}
|
||||
isModerator={isModerator}
|
||||
/>
|
||||
}
|
||||
</li>
|
||||
</Flipped>
|
||||
))}
|
||||
</Flipper>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -162,37 +156,40 @@ ParticipantList.propTypes =
|
|||
roomClient : PropTypes.any.isRequired,
|
||||
advancedMode : PropTypes.bool,
|
||||
isModerator : PropTypes.bool,
|
||||
passivePeers : PropTypes.array,
|
||||
participants : PropTypes.array,
|
||||
spotlights : PropTypes.array,
|
||||
selectedPeerId : PropTypes.string,
|
||||
spotlightPeers : PropTypes.array,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const makeMapStateToProps = () =>
|
||||
{
|
||||
const hasPermission = makePermissionSelector(permissions.MODERATE_ROOM);
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
return {
|
||||
isModerator :
|
||||
state.me.roles.some((role) =>
|
||||
state.room.permissionsFromRoles.MODERATE_ROOM.includes(role)),
|
||||
passivePeers : passivePeersSelector(state),
|
||||
selectedPeerId : state.room.selectedPeerId,
|
||||
spotlightPeers : spotlightSortedPeersSelector(state)
|
||||
isModerator : hasPermission(state),
|
||||
participants : participantListSelector(state),
|
||||
spotlights : state.room.spotlights,
|
||||
selectedPeerId : state.room.selectedPeerId
|
||||
};
|
||||
};
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const ParticipantListContainer = withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
makeMapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.room.permissionsFromRoles === next.room.permissionsFromRoles &&
|
||||
prev.room === next.room &&
|
||||
prev.me.roles === next.me.roles &&
|
||||
prev.peers === next.peers &&
|
||||
prev.room.spotlights === next.room.spotlights &&
|
||||
prev.room.selectedPeerId === next.room.selectedPeerId
|
||||
prev.peers === next.peers
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,9 @@ import Peer from '../Containers/Peer';
|
|||
import Me from '../Containers/Me';
|
||||
|
||||
const RATIO = 1.334;
|
||||
const PADDING_V = 50;
|
||||
const PADDING_H = 0;
|
||||
const PADDING = 60;
|
||||
|
||||
const styles = () =>
|
||||
const styles = (theme) =>
|
||||
({
|
||||
root :
|
||||
{
|
||||
|
|
@ -23,6 +22,7 @@ const styles = () =>
|
|||
display : 'flex',
|
||||
flexDirection : 'row',
|
||||
flexWrap : 'wrap',
|
||||
overflow : 'hidden',
|
||||
justifyContent : 'center',
|
||||
alignItems : 'center',
|
||||
alignContent : 'center'
|
||||
|
|
@ -36,6 +36,14 @@ const styles = () =>
|
|||
{
|
||||
paddingTop : 60,
|
||||
transition : 'padding .5s'
|
||||
},
|
||||
buttonControlBar :
|
||||
{
|
||||
paddingLeft : 60,
|
||||
[theme.breakpoints.down('sm')] :
|
||||
{
|
||||
paddingLeft : 0
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -66,9 +74,11 @@ class Democratic extends React.PureComponent
|
|||
return;
|
||||
}
|
||||
|
||||
const width = this.peersRef.current.clientWidth - PADDING_H;
|
||||
const height = this.peersRef.current.clientHeight -
|
||||
(this.props.toolbarsVisible || this.props.permanentTopBar ? PADDING_V : PADDING_H);
|
||||
const width =
|
||||
this.peersRef.current.clientWidth - (this.props.buttonControlBar ? PADDING : 0);
|
||||
const height =
|
||||
this.peersRef.current.clientHeight -
|
||||
(this.props.toolbarsVisible || this.props.permanentTopBar ? PADDING : 0);
|
||||
|
||||
let x, y, space;
|
||||
|
||||
|
|
@ -130,6 +140,7 @@ class Democratic extends React.PureComponent
|
|||
spotlightsPeers,
|
||||
toolbarsVisible,
|
||||
permanentTopBar,
|
||||
buttonControlBar,
|
||||
classes
|
||||
} = this.props;
|
||||
|
||||
|
|
@ -144,7 +155,8 @@ class Democratic extends React.PureComponent
|
|||
className={classnames(
|
||||
classes.root,
|
||||
toolbarsVisible || permanentTopBar ?
|
||||
classes.showingToolBar : classes.hiddenToolBar
|
||||
classes.showingToolBar : classes.hiddenToolBar,
|
||||
buttonControlBar ? classes.buttonControlBar : null
|
||||
)}
|
||||
ref={this.peersRef}
|
||||
>
|
||||
|
|
@ -176,7 +188,9 @@ Democratic.propTypes =
|
|||
boxes : PropTypes.number,
|
||||
spotlightsPeers : PropTypes.array.isRequired,
|
||||
toolbarsVisible : PropTypes.bool.isRequired,
|
||||
permanentTopBar : PropTypes.bool,
|
||||
permanentTopBar : PropTypes.bool.isRequired,
|
||||
buttonControlBar : PropTypes.bool.isRequired,
|
||||
toolAreaOpen : PropTypes.bool.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
|
|
@ -186,7 +200,9 @@ const mapStateToProps = (state) =>
|
|||
boxes : videoBoxesSelector(state),
|
||||
spotlightsPeers : spotlightPeersSelector(state),
|
||||
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.room.spotlights === next.room.spotlights &&
|
||||
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));
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const styles = () =>
|
|||
height : '100%',
|
||||
width : '100%',
|
||||
display : 'grid',
|
||||
overflow : 'hidden',
|
||||
gridTemplateColumns : '1fr',
|
||||
gridTemplateRows : '1fr 0.25fr'
|
||||
},
|
||||
|
|
@ -49,7 +50,7 @@ const styles = () =>
|
|||
},
|
||||
'&.active' :
|
||||
{
|
||||
opacity : '0.6'
|
||||
borderColor : 'var(--selected-peer-border-color)'
|
||||
}
|
||||
},
|
||||
hiddenToolBar :
|
||||
|
|
@ -123,6 +124,9 @@ class Filmstrip extends React.PureComponent
|
|||
|
||||
const root = this.rootContainer.current;
|
||||
|
||||
if (!root)
|
||||
return;
|
||||
|
||||
const availableWidth = root.clientWidth;
|
||||
// Grid is:
|
||||
// 4/5 speaker
|
||||
|
|
@ -279,7 +283,7 @@ class Filmstrip extends React.PureComponent
|
|||
<Me
|
||||
advancedMode={advancedMode}
|
||||
style={peerStyle}
|
||||
smallButtons
|
||||
smallContainer
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
|
|
@ -302,7 +306,7 @@ class Filmstrip extends React.PureComponent
|
|||
advancedMode={advancedMode}
|
||||
id={peerId}
|
||||
style={peerStyle}
|
||||
smallButtons
|
||||
smallContainer
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
|
|
@ -331,6 +335,7 @@ Filmstrip.propTypes = {
|
|||
spotlights : PropTypes.array.isRequired,
|
||||
boxes : PropTypes.number,
|
||||
toolbarsVisible : PropTypes.bool.isRequired,
|
||||
toolAreaOpen : PropTypes.bool.isRequired,
|
||||
permanentTopBar : PropTypes.bool,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
|
@ -346,6 +351,7 @@ const mapStateToProps = (state) =>
|
|||
spotlights : state.room.spotlights,
|
||||
boxes : videoBoxesSelector(state),
|
||||
toolbarsVisible : state.room.toolbarsVisible,
|
||||
toolAreaOpen : state.toolarea.toolAreaOpen,
|
||||
permanentTopBar : state.settings.permanentTopBar
|
||||
};
|
||||
};
|
||||
|
|
@ -361,6 +367,7 @@ export default withRoomContext(connect(
|
|||
prev.room.activeSpeakerId === next.room.activeSpeakerId &&
|
||||
prev.room.selectedPeerId === next.room.selectedPeerId &&
|
||||
prev.room.toolbarsVisible === next.room.toolbarsVisible &&
|
||||
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen &&
|
||||
prev.settings.permanentTopBar === next.settings.permanentTopBar &&
|
||||
prev.peers === next.peers &&
|
||||
prev.consumers === next.consumers &&
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { FormattedMessage } from 'react-intl';
|
|||
import CookieConsent from 'react-cookie-consent';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import SwipeableDrawer from '@material-ui/core/SwipeableDrawer';
|
||||
import Drawer from '@material-ui/core/Drawer';
|
||||
import Hidden from '@material-ui/core/Hidden';
|
||||
import Notifications from './Notifications/Notifications';
|
||||
import MeetingDrawer from './MeetingDrawer/MeetingDrawer';
|
||||
|
|
@ -25,8 +26,11 @@ import Settings from './Settings/Settings';
|
|||
import TopBar from './Controls/TopBar';
|
||||
import WakeLock from 'react-wakelock-react16';
|
||||
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) =>
|
||||
({
|
||||
|
|
@ -42,6 +46,27 @@ const styles = (theme) =>
|
|||
backgroundSize : 'cover',
|
||||
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 :
|
||||
{
|
||||
width : '30vw',
|
||||
|
|
@ -142,6 +167,9 @@ class Room extends React.PureComponent
|
|||
room,
|
||||
browser,
|
||||
advancedMode,
|
||||
showNotifications,
|
||||
buttonControlBar,
|
||||
drawerOverlayed,
|
||||
toolAreaOpen,
|
||||
toggleToolArea,
|
||||
classes,
|
||||
|
|
@ -154,6 +182,8 @@ class Room extends React.PureComponent
|
|||
democratic : Democratic
|
||||
}[room.mode];
|
||||
|
||||
const container = window !== undefined ? window.document.body : undefined;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
{ !isElectron() &&
|
||||
|
|
@ -178,7 +208,9 @@ class Room extends React.PureComponent
|
|||
|
||||
<AudioPeers />
|
||||
|
||||
{ showNotifications &&
|
||||
<Notifications />
|
||||
}
|
||||
|
||||
<CssBaseline />
|
||||
|
||||
|
|
@ -188,9 +220,11 @@ class Room extends React.PureComponent
|
|||
onFullscreen={this.handleToggleFullscreen}
|
||||
/>
|
||||
|
||||
{ (browser.platform === 'mobile' || drawerOverlayed) ?
|
||||
<nav>
|
||||
<Hidden implementation='css'>
|
||||
<SwipeableDrawer
|
||||
container={container}
|
||||
variant='temporary'
|
||||
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
|
||||
open={toolAreaOpen}
|
||||
|
|
@ -199,11 +233,31 @@ class Room extends React.PureComponent
|
|||
classes={{
|
||||
paper : classes.drawerPaper
|
||||
}}
|
||||
ModalProps={{
|
||||
keepMounted : true // Better open performance on mobile.
|
||||
}}
|
||||
>
|
||||
<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' &&
|
||||
<WakeLock />
|
||||
|
|
@ -211,6 +265,10 @@ class Room extends React.PureComponent
|
|||
|
||||
<View advancedMode={advancedMode} />
|
||||
|
||||
{ buttonControlBar &&
|
||||
<ButtonControlBar />
|
||||
}
|
||||
|
||||
{ room.lockDialogOpen &&
|
||||
<LockDialog />
|
||||
}
|
||||
|
|
@ -222,6 +280,13 @@ class Room extends React.PureComponent
|
|||
{ room.extraVideoOpen &&
|
||||
<ExtraVideo />
|
||||
}
|
||||
{ room.helpOpen &&
|
||||
<Help />
|
||||
}
|
||||
{ room.aboutOpen &&
|
||||
<About />
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -232,6 +297,9 @@ Room.propTypes =
|
|||
room : appPropTypes.Room.isRequired,
|
||||
browser : PropTypes.object.isRequired,
|
||||
advancedMode : PropTypes.bool.isRequired,
|
||||
showNotifications : PropTypes.bool.isRequired,
|
||||
buttonControlBar : PropTypes.bool.isRequired,
|
||||
drawerOverlayed : PropTypes.bool.isRequired,
|
||||
toolAreaOpen : PropTypes.bool.isRequired,
|
||||
setToolbarsVisible : PropTypes.func.isRequired,
|
||||
toggleToolArea : PropTypes.func.isRequired,
|
||||
|
|
@ -244,6 +312,9 @@ const mapStateToProps = (state) =>
|
|||
room : state.room,
|
||||
browser : state.me.browser,
|
||||
advancedMode : state.settings.advancedMode,
|
||||
showNotifications : state.settings.showNotifications,
|
||||
buttonControlBar : state.settings.buttonControlBar,
|
||||
drawerOverlayed : state.settings.drawerOverlayed,
|
||||
toolAreaOpen : state.toolarea.toolAreaOpen
|
||||
});
|
||||
|
||||
|
|
@ -270,6 +341,9 @@ export default connect(
|
|||
prev.room === next.room &&
|
||||
prev.me.browser === next.me.browser &&
|
||||
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
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
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 consumersSelect = (state) => state.consumers;
|
||||
const spotlightsSelector = (state) => state.room.spotlights;
|
||||
|
|
@ -12,7 +15,8 @@ const peersKeySelector = createSelector(
|
|||
peersSelector,
|
||||
(peers) => Object.keys(peers)
|
||||
);
|
||||
const peersValueSelector = createSelector(
|
||||
|
||||
export const peersValueSelector = createSelector(
|
||||
peersSelector,
|
||||
(peers) => Object.values(peers)
|
||||
);
|
||||
|
|
@ -113,10 +117,33 @@ export const spotlightPeersSelector = createSelector(
|
|||
export const spotlightSortedPeersSelector = createSelector(
|
||||
spotlightsSelector,
|
||||
peersValueSelector,
|
||||
(spotlights, peers) => peers.filter((peer) => spotlights.includes(peer.id))
|
||||
(spotlights, peers) =>
|
||||
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(
|
||||
peersSelector,
|
||||
(peers) => Object.values(peers).length
|
||||
|
|
@ -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;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
@ -26,10 +26,14 @@ const styles = (theme) =>
|
|||
});
|
||||
|
||||
const AppearenceSettings = ({
|
||||
isMobile,
|
||||
room,
|
||||
settings,
|
||||
onTogglePermanentTopBar,
|
||||
onToggleHiddenControls,
|
||||
onToggleButtonControlBar,
|
||||
onToggleShowNotifications,
|
||||
onToggleDrawerOverlayed,
|
||||
handleChangeMode,
|
||||
classes
|
||||
}) =>
|
||||
|
|
@ -101,22 +105,53 @@ const AppearenceSettings = ({
|
|||
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>
|
||||
);
|
||||
};
|
||||
|
||||
AppearenceSettings.propTypes =
|
||||
{
|
||||
isMobile : PropTypes.bool.isRequired,
|
||||
room : appPropTypes.Room.isRequired,
|
||||
settings : PropTypes.object.isRequired,
|
||||
onTogglePermanentTopBar : PropTypes.func.isRequired,
|
||||
onToggleHiddenControls : PropTypes.func.isRequired,
|
||||
onToggleButtonControlBar : PropTypes.func.isRequired,
|
||||
onToggleShowNotifications : PropTypes.func.isRequired,
|
||||
onToggleDrawerOverlayed : PropTypes.func.isRequired,
|
||||
handleChangeMode : PropTypes.func.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
({
|
||||
isMobile : state.me.browser.platform === 'mobile',
|
||||
room : state.room,
|
||||
settings : state.settings
|
||||
});
|
||||
|
|
@ -124,6 +159,9 @@ const mapStateToProps = (state) =>
|
|||
const mapDispatchToProps = {
|
||||
onTogglePermanentTopBar : settingsActions.togglePermanentTopBar,
|
||||
onToggleHiddenControls : settingsActions.toggleHiddenControls,
|
||||
onToggleShowNotifications : settingsActions.toggleShowNotifications,
|
||||
onToggleButtonControlBar : settingsActions.toggleButtonControlBar,
|
||||
onToggleDrawerOverlayed : settingsActions.toggleDrawerOverlayed,
|
||||
handleChangeMode : roomActions.setDisplayMode
|
||||
};
|
||||
|
||||
|
|
@ -135,6 +173,7 @@ export default connect(
|
|||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.me.browser === next.me.browser &&
|
||||
prev.room === next.room &&
|
||||
prev.settings === next.settings
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@ import { connect } from 'react-redux';
|
|||
import * as appPropTypes from '../appPropTypes';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../RoomContext';
|
||||
import * as settingsActions from '../../actions/settingsActions';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
|
|
@ -23,6 +26,9 @@ const styles = (theme) =>
|
|||
});
|
||||
|
||||
const MediaSettings = ({
|
||||
setEchoCancellation,
|
||||
setAutoGainControl,
|
||||
setNoiseSuppression,
|
||||
roomClient,
|
||||
me,
|
||||
settings,
|
||||
|
|
@ -247,6 +253,51 @@ const MediaSettings = ({
|
|||
/>
|
||||
</FormHelperText>
|
||||
</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>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
@ -255,6 +306,9 @@ const MediaSettings = ({
|
|||
MediaSettings.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
setEchoCancellation : PropTypes.func.isRequired,
|
||||
setAutoGainControl : PropTypes.func.isRequired,
|
||||
setNoiseSuppression : PropTypes.func.isRequired,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
settings : PropTypes.object.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
|
|
@ -268,9 +322,15 @@ const mapStateToProps = (state) =>
|
|||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setEchoCancellation : settingsActions.setEchoCancellation,
|
||||
setAutoGainControl : settingsActions.toggleAutoGainControl,
|
||||
setNoiseSuppression : settingsActions.toggleNoiseSuppression
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ const Settings = ({
|
|||
/>
|
||||
<Tab
|
||||
label={intl.formatMessage({
|
||||
id : 'label.appearence',
|
||||
id : 'label.appearance',
|
||||
defaultMessage : 'Appearence'
|
||||
})}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,12 @@ import classnames from 'classnames';
|
|||
import { withStyles } from '@material-ui/core/styles';
|
||||
import EditableInput from '../Controls/EditableInput';
|
||||
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 SignalCellular0BarIcon from '@material-ui/icons/SignalCellular0Bar';
|
||||
import SignalCellular1BarIcon from '@material-ui/icons/SignalCellular1Bar';
|
||||
import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar';
|
||||
import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar';
|
||||
import SignalCellularAltIcon from '@material-ui/icons/SignalCellularAlt';
|
||||
|
||||
const logger = new Logger('VideoView');
|
||||
|
||||
|
|
@ -153,6 +152,7 @@ class VideoView extends React.PureComponent
|
|||
{
|
||||
const {
|
||||
isMe,
|
||||
showQuality,
|
||||
isScreen,
|
||||
displayName,
|
||||
showPeerInfo,
|
||||
|
|
@ -162,8 +162,6 @@ class VideoView extends React.PureComponent
|
|||
videoMultiLayer,
|
||||
audioScore,
|
||||
videoScore,
|
||||
// consumerSpatialLayers,
|
||||
// consumerTemporalLayers,
|
||||
consumerCurrentSpatialLayer,
|
||||
consumerCurrentTemporalLayer,
|
||||
consumerPreferredSpatialLayer,
|
||||
|
|
@ -180,13 +178,17 @@ class VideoView extends React.PureComponent
|
|||
videoHeight
|
||||
} = this.state;
|
||||
|
||||
let quality = <SignalCellularOffIcon style={{ color: red[500] }}/>;
|
||||
let quality = null;
|
||||
|
||||
if (showQuality)
|
||||
{
|
||||
quality = <SignalCellularOffIcon style={{ color: red[500] }}/>;
|
||||
|
||||
if (videoScore || audioScore)
|
||||
{
|
||||
const score = videoScore ? videoScore : audioScore;
|
||||
|
||||
switch (score.producerScore)
|
||||
switch (isMe ? score.score : score.producerScore)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
|
|
@ -215,16 +217,16 @@ class VideoView extends React.PureComponent
|
|||
|
||||
case 7:
|
||||
case 8:
|
||||
case 9:
|
||||
{
|
||||
quality = <SignalCellular3BarIcon style={{ color: yellow[500] }}/>;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 9:
|
||||
case 10:
|
||||
{
|
||||
quality = <SignalCellularAltIcon style={{ color: green[500] }}/>;
|
||||
quality = null;
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -235,6 +237,7 @@ class VideoView extends React.PureComponent
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
|
|
@ -261,7 +264,7 @@ class VideoView extends React.PureComponent
|
|||
<p>{videoWidth}x{videoHeight}</p>
|
||||
}
|
||||
</div>
|
||||
{ !isMe &&
|
||||
{ showQuality &&
|
||||
<div className={classnames(classes.box, 'right')}>
|
||||
{
|
||||
quality
|
||||
|
|
@ -441,6 +444,7 @@ class VideoView extends React.PureComponent
|
|||
VideoView.propTypes =
|
||||
{
|
||||
isMe : PropTypes.bool,
|
||||
showQuality : PropTypes.bool,
|
||||
isScreen : PropTypes.bool,
|
||||
displayName : PropTypes.string,
|
||||
showPeerInfo : PropTypes.bool,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import messagesCzech from './translations/cs';
|
|||
import messagesItalian from './translations/it';
|
||||
import messagesUkrainian from './translations/uk';
|
||||
import messagesTurkish from './translations/tr';
|
||||
import messagesLatvian from './translations/lv';
|
||||
|
||||
import './index.css';
|
||||
|
||||
|
|
@ -63,7 +64,8 @@ const messages =
|
|||
'cs' : messagesCzech,
|
||||
'it' : messagesItalian,
|
||||
'uk' : messagesUkrainian,
|
||||
'tr' : messagesTurkish
|
||||
'tr' : messagesTurkish,
|
||||
'lv' : messagesLatvian
|
||||
};
|
||||
|
||||
const locale = navigator.language.split(/[-_]/)[0]; // language without region code
|
||||
|
|
@ -114,6 +116,13 @@ function run()
|
|||
const displayName = parameters.get('displayName');
|
||||
const muted = parameters.get('muted') === 'true';
|
||||
|
||||
const { pathname } = window.location;
|
||||
|
||||
let basePath = pathname.substring(0, pathname.lastIndexOf('/'));
|
||||
|
||||
if (!basePath)
|
||||
basePath = '/';
|
||||
|
||||
// Get current device.
|
||||
const device = deviceInfo();
|
||||
|
||||
|
|
@ -132,7 +141,8 @@ function run()
|
|||
produce,
|
||||
forceTcp,
|
||||
displayName,
|
||||
muted
|
||||
muted,
|
||||
basePath
|
||||
});
|
||||
|
||||
global.CLIENT = roomClient;
|
||||
|
|
@ -144,7 +154,7 @@ function run()
|
|||
<PersistGate loading={<LoadingView />} persistor={persistor}>
|
||||
<RoomContext.Provider value={roomClient}>
|
||||
<SnackbarProvider>
|
||||
<Router>
|
||||
<Router basename={basePath}>
|
||||
<Suspense fallback={<LoadingView />}>
|
||||
<React.Fragment>
|
||||
<Route exact path='/' component={ChooseRoom} />
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
};
|
||||
|
|
@ -110,6 +110,11 @@ const consumers = (state = initialState, action) =>
|
|||
return { ...state, [consumerId]: newConsumer };
|
||||
}
|
||||
|
||||
case 'CLEAR_CONSUMERS':
|
||||
{
|
||||
return initialState;
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
const peer = (state = {}, action) =>
|
||||
const initialState = {};
|
||||
|
||||
const peer = (state = initialState, action) =>
|
||||
{
|
||||
switch (action.type)
|
||||
{
|
||||
|
|
@ -27,6 +29,12 @@ const peer = (state = {}, action) =>
|
|||
raisedHandTimestamp : action.payload.raisedHandTimestamp
|
||||
};
|
||||
|
||||
case 'SET_PEER_RAISED_HAND_IN_PROGRESS':
|
||||
return {
|
||||
...state,
|
||||
raisedHandInProgress : action.payload.flag
|
||||
};
|
||||
|
||||
case 'ADD_CONSUMER':
|
||||
{
|
||||
const consumers = [ ...state.consumers, action.payload.consumer.id ];
|
||||
|
|
@ -62,12 +70,24 @@ const peer = (state = {}, action) =>
|
|||
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:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const peers = (state = {}, action) =>
|
||||
const peers = (state = initialState, action) =>
|
||||
{
|
||||
switch (action.type)
|
||||
{
|
||||
|
|
@ -91,10 +111,13 @@ const peers = (state = {}, action) =>
|
|||
case 'SET_PEER_AUDIO_IN_PROGRESS':
|
||||
case 'SET_PEER_SCREEN_IN_PROGRESS':
|
||||
case 'SET_PEER_RAISED_HAND':
|
||||
case 'SET_PEER_RAISED_HAND_IN_PROGRESS':
|
||||
case 'SET_PEER_PICTURE':
|
||||
case 'ADD_CONSUMER':
|
||||
case 'ADD_PEER_ROLE':
|
||||
case 'REMOVE_PEER_ROLE':
|
||||
case 'STOP_PEER_AUDIO_IN_PROGRESS':
|
||||
case 'STOP_PEER_VIDEO_IN_PROGRESS':
|
||||
{
|
||||
const oldPeer = state[action.payload.peerId];
|
||||
|
||||
|
|
@ -118,6 +141,11 @@ const peers = (state = {}, action) =>
|
|||
return { ...state, [oldPeer.id]: peer(oldPeer, action) };
|
||||
}
|
||||
|
||||
case 'CLEAR_PEERS':
|
||||
{
|
||||
return initialState;
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,17 @@ const producers = (state = initialState, action) =>
|
|||
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:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const initialState =
|
|||
locked : false,
|
||||
inLobby : false,
|
||||
signInRequired : false,
|
||||
overRoomLimit : false,
|
||||
// access code to the room if locked and joinByAccessCode == true
|
||||
accessCode : '',
|
||||
// if true: accessCode is a possibility to open the room
|
||||
|
|
@ -21,6 +22,8 @@ const initialState =
|
|||
spotlights : [],
|
||||
settingsOpen : false,
|
||||
extraVideoOpen : false,
|
||||
helpOpen : false,
|
||||
aboutOpen : false,
|
||||
currentSettingsTab : 'media', // media, appearence, advanced
|
||||
lockDialogOpen : false,
|
||||
joined : false,
|
||||
|
|
@ -30,18 +33,8 @@ const initialState =
|
|||
closeMeetingInProgress : false,
|
||||
clearChatInProgress : false,
|
||||
clearFileSharingInProgress : false,
|
||||
userRoles : { NORMAL: 'normal' }, // Default role
|
||||
permissionsFromRoles : {
|
||||
CHANGE_ROOM_LOCK : [],
|
||||
PROMOTE_PEER : [],
|
||||
SEND_CHAT : [],
|
||||
MODERATE_CHAT : [],
|
||||
SHARE_SCREEN : [],
|
||||
EXTRA_VIDEO : [],
|
||||
SHARE_FILE : [],
|
||||
MODERATE_FILES : [],
|
||||
MODERATE_ROOM : []
|
||||
}
|
||||
roomPermissions : null,
|
||||
allowWhenRoleMissing : null
|
||||
};
|
||||
|
||||
const room = (state = initialState, action) =>
|
||||
|
|
@ -88,7 +81,12 @@ const room = (state = initialState, action) =>
|
|||
|
||||
return { ...state, signInRequired };
|
||||
}
|
||||
case 'SET_OVER_ROOM_LIMIT':
|
||||
{
|
||||
const { overRoomLimit } = action.payload;
|
||||
|
||||
return { ...state, overRoomLimit };
|
||||
}
|
||||
case 'SET_ACCESS_CODE':
|
||||
{
|
||||
const { accessCode } = action.payload;
|
||||
|
|
@ -124,6 +122,20 @@ const room = (state = initialState, action) =>
|
|||
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':
|
||||
{
|
||||
const { tab } = action.payload;
|
||||
|
|
@ -200,6 +212,11 @@ const room = (state = initialState, action) =>
|
|||
return { ...state, spotlights };
|
||||
}
|
||||
|
||||
case 'CLEAR_SPOTLIGHTS':
|
||||
{
|
||||
return { ...state, spotlights: [] };
|
||||
}
|
||||
|
||||
case 'SET_LOBBY_PEERS_PROMOTION_IN_PROGRESS':
|
||||
return { ...state, lobbyPeersPromotionInProgress: action.payload.flag };
|
||||
|
||||
|
|
@ -218,18 +235,18 @@ const room = (state = initialState, action) =>
|
|||
case 'CLEAR_FILE_SHARING_IN_PROGRESS':
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -4,12 +4,23 @@ const initialState =
|
|||
selectedWebcam : null,
|
||||
selectedAudioDevice : null,
|
||||
advancedMode : false,
|
||||
sampleRate : 48000,
|
||||
channelCount : 1,
|
||||
volume : 1.0,
|
||||
autoGainControl : true,
|
||||
echoCancellation : true,
|
||||
noiseSuppression : true,
|
||||
sampleSize : 16,
|
||||
// low, medium, high, veryhigh, ultra
|
||||
resolution : window.config.defaultResolution || 'medium',
|
||||
lastN : 4,
|
||||
permanentTopBar : true,
|
||||
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) =>
|
||||
|
|
@ -45,6 +56,83 @@ const settings = (state = initialState, action) =>
|
|||
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':
|
||||
{
|
||||
const { lastN } = action.payload;
|
||||
|
|
@ -59,6 +147,20 @@ const settings = (state = initialState, action) =>
|
|||
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':
|
||||
{
|
||||
const hiddenControls = !state.hiddenControls;
|
||||
|
|
@ -73,6 +175,13 @@ const settings = (state = initialState, action) =>
|
|||
return { ...state, notificationSounds };
|
||||
}
|
||||
|
||||
case 'TOGGLE_SHOW_NOTIFICATIONS':
|
||||
{
|
||||
const showNotifications = !state.showNotifications;
|
||||
|
||||
return { ...state, showNotifications };
|
||||
}
|
||||
|
||||
case 'SET_VIDEO_RESOLUTION':
|
||||
{
|
||||
const { resolution } = action.payload;
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@
|
|||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.overRoomLimit": null,
|
||||
"room.help": null,
|
||||
"room.about": null,
|
||||
"room.shortcutKeys": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -79,6 +83,10 @@
|
|||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.muteScreenSharing": null,
|
||||
"tooltip.muteParticipantAudioModerator": null,
|
||||
"tooltip.muteParticipantVideoModerator": null,
|
||||
"tooltip.muteScreenSharingModerator": null,
|
||||
|
||||
"label.roomName": "房间名称",
|
||||
"label.chooseRoomButton": "继续",
|
||||
|
|
@ -92,6 +100,7 @@
|
|||
"label.filesharing": "文件共享",
|
||||
"label.participants": "参与者",
|
||||
"label.shareFile": "共享文件",
|
||||
"label.shareGalleryFile": null,
|
||||
"label.fileSharingUnsupported": "不支持文件共享",
|
||||
"label.unknown": "未知",
|
||||
"label.democratic": "民主视图",
|
||||
|
|
@ -103,10 +112,11 @@
|
|||
"label.ultra": "超高 (UHD)",
|
||||
"label.close": "关闭",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.appearance": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.moreActions": null,
|
||||
|
||||
"settings.settings": "设置",
|
||||
"settings.camera": "视频设备",
|
||||
|
|
@ -126,6 +136,12 @@
|
|||
"settings.lastn": "可见视频数量",
|
||||
"settings.hiddenControls": 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.startingFileShare": "正在尝试共享文件",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@
|
|||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.overRoomLimit": null,
|
||||
"room.help": null,
|
||||
"room.about": null,
|
||||
"room.shortcutKeys": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -78,6 +82,10 @@
|
|||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.muteScreenSharing": null,
|
||||
"tooltip.muteParticipantAudioModerator": null,
|
||||
"tooltip.muteParticipantVideoModerator": null,
|
||||
"tooltip.muteScreenSharingModerator": null,
|
||||
|
||||
"label.roomName": "Jméno místnosti",
|
||||
"label.chooseRoomButton": "Pokračovat",
|
||||
|
|
@ -91,6 +99,7 @@
|
|||
"label.filesharing": "Sdílení souborů",
|
||||
"label.participants": "Účastníci",
|
||||
"label.shareFile": "Sdílet soubor",
|
||||
"label.shareGalleryFile": null,
|
||||
"label.fileSharingUnsupported": "Sdílení souborů není podporováno",
|
||||
"label.unknown": "Neznámý",
|
||||
"label.democratic": "Rozvržení: Demokratické",
|
||||
|
|
@ -102,10 +111,11 @@
|
|||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Zavřít",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.appearance": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.moreActions": null,
|
||||
|
||||
"settings.settings": "Nastavení",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -125,6 +135,12 @@
|
|||
"settings.lastn": null,
|
||||
"settings.hiddenControls": 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.startingFileShare": "Pokouším se sdílet soubor",
|
||||
|
|
|
|||
|
|
@ -51,19 +51,23 @@
|
|||
"room.videoPaused": "Video gestoppt",
|
||||
"room.muteAll": "Alle stummschalten",
|
||||
"room.stopAllVideo": "Alle Videos stoppen",
|
||||
"room.closeMeeting": "Meeting schließen",
|
||||
"room.clearChat": null,
|
||||
"room.clearFileSharing": null,
|
||||
"room.closeMeeting": "Meeting beenden",
|
||||
"room.clearChat": "Liste löschen",
|
||||
"room.clearFileSharing": "Liste löschen",
|
||||
"room.speechUnsupported": "Dein Browser unterstützt keine Spracherkennung",
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.moderatoractions": "Moderator Aktionen",
|
||||
"room.raisedHand": "{displayName} hebt die Hand",
|
||||
"room.loweredHand": "{displayName} senkt die Hand",
|
||||
"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.lostRole": null,
|
||||
"roles.gotRole": "Rolle erhalten: {role}",
|
||||
"roles.lostRole": "Rolle entzogen: {role}",
|
||||
|
||||
"tooltip.login": "Anmelden",
|
||||
"tooltip.logout": "Abmelden",
|
||||
|
|
@ -75,10 +79,14 @@
|
|||
"tooltip.lobby": "Warteraum",
|
||||
"tooltip.settings": "Einstellungen",
|
||||
"tooltip.participants": "Teilnehmer",
|
||||
"tooltip.kickParticipant": "Teilnehmer rauswerfen",
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.kickParticipant": "Rauswerfen",
|
||||
"tooltip.muteParticipant": "Stummschalten",
|
||||
"tooltip.muteParticipantVideo": "Video stoppen",
|
||||
"tooltip.raisedHand": "Hand heben",
|
||||
"tooltip.muteScreenSharing": "Stoppe Bildschirmfreigabe",
|
||||
"tooltip.muteParticipantAudioModerator": null,
|
||||
"tooltip.muteParticipantVideoModerator": null,
|
||||
"tooltip.muteScreenSharingModerator": null,
|
||||
|
||||
"label.roomName": "Name des Raums",
|
||||
"label.chooseRoomButton": "Weiter",
|
||||
|
|
@ -92,21 +100,23 @@
|
|||
"label.filesharing": "Dateien",
|
||||
"label.participants": "Teilnehmer",
|
||||
"label.shareFile": "Datei hochladen",
|
||||
"label.shareGalleryFile": "Bild teilen",
|
||||
"label.fileSharingUnsupported": "Dateifreigabe nicht unterstützt",
|
||||
"label.unknown": "Unbekannt",
|
||||
"label.democratic": "Demokratisch",
|
||||
"label.filmstrip": "Filmstreifen",
|
||||
"label.low": "Niedrig",
|
||||
"label.medium": "Medium",
|
||||
"label.medium": "Mittel",
|
||||
"label.high": "Hoch (HD)",
|
||||
"label.veryHigh": "Sehr hoch (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Schließen",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.media": "Audio / Video",
|
||||
"label.appearance": "Ansicht",
|
||||
"label.advanced": "Erweitert",
|
||||
"label.addVideo": "Video hinzufügen",
|
||||
"label.promoteAllPeers": "Alle Teilnehmer reinlassen",
|
||||
"label.moreActions": "Weitere Aktionen",
|
||||
|
||||
"settings.settings": "Einstellungen",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -124,8 +134,14 @@
|
|||
"settings.advancedMode": "Erweiterter Modus",
|
||||
"settings.permanentTopBar": "Permanente obere Leiste",
|
||||
"settings.lastn": "Anzahl der sichtbaren Videos",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
"settings.hiddenControls": "Medienwerkzeugleiste automatisch ausblenden",
|
||||
"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.startingFileShare": "Starte Teilen der Datei",
|
||||
|
|
@ -167,8 +183,8 @@
|
|||
"devices.cameraDisconnected": "Kamera getrennt",
|
||||
"devices.cameraError": "Fehler mit deiner Kamera",
|
||||
|
||||
"moderator.clearChat": null,
|
||||
"moderator.clearFiles": null,
|
||||
"moderator.muteAudio": null,
|
||||
"moderator.muteVideo": null
|
||||
"moderator.clearChat": "Moderator hat Chat gelöscht",
|
||||
"moderator.clearFiles": "Moderator hat geteilte Dateiliste gelöscht",
|
||||
"moderator.muteAudio": "Moderator hat dich stummgeschaltet",
|
||||
"moderator.muteVideo": "Moderator hat dein Video gestoppt"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@
|
|||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.overRoomLimit": null,
|
||||
"room.help": null,
|
||||
"room.about": null,
|
||||
"room.shortcutKeys": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -79,6 +83,10 @@
|
|||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.muteScreenSharing": null,
|
||||
"tooltip.muteParticipantAudioModerator": null,
|
||||
"tooltip.muteParticipantVideoModerator": null,
|
||||
"tooltip.muteScreenSharingModerator": null,
|
||||
|
||||
"label.roomName": "Værelsesnavn",
|
||||
"label.chooseRoomButton": "Fortsæt",
|
||||
|
|
@ -92,6 +100,7 @@
|
|||
"label.filesharing": "Fildeling",
|
||||
"label.participants": "Deltagere",
|
||||
"label.shareFile": "Del fil",
|
||||
"label.shareGalleryFile": null,
|
||||
"label.fileSharingUnsupported": "Fildeling er ikke understøttet",
|
||||
"label.unknown": "Ukendt",
|
||||
"label.democracy": "Galleri visning",
|
||||
|
|
@ -103,10 +112,11 @@
|
|||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Luk",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.appearance": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.moreActions": null,
|
||||
|
||||
"settings.settings": "Indstillinger",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -126,6 +136,12 @@
|
|||
"settings.lastn": "Antal synlige videoer",
|
||||
"settings.hiddenControls": 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.startingFileShare": "Forsøger at dele filen",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@
|
|||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.overRoomLimit": null,
|
||||
"room.help": null,
|
||||
"room.about": null,
|
||||
"room.shortcutKeys": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -79,6 +83,10 @@
|
|||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.muteScreenSharing": null,
|
||||
"tooltip.muteParticipantAudioModerator": null,
|
||||
"tooltip.muteParticipantVideoModerator": null,
|
||||
"tooltip.muteScreenSharingModerator": null,
|
||||
|
||||
"label.roomName": "Όνομα δωματίου",
|
||||
"label.chooseRoomButton": "Συνέχεια",
|
||||
|
|
@ -92,6 +100,7 @@
|
|||
"label.filesharing": "Διαμοιρασμοός αρχείου",
|
||||
"label.participants": "Συμμετέχοντες",
|
||||
"label.shareFile": "Διαμοιραστείτε ένα αρχείο",
|
||||
"label.shareGalleryFile": null,
|
||||
"label.fileSharingUnsupported": "Ο διαμοιρασμός αρχείων δεν υποστηρίζεται",
|
||||
"label.unknown": "Άγνωστο",
|
||||
"label.democratic": null,
|
||||
|
|
@ -103,10 +112,11 @@
|
|||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Κλείσιμο",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.appearance": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.moreActions": null,
|
||||
|
||||
"settings.settings": "Ρυθμίσεις",
|
||||
"settings.camera": "Κάμερα",
|
||||
|
|
@ -126,6 +136,12 @@
|
|||
"settings.lastn": "Αριθμός ορατών βίντεο",
|
||||
"settings.hiddenControls": 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.startingFileShare": "Προσπάθεια διαμοιρασμού αρχείου",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@
|
|||
"room.raisedHand": "{displayName} raised their hand",
|
||||
"room.loweredHand": "{displayName} put their hand down",
|
||||
"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",
|
||||
|
||||
|
|
@ -79,6 +83,10 @@
|
|||
"tooltip.muteParticipant": "Mute participant",
|
||||
"tooltip.muteParticipantVideo": "Mute participant video",
|
||||
"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.chooseRoomButton": "Continue",
|
||||
|
|
@ -92,6 +100,7 @@
|
|||
"label.filesharing": "File sharing",
|
||||
"label.participants": "Participants",
|
||||
"label.shareFile": "Share file",
|
||||
"label.shareGalleryFile": "Share image",
|
||||
"label.fileSharingUnsupported": "File sharing not supported",
|
||||
"label.unknown": "Unknown",
|
||||
"label.democratic": "Democratic view",
|
||||
|
|
@ -103,10 +112,11 @@
|
|||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Close",
|
||||
"label.media": "Media",
|
||||
"label.appearence": "Appearence",
|
||||
"label.appearance": "Appearence",
|
||||
"label.advanced": "Advanced",
|
||||
"label.addVideo": "Add video",
|
||||
"label.promoteAllPeers": "Promote all",
|
||||
"label.moreActions": "More actions",
|
||||
|
||||
"settings.settings": "Settings",
|
||||
"settings.camera": "Camera",
|
||||
|
|
@ -126,6 +136,12 @@
|
|||
"settings.lastn": "Number of visible videos",
|
||||
"settings.hiddenControls": "Hidden media controls",
|
||||
"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.startingFileShare": "Attempting to share file",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@
|
|||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.overRoomLimit": null,
|
||||
"room.help": null,
|
||||
"room.about": null,
|
||||
"room.shortcutKeys": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -79,6 +83,10 @@
|
|||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.muteScreenSharing": null,
|
||||
"tooltip.muteParticipantAudioModerator": null,
|
||||
"tooltip.muteParticipantVideoModerator": null,
|
||||
"tooltip.muteScreenSharingModerator": null,
|
||||
|
||||
"label.roomName": "Nombre de la sala",
|
||||
"label.chooseRoomButton": "Continuar",
|
||||
|
|
@ -92,6 +100,7 @@
|
|||
"label.filesharing": "Compartir ficheros",
|
||||
"label.participants": "Participantes",
|
||||
"label.shareFile": "Compartir fichero",
|
||||
"label.shareGalleryFile": null,
|
||||
"label.fileSharingUnsupported": "Compartir ficheros no está disponible",
|
||||
"label.unknown": "Desconocido",
|
||||
"label.democratic": "Vista democrática",
|
||||
|
|
@ -103,10 +112,11 @@
|
|||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Cerrar",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.appearance": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.moreActions": null,
|
||||
|
||||
"settings.settings": "Ajustes",
|
||||
"settings.camera": "Cámara",
|
||||
|
|
@ -126,6 +136,12 @@
|
|||
"settings.lastn": "Cantidad de videos visibles",
|
||||
"settings.hiddenControls": 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.startingFileShare": "Intentando compartir el fichero",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@
|
|||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.overRoomLimit": null,
|
||||
"room.help": null,
|
||||
"room.about": null,
|
||||
"room.shortcutKeys": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -79,6 +83,10 @@
|
|||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.muteScreenSharing": null,
|
||||
"tooltip.muteParticipantAudioModerator": null,
|
||||
"tooltip.muteParticipantVideoModerator": null,
|
||||
"tooltip.muteScreenSharingModerator": null,
|
||||
|
||||
"label.roomName": "Nom de la salle",
|
||||
"label.chooseRoomButton": "Continuer",
|
||||
|
|
@ -92,6 +100,7 @@
|
|||
"label.filesharing": "Partage de fichier",
|
||||
"label.participants": "Participants",
|
||||
"label.shareFile": "Partager un fichier",
|
||||
"label.shareGalleryFile": null,
|
||||
"label.fileSharingUnsupported": "Partage de fichier non supporté",
|
||||
"label.unknown": "Inconnu",
|
||||
"label.democratic": "Vue démocratique",
|
||||
|
|
@ -103,10 +112,11 @@
|
|||
"label.ultra": "Ultra Haute Définition",
|
||||
"label.close": "Fermer",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.appearance": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.moreActions": null,
|
||||
|
||||
"settings.settings": "Paramètres",
|
||||
"settings.camera": "Caméra",
|
||||
|
|
@ -126,6 +136,12 @@
|
|||
"settings.lastn": "Nombre de vidéos visibles",
|
||||
"settings.hiddenControls": 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.startingFileShare": "Début du transfert de fichier",
|
||||
|
|
|
|||
|
|
@ -52,18 +52,22 @@
|
|||
"room.muteAll": "Utišaj sve",
|
||||
"room.stopAllVideo": "Ugasi sve kamere",
|
||||
"room.closeMeeting": "Završi sastanak",
|
||||
"room.clearChat": null,
|
||||
"room.clearFileSharing": null,
|
||||
"room.clearChat": "Izbriši razgovor",
|
||||
"room.clearFileSharing": "Izbriši dijeljene datoteke",
|
||||
"room.speechUnsupported": "Vaš preglednik ne podržava prepoznavanje govora",
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.moderatoractions": "Akcije moderatora",
|
||||
"room.raisedHand": "{displayName} je podigao ruku",
|
||||
"room.loweredHand": "{displayName} je spustio ruku",
|
||||
"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",
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
"roles.gotRole": "Dodijeljena vam je uloga: {role}",
|
||||
"roles.lostRole": "Uloga: {role} je povučena",
|
||||
|
||||
"tooltip.login": "Prijava",
|
||||
"tooltip.logout": "Odjava",
|
||||
|
|
@ -74,11 +78,15 @@
|
|||
"tooltip.leaveFullscreen": "Izađi iz punog ekrana",
|
||||
"tooltip.lobby": "Prikaži predvorje",
|
||||
"tooltip.settings": "Prikaži postavke",
|
||||
"tooltip.participants": "Pokažite sudionike",
|
||||
"tooltip.participants": "Prikaži sudionike",
|
||||
"tooltip.kickParticipant": "Izbaci sudionika",
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.muteParticipant": "Utišaj sudionika",
|
||||
"tooltip.muteParticipantVideo": "Ne primaj video sudionika",
|
||||
"tooltip.raisedHand": "Podigni ruku",
|
||||
"tooltip.muteScreenSharing": null,
|
||||
"tooltip.muteParticipantAudioModerator": null,
|
||||
"tooltip.muteParticipantVideoModerator": null,
|
||||
"tooltip.muteScreenSharingModerator": null,
|
||||
|
||||
"label.roomName": "Naziv sobe",
|
||||
"label.chooseRoomButton": "Nastavi",
|
||||
|
|
@ -92,6 +100,7 @@
|
|||
"label.filesharing": "Dijeljenje datoteka",
|
||||
"label.participants": "Sudionici",
|
||||
"label.shareFile": "Dijeli datoteku",
|
||||
"label.shareGalleryFile": "Dijeli sliku",
|
||||
"label.fileSharingUnsupported": "Dijeljenje datoteka nije podržano",
|
||||
"label.unknown": "Nepoznato",
|
||||
"label.democratic":"Demokratski prikaz",
|
||||
|
|
@ -102,11 +111,12 @@
|
|||
"label.veryHigh": "Vrlo visoka (FHD)",
|
||||
"label.ultra": "Ultra visoka (UHD)",
|
||||
"label.close": "Zatvori",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.media": "Medij",
|
||||
"label.appearance": "Prikaz",
|
||||
"label.advanced": "Napredno",
|
||||
"label.addVideo": "Dodaj video",
|
||||
"label.promoteAllPeers": "Promoviraj sve",
|
||||
"label.moreActions": null,
|
||||
|
||||
"settings.settings": "Postavke",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -116,16 +126,22 @@
|
|||
"settings.selectAudio": "Odaberi uređaj za zvuk",
|
||||
"settings.cantSelectAudio": "Nije moguće odabrati uređaj za zvuk",
|
||||
"settings.audioOutput": "Uređaj za izlaz zvuka",
|
||||
"settings.selectAudioOutput": "Odaberite audio izlazni uređaj",
|
||||
"settings.cantSelectAudioOutput": "Nije moguće odabrati audio izlazni uređaj",
|
||||
"settings.selectAudioOutput": "Odaberite izlazni uređaj za zvuk",
|
||||
"settings.cantSelectAudioOutput": "Nije moguće odabrati izlazni uređaj za zvuk",
|
||||
"settings.resolution": "Odaberi video rezoluciju",
|
||||
"settings.layout": "Način prikaza",
|
||||
"settings.selectRoomLayout": "Odaberi način prikaza",
|
||||
"settings.advancedMode": "Napredne mogućnosti",
|
||||
"settings.permanentTopBar": "Stalna gornja šipka",
|
||||
"settings.lastn": "Broj vidljivih videozapisa",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
"settings.hiddenControls": "Skrivene kontrole medija",
|
||||
"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.startingFileShare": "Pokušaj dijeljenja datoteke",
|
||||
|
|
@ -167,8 +183,8 @@
|
|||
"devices.cameraDisconnected": "Kamera odspojena",
|
||||
"devices.cameraError": "Greška prilikom pristupa kameri",
|
||||
|
||||
"moderator.clearChat": null,
|
||||
"moderator.clearFiles": null,
|
||||
"moderator.muteAudio": null,
|
||||
"moderator.muteVideo": null
|
||||
"moderator.clearChat": "Moderator je izbrisao razgovor",
|
||||
"moderator.clearFiles": "Moderator je izbrisao datoteke",
|
||||
"moderator.muteAudio": "Moderator je utišao tvoj zvuk",
|
||||
"moderator.muteVideo": "Moderator je zaustavio tvoj video"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
{
|
||||
"socket.disconnected": "A kapcsolat lebomlott",
|
||||
"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",
|
||||
|
||||
"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.consentUnderstand": "I understand",
|
||||
"room.joined": "Csatlakozátál a konferenciához",
|
||||
"room.consentUnderstand": "Megértettem",
|
||||
"room.joined": "Csatlakoztál a konferenciához",
|
||||
"room.cantJoin": "Sikertelen csatlakozás a konferenciához",
|
||||
"room.youLocked": "A konferenciába való belépés letiltva",
|
||||
"room.cantLock": "Sikertelen a konferenciaba való belépés letiltása",
|
||||
"room.cantLock": "Sikertelen a konferenciába való belépés letiltása",
|
||||
"room.youUnLocked": "A konferenciába való belépés engedélyezve",
|
||||
"room.cantUnLock": "Sikertelen a konferenciába való belépés engedélyezése",
|
||||
"room.locked": "A konferenciába való belépés letiltva",
|
||||
"room.unlocked": "A konferenciába való belépés engedélyezve",
|
||||
"room.newLobbyPeer": "Új részvevő lépett be a konferencia előszobájába",
|
||||
"room.lobbyPeerLeft": "A konferencia előszobájából a részvevő távozott",
|
||||
"room.lobbyPeerChangedDisplayName": "Az előszobai résztvevő meváltoztatta a nevét: {displayName}",
|
||||
"room.lobbyPeerChangedPicture": "Az előszobai résztvevő meváltoztatta a képét",
|
||||
"room.lobbyPeerChangedDisplayName": "Az előszobai résztvevő megváltoztatta a nevét: {displayName}",
|
||||
"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.accessCodeOn": "A konferencia hozzáférési kódja aktiválva",
|
||||
"room.accessCodeOff": "A konferencia hozzáférési kódka deaktiválva",
|
||||
|
|
@ -39,8 +39,8 @@
|
|||
"room.audioOnly": "csak Hang",
|
||||
"room.audioVideo": "Hang és Videó",
|
||||
"room.youAreReady": "Ok, kész vagy",
|
||||
"room.emptyRequireLogin": "A konferencia üres! Be kell lépned a konferecnia elkezdéséhez, vagy várnod kell amíg a házigazda becsatlakozik.",
|
||||
"room.locketWait": "A konferencia szobába a a belépés tilos - Várj amíg valaki be nem enged ...",
|
||||
"room.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": "Az automatikus belépés tiltva van - Várj amíg valaki beenged ...",
|
||||
"room.lobbyAdministration": "Előszoba adminisztráció",
|
||||
"room.peersInLobby": "Résztvevők az előszobában",
|
||||
"room.lobbyEmpty": "Épp senki sincs a konferencia előszobájában",
|
||||
|
|
@ -49,25 +49,29 @@
|
|||
"room.spotlights": "Látható résztvevők",
|
||||
"room.passive": "Passzív résztvevők",
|
||||
"room.videoPaused": "Ez a videóstream szünetel",
|
||||
"room.muteAll": null,
|
||||
"room.stopAllVideo": null,
|
||||
"room.closeMeeting": null,
|
||||
"room.clearChat": null,
|
||||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.muteAll": "Mindenki némítása",
|
||||
"room.stopAllVideo": "Mindenki video némítása",
|
||||
"room.closeMeeting": "Konferencia lebontása",
|
||||
"room.clearChat": "Chat történelem kiürítése",
|
||||
"room.clearFileSharing": "File megosztás kiürítése",
|
||||
"room.speechUnsupported": "A böngésződ nem támogatja a hangfelismerést",
|
||||
"room.moderatoractions": "Moderátor funkciók",
|
||||
"room.raisedHand": "{displayName} jelentkezik",
|
||||
"room.loweredHand": "{displayName} leeresztette a kezét",
|
||||
"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.lostRole": null,
|
||||
"roles.gotRole": "{role} szerepet kaptál",
|
||||
"roles.lostRole": "Elvesztetted a {role} szerepet",
|
||||
|
||||
"tooltip.login": "Belé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.unLockRoom": "konferenciába való belépés engedélyezése",
|
||||
"tooltip.enterFullscreen": "Teljes képernyős mód",
|
||||
|
|
@ -75,10 +79,14 @@
|
|||
"tooltip.lobby": "Az előszobában várakozók listája",
|
||||
"tooltip.settings": "Beállítások",
|
||||
"tooltip.participants": "Résztvevők",
|
||||
"tooltip.kickParticipant": null,
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.kickParticipant": "Résztvevő kirúgása",
|
||||
"tooltip.muteParticipant": "Résztvevő némítása",
|
||||
"tooltip.muteParticipantVideo": "Résztvevő videóstreamének némítása",
|
||||
"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.chooseRoomButton": "Tovább",
|
||||
|
|
@ -92,6 +100,7 @@
|
|||
"label.filesharing": "Fájl megosztás",
|
||||
"label.participants": "Résztvevők",
|
||||
"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.unknown": "Ismeretlen",
|
||||
"label.democratic": "Egyforma képméretű képkiosztás",
|
||||
|
|
@ -102,11 +111,12 @@
|
|||
"label.veryHigh": "Nagyon magas (FHD)",
|
||||
"label.ultra": "Ultra magas (UHD)",
|
||||
"label.close": "Bezár",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.media": "Média",
|
||||
"label.appearance": "Megjelenés",
|
||||
"label.advanced": "Részletek",
|
||||
"label.addVideo": "Videó hozzáadása",
|
||||
"label.promoteAllPeers": "Mindenkit beengedek",
|
||||
"label.moreActions": "További műveletek",
|
||||
|
||||
"settings.settings": "Beállítások",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -124,13 +134,19 @@
|
|||
"settings.advancedMode": "Részletes információk",
|
||||
"settings.permanentTopBar": "Állandó felső sáv",
|
||||
"settings.lastn": "A látható videók száma",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
"settings.hiddenControls": "Média Gombok automatikus elrejtése",
|
||||
"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.startingFileShare": "Fájl megosztása",
|
||||
"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.finished": "A fájl letöltés befejeződött",
|
||||
"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",
|
||||
|
||||
"device.audioUnsupported": "A hnag nem támogatott",
|
||||
"device.audioUnsupported": "A hang nem támogatott",
|
||||
"device.activateAudio": "Hang aktiválása",
|
||||
"device.muteAudio": "Hang némítása",
|
||||
"device.unMuteAudio": "Hang némítás kikapcsolása",
|
||||
|
|
@ -151,9 +167,9 @@
|
|||
|
||||
"device.screenSharingUnsupported": "A képernyő megosztás nem támogatott",
|
||||
"device.startScreenSharing": "Képernyőmegosztás indítása",
|
||||
"device.stopScreenSharing": "Képernyőmegosztás leáłłítása",
|
||||
"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.microphoneMute": "A mikrofon némítva lett",
|
||||
"devices.microphoneUnMute": "A mikrofon némítása ki lett kapocsolva",
|
||||
|
|
@ -167,8 +183,8 @@
|
|||
"devices.cameraDisconnected": "A kamera kapcsolata lebomlott",
|
||||
"devices.cameraError": "Hiba történt a kamera elérése során",
|
||||
|
||||
"moderator.clearChat": null,
|
||||
"moderator.clearFiles": null,
|
||||
"moderator.muteAudio": null,
|
||||
"moderator.muteVideo": null
|
||||
"moderator.clearChat": "A moderátor kiürítette a chat történelmet",
|
||||
"moderator.clearFiles": "A moderátor kiürítette a file megosztás történelmet",
|
||||
"moderator.muteAudio": "A moderátor elnémította a hangod",
|
||||
"moderator.muteVideo": "A moderátor elnémította a videód"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,25 +49,29 @@
|
|||
"room.spotlights": "Partecipanti in Evidenza",
|
||||
"room.passive": "Participanti Passivi",
|
||||
"room.videoPaused": "Il video è in pausa",
|
||||
"room.muteAll": null,
|
||||
"room.stopAllVideo": null,
|
||||
"room.closeMeeting": null,
|
||||
"room.clearChat": null,
|
||||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.muteAll": "Muta tutti",
|
||||
"room.stopAllVideo": "Ferma tutti i video",
|
||||
"room.closeMeeting": "Termina meeting",
|
||||
"room.clearChat": "Pulisci chat",
|
||||
"room.clearFileSharing": "Pulisci file sharing",
|
||||
"room.speechUnsupported": "Il tuo browser non supporta il riconoscimento vocale",
|
||||
"room.moderatoractions": "Azioni moderatore",
|
||||
"room.raisedHand": "{displayName} ha alzato la mano",
|
||||
"room.loweredHand": "{displayName} ha abbassato la mano",
|
||||
"room.extraVideo": "Video extra",
|
||||
"room.overRoomLimit": "La stanza è piena, riprova più tardi.",
|
||||
"room.help": "Aiuto",
|
||||
"room.about": "Informazioni su",
|
||||
"room.shortcutKeys": "Scorciatoie da tastiera",
|
||||
|
||||
"me.mutedPTT": null,
|
||||
"me.mutedPTT": "Sei mutato, tieni premuto SPAZIO per parlare",
|
||||
|
||||
"roles.gotRole": null,
|
||||
"roles.lostRole": null,
|
||||
"roles.gotRole": "Hai ottenuto il ruolo: {role}",
|
||||
"roles.lostRole": "Hai perso il ruolo: {role}",
|
||||
|
||||
"tooltip.login": "Log in",
|
||||
"tooltip.logout": "Log out",
|
||||
"tooltip.admitFromLobby": "Ammetti dalla lobby",
|
||||
"tooltip.admitFromLobby": "Accetta partecipante dalla lobby",
|
||||
"tooltip.lockRoom": "Blocca stanza",
|
||||
"tooltip.unLockRoom": "Sblocca stanza",
|
||||
"tooltip.enterFullscreen": "Modalità schermo intero",
|
||||
|
|
@ -75,9 +79,14 @@
|
|||
"tooltip.lobby": "Mostra lobby",
|
||||
"tooltip.settings": "Mostra impostazioni",
|
||||
"tooltip.participants": "Mostra partecipanti",
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.kickParticipant": "Espelli partecipante",
|
||||
"tooltip.muteParticipant": "Muta partecipante",
|
||||
"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.chooseRoomButton": "Continua",
|
||||
|
|
@ -91,6 +100,7 @@
|
|||
"label.filesharing": "Condivisione file",
|
||||
"label.participants": "Partecipanti",
|
||||
"label.shareFile": "Condividi file",
|
||||
"label.shareGalleryFile": "Condividi immagine",
|
||||
"label.fileSharingUnsupported": "Condivisione file non supportata",
|
||||
"label.unknown": "Sconosciuto",
|
||||
"label.democratic": "Vista Democratica",
|
||||
|
|
@ -101,11 +111,12 @@
|
|||
"label.veryHigh": "Molto alta (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Chiudi",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.media": "Media",
|
||||
"label.appearance": "Aspetto",
|
||||
"label.advanced": "Avanzate",
|
||||
"label.addVideo": "Aggiungi video",
|
||||
"label.promoteAllPeers": "Promuovi tutti",
|
||||
"label.moreActions": "Altre azioni",
|
||||
|
||||
"settings.settings": "Impostazioni",
|
||||
"settings.camera": "Videocamera",
|
||||
|
|
@ -123,8 +134,14 @@
|
|||
"settings.advancedMode": "Modalità avanzata",
|
||||
"settings.permanentTopBar": "Barra superiore permanente",
|
||||
"settings.lastn": "Numero di video visibili",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
"settings.hiddenControls": "Controlli media nascosti",
|
||||
"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.startingFileShare": "Tentativo di condivisione file",
|
||||
|
|
@ -166,8 +183,8 @@
|
|||
"devices.cameraDisconnected": "Videocamera scollegata",
|
||||
"devices.cameraError": "Errore con l'accesso alla videocamera",
|
||||
|
||||
"moderator.clearChat": null,
|
||||
"moderator.clearFiles": null,
|
||||
"moderator.muteAudio": null,
|
||||
"moderator.muteVideo": null
|
||||
"moderator.clearChat": "Il moderatore ha pulito la chat",
|
||||
"moderator.clearFiles": "Il moderatore ha pulito i file",
|
||||
"moderator.muteAudio": "Il moderatore ha mutato il tuo audio",
|
||||
"moderator.muteVideo": "Il moderatore ha fermato il tuo video"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -59,6 +59,10 @@
|
|||
"room.raisedHand": "{displayName} rakk opp hånden",
|
||||
"room.loweredHand": "{displayName} tok ned hånden",
|
||||
"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",
|
||||
|
||||
|
|
@ -79,6 +83,10 @@
|
|||
"tooltip.muteParticipant": "Demp deltaker",
|
||||
"tooltip.muteParticipantVideo": "Demp deltakervideo",
|
||||
"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.chooseRoomButton": "Fortsett",
|
||||
|
|
@ -92,6 +100,7 @@
|
|||
"label.filesharing": "Fildeling",
|
||||
"label.participants": "Deltakere",
|
||||
"label.shareFile": "Del fil",
|
||||
"label.shareGalleryFile": "Del bilde",
|
||||
"label.fileSharingUnsupported": "Fildeling ikke støttet",
|
||||
"label.unknown": "Ukjent",
|
||||
"label.democratic": "Demokratisk",
|
||||
|
|
@ -103,10 +112,11 @@
|
|||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Lukk",
|
||||
"label.media": "Media",
|
||||
"label.appearence": "Utseende",
|
||||
"label.appearance": "Utseende",
|
||||
"label.advanced": "Avansert",
|
||||
"label.addVideo": "Legg til video",
|
||||
"label.promoteAllPeers": "Slipp inn alle",
|
||||
"label.moreActions": "Flere handlinger",
|
||||
|
||||
"settings.settings": "Innstillinger",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -126,6 +136,12 @@
|
|||
"settings.lastn": "Antall videoer synlig",
|
||||
"settings.hiddenControls": "Skjul media knapper",
|
||||
"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.startingFileShare": "Starter fildeling",
|
||||
|
|
|
|||
|
|
@ -49,21 +49,25 @@
|
|||
"room.spotlights": "Aktywni uczestnicy",
|
||||
"room.passive": "Pasywni uczestnicy",
|
||||
"room.videoPaused": "To wideo jest wstrzymane.",
|
||||
"room.muteAll": null,
|
||||
"room.stopAllVideo": null,
|
||||
"room.closeMeeting": null,
|
||||
"room.clearChat": null,
|
||||
"room.clearFileSharing": null,
|
||||
"room.speechUnsupported": null,
|
||||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.muteAll": "Wycisz wszystkich",
|
||||
"room.stopAllVideo": "Zatrzymaj wszystkie Video",
|
||||
"room.closeMeeting": "Zamknij spotkanie",
|
||||
"room.clearChat": "Wyczyść Chat",
|
||||
"room.clearFileSharing": "Wyczyść pliki",
|
||||
"room.speechUnsupported": "Twoja przeglądarka nie rozpoznaje mowy",
|
||||
"room.moderatoractions": "Akcje moderatora",
|
||||
"room.raisedHand": "{displayName} podniósł rękę",
|
||||
"room.loweredHand": "{displayName} opuścił rękę",
|
||||
"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.lostRole": null,
|
||||
"roles.gotRole": "Masz rolę {role}",
|
||||
"roles.lostRole": "Nie masz już roli {role}",
|
||||
|
||||
"tooltip.login": "Zaloguj",
|
||||
"tooltip.logout": "Wyloguj",
|
||||
|
|
@ -75,10 +79,14 @@
|
|||
"tooltip.lobby": "Pokaż poczekalnię",
|
||||
"tooltip.settings": "Pokaż ustawienia",
|
||||
"tooltip.participants": "Pokaż uczestników",
|
||||
"tooltip.kickParticipant": null,
|
||||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.kickParticipant": "Wyrzuć użytkownika",
|
||||
"tooltip.muteParticipant": "Wycisz użytkownika",
|
||||
"tooltip.muteParticipantVideo": "Wyłącz wideo użytkownika",
|
||||
"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.chooseRoomButton": "Kontynuuj",
|
||||
|
|
@ -92,6 +100,7 @@
|
|||
"label.filesharing": "Udostępnianie plików",
|
||||
"label.participants": "Uczestnicy",
|
||||
"label.shareFile": "Udostępnij plik",
|
||||
"label.shareGalleryFile": "Udostępnij obraz",
|
||||
"label.fileSharingUnsupported": "Udostępnianie plików nie jest obsługiwane",
|
||||
"label.unknown": "Nieznane",
|
||||
"label.democratic": "Układ demokratyczny",
|
||||
|
|
@ -102,11 +111,12 @@
|
|||
"label.veryHigh": "Bardzo wysoka (FHD)",
|
||||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Zamknij",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.media": "Media",
|
||||
"label.appearance": "Wygląd",
|
||||
"label.advanced": "Zaawansowane",
|
||||
"label.addVideo": "Dodaj wideo",
|
||||
"label.promoteAllPeers": "Wpuść wszystkich",
|
||||
"label.moreActions": "Więcej akcji",
|
||||
|
||||
"settings.settings": "Ustawienia",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -124,8 +134,14 @@
|
|||
"settings.advancedMode": "Tryb zaawansowany",
|
||||
"settings.permanentTopBar": "Stały górny pasek",
|
||||
"settings.lastn": "Liczba widocznych uczestników (zdalnych)",
|
||||
"settings.hiddenControls": null,
|
||||
"settings.notificationSounds": null,
|
||||
"settings.hiddenControls": "Ukryte kontrolki mediów",
|
||||
"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.startingFileShare": "Próba udostępnienia pliku",
|
||||
|
|
@ -167,8 +183,8 @@
|
|||
"devices.cameraDisconnected": "Kamera odłączona",
|
||||
"devices.cameraError": "Wystąpił błąd podczas uzyskiwania dostępu do kamery",
|
||||
|
||||
"moderator.clearChat": null,
|
||||
"moderator.clearFiles": null,
|
||||
"moderator.muteAudio": null,
|
||||
"moderator.muteVideo": null
|
||||
"moderator.clearChat": "Moderator wyczyścił chat",
|
||||
"moderator.clearFiles": "Moderator wyczyścił pliki",
|
||||
"moderator.muteAudio": "Moderator wyciszył audio",
|
||||
"moderator.muteVideo": "Moderator wyciszył twoje video"
|
||||
}
|
||||
|
|
@ -59,6 +59,10 @@
|
|||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.overRoomLimit": null,
|
||||
"room.help": null,
|
||||
"room.about": null,
|
||||
"room.shortcutKeys": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -79,6 +83,10 @@
|
|||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.muteScreenSharing": null,
|
||||
"tooltip.muteParticipantAudioModerator": null,
|
||||
"tooltip.muteParticipantVideoModerator": null,
|
||||
"tooltip.muteScreenSharingModerator": null,
|
||||
|
||||
"label.roomName": "Nome da sala",
|
||||
"label.chooseRoomButton": "Continuar",
|
||||
|
|
@ -92,6 +100,7 @@
|
|||
"label.filesharing": "Partilha de ficheiro",
|
||||
"label.participants": "Participantes",
|
||||
"label.shareFile": "Partilhar ficheiro",
|
||||
"label.shareGalleryFile": null,
|
||||
"label.fileSharingUnsupported": "Partilha de ficheiro não disponível",
|
||||
"label.unknown": "Desconhecido",
|
||||
"label.democratic": "Vista democrática",
|
||||
|
|
@ -103,10 +112,11 @@
|
|||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Fechar",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.appearance": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.moreActions": null,
|
||||
|
||||
"settings.settings": "Definições",
|
||||
"settings.camera": "Camera",
|
||||
|
|
@ -126,6 +136,12 @@
|
|||
"settings.lastn": "Número de vídeos visíveis",
|
||||
"settings.hiddenControls": 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.startingFileShare": "Tentando partilha de ficheiro",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@
|
|||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.overRoomLimit": null,
|
||||
"room.help": null,
|
||||
"room.about": null,
|
||||
"room.shortcutKeys": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -79,6 +83,10 @@
|
|||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.muteScreenSharing": null,
|
||||
"tooltip.muteParticipantAudioModerator": null,
|
||||
"tooltip.muteParticipantVideoModerator": null,
|
||||
"tooltip.muteScreenSharingModerator": null,
|
||||
|
||||
"label.roomName": "Numele camerei",
|
||||
"label.chooseRoomButton": "Continuare",
|
||||
|
|
@ -92,6 +100,7 @@
|
|||
"label.filesharing": "Partajarea fișierelor",
|
||||
"label.participants": "Participanți",
|
||||
"label.shareFile": "Partajează fișierul",
|
||||
"label.shareGalleryFile": null,
|
||||
"label.fileSharingUnsupported": "Partajarea fișierelor nu este acceptată",
|
||||
"label.unknown": "Necunoscut",
|
||||
"label.democratic": "Distribuție egală a dimensiunii imaginii",
|
||||
|
|
@ -103,10 +112,11 @@
|
|||
"label.ultra": "Rezoluție ultra înaltă (UHD)",
|
||||
"label.close": "Închide",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.appearance": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.moreActions": null,
|
||||
|
||||
"settings.settings": "Setări",
|
||||
"settings.camera": "Cameră video",
|
||||
|
|
@ -126,6 +136,12 @@
|
|||
"settings.lastn": "Numărul de videoclipuri vizibile",
|
||||
"settings.hiddenControls": 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.startingFileShare": "Partajarea fișierului",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@
|
|||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.overRoomLimit": null,
|
||||
"room.help": null,
|
||||
"room.about": null,
|
||||
"room.shortcutKeys": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -79,6 +83,10 @@
|
|||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.muteScreenSharing": null,
|
||||
"tooltip.muteParticipantAudioModerator": null,
|
||||
"tooltip.muteParticipantVideoModerator": null,
|
||||
"tooltip.muteScreenSharingModerator": null,
|
||||
|
||||
"label.roomName": "Oda adı",
|
||||
"label.chooseRoomButton": "Devam",
|
||||
|
|
@ -92,6 +100,7 @@
|
|||
"label.filesharing": "Dosya paylaşım",
|
||||
"label.participants": "Katılımcı",
|
||||
"label.shareFile": "Dosya paylaş",
|
||||
"label.shareGalleryFile": null,
|
||||
"label.fileSharingUnsupported": "Dosya paylaşımı desteklenmiyor",
|
||||
"label.unknown": "Bilinmeyen",
|
||||
"label.democratic": "Demokratik görünüm",
|
||||
|
|
@ -103,10 +112,11 @@
|
|||
"label.ultra": "Ultra (UHD)",
|
||||
"label.close": "Kapat",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.appearance": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.moreActions": null,
|
||||
|
||||
"settings.settings": "Ayarlar",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
@ -123,6 +133,12 @@
|
|||
"settings.lastn": "İzlenebilir video sayısı",
|
||||
"settings.hiddenControls": 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.startingFileShare": "Paylaşılan dosyaya erişiliyor",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@
|
|||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
"room.overRoomLimit": null,
|
||||
"room.help": null,
|
||||
"room.about": null,
|
||||
"room.shortcutKeys": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -79,6 +83,10 @@
|
|||
"tooltip.muteParticipant": null,
|
||||
"tooltip.muteParticipantVideo": null,
|
||||
"tooltip.raisedHand": null,
|
||||
"tooltip.muteScreenSharing": null,
|
||||
"tooltip.muteParticipantAudioModerator": null,
|
||||
"tooltip.muteParticipantVideoModerator": null,
|
||||
"tooltip.muteScreenSharingModerator": null,
|
||||
|
||||
"label.roomName": "Назва кімнати",
|
||||
"label.chooseRoomButton": "Продовжити",
|
||||
|
|
@ -92,6 +100,7 @@
|
|||
"label.filesharing": "Обмін файлами",
|
||||
"label.participants": "Учасники",
|
||||
"label.shareFile": "Надіслати файл",
|
||||
"label.shareGalleryFile": null,
|
||||
"label.fileSharingUnsupported": "Обмін файлами не підтримується",
|
||||
"label.unknown": "Невідомо",
|
||||
"label.democrat": "Демократичний вигляд",
|
||||
|
|
@ -103,10 +112,11 @@
|
|||
"label.ultra": "Ультра (UHD)",
|
||||
"label.close": "Закрити",
|
||||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.appearance": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
"label.promoteAllPeers": null,
|
||||
"label.moreActions": null,
|
||||
|
||||
"settings.settings": "Налаштування",
|
||||
"settings.camera": "Камера",
|
||||
|
|
@ -126,6 +136,12 @@
|
|||
"settings.lastn": "Кількість видимих відео",
|
||||
"settings.hiddenControls": 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.startingFileShare": "Спроба поділитися файлом",
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
};
|
||||
|
|
@ -1,5 +1,23 @@
|
|||
const os = require('os');
|
||||
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 axios = require('axios');
|
||||
|
||||
|
|
@ -96,8 +114,8 @@ module.exports =
|
|||
this._queue = new AwaitQueue();
|
||||
}
|
||||
|
||||
// rooms: number of rooms
|
||||
// peers: number of peers
|
||||
// rooms: rooms object
|
||||
// peers: peers object
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async log({ rooms, peers })
|
||||
{
|
||||
|
|
@ -106,9 +124,9 @@ module.exports =
|
|||
// Do your logging in here, use queue to keep correct order
|
||||
|
||||
// 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
|
||||
console.log('Number of peers: ', peers);
|
||||
console.log('Number of peers: ', peers.size);
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
|
|
@ -216,37 +234,48 @@ module.exports =
|
|||
accessFromRoles : {
|
||||
// The role(s) will gain access to the room
|
||||
// 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
|
||||
// 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 : [ userRoles.NORMAL ]
|
||||
[BYPASS_LOBBY] : [ userRoles.NORMAL ]
|
||||
},
|
||||
permissionsFromRoles : {
|
||||
// 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
|
||||
PROMOTE_PEER : [ userRoles.NORMAL ],
|
||||
[PROMOTE_PEER] : [ userRoles.NORMAL ],
|
||||
// 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
|
||||
MODERATE_CHAT : [ userRoles.MODERATOR ],
|
||||
[MODERATE_CHAT] : [ userRoles.MODERATOR ],
|
||||
// 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
|
||||
EXTRA_VIDEO : [ userRoles.NORMAL ],
|
||||
[EXTRA_VIDEO] : [ userRoles.NORMAL ],
|
||||
// The role(s) have permission to share files
|
||||
SHARE_FILE : [ userRoles.NORMAL ],
|
||||
[SHARE_FILE] : [ userRoles.NORMAL ],
|
||||
// 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)
|
||||
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
|
||||
// are allready users in the room
|
||||
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 :
|
||||
{
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class Lobby extends EventEmitter
|
|||
|
||||
return Object.values(this._peers).map((peer) =>
|
||||
({
|
||||
peerId : peer.id,
|
||||
id : peer.id,
|
||||
displayName : peer.displayName,
|
||||
picture : peer.picture
|
||||
}));
|
||||
|
|
@ -154,8 +154,6 @@ class Lobby extends EventEmitter
|
|||
this.emit('lobbyEmpty');
|
||||
};
|
||||
|
||||
this._notification(peer.socket, 'enteredLobby');
|
||||
|
||||
this._peers[peer.id] = peer;
|
||||
|
||||
peer.on('gotRole', peer.gotRoleHandler);
|
||||
|
|
@ -165,6 +163,8 @@ class Lobby extends EventEmitter
|
|||
peer.socket.on('request', peer.socketRequestHandler);
|
||||
|
||||
peer.on('close', peer.closeHandler);
|
||||
|
||||
this._notification(peer.socket, 'enteredLobby');
|
||||
}
|
||||
|
||||
async _handleSocketRequest(peer, request, cb)
|
||||
|
|
@ -190,6 +190,7 @@ class Lobby extends EventEmitter
|
|||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'changePicture':
|
||||
{
|
||||
const { picture } = request.data;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ class Peer extends EventEmitter
|
|||
|
||||
this._email = null;
|
||||
|
||||
this._routerId = null;
|
||||
|
||||
this._rtpCapabilities = null;
|
||||
|
||||
this._raisedHand = false;
|
||||
|
|
@ -62,10 +64,10 @@ class Peer extends EventEmitter
|
|||
|
||||
// Iterate and close all mediasoup Transport associated to this Peer, so all
|
||||
// its Producers and Consumers will also be closed.
|
||||
this.transports.forEach((transport) =>
|
||||
for (const transport of this.transports.values())
|
||||
{
|
||||
transport.close();
|
||||
});
|
||||
}
|
||||
|
||||
if (this.socket)
|
||||
this.socket.disconnect(true);
|
||||
|
|
@ -238,6 +240,16 @@ class Peer extends EventEmitter
|
|||
this._email = email;
|
||||
}
|
||||
|
||||
get routerId()
|
||||
{
|
||||
return this._routerId;
|
||||
}
|
||||
|
||||
set routerId(routerId)
|
||||
{
|
||||
this._routerId = routerId;
|
||||
}
|
||||
|
||||
get rtpCapabilities()
|
||||
{
|
||||
return this._rtpCapabilities;
|
||||
|
|
|
|||
|
|
@ -1,36 +1,59 @@
|
|||
const EventEmitter = require('events').EventEmitter;
|
||||
const AwaitQueue = require('awaitqueue');
|
||||
const axios = require('axios');
|
||||
const Logger = require('./Logger');
|
||||
const Lobby = require('./Lobby');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const userRoles = require('../userRoles');
|
||||
|
||||
const {
|
||||
BYPASS_ROOM_LOCK,
|
||||
BYPASS_LOBBY
|
||||
} = require('../access');
|
||||
|
||||
const permissions = require('../permissions'), {
|
||||
CHANGE_ROOM_LOCK,
|
||||
PROMOTE_PEER,
|
||||
SEND_CHAT,
|
||||
MODERATE_CHAT,
|
||||
SHARE_SCREEN,
|
||||
EXTRA_VIDEO,
|
||||
SHARE_FILE,
|
||||
MODERATE_FILES,
|
||||
MODERATE_ROOM
|
||||
} = permissions;
|
||||
|
||||
const config = require('../config/config');
|
||||
|
||||
const logger = new Logger('Room');
|
||||
|
||||
// In case they are not configured properly
|
||||
const accessFromRoles =
|
||||
const roomAccess =
|
||||
{
|
||||
BYPASS_ROOM_LOCK : [ userRoles.ADMIN ],
|
||||
BYPASS_LOBBY : [ userRoles.NORMAL ],
|
||||
[BYPASS_ROOM_LOCK] : [ userRoles.ADMIN ],
|
||||
[BYPASS_LOBBY] : [ userRoles.NORMAL ],
|
||||
...config.accessFromRoles
|
||||
};
|
||||
|
||||
const permissionsFromRoles =
|
||||
const roomPermissions =
|
||||
{
|
||||
CHANGE_ROOM_LOCK : [ userRoles.NORMAL ],
|
||||
PROMOTE_PEER : [ userRoles.NORMAL ],
|
||||
SEND_CHAT : [ userRoles.NORMAL ],
|
||||
MODERATE_CHAT : [ userRoles.MODERATOR ],
|
||||
SHARE_SCREEN : [ userRoles.NORMAL ],
|
||||
EXTRA_VIDEO : [ userRoles.NORMAL ],
|
||||
SHARE_FILE : [ userRoles.NORMAL ],
|
||||
MODERATE_FILES : [ userRoles.MODERATOR ],
|
||||
MODERATE_ROOM : [ userRoles.MODERATOR ],
|
||||
[CHANGE_ROOM_LOCK] : [ userRoles.NORMAL ],
|
||||
[PROMOTE_PEER] : [ userRoles.NORMAL ],
|
||||
[SEND_CHAT] : [ userRoles.NORMAL ],
|
||||
[MODERATE_CHAT] : [ userRoles.MODERATOR ],
|
||||
[SHARE_SCREEN] : [ userRoles.NORMAL ],
|
||||
[EXTRA_VIDEO] : [ userRoles.NORMAL ],
|
||||
[SHARE_FILE] : [ userRoles.NORMAL ],
|
||||
[MODERATE_FILES] : [ userRoles.MODERATOR ],
|
||||
[MODERATE_ROOM] : [ userRoles.MODERATOR ],
|
||||
...config.permissionsFromRoles
|
||||
};
|
||||
|
||||
const roomAllowWhenRoleMissing = config.allowWhenRoleMissing || [];
|
||||
|
||||
const ROUTER_SCALE_SIZE = config.routerScaleSize || 40;
|
||||
|
||||
class Room extends EventEmitter
|
||||
{
|
||||
/**
|
||||
|
|
@ -38,32 +61,49 @@ class Room extends EventEmitter
|
|||
*
|
||||
* @async
|
||||
*
|
||||
* @param {mediasoup.Worker} mediasoupWorker - The mediasoup Worker in which a new
|
||||
* @param {mediasoup.Worker} mediasoupWorkers - The mediasoup Worker in which a new
|
||||
* mediasoup Router must be created.
|
||||
* @param {String} roomId - Id of the Room instance.
|
||||
*/
|
||||
static async create({ mediasoupWorker, roomId })
|
||||
static async create({ mediasoupWorkers, roomId })
|
||||
{
|
||||
logger.info('create() [roomId:"%s"]', roomId);
|
||||
|
||||
// Shuffle workers to get random cores
|
||||
let shuffledWorkers = mediasoupWorkers.sort(() => Math.random() - 0.5);
|
||||
|
||||
// Router media codecs.
|
||||
const mediaCodecs = config.mediasoup.router.mediaCodecs;
|
||||
|
||||
// Create a mediasoup Router.
|
||||
const mediasoupRouter = await mediasoupWorker.createRouter({ mediaCodecs });
|
||||
const mediasoupRouters = new Map();
|
||||
|
||||
// Create a mediasoup AudioLevelObserver.
|
||||
const audioLevelObserver = await mediasoupRouter.createAudioLevelObserver(
|
||||
let firstRouter = null;
|
||||
|
||||
for (const worker of shuffledWorkers)
|
||||
{
|
||||
const router = await worker.createRouter({ mediaCodecs });
|
||||
|
||||
if (!firstRouter)
|
||||
firstRouter = router;
|
||||
|
||||
mediasoupRouters.set(router.id, router);
|
||||
}
|
||||
|
||||
// Create a mediasoup AudioLevelObserver on first router
|
||||
const audioLevelObserver = await firstRouter.createAudioLevelObserver(
|
||||
{
|
||||
maxEntries : 1,
|
||||
threshold : -80,
|
||||
interval : 800
|
||||
});
|
||||
|
||||
return new Room({ roomId, mediasoupRouter, audioLevelObserver });
|
||||
firstRouter = null;
|
||||
shuffledWorkers = null;
|
||||
|
||||
return new Room({ roomId, mediasoupRouters, audioLevelObserver });
|
||||
}
|
||||
|
||||
constructor({ roomId, mediasoupRouter, audioLevelObserver })
|
||||
constructor({ roomId, mediasoupRouters, audioLevelObserver })
|
||||
{
|
||||
logger.info('constructor() [roomId:"%s"]', roomId);
|
||||
|
||||
|
|
@ -78,6 +118,9 @@ class Room extends EventEmitter
|
|||
// Closed flag.
|
||||
this._closed = false;
|
||||
|
||||
// Joining queue
|
||||
this._queue = new AwaitQueue();
|
||||
|
||||
// Locked flag.
|
||||
this._locked = false;
|
||||
|
||||
|
|
@ -98,8 +141,15 @@ class Room extends EventEmitter
|
|||
|
||||
this._peers = {};
|
||||
|
||||
// mediasoup Router instance.
|
||||
this._mediasoupRouter = mediasoupRouter;
|
||||
this._selfDestructTimeout = null;
|
||||
|
||||
// Array of mediasoup Router instances.
|
||||
this._mediasoupRouters = mediasoupRouters;
|
||||
|
||||
// The router we are currently putting peers in
|
||||
this._routerIterator = this._mediasoupRouters.values();
|
||||
|
||||
this._currentRouter = this._routerIterator.next().value;
|
||||
|
||||
// mediasoup AudioLevelObserver.
|
||||
this._audioLevelObserver = audioLevelObserver;
|
||||
|
|
@ -122,8 +172,23 @@ class Room extends EventEmitter
|
|||
|
||||
this._closed = true;
|
||||
|
||||
this._queue.close();
|
||||
|
||||
this._queue = null;
|
||||
|
||||
if (this._selfDestructTimeout)
|
||||
clearTimeout(this._selfDestructTimeout);
|
||||
|
||||
this._selfDestructTimeout = null;
|
||||
|
||||
this._chatHistory = null;
|
||||
|
||||
this._fileHistory = null;
|
||||
|
||||
this._lobby.close();
|
||||
|
||||
this._lobby = null;
|
||||
|
||||
// Close the peers.
|
||||
for (const peer in this._peers)
|
||||
{
|
||||
|
|
@ -133,8 +198,19 @@ class Room extends EventEmitter
|
|||
|
||||
this._peers = null;
|
||||
|
||||
// Close the mediasoup Router.
|
||||
this._mediasoupRouter.close();
|
||||
// Close the mediasoup Routers.
|
||||
for (const router of this._mediasoupRouters.values())
|
||||
{
|
||||
router.close();
|
||||
}
|
||||
|
||||
this._routerIterator = null;
|
||||
|
||||
this._currentRouter = null;
|
||||
|
||||
this._mediasoupRouters.clear();
|
||||
|
||||
this._audioLevelObserver = null;
|
||||
|
||||
// Emit 'close' event.
|
||||
this.emit('close');
|
||||
|
|
@ -173,21 +249,34 @@ class Room extends EventEmitter
|
|||
// Returning user
|
||||
if (returning)
|
||||
this._peerJoining(peer, true);
|
||||
else if ( // Has a role that is allowed to bypass room lock
|
||||
peer.roles.some((role) => accessFromRoles.BYPASS_ROOM_LOCK.includes(role))
|
||||
)
|
||||
// Has a role that is allowed to bypass room lock
|
||||
else if (this._hasAccess(peer, BYPASS_ROOM_LOCK))
|
||||
this._peerJoining(peer);
|
||||
else if (
|
||||
'maxUsersPerRoom' in config &&
|
||||
(
|
||||
Object.keys(this._peers).length +
|
||||
this._lobby.peerList().length
|
||||
) >= config.maxUsersPerRoom)
|
||||
{
|
||||
this._handleOverRoomLimit(peer);
|
||||
}
|
||||
else if (this._locked)
|
||||
this._parkPeer(peer);
|
||||
else
|
||||
{
|
||||
// Has a role that is allowed to bypass lobby
|
||||
peer.roles.some((role) => accessFromRoles.BYPASS_LOBBY.includes(role)) ?
|
||||
this._hasAccess(peer, BYPASS_LOBBY) ?
|
||||
this._peerJoining(peer) :
|
||||
this._handleGuest(peer);
|
||||
}
|
||||
}
|
||||
|
||||
_handleOverRoomLimit(peer)
|
||||
{
|
||||
this._notification(peer.socket, 'overRoomLimit');
|
||||
}
|
||||
|
||||
_handleGuest(peer)
|
||||
{
|
||||
if (config.activateOnHostJoin && !this.checkEmpty())
|
||||
|
|
@ -209,11 +298,7 @@ class Room extends EventEmitter
|
|||
|
||||
this._peerJoining(promotedPeer);
|
||||
|
||||
for (
|
||||
const peer of this._getPeersWithPermission({
|
||||
permission : permissionsFromRoles.PROMOTE_PEER
|
||||
})
|
||||
)
|
||||
for (const peer of this._getAllowedPeers(PROMOTE_PEER))
|
||||
{
|
||||
this._notification(peer.socket, 'lobby:promotedPeer', { peerId: id });
|
||||
}
|
||||
|
|
@ -221,9 +306,8 @@ class Room extends EventEmitter
|
|||
|
||||
this._lobby.on('peerRolesChanged', (peer) =>
|
||||
{
|
||||
if ( // Has a role that is allowed to bypass room lock
|
||||
peer.roles.some((role) => accessFromRoles.BYPASS_ROOM_LOCK.includes(role))
|
||||
)
|
||||
// Has a role that is allowed to bypass room lock
|
||||
if (this._hasAccess(peer, BYPASS_ROOM_LOCK))
|
||||
{
|
||||
this._lobby.promotePeer(peer.id);
|
||||
|
||||
|
|
@ -232,7 +316,7 @@ class Room extends EventEmitter
|
|||
|
||||
if ( // Has a role that is allowed to bypass lobby
|
||||
!this._locked &&
|
||||
peer.roles.some((role) => accessFromRoles.BYPASS_LOBBY.includes(role))
|
||||
this._hasAccess(peer, BYPASS_LOBBY)
|
||||
)
|
||||
{
|
||||
this._lobby.promotePeer(peer.id);
|
||||
|
|
@ -245,11 +329,7 @@ class Room extends EventEmitter
|
|||
{
|
||||
const { id, displayName } = changedPeer;
|
||||
|
||||
for (
|
||||
const peer of this._getPeersWithPermission({
|
||||
permission : permissionsFromRoles.PROMOTE_PEER
|
||||
})
|
||||
)
|
||||
for (const peer of this._getAllowedPeers(PROMOTE_PEER))
|
||||
{
|
||||
this._notification(peer.socket, 'lobby:changeDisplayName', { peerId: id, displayName });
|
||||
}
|
||||
|
|
@ -259,11 +339,7 @@ class Room extends EventEmitter
|
|||
{
|
||||
const { id, picture } = changedPeer;
|
||||
|
||||
for (
|
||||
const peer of this._getPeersWithPermission({
|
||||
permission : permissionsFromRoles.PROMOTE_PEER
|
||||
})
|
||||
)
|
||||
for (const peer of this._getAllowedPeers(PROMOTE_PEER))
|
||||
{
|
||||
this._notification(peer.socket, 'lobby:changePicture', { peerId: id, picture });
|
||||
}
|
||||
|
|
@ -275,11 +351,7 @@ class Room extends EventEmitter
|
|||
|
||||
const { id } = closedPeer;
|
||||
|
||||
for (
|
||||
const peer of this._getPeersWithPermission({
|
||||
permission : permissionsFromRoles.PROMOTE_PEER
|
||||
})
|
||||
)
|
||||
for (const peer of this._getAllowedPeers(PROMOTE_PEER))
|
||||
{
|
||||
this._notification(peer.socket, 'lobby:peerClosed', { peerId: id });
|
||||
}
|
||||
|
|
@ -339,7 +411,7 @@ class Room extends EventEmitter
|
|||
);
|
||||
}
|
||||
|
||||
async dump()
|
||||
dump()
|
||||
{
|
||||
return {
|
||||
roomId : this._roomId,
|
||||
|
|
@ -356,7 +428,10 @@ class Room extends EventEmitter
|
|||
{
|
||||
logger.debug('selfDestructCountdown() started');
|
||||
|
||||
setTimeout(() =>
|
||||
if (this._selfDestructTimeout)
|
||||
clearTimeout(this._selfDestructTimeout);
|
||||
|
||||
this._selfDestructTimeout = setTimeout(() =>
|
||||
{
|
||||
if (this._closed)
|
||||
return;
|
||||
|
|
@ -382,17 +457,15 @@ class Room extends EventEmitter
|
|||
{
|
||||
this._lobby.parkPeer(parkPeer);
|
||||
|
||||
for (
|
||||
const peer of this._getPeersWithPermission({
|
||||
permission : permissionsFromRoles.PROMOTE_PEER
|
||||
})
|
||||
)
|
||||
for (const peer of this._getAllowedPeers(PROMOTE_PEER))
|
||||
{
|
||||
this._notification(peer.socket, 'parkedPeer', { peerId: parkPeer.id });
|
||||
}
|
||||
}
|
||||
|
||||
async _peerJoining(peer, returning = false)
|
||||
_peerJoining(peer, returning = false)
|
||||
{
|
||||
this._queue.push(async () =>
|
||||
{
|
||||
peer.socket.join(this._roomId);
|
||||
|
||||
|
|
@ -401,6 +474,9 @@ class Room extends EventEmitter
|
|||
|
||||
this._peers[peer.id] = peer;
|
||||
|
||||
// Assign routerId
|
||||
peer.routerId = await this._getRouterId();
|
||||
|
||||
this._handlePeer(peer);
|
||||
|
||||
if (returning)
|
||||
|
|
@ -452,45 +528,20 @@ class Room extends EventEmitter
|
|||
|
||||
this._notification(peer.socket, 'roomReady', { turnServers });
|
||||
}
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('_peerJoining() [error:"%o"]', error);
|
||||
});
|
||||
}
|
||||
|
||||
_handlePeer(peer)
|
||||
{
|
||||
logger.debug('_handlePeer() [peer:"%s"]', peer.id);
|
||||
|
||||
peer.socket.on('request', (request, cb) =>
|
||||
{
|
||||
logger.debug(
|
||||
'Peer "request" event [method:"%s", peerId:"%s"]',
|
||||
request.method, peer.id);
|
||||
|
||||
this._handleSocketRequest(peer, request, cb)
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('"request" failed [error:"%o"]', error);
|
||||
|
||||
cb(error);
|
||||
});
|
||||
});
|
||||
|
||||
peer.on('close', () =>
|
||||
{
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
// If the Peer was joined, notify all Peers.
|
||||
if (peer.joined)
|
||||
this._notification(peer.socket, 'peerClosed', { peerId: peer.id }, true);
|
||||
|
||||
// Remove from lastN
|
||||
this._lastN = this._lastN.filter((id) => id !== peer.id);
|
||||
|
||||
delete this._peers[peer.id];
|
||||
|
||||
// If this is the last Peer in the room and
|
||||
// lobby is empty, close the room after a while.
|
||||
if (this.checkEmpty() && this._lobby.checkEmpty())
|
||||
this.selfDestructCountdown();
|
||||
this._handlePeerClose(peer);
|
||||
});
|
||||
|
||||
peer.on('displayNameChanged', ({ oldDisplayName }) =>
|
||||
|
|
@ -534,7 +585,7 @@ class Room extends EventEmitter
|
|||
|
||||
// Got permission to promote peers, notify peer of
|
||||
// peers in lobby
|
||||
if (permissionsFromRoles.PROMOTE_PEER.includes(newRole))
|
||||
if (roomPermissions.PROMOTE_PEER.includes(newRole))
|
||||
{
|
||||
const lobbyPeers = this._lobby.peerList();
|
||||
|
||||
|
|
@ -556,15 +607,81 @@ class Room extends EventEmitter
|
|||
role : oldRole
|
||||
}, true, true);
|
||||
});
|
||||
|
||||
peer.socket.on('request', (request, cb) =>
|
||||
{
|
||||
logger.debug(
|
||||
'Peer "request" event [method:"%s", peerId:"%s"]',
|
||||
request.method, peer.id);
|
||||
|
||||
this._handleSocketRequest(peer, request, cb)
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('"request" failed [error:"%o"]', error);
|
||||
|
||||
cb(error);
|
||||
});
|
||||
});
|
||||
|
||||
// Peer left before we were done joining
|
||||
if (peer.closed)
|
||||
this._handlePeerClose(peer);
|
||||
}
|
||||
|
||||
_handlePeerClose(peer)
|
||||
{
|
||||
logger.debug('_handlePeerClose() [peer:"%s"]', peer.id);
|
||||
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
// If the Peer was joined, notify all Peers.
|
||||
if (peer.joined)
|
||||
this._notification(peer.socket, 'peerClosed', { peerId: peer.id }, true);
|
||||
|
||||
// Remove from lastN
|
||||
this._lastN = this._lastN.filter((id) => id !== peer.id);
|
||||
|
||||
// Need this to know if this peer was the last with PROMOTE_PEER
|
||||
const hasPromotePeer = peer.roles.some((role) =>
|
||||
roomPermissions[PROMOTE_PEER].includes(role)
|
||||
);
|
||||
|
||||
delete this._peers[peer.id];
|
||||
|
||||
// No peers left with PROMOTE_PEER, might need to give
|
||||
// lobbyPeers to peers that are left.
|
||||
if (
|
||||
hasPromotePeer &&
|
||||
!this._lobby.checkEmpty() &&
|
||||
roomAllowWhenRoleMissing.includes(PROMOTE_PEER) &&
|
||||
this._getPeersWithPermission(PROMOTE_PEER).length === 0
|
||||
)
|
||||
{
|
||||
const lobbyPeers = this._lobby.peerList();
|
||||
|
||||
for (const allowedPeer of this._getAllowedPeers(PROMOTE_PEER))
|
||||
{
|
||||
this._notification(allowedPeer.socket, 'parkedPeers', { lobbyPeers });
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the last Peer in the room and
|
||||
// lobby is empty, close the room after a while.
|
||||
if (this.checkEmpty() && this._lobby.checkEmpty())
|
||||
this.selfDestructCountdown();
|
||||
}
|
||||
|
||||
async _handleSocketRequest(peer, request, cb)
|
||||
{
|
||||
const router =
|
||||
this._mediasoupRouters.get(peer.routerId);
|
||||
|
||||
switch (request.method)
|
||||
{
|
||||
case 'getRouterRtpCapabilities':
|
||||
{
|
||||
cb(null, this._mediasoupRouter.rtpCapabilities);
|
||||
cb(null, router.rtpCapabilities);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -589,24 +706,25 @@ class Room extends EventEmitter
|
|||
// Tell the new Peer about already joined Peers.
|
||||
// And also create Consumers for existing Producers.
|
||||
|
||||
const joinedPeers =
|
||||
[
|
||||
...this._getJoinedPeers()
|
||||
];
|
||||
const joinedPeers = this._getJoinedPeers(peer);
|
||||
|
||||
const peerInfos = joinedPeers
|
||||
.filter((joinedPeer) => joinedPeer.id !== peer.id)
|
||||
.map((joinedPeer) => (joinedPeer.peerInfo));
|
||||
|
||||
const lobbyPeers = this._lobby.peerList();
|
||||
let lobbyPeers = [];
|
||||
|
||||
// Allowed to promote peers, notify about lobbypeers
|
||||
if (this._hasPermission(peer, PROMOTE_PEER))
|
||||
lobbyPeers = this._lobby.peerList();
|
||||
|
||||
cb(null, {
|
||||
roles : peer.roles,
|
||||
peers : peerInfos,
|
||||
tracker : config.fileTracker,
|
||||
authenticated : peer.authenticated,
|
||||
permissionsFromRoles : permissionsFromRoles,
|
||||
roomPermissions : roomPermissions,
|
||||
userRoles : userRoles,
|
||||
allowWhenRoleMissing : roomAllowWhenRoleMissing,
|
||||
chatHistory : this._chatHistory,
|
||||
fileHistory : this._fileHistory,
|
||||
lastNHistory : this._lastN,
|
||||
|
|
@ -633,7 +751,7 @@ class Room extends EventEmitter
|
|||
}
|
||||
|
||||
// Notify the new Peer to all other Peers.
|
||||
for (const otherPeer of this._getJoinedPeers({ excludePeer: peer }))
|
||||
for (const otherPeer of this._getJoinedPeers(peer))
|
||||
{
|
||||
this._notification(
|
||||
otherPeer.socket,
|
||||
|
|
@ -673,7 +791,7 @@ class Room extends EventEmitter
|
|||
webRtcTransportOptions.enableTcp = true;
|
||||
}
|
||||
|
||||
const transport = await this._mediasoupRouter.createWebRtcTransport(
|
||||
const transport = await router.createWebRtcTransport(
|
||||
webRtcTransportOptions
|
||||
);
|
||||
|
||||
|
|
@ -743,15 +861,13 @@ class Room extends EventEmitter
|
|||
|
||||
if (
|
||||
appData.source === 'screen' &&
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.SHARE_SCREEN.includes(role))
|
||||
!this._hasPermission(peer, SHARE_SCREEN)
|
||||
)
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
if (
|
||||
appData.source === 'extravideo' &&
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.EXTRA_VIDEO.includes(role))
|
||||
!this._hasPermission(peer, EXTRA_VIDEO)
|
||||
)
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
|
|
@ -772,6 +888,19 @@ class Room extends EventEmitter
|
|||
const producer =
|
||||
await transport.produce({ kind, rtpParameters, appData });
|
||||
|
||||
const pipeRouters = this._getRoutersToPipeTo(peer.routerId);
|
||||
|
||||
for (const [ routerId, destinationRouter ] of this._mediasoupRouters)
|
||||
{
|
||||
if (pipeRouters.includes(routerId))
|
||||
{
|
||||
await router.pipeToRouter({
|
||||
producerId : producer.id,
|
||||
router : destinationRouter
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Store the Producer into the Peer data Object.
|
||||
peer.addProducer(producer.id, producer);
|
||||
|
||||
|
|
@ -791,7 +920,7 @@ class Room extends EventEmitter
|
|||
cb(null, { id: producer.id });
|
||||
|
||||
// Optimization: Create a server-side Consumer for each Peer.
|
||||
for (const otherPeer of this._getJoinedPeers({ excludePeer: peer }))
|
||||
for (const otherPeer of this._getJoinedPeers(peer))
|
||||
{
|
||||
this._createConsumer(
|
||||
{
|
||||
|
|
@ -1053,9 +1182,7 @@ class Room extends EventEmitter
|
|||
|
||||
case 'chatMessage':
|
||||
{
|
||||
if (
|
||||
!peer.roles.some((role) => permissionsFromRoles.SEND_CHAT.includes(role))
|
||||
)
|
||||
if (!this._hasPermission(peer, SEND_CHAT))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
const { chatMessage } = request.data;
|
||||
|
|
@ -1076,11 +1203,7 @@ class Room extends EventEmitter
|
|||
|
||||
case 'moderator:clearChat':
|
||||
{
|
||||
if (
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.MODERATE_CHAT.includes(role)
|
||||
)
|
||||
)
|
||||
if (!this._hasPermission(peer, MODERATE_CHAT))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
this._chatHistory = [];
|
||||
|
|
@ -1096,11 +1219,7 @@ class Room extends EventEmitter
|
|||
|
||||
case 'lockRoom':
|
||||
{
|
||||
if (
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role)
|
||||
)
|
||||
)
|
||||
if (!this._hasPermission(peer, CHANGE_ROOM_LOCK))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
this._locked = true;
|
||||
|
|
@ -1118,11 +1237,7 @@ class Room extends EventEmitter
|
|||
|
||||
case 'unlockRoom':
|
||||
{
|
||||
if (
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role)
|
||||
)
|
||||
)
|
||||
if (!this._hasPermission(peer, CHANGE_ROOM_LOCK))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
this._locked = false;
|
||||
|
|
@ -1180,11 +1295,7 @@ class Room extends EventEmitter
|
|||
|
||||
case 'promotePeer':
|
||||
{
|
||||
if (
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.PROMOTE_PEER.includes(role)
|
||||
)
|
||||
)
|
||||
if (!this._hasPermission(peer, PROMOTE_PEER))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
const { peerId } = request.data;
|
||||
|
|
@ -1199,11 +1310,7 @@ class Room extends EventEmitter
|
|||
|
||||
case 'promoteAllPeers':
|
||||
{
|
||||
if (
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.PROMOTE_PEER.includes(role)
|
||||
)
|
||||
)
|
||||
if (!this._hasPermission(peer, PROMOTE_PEER))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
this._lobby.promoteAllPeers();
|
||||
|
|
@ -1216,11 +1323,7 @@ class Room extends EventEmitter
|
|||
|
||||
case 'sendFile':
|
||||
{
|
||||
if (
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.SHARE_FILE.includes(role)
|
||||
)
|
||||
)
|
||||
if (!this._hasPermission(peer, SHARE_FILE))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
const { magnetUri } = request.data;
|
||||
|
|
@ -1241,11 +1344,7 @@ class Room extends EventEmitter
|
|||
|
||||
case 'moderator:clearFileSharing':
|
||||
{
|
||||
if (
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.MODERATE_FILES.includes(role)
|
||||
)
|
||||
)
|
||||
if (!this._hasPermission(peer, MODERATE_FILES))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
this._fileHistory = [];
|
||||
|
|
@ -1278,13 +1377,28 @@ class Room extends EventEmitter
|
|||
break;
|
||||
}
|
||||
|
||||
case 'moderator:mute':
|
||||
{
|
||||
if (!this._hasPermission(peer, MODERATE_ROOM))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
const { peerId } = request.data;
|
||||
|
||||
const mutePeer = this._peers[peerId];
|
||||
|
||||
if (!mutePeer)
|
||||
throw new Error(`peer with id "${peerId}" not found`);
|
||||
|
||||
this._notification(mutePeer.socket, 'moderator:mute');
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'moderator:muteAll':
|
||||
{
|
||||
if (
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.MODERATE_ROOM.includes(role)
|
||||
)
|
||||
)
|
||||
if (!this._hasPermission(peer, MODERATE_ROOM))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
// Spread to others
|
||||
|
|
@ -1295,13 +1409,28 @@ class Room extends EventEmitter
|
|||
break;
|
||||
}
|
||||
|
||||
case 'moderator:stopVideo':
|
||||
{
|
||||
if (!this._hasPermission(peer, MODERATE_ROOM))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
const { peerId } = request.data;
|
||||
|
||||
const stopVideoPeer = this._peers[peerId];
|
||||
|
||||
if (!stopVideoPeer)
|
||||
throw new Error(`peer with id "${peerId}" not found`);
|
||||
|
||||
this._notification(stopVideoPeer.socket, 'moderator:stopVideo');
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'moderator:stopAllVideo':
|
||||
{
|
||||
if (
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.MODERATE_ROOM.includes(role)
|
||||
)
|
||||
)
|
||||
if (!this._hasPermission(peer, MODERATE_ROOM))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
// Spread to others
|
||||
|
|
@ -1314,11 +1443,7 @@ class Room extends EventEmitter
|
|||
|
||||
case 'moderator:closeMeeting':
|
||||
{
|
||||
if (
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.MODERATE_ROOM.includes(role)
|
||||
)
|
||||
)
|
||||
if (!this._hasPermission(peer, MODERATE_ROOM))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
this._notification(peer.socket, 'moderator:kick', null, true);
|
||||
|
|
@ -1333,11 +1458,7 @@ class Room extends EventEmitter
|
|||
|
||||
case 'moderator:kickPeer':
|
||||
{
|
||||
if (
|
||||
!peer.roles.some(
|
||||
(role) => permissionsFromRoles.MODERATE_ROOM.includes(role)
|
||||
)
|
||||
)
|
||||
if (!this._hasPermission(peer, MODERATE_ROOM))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
const { peerId } = request.data;
|
||||
|
|
@ -1356,6 +1477,25 @@ class Room extends EventEmitter
|
|||
break;
|
||||
}
|
||||
|
||||
case 'moderator:lowerHand':
|
||||
{
|
||||
if (!this._hasPermission(peer, MODERATE_ROOM))
|
||||
throw new Error('peer not authorized');
|
||||
|
||||
const { peerId } = request.data;
|
||||
|
||||
const lowerPeer = this._peers[peerId];
|
||||
|
||||
if (!lowerPeer)
|
||||
throw new Error(`peer with id "${peerId}" not found`);
|
||||
|
||||
this._notification(lowerPeer.socket, 'moderator:lowerHand');
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
logger.error('unknown request.method "%s"', request.method);
|
||||
|
|
@ -1379,6 +1519,8 @@ class Room extends EventEmitter
|
|||
producer.id
|
||||
);
|
||||
|
||||
const router = this._mediasoupRouters.get(producerPeer.routerId);
|
||||
|
||||
// Optimization:
|
||||
// - Create the server-side Consumer. If video, do it paused.
|
||||
// - Tell its Peer about it and wait for its response.
|
||||
|
|
@ -1389,7 +1531,7 @@ class Room extends EventEmitter
|
|||
// NOTE: Don't create the Consumer if the remote Peer cannot consume it.
|
||||
if (
|
||||
!consumerPeer.rtpCapabilities ||
|
||||
!this._mediasoupRouter.canConsume(
|
||||
!router.canConsume(
|
||||
{
|
||||
producerId : producer.id,
|
||||
rtpCapabilities : consumerPeer.rtpCapabilities
|
||||
|
|
@ -1515,16 +1657,54 @@ class Room extends EventEmitter
|
|||
}
|
||||
}
|
||||
|
||||
_hasPermission(peer, permission)
|
||||
{
|
||||
const hasPermission = peer.roles.some((role) =>
|
||||
roomPermissions[permission].includes(role)
|
||||
);
|
||||
|
||||
if (hasPermission)
|
||||
return true;
|
||||
|
||||
// Allow if config is set, and no one is present
|
||||
if (
|
||||
roomAllowWhenRoleMissing.includes(permission) &&
|
||||
this._getPeersWithPermission(permission).length === 0
|
||||
)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_hasAccess(peer, access)
|
||||
{
|
||||
return peer.roles.some((role) => roomAccess[access].includes(role));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the list of joined peers.
|
||||
*/
|
||||
_getJoinedPeers({ excludePeer = undefined } = {})
|
||||
_getJoinedPeers(excludePeer = undefined)
|
||||
{
|
||||
return Object.values(this._peers)
|
||||
.filter((peer) => peer.joined && peer !== excludePeer);
|
||||
}
|
||||
|
||||
_getPeersWithPermission({ permission = null, excludePeer = undefined, joined = true })
|
||||
_getAllowedPeers(permission = null, excludePeer = undefined, joined = true)
|
||||
{
|
||||
const peers = this._getPeersWithPermission(permission, excludePeer, joined);
|
||||
|
||||
if (peers.length > 0)
|
||||
return peers;
|
||||
|
||||
// Allow if config is set, and no one is present
|
||||
if (roomAllowWhenRoleMissing.includes(permission))
|
||||
return Object.values(this._peers);
|
||||
|
||||
return peers;
|
||||
}
|
||||
|
||||
_getPeersWithPermission(permission = null, excludePeer = undefined, joined = true)
|
||||
{
|
||||
return Object.values(this._peers)
|
||||
.filter(
|
||||
|
|
@ -1532,7 +1712,7 @@ class Room extends EventEmitter
|
|||
peer.joined === joined &&
|
||||
peer !== excludePeer &&
|
||||
peer.roles.some(
|
||||
(role) => permission.includes(role)
|
||||
(role) => roomPermissions[permission].includes(role)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
@ -1601,6 +1781,84 @@ class Room extends EventEmitter
|
|||
socket.emit('notification', { method, data });
|
||||
}
|
||||
}
|
||||
|
||||
async _pipeProducersToNewRouter()
|
||||
{
|
||||
const peersToPipe =
|
||||
Object.values(this._peers)
|
||||
.filter((peer) => peer.routerId !== this._currentRouter.id);
|
||||
|
||||
for (const peer of peersToPipe)
|
||||
{
|
||||
const srcRouter = this._mediasoupRouters.get(peer.routerId);
|
||||
|
||||
for (const producerId of peer.producers.keys())
|
||||
{
|
||||
await srcRouter.pipeToRouter({
|
||||
producerId,
|
||||
router : this._currentRouter
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _getRouterId()
|
||||
{
|
||||
if (this._currentRouter)
|
||||
{
|
||||
const routerLoad =
|
||||
Object.values(this._peers)
|
||||
.filter((peer) => peer.routerId === this._currentRouter.id).length;
|
||||
|
||||
if (routerLoad >= ROUTER_SCALE_SIZE)
|
||||
{
|
||||
this._currentRouter = this._routerIterator.next().value;
|
||||
|
||||
if (this._currentRouter)
|
||||
{
|
||||
await this._pipeProducersToNewRouter();
|
||||
|
||||
return this._currentRouter.id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return this._currentRouter.id;
|
||||
}
|
||||
}
|
||||
|
||||
return this._getLeastLoadedRouter();
|
||||
}
|
||||
|
||||
// Returns an array of router ids we need to pipe to
|
||||
_getRoutersToPipeTo(originRouterId)
|
||||
{
|
||||
return Object.values(this._peers)
|
||||
.map((peer) => peer.routerId)
|
||||
.filter((routerId, index, self) =>
|
||||
routerId !== originRouterId && self.indexOf(routerId) === index
|
||||
);
|
||||
}
|
||||
|
||||
_getLeastLoadedRouter()
|
||||
{
|
||||
let load = Infinity;
|
||||
let id;
|
||||
|
||||
for (const routerId of this._mediasoupRouters.keys())
|
||||
{
|
||||
const routerLoad =
|
||||
Object.values(this._peers).filter((peer) => peer.routerId === routerId).length;
|
||||
|
||||
if (routerLoad < load)
|
||||
{
|
||||
id = routerId;
|
||||
load = routerLoad;
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Room;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"license": "MIT",
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"start": "DEBUG=${DEBUG:='*mediasoup* *INFO* *WARN* *ERROR*'} INTERACTIVE=${INTERACTIVE:='true'} node server.js",
|
||||
"start": "node server.js",
|
||||
"connect": "node connect.js",
|
||||
"lint": "eslint -c .eslintrc.json --ext .js *.js lib/"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
};
|
||||
|
|
@ -35,6 +35,7 @@ const RedisStore = require('connect-redis')(expressSession);
|
|||
const sharedSession = require('express-socket.io-session');
|
||||
const interactiveServer = require('./lib/interactiveServer');
|
||||
const promExporter = require('./lib/promExporter');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
/* eslint-disable no-console */
|
||||
console.log('- process.env.DEBUG:', process.env.DEBUG);
|
||||
|
|
@ -55,10 +56,6 @@ if ('StatusLogger' in config)
|
|||
// @type {Array<mediasoup.Worker>}
|
||||
const mediasoupWorkers = [];
|
||||
|
||||
// Index of next mediasoup Worker to use.
|
||||
// @type {Number}
|
||||
let nextMediasoupWorkerIdx = 0;
|
||||
|
||||
// Map of Room instances indexed by roomId.
|
||||
const rooms = new Map();
|
||||
|
||||
|
|
@ -129,6 +126,8 @@ let oidcClient;
|
|||
let oidcStrategy;
|
||||
|
||||
async function run()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Open the interactive server.
|
||||
await interactiveServer(rooms, peers);
|
||||
|
|
@ -157,23 +156,29 @@ async function run()
|
|||
// Run WebSocketServer.
|
||||
await runWebSocketServer();
|
||||
|
||||
// Log rooms status every 30 seconds.
|
||||
setInterval(() =>
|
||||
const errorHandler = (err, req, res, next) =>
|
||||
{
|
||||
for (const room of rooms.values())
|
||||
{
|
||||
room.logStatus();
|
||||
}
|
||||
}, 120000);
|
||||
const trackingId = uuidv4();
|
||||
|
||||
// check for deserted rooms
|
||||
setInterval(() =>
|
||||
{
|
||||
for (const room of rooms.values())
|
||||
{
|
||||
room.checkEmpty();
|
||||
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);
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
function statusLog()
|
||||
|
|
@ -181,8 +186,8 @@ function statusLog()
|
|||
if (statusLogger)
|
||||
{
|
||||
statusLogger.log({
|
||||
rooms : rooms.size,
|
||||
peers : peers.size
|
||||
rooms : rooms,
|
||||
peers : peers
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -363,7 +368,9 @@ async function setupAuth()
|
|||
app.get(
|
||||
'/auth/callback',
|
||||
passport.authenticate('oidc', { failureRedirect: '/auth/login' }),
|
||||
async (req, res) =>
|
||||
async (req, res, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const state = JSON.parse(base64.decode(req.query.state));
|
||||
|
||||
|
|
@ -396,6 +403,11 @@ async function setupAuth()
|
|||
picture : peer.picture
|
||||
}));
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -581,6 +593,7 @@ async function runWebSocketServer()
|
|||
{
|
||||
logger.error('room creation or room joining failed [error:"%o"]', error);
|
||||
|
||||
if (socket)
|
||||
socket.disconnect(true);
|
||||
|
||||
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).
|
||||
*/
|
||||
|
|
@ -644,9 +644,9 @@ async function getOrCreateRoom({ 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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue