{
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,
diff --git a/app/src/index.js b/app/src/index.js
index 90bfa4d..e89d06d 100644
--- a/app/src/index.js
+++ b/app/src/index.js
@@ -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
@@ -113,6 +115,13 @@ function run()
const forceTcp = parameters.get('forceTcp') === 'true';
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()
} persistor={persistor}>
-
+ }>
diff --git a/app/src/permissions.js b/app/src/permissions.js
new file mode 100644
index 0000000..864bdbd
--- /dev/null
+++ b/app/src/permissions.js
@@ -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'
+};
\ No newline at end of file
diff --git a/app/src/reducers/consumers.js b/app/src/reducers/consumers.js
index 68a4a4a..6be31ae 100644
--- a/app/src/reducers/consumers.js
+++ b/app/src/reducers/consumers.js
@@ -110,6 +110,11 @@ const consumers = (state = initialState, action) =>
return { ...state, [consumerId]: newConsumer };
}
+ case 'CLEAR_CONSUMERS':
+ {
+ return initialState;
+ }
+
default:
return state;
}
diff --git a/app/src/reducers/peers.js b/app/src/reducers/peers.js
index 4c8bee1..32d6fef 100644
--- a/app/src/reducers/peers.js
+++ b/app/src/reducers/peers.js
@@ -1,4 +1,6 @@
-const peer = (state = {}, action) =>
+const initialState = {};
+
+const peer = (state = initialState, action) =>
{
switch (action.type)
{
@@ -26,6 +28,12 @@ const peer = (state = {}, action) =>
raisedHand : action.payload.raisedHand,
raisedHandTimestamp : action.payload.raisedHandTimestamp
};
+
+ case 'SET_PEER_RAISED_HAND_IN_PROGRESS':
+ return {
+ ...state,
+ raisedHandInProgress : action.payload.flag
+ };
case 'ADD_CONSUMER':
{
@@ -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;
}
diff --git a/app/src/reducers/producers.js b/app/src/reducers/producers.js
index 64aee90..a27c06e 100644
--- a/app/src/reducers/producers.js
+++ b/app/src/reducers/producers.js
@@ -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;
}
diff --git a/app/src/reducers/room.js b/app/src/reducers/room.js
index f4bc6ab..d784219 100644
--- a/app/src/reducers/room.js
+++ b/app/src/reducers/room.js
@@ -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:
diff --git a/app/src/reducers/settings.js b/app/src/reducers/settings.js
index 549219c..1c375bf 100644
--- a/app/src/reducers/settings.js
+++ b/app/src/reducers/settings.js
@@ -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;
diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json
index e82e9d5..0ae3fe0 100644
--- a/app/src/translations/cn.json
+++ b/app/src/translations/cn.json
@@ -59,7 +59,11 @@
"room.raisedHand": null,
"room.loweredHand": null,
"room.extraVideo": null,
-
+ "room.overRoomLimit": null,
+ "room.help": null,
+ "room.about": null,
+ "room.shortcutKeys": null,
+
"me.mutedPTT": null,
"roles.gotRole": 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": "正在尝试共享文件",
diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json
index 1a2b3c5..8edfc1c 100644
--- a/app/src/translations/cs.json
+++ b/app/src/translations/cs.json
@@ -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",
diff --git a/app/src/translations/de.json b/app/src/translations/de.json
index a6f6ac2..0a2368a 100644
--- a/app/src/translations/de.json
+++ b/app/src/translations/de.json
@@ -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"
}
diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json
index 4027363..fb9e6fc 100644
--- a/app/src/translations/dk.json
+++ b/app/src/translations/dk.json
@@ -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",
diff --git a/app/src/translations/el.json b/app/src/translations/el.json
index 5f93f42..2d9cb94 100644
--- a/app/src/translations/el.json
+++ b/app/src/translations/el.json
@@ -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": "Προσπάθεια διαμοιρασμού αρχείου",
diff --git a/app/src/translations/en.json b/app/src/translations/en.json
index 8fe4fa1..5014089 100644
--- a/app/src/translations/en.json
+++ b/app/src/translations/en.json
@@ -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",
diff --git a/app/src/translations/es.json b/app/src/translations/es.json
index d73506c..629bb2a 100644
--- a/app/src/translations/es.json
+++ b/app/src/translations/es.json
@@ -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",
diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json
index f884c36..9776ac0 100644
--- a/app/src/translations/fr.json
+++ b/app/src/translations/fr.json
@@ -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",
diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json
index eb9d08e..4788483 100644
--- a/app/src/translations/hr.json
+++ b/app/src/translations/hr.json
@@ -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"
}
diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json
index f25b06a..dee4034 100644
--- a/app/src/translations/hu.json
+++ b/app/src/translations/hu.json
@@ -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"
}
diff --git a/app/src/translations/it.json b/app/src/translations/it.json
index d027e7a..7a8bf62 100644
--- a/app/src/translations/it.json
+++ b/app/src/translations/it.json
@@ -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,
-
- "me.mutedPTT": 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",
- "roles.gotRole": null,
- "roles.lostRole": null,
+ "me.mutedPTT": "Sei mutato, tieni premuto SPAZIO per parlare",
+
+ "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",
@@ -140,7 +157,7 @@
"devices.devicesChanged": "Il tuo dispositivo è cambiato, configura i dispositivi nel menù di impostazioni",
"device.audioUnsupported": "Dispositivo audio non supportato",
- "device.activateAudio": "Attiva audio",
+ "device.activateAudio": "Attiva audio",
"device.muteAudio": "Silenzia audio",
"device.unMuteAudio": "Riattiva audio",
@@ -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"
+}
\ No newline at end of file
diff --git a/app/src/translations/lv.json b/app/src/translations/lv.json
new file mode 100644
index 0000000..27adce1
--- /dev/null
+++ b/app/src/translations/lv.json
@@ -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"
+}
diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json
index e803bda..172fcf9 100644
--- a/app/src/translations/nb.json
+++ b/app/src/translations/nb.json
@@ -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",
diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json
index 399f788..dd12971 100644
--- a/app/src/translations/pl.json
+++ b/app/src/translations/pl.json
@@ -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"
}
\ No newline at end of file
diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json
index d66d8da..13fd0f6 100644
--- a/app/src/translations/pt.json
+++ b/app/src/translations/pt.json
@@ -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",
diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json
index b37b36a..8e05b0b 100644
--- a/app/src/translations/ro.json
+++ b/app/src/translations/ro.json
@@ -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",
diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json
index d3d20e3..dc05c2f 100644
--- a/app/src/translations/tr.json
+++ b/app/src/translations/tr.json
@@ -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",
diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json
index dcb3c7d..b06aef4 100644
--- a/app/src/translations/uk.json
+++ b/app/src/translations/uk.json
@@ -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": "Спроба поділитися файлом",
diff --git a/server/access.js b/server/access.js
new file mode 100644
index 0000000..479e9e2
--- /dev/null
+++ b/server/access.js
@@ -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'
+};
\ No newline at end of file
diff --git a/server/config/config.example.js b/server/config/config.example.js
index 6ff279a..e08f910 100644
--- a/server/config/config.example.js
+++ b/server/config/config.example.js
@@ -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,39 +234,50 @@ 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,
+ 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 :
+ mediasoup :
{
numWorkers : Object.keys(os.cpus()).length,
// mediasoup Worker settings.
diff --git a/server/lib/Lobby.js b/server/lib/Lobby.js
index 0bcfb66..45cdc06 100644
--- a/server/lib/Lobby.js
+++ b/server/lib/Lobby.js
@@ -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)
@@ -189,7 +189,8 @@ class Lobby extends EventEmitter
cb();
break;
- }
+ }
+
case 'changePicture':
{
const { picture } = request.data;
diff --git a/server/lib/Peer.js b/server/lib/Peer.js
index a345a16..8e11ca2 100644
--- a/server/lib/Peer.js
+++ b/server/lib/Peer.js
@@ -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;
diff --git a/server/lib/Room.js b/server/lib/Room.js
index 9200be9..74accc4 100644
--- a/server/lib/Room.js
+++ b/server/lib/Room.js
@@ -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,115 +457,91 @@ 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)
{
- peer.socket.join(this._roomId);
-
- // If we don't have this peer, add to end
- !this._lastN.includes(peer.id) && this._lastN.push(peer.id);
-
- this._peers[peer.id] = peer;
-
- this._handlePeer(peer);
-
- if (returning)
+ this._queue.push(async () =>
{
- this._notification(peer.socket, 'roomBack');
- }
- else
- {
- const token = jwt.sign({ id: peer.id }, this._uuid, { noTimestamp: true });
+ peer.socket.join(this._roomId);
- peer.socket.handshake.session.token = token;
+ // If we don't have this peer, add to end
+ !this._lastN.includes(peer.id) && this._lastN.push(peer.id);
- peer.socket.handshake.session.save();
+ this._peers[peer.id] = peer;
- let turnServers;
-
- if ('turnAPIURI' in config)
+ // Assign routerId
+ peer.routerId = await this._getRouterId();
+
+ this._handlePeer(peer);
+
+ if (returning)
{
- try
- {
- const { data } = await axios.get(
- config.turnAPIURI,
- {
- params : {
- ...config.turnAPIparams,
- 'api_key' : config.turnAPIKey,
- 'ip' : peer.socket.request.connection.remoteAddress
- }
- });
-
- turnServers = [ {
- urls : data.uris,
- username : data.username,
- credential : data.password
- } ];
- }
- catch (error)
- {
- if ('backupTurnServers' in config)
- turnServers = config.backupTurnServers;
-
- logger.error('_peerJoining() | error on REST turn [error:"%o"]', error);
- }
+ this._notification(peer.socket, 'roomBack');
}
- else if ('backupTurnServers' in config)
+ else
{
- turnServers = config.backupTurnServers;
+ const token = jwt.sign({ id: peer.id }, this._uuid, { noTimestamp: true });
+
+ peer.socket.handshake.session.token = token;
+
+ peer.socket.handshake.session.save();
+
+ let turnServers;
+
+ if ('turnAPIURI' in config)
+ {
+ try
+ {
+ const { data } = await axios.get(
+ config.turnAPIURI,
+ {
+ params : {
+ ...config.turnAPIparams,
+ 'api_key' : config.turnAPIKey,
+ 'ip' : peer.socket.request.connection.remoteAddress
+ }
+ });
+
+ turnServers = [ {
+ urls : data.uris,
+ username : data.username,
+ credential : data.password
+ } ];
+ }
+ catch (error)
+ {
+ if ('backupTurnServers' in config)
+ turnServers = config.backupTurnServers;
+
+ logger.error('_peerJoining() | error on REST turn [error:"%o"]', error);
+ }
+ }
+ else if ('backupTurnServers' in config)
+ {
+ turnServers = config.backupTurnServers;
+ }
+
+ this._notification(peer.socket, 'roomReady', { turnServers });
}
-
- 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;
diff --git a/server/package.json b/server/package.json
index ef96f36..d407d03 100644
--- a/server/package.json
+++ b/server/package.json
@@ -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/"
},
diff --git a/server/permissions.js b/server/permissions.js
new file mode 100644
index 0000000..dd3bdbb
--- /dev/null
+++ b/server/permissions.js
@@ -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'
+};
\ No newline at end of file
diff --git a/server/server.js b/server/server.js
index dbf9a8e..27dc12c 100755
--- a/server/server.js
+++ b/server/server.js
@@ -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}
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();
@@ -130,50 +127,58 @@ let oidcStrategy;
async function run()
{
- // Open the interactive server.
- await interactiveServer(rooms, peers);
-
- // start Prometheus exporter
- if (config.prometheus)
+ try
{
- await promExporter(rooms, peers, config.prometheus);
- }
+ // Open the interactive server.
+ await interactiveServer(rooms, peers);
- if (typeof(config.auth) === 'undefined')
- {
- logger.warn('Auth is not configured properly!');
- }
- else
- {
- await setupAuth();
- }
-
- // Run a mediasoup Worker.
- await runMediasoupWorkers();
-
- // Run HTTPS server.
- await runHttpsServer();
-
- // Run WebSocketServer.
- await runWebSocketServer();
-
- // Log rooms status every 30 seconds.
- setInterval(() =>
- {
- for (const room of rooms.values())
+ // start Prometheus exporter
+ if (config.prometheus)
{
- room.logStatus();
+ await promExporter(rooms, peers, config.prometheus);
}
- }, 120000);
- // check for deserted rooms
- setInterval(() =>
- {
- for (const room of rooms.values())
+ if (typeof(config.auth) === 'undefined')
{
- room.checkEmpty();
+ logger.warn('Auth is not configured properly!');
}
- }, 10000);
+ else
+ {
+ await setupAuth();
+ }
+
+ // Run a mediasoup Worker.
+ await runMediasoupWorkers();
+
+ // Run HTTPS server.
+ await runHttpsServer();
+
+ // Run WebSocketServer.
+ await runWebSocketServer();
+
+ const errorHandler = (err, req, res, next) =>
+ {
+ const trackingId = uuidv4();
+
+ res.status(500).send(
+ `
Internal Server Error
+
If you report this error, please also report this
+ tracking ID which makes it possible to locate your session
+ in the logs which are available to the system administrator:
+ ${trackingId}
`
+ );
+ logger.error(
+ 'Express error handler dump with tracking ID: %s, error dump: %o',
+ trackingId, err);
+ };
+
+ // eslint-disable-next-line no-unused-vars
+ app.use(errorHandler);
+ }
+ catch (error)
+ {
+ logger.error('run() [error:"%o"]', error);
+ }
}
function statusLog()
@@ -181,8 +186,8 @@ function statusLog()
if (statusLogger)
{
statusLogger.log({
- rooms : rooms.size,
- peers : peers.size
+ rooms : rooms,
+ peers : peers
});
}
}
@@ -363,38 +368,45 @@ async function setupAuth()
app.get(
'/auth/callback',
passport.authenticate('oidc', { failureRedirect: '/auth/login' }),
- async (req, res) =>
+ async (req, res, next) =>
{
- const state = JSON.parse(base64.decode(req.query.state));
-
- const { peerId, roomId } = state;
-
- req.session.peerId = peerId;
- req.session.roomId = roomId;
-
- let peer = peers.get(peerId);
-
- if (!peer) // User has no socket session yet, make temporary
- peer = new Peer({ id: peerId, roomId });
-
- if (peer.roomId !== roomId) // The peer is mischievous
- throw new Error('peer authenticated with wrong room');
-
- if (typeof config.userMapping === 'function')
+ try
{
- await config.userMapping({
- peer,
- roomId,
- userinfo : req.user._userinfo
- });
+ const state = JSON.parse(base64.decode(req.query.state));
+
+ const { peerId, roomId } = state;
+
+ req.session.peerId = peerId;
+ req.session.roomId = roomId;
+
+ let peer = peers.get(peerId);
+
+ if (!peer) // User has no socket session yet, make temporary
+ peer = new Peer({ id: peerId, roomId });
+
+ if (peer.roomId !== roomId) // The peer is mischievous
+ throw new Error('peer authenticated with wrong room');
+
+ if (typeof config.userMapping === 'function')
+ {
+ await config.userMapping({
+ peer,
+ roomId,
+ userinfo : req.user._userinfo
+ });
+ }
+
+ peer.authenticated = true;
+
+ res.send(loginHelper({
+ displayName : peer.displayName,
+ picture : peer.picture
+ }));
+ }
+ catch (error)
+ {
+ return next(error);
}
-
- peer.authenticated = true;
-
- res.send(loginHelper({
- displayName : peer.displayName,
- picture : peer.picture
- }));
}
);
}
@@ -581,7 +593,8 @@ async function runWebSocketServer()
{
logger.error('room creation or room joining failed [error:"%o"]', error);
- socket.disconnect(true);
+ 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);