Merge remote-tracking branch 'origin/develop' into feat-output-select

auto_join_3.3
Mészáros Mihály 2020-04-22 13:07:21 +02:00
commit 5e3f50c052
40 changed files with 698 additions and 223 deletions

View File

@ -115,6 +115,12 @@ To integrate with an LMS (e.g. Moodle), have a look at [LTI](LTI/LTI.md).
* You need an additional [TURN](https://github.com/coturn/coturn)-server for clients located behind restrictive firewalls! Add your server and credentials to `app/config.js` * You need an additional [TURN](https://github.com/coturn/coturn)-server for clients located behind restrictive firewalls! Add your server and credentials to `app/config.js`
## Community-driven support
* Open mailing list: community@lists.edumeet.org
* Subscribe: lists.edumeet.org/sympa/subscribe/community/
* Open archive: lists.edumeet.org/sympa/arc/community/
## Authors ## Authors
* Håvar Aambø Fosstveit * Håvar Aambø Fosstveit

View File

@ -11,6 +11,7 @@
"@material-ui/core": "^4.5.1", "@material-ui/core": "^4.5.1",
"@material-ui/icons": "^4.5.1", "@material-ui/icons": "^4.5.1",
"bowser": "^2.7.0", "bowser": "^2.7.0",
"classnames": "^2.2.6",
"create-torrent": "^4.4.1", "create-torrent": "^4.4.1",
"dompurify": "^2.0.7", "dompurify": "^2.0.7",
"domready": "^1.0.8", "domready": "^1.0.8",

View File

@ -199,6 +199,9 @@ export default class RoomClient
// @type {mediasoupClient.Device} // @type {mediasoupClient.Device}
this._mediasoupDevice = null; this._mediasoupDevice = null;
// Put the browser info into state
store.dispatch(meActions.setBrowser(device));
// Our WebTorrent client // Our WebTorrent client
this._webTorrent = null; this._webTorrent = null;
@ -206,13 +209,10 @@ export default class RoomClient
store.dispatch(settingsActions.setVideoResolution(defaultResolution)); store.dispatch(settingsActions.setVideoResolution(defaultResolution));
// Max spotlights // Max spotlights
if (device.bowser.getPlatformType() === 'desktop') if (device.platform === 'desktop')
this._maxSpotlights = lastN; this._maxSpotlights = lastN;
else else
{
this._maxSpotlights = mobileLastN; this._maxSpotlights = mobileLastN;
store.dispatch(meActions.setIsMobile());
}
store.dispatch( store.dispatch(
settingsActions.setLastN(this._maxSpotlights)); settingsActions.setLastN(this._maxSpotlights));
@ -1328,6 +1328,50 @@ export default class RoomClient
lobbyPeerActions.setLobbyPeerPromotionInProgress(peerId, false)); lobbyPeerActions.setLobbyPeerPromotionInProgress(peerId, false));
} }
async clearChat()
{
logger.debug('clearChat()');
store.dispatch(
roomActions.setClearChatInProgress(true));
try
{
await this.sendRequest('moderator:clearChat');
store.dispatch(chatActions.clearChat());
}
catch (error)
{
logger.error('clearChat() failed: %o', error);
}
store.dispatch(
roomActions.setClearChatInProgress(false));
}
async clearFileSharing()
{
logger.debug('clearFileSharing()');
store.dispatch(
roomActions.setClearFileSharingInProgress(true));
try
{
await this.sendRequest('moderator:clearFileSharing');
store.dispatch(fileActions.clearFiles());
}
catch (error)
{
logger.error('clearFileSharing() failed: %o', error);
}
store.dispatch(
roomActions.setClearFileSharingInProgress(false));
}
async kickPeer(peerId) async kickPeer(peerId)
{ {
logger.debug('kickPeer() [peerId:"%s"]', peerId); logger.debug('kickPeer() [peerId:"%s"]', peerId);
@ -2185,6 +2229,21 @@ export default class RoomClient
break; break;
} }
case 'moderator:clearChat':
{
store.dispatch(chatActions.clearChat());
store.dispatch(requestActions.notify(
{
text : intl.formatMessage({
id : 'moderator.clearChat',
defaultMessage : 'Moderator cleared the chat'
})
}));
break;
}
case 'sendFile': case 'sendFile':
{ {
const { peerId, magnetUri } = notification.data; const { peerId, magnetUri } = notification.data;
@ -2213,6 +2272,21 @@ export default class RoomClient
break; break;
} }
case 'moderator:clearFileSharing':
{
store.dispatch(fileActions.clearFiles());
store.dispatch(requestActions.notify(
{
text : intl.formatMessage({
id : 'moderator.clearFiles',
defaultMessage : 'Moderator cleared the files'
})
}));
break;
}
case 'producerScore': case 'producerScore':
{ {
const { producerId, score } = notification.data; const { producerId, score } = notification.data;
@ -2339,8 +2413,8 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : intl.formatMessage({ text : intl.formatMessage({
id : 'moderator.mute', id : 'moderator.muteAudio',
defaultMessage : 'Moderator muted your microphone' defaultMessage : 'Moderator muted your audio'
}) })
})); }));
} }
@ -2358,7 +2432,7 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : intl.formatMessage({ text : intl.formatMessage({
id : 'moderator.mute', id : 'moderator.muteVideo',
defaultMessage : 'Moderator stopped your video' defaultMessage : 'Moderator stopped your video'
}) })
})); }));

View File

@ -15,3 +15,8 @@ export const addChatHistory = (chatHistory) =>
type : 'ADD_CHAT_HISTORY', type : 'ADD_CHAT_HISTORY',
payload : { chatHistory } payload : { chatHistory }
}); });
export const clearChat = () =>
({
type : 'CLEAR_CHAT'
});

View File

@ -33,3 +33,8 @@ export const setFileDone = (magnetUri, sharedFiles) =>
type : 'SET_FILE_DONE', type : 'SET_FILE_DONE',
payload : { magnetUri, sharedFiles } payload : { magnetUri, sharedFiles }
}); });
export const clearFiles = () =>
({
type : 'CLEAR_FILES'
});

View File

@ -4,9 +4,10 @@ export const setMe = ({ peerId, loginEnabled }) =>
payload : { peerId, loginEnabled } payload : { peerId, loginEnabled }
}); });
export const setIsMobile = () => export const setBrowser = (browser) =>
({ ({
type : 'SET_IS_MOBILE' type : 'SET_BROWSER',
payload : { browser }
}); });
export const loggedIn = (flag) => export const loggedIn = (flag) =>

View File

@ -129,6 +129,18 @@ export const setCloseMeetingInProgress = (flag) =>
payload : { flag } payload : { flag }
}); });
export const setClearChatInProgress = (flag) =>
({
type : 'CLEAR_CHAT_IN_PROGRESS',
payload : { flag }
});
export const setClearFileSharingInProgress = (flag) =>
({
type : 'CLEAR_FILE_SHARING_IN_PROGRESS',
payload : { flag }
});
export const setUserRoles = (userRoles) => export const setUserRoles = (userRoles) =>
({ ({
type : 'SET_USER_ROLES', type : 'SET_USER_ROLES',

View File

@ -385,7 +385,7 @@ const Me = (props) =>
</Fab> </Fab>
</div> </div>
</Tooltip> </Tooltip>
{ !me.isMobile && { me.browser.platform !== 'mobile' &&
<Tooltip title={screenTip} placement='left'> <Tooltip title={screenTip} placement='left'>
<div> <div>
<Fab <Fab

View File

@ -121,7 +121,7 @@ const Peer = (props) =>
advancedMode, advancedMode,
peer, peer,
activeSpeaker, activeSpeaker,
isMobile, browser,
micConsumer, micConsumer,
webcamConsumer, webcamConsumer,
screenConsumer, screenConsumer,
@ -260,7 +260,7 @@ const Peer = (props) =>
</div> </div>
</Tooltip> </Tooltip>
{ !isMobile && { browser.platform !== 'mobile' &&
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'label.newWindow', id : 'label.newWindow',
@ -408,7 +408,7 @@ const Peer = (props) =>
}, 2000); }, 2000);
}} }}
> >
{ !isMobile && { browser.platform !== 'mobile' &&
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'label.newWindow', id : 'label.newWindow',
@ -509,7 +509,7 @@ Peer.propTypes =
screenConsumer : appPropTypes.Consumer, screenConsumer : appPropTypes.Consumer,
windowConsumer : PropTypes.string, windowConsumer : PropTypes.string,
activeSpeaker : PropTypes.bool, activeSpeaker : PropTypes.bool,
isMobile : PropTypes.bool, browser : PropTypes.object.isRequired,
spacing : PropTypes.number, spacing : PropTypes.number,
style : PropTypes.object, style : PropTypes.object,
smallButtons : PropTypes.bool, smallButtons : PropTypes.bool,
@ -530,7 +530,7 @@ const makeMapStateToProps = (initialState, { id }) =>
...getPeerConsumers(state, id), ...getPeerConsumers(state, id),
windowConsumer : state.room.windowConsumer, windowConsumer : state.room.windowConsumer,
activeSpeaker : id === state.room.activeSpeakerId, activeSpeaker : id === state.room.activeSpeakerId,
isMobile : state.me.isMobile browser : state.me.browser
}; };
}; };
@ -565,7 +565,7 @@ export default withRoomContext(connect(
prev.consumers === next.consumers && prev.consumers === next.consumers &&
prev.room.activeSpeakerId === next.room.activeSpeakerId && prev.room.activeSpeakerId === next.room.activeSpeakerId &&
prev.room.windowConsumer === next.room.windowConsumer && prev.room.windowConsumer === next.room.windowConsumer &&
prev.me.isMobile === next.me.isMobile prev.me.browser === next.me.browser
); );
} }
} }

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import ChatModerator from './ChatModerator';
import MessageList from './MessageList'; import MessageList from './MessageList';
import ChatInput from './ChatInput'; import ChatInput from './ChatInput';
@ -25,6 +26,7 @@ const Chat = (props) =>
return ( return (
<Paper className={classes.root}> <Paper className={classes.root}>
<ChatModerator />
<MessageList /> <MessageList />
<ChatInput /> <ChatInput />
</Paper> </Paper>

View File

@ -0,0 +1,104 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withRoomContext } from '../../../RoomContext';
import { withStyles } from '@material-ui/core/styles';
import { useIntl, FormattedMessage } from 'react-intl';
import Button from '@material-ui/core/Button';
const styles = (theme) =>
({
root :
{
padding : theme.spacing(1),
width : '100%',
overflow : 'hidden',
cursor : 'auto',
display : 'flex',
listStyleType : 'none',
boxShadow : '0 2px 5px 2px rgba(0, 0, 0, 0.2)',
backgroundColor : 'rgba(255, 255, 255, 1)'
},
listheader :
{
padding : theme.spacing(1),
fontWeight : 'bolder'
},
actionButton :
{
marginLeft : 'auto'
}
});
const ChatModerator = (props) =>
{
const intl = useIntl();
const {
roomClient,
isChatModerator,
room,
classes
} = props;
if (!isChatModerator)
return;
return (
<ul className={classes.root}>
<li className={classes.listheader}>
<FormattedMessage
id='room.moderatoractions'
defaultMessage='Moderator actions'
/>
</li>
<Button
aria-label={intl.formatMessage({
id : 'room.clearChat',
defaultMessage : 'Clear chat'
})}
className={classes.actionButton}
variant='contained'
color='secondary'
disabled={room.clearChatInProgress}
onClick={() => roomClient.clearChat()}
>
<FormattedMessage
id='room.clearChat'
defaultMessage='Clear chat'
/>
</Button>
</ul>
);
};
ChatModerator.propTypes =
{
roomClient : PropTypes.any.isRequired,
isChatModerator : PropTypes.bool,
room : PropTypes.object,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
({
isChatModerator :
state.me.roles.some((role) =>
state.room.permissionsFromRoles.MODERATE_CHAT.includes(role)),
room : state.room
});
export default withRoomContext(connect(
mapStateToProps,
null,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.room === next.room &&
prev.me === next.me
);
}
}
)(withStyles(styles)(ChatModerator)));

View File

@ -5,6 +5,7 @@ import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import FileList from './FileList'; import FileList from './FileList';
import FileSharingModerator from './FileSharingModerator';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
@ -58,6 +59,7 @@ const FileSharing = (props) =>
return ( return (
<Paper className={classes.root}> <Paper className={classes.root}>
<FileSharingModerator />
<input <input
className={classes.input} className={classes.input}
type='file' type='file'

View File

@ -0,0 +1,104 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withRoomContext } from '../../../RoomContext';
import { withStyles } from '@material-ui/core/styles';
import { useIntl, FormattedMessage } from 'react-intl';
import Button from '@material-ui/core/Button';
const styles = (theme) =>
({
root :
{
padding : theme.spacing(1),
width : '100%',
overflow : 'hidden',
cursor : 'auto',
display : 'flex',
listStyleType : 'none',
boxShadow : '0 2px 5px 2px rgba(0, 0, 0, 0.2)',
backgroundColor : 'rgba(255, 255, 255, 1)'
},
listheader :
{
padding : theme.spacing(1),
fontWeight : 'bolder'
},
actionButton :
{
marginLeft : 'auto'
}
});
const FileSharingModerator = (props) =>
{
const intl = useIntl();
const {
roomClient,
isFileSharingModerator,
room,
classes
} = props;
if (!isFileSharingModerator)
return;
return (
<ul className={classes.root}>
<li className={classes.listheader}>
<FormattedMessage
id='room.moderatoractions'
defaultMessage='Moderator actions'
/>
</li>
<Button
aria-label={intl.formatMessage({
id : 'room.clearFileSharing',
defaultMessage : 'Clear files'
})}
className={classes.actionButton}
variant='contained'
color='secondary'
disabled={room.clearFileSharingInProgress}
onClick={() => roomClient.clearFileSharing()}
>
<FormattedMessage
id='room.clearFileSharing'
defaultMessage='Clear files'
/>
</Button>
</ul>
);
};
FileSharingModerator.propTypes =
{
roomClient : PropTypes.any.isRequired,
isFileSharingModerator : PropTypes.bool,
room : PropTypes.object,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
({
isFileSharingModerator :
state.me.roles.some((role) =>
state.room.permissionsFromRoles.MODERATE_FILES.includes(role)),
room : state.room
});
export default withRoomContext(connect(
mapStateToProps,
null,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.room === next.room &&
prev.me === next.me
);
}
}
)(withStyles(styles)(FileSharingModerator)));

View File

@ -16,10 +16,6 @@ const styles = (theme) =>
cursor : 'auto', cursor : 'auto',
display : 'flex' display : 'flex'
}, },
actionButtons :
{
display : 'flex'
},
divider : divider :
{ {
marginLeft : theme.spacing(2) marginLeft : theme.spacing(2)
@ -43,7 +39,6 @@ const ListModerator = (props) =>
id : 'room.muteAll', id : 'room.muteAll',
defaultMessage : 'Mute all' defaultMessage : 'Mute all'
})} })}
className={classes.actionButton}
variant='contained' variant='contained'
color='secondary' color='secondary'
disabled={room.muteAllInProgress} disabled={room.muteAllInProgress}
@ -60,7 +55,6 @@ const ListModerator = (props) =>
id : 'room.stopAllVideo', id : 'room.stopAllVideo',
defaultMessage : 'Stop all video' defaultMessage : 'Stop all video'
})} })}
className={classes.actionButton}
variant='contained' variant='contained'
color='secondary' color='secondary'
disabled={room.stopAllVideoInProgress} disabled={room.stopAllVideoInProgress}
@ -77,7 +71,6 @@ const ListModerator = (props) =>
id : 'room.closeMeeting', id : 'room.closeMeeting',
defaultMessage : 'Close meeting' defaultMessage : 'Close meeting'
})} })}
className={classes.actionButton}
variant='contained' variant='contained'
color='secondary' color='secondary'
disabled={room.closeMeetingInProgress} disabled={room.closeMeetingInProgress}

View File

@ -139,7 +139,7 @@ class Room extends React.PureComponent
{ {
const { const {
room, room,
isMobile, browser,
advancedMode, advancedMode,
toolAreaOpen, toolAreaOpen,
toggleToolArea, toggleToolArea,
@ -204,7 +204,7 @@ class Room extends React.PureComponent
</Hidden> </Hidden>
</nav> </nav>
{ isMobile && { browser.platform === 'mobile' && browser.os !== 'ios' &&
<WakeLock /> <WakeLock />
} }
@ -225,7 +225,7 @@ class Room extends React.PureComponent
Room.propTypes = Room.propTypes =
{ {
room : appPropTypes.Room.isRequired, room : appPropTypes.Room.isRequired,
isMobile : PropTypes.bool.isRequired, browser : PropTypes.object.isRequired,
advancedMode : PropTypes.bool.isRequired, advancedMode : PropTypes.bool.isRequired,
toolAreaOpen : PropTypes.bool.isRequired, toolAreaOpen : PropTypes.bool.isRequired,
setToolbarsVisible : PropTypes.func.isRequired, setToolbarsVisible : PropTypes.func.isRequired,
@ -237,7 +237,7 @@ Room.propTypes =
const mapStateToProps = (state) => const mapStateToProps = (state) =>
({ ({
room : state.room, room : state.room,
isMobile : state.me.isMobile, browser : state.me.browser,
advancedMode : state.settings.advancedMode, advancedMode : state.settings.advancedMode,
toolAreaOpen : state.toolarea.toolAreaOpen toolAreaOpen : state.toolarea.toolAreaOpen
}); });
@ -263,7 +263,7 @@ export default connect(
{ {
return ( return (
prev.room === next.room && prev.room === next.room &&
prev.me.isMobile === next.me.isMobile && prev.me.browser === next.me.browser &&
prev.settings.advancedMode === next.settings.advancedMode && prev.settings.advancedMode === next.settings.advancedMode &&
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen
); );

View File

@ -9,7 +9,7 @@ import SignalCellular0BarIcon from '@material-ui/icons/SignalCellular0Bar';
import SignalCellular1BarIcon from '@material-ui/icons/SignalCellular1Bar'; import SignalCellular1BarIcon from '@material-ui/icons/SignalCellular1Bar';
import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar'; import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar';
import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar'; import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar';
import SignalCellular4BarIcon from '@material-ui/icons/SignalCellular4Bar'; import SignalCellularAltIcon from '@material-ui/icons/SignalCellularAlt';
const styles = (theme) => const styles = (theme) =>
({ ({
@ -217,7 +217,7 @@ class VideoView extends React.PureComponent
case 9: case 9:
case 10: case 10:
{ {
quality = <SignalCellular4BarIcon style={{ color: green[500] }}/>; quality = <SignalCellularAltIcon style={{ color: green[500] }}/>;
break; break;
} }

View File

@ -24,6 +24,8 @@ export default function()
return { return {
flag, flag,
os : browser.getOSName(true), // ios, android, linux...
platform : browser.getPlatformType(true), // mobile, desktop, tablet
name : browser.getBrowserName(), name : browser.getBrowserName(),
version : browser.getBrowserVersion(), version : browser.getBrowserVersion(),
bowser : browser bowser : browser

View File

@ -30,6 +30,11 @@ const chat = (state = [], action) =>
return [ ...state, ...chatHistory ]; return [ ...state, ...chatHistory ];
} }
case 'CLEAR_CHAT':
{
return [];
}
default: default:
return state; return state;
} }

View File

@ -85,6 +85,9 @@ const files = (state = {}, action) =>
return { ...state, [magnetUri]: newFile }; return { ...state, [magnetUri]: newFile };
} }
case 'CLEAR_FILES':
return {};
default: default:
return state; return state;
} }

View File

@ -2,7 +2,7 @@ const initialState =
{ {
id : null, id : null,
picture : null, picture : null,
isMobile : false, browser : null,
roles : [ 'normal' ], // Default role roles : [ 'normal' ], // Default role
canSendMic : false, canSendMic : false,
canSendWebcam : false, canSendWebcam : false,
@ -39,9 +39,11 @@ const me = (state = initialState, action) =>
}; };
} }
case 'SET_IS_MOBILE': case 'SET_BROWSER':
{ {
return { ...state, isMobile: true }; const { browser } = action.payload;
return { ...state, browser };
} }
case 'LOGGED_IN': case 'LOGGED_IN':

View File

@ -22,13 +22,17 @@ const initialState =
muteAllInProgress : false, muteAllInProgress : false,
stopAllVideoInProgress : false, stopAllVideoInProgress : false,
closeMeetingInProgress : false, closeMeetingInProgress : false,
clearChatInProgress : false,
clearFileSharingInProgress : false,
userRoles : { NORMAL: 'normal' }, // Default role userRoles : { NORMAL: 'normal' }, // Default role
permissionsFromRoles : { permissionsFromRoles : {
CHANGE_ROOM_LOCK : [], CHANGE_ROOM_LOCK : [],
PROMOTE_PEER : [], PROMOTE_PEER : [],
SEND_CHAT : [], SEND_CHAT : [],
MODERATE_CHAT : [],
SHARE_SCREEN : [], SHARE_SCREEN : [],
SHARE_FILE : [], SHARE_FILE : [],
MODERATE_FILES : [],
MODERATE_ROOM : [] MODERATE_ROOM : []
} }
}; };
@ -184,6 +188,12 @@ const room = (state = initialState, action) =>
case 'CLOSE_MEETING_IN_PROGRESS': case 'CLOSE_MEETING_IN_PROGRESS':
return { ...state, closeMeetingInProgress: action.payload.flag }; return { ...state, closeMeetingInProgress: action.payload.flag };
case 'CLEAR_CHAT_IN_PROGRESS':
return { ...state, clearChatInProgress: action.payload.flag };
case 'CLEAR_FILE_SHARING_IN_PROGRESS':
return { ...state, clearFileSharingInProgress: action.payload.flag };
case 'SET_USER_ROLES': case 'SET_USER_ROLES':
{ {
const { userRoles } = action.payload; const { userRoles } = action.payload;

View File

@ -52,6 +52,8 @@
"room.muteAll": null, "room.muteAll": null,
"room.stopAllVideo": null, "room.stopAllVideo": null,
"room.closeMeeting": null, "room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -146,5 +148,10 @@
"devices.screenSharingError": "访问屏幕时发生错误", "devices.screenSharingError": "访问屏幕时发生错误",
"devices.cameraDisconnected": "相机已断开连接", "devices.cameraDisconnected": "相机已断开连接",
"devices.cameraError": "访问相机时发生错误" "devices.cameraError": "访问相机时发生错误",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null
} }

View File

@ -51,6 +51,8 @@
"room.muteAll": null, "room.muteAll": null,
"room.stopAllVideo": null, "room.stopAllVideo": null,
"room.closeMeeting": null, "room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -141,5 +143,10 @@
"devices.screenSharingError": "Při přístupu k vaší obrazovce se vyskytla chyba", "devices.screenSharingError": "Při přístupu k vaší obrazovce se vyskytla chyba",
"devices.cameraDisconnected": "Kamera odpojena", "devices.cameraDisconnected": "Kamera odpojena",
"devices.cameraError": "Při přístupu k vaší kameře se vyskytla chyba" "devices.cameraError": "Při přístupu k vaší kameře se vyskytla chyba",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null
} }

View File

@ -52,6 +52,8 @@
"room.muteAll": "Alle stummschalten", "room.muteAll": "Alle stummschalten",
"room.stopAllVideo": "Alle Videos stoppen", "room.stopAllVideo": "Alle Videos stoppen",
"room.closeMeeting": "Meeting schließen", "room.closeMeeting": "Meeting schließen",
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": "Dein Browser unterstützt keine Spracherkennung", "room.speechUnsupported": "Dein Browser unterstützt keine Spracherkennung",
"me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen", "me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen",
@ -146,5 +148,10 @@
"devices.screenSharingError": "Fehler bei der Bildschirmfreigabe", "devices.screenSharingError": "Fehler bei der Bildschirmfreigabe",
"devices.cameraDisconnected": "Kamera getrennt", "devices.cameraDisconnected": "Kamera getrennt",
"devices.cameraError": "Fehler mit deiner Kamera" "devices.cameraError": "Fehler mit deiner Kamera",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null
} }

View File

@ -52,6 +52,8 @@
"room.muteAll": null, "room.muteAll": null,
"room.stopAllVideo": null, "room.stopAllVideo": null,
"room.closeMeeting": null, "room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -147,5 +149,10 @@
"devices.screenSharingError": "Der opstod en fejl ved adgang til skærmdeling", "devices.screenSharingError": "Der opstod en fejl ved adgang til skærmdeling",
"device.cameraDisconnected": "Kamera frakoblet", "device.cameraDisconnected": "Kamera frakoblet",
"device.cameraError": "Der opstod en fejl ved tilkobling af dit kamera" "device.cameraError": "Der opstod en fejl ved tilkobling af dit kamera",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null
} }

View File

@ -52,6 +52,8 @@
"room.muteAll": null, "room.muteAll": null,
"room.stopAllVideo": null, "room.stopAllVideo": null,
"room.closeMeeting": null, "room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -146,5 +148,10 @@
"devices.screenSharingError": "Παρουσιάστηκε σφάλμα κατά την πρόσβαση στην οθόνη σας", "devices.screenSharingError": "Παρουσιάστηκε σφάλμα κατά την πρόσβαση στην οθόνη σας",
"devices.cameraDisconnected": "Η κάμερα αποσυνδέθηκε", "devices.cameraDisconnected": "Η κάμερα αποσυνδέθηκε",
"devices.cameraError": "Παρουσιάστηκε σφάλμα κατά την πρόσβαση στην κάμερά σας" "devices.cameraError": "Παρουσιάστηκε σφάλμα κατά την πρόσβαση στην κάμερά σας",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null
} }

View File

@ -52,6 +52,8 @@
"room.muteAll": "Mute all", "room.muteAll": "Mute all",
"room.stopAllVideo": "Stop all video", "room.stopAllVideo": "Stop all video",
"room.closeMeeting": "Close meeting", "room.closeMeeting": "Close meeting",
"room.clearChat": "Clear chat",
"room.clearFileSharing": "Clear files",
"room.speechUnsupported": "Your browser does not support speech recognition", "room.speechUnsupported": "Your browser does not support speech recognition",
"me.mutedPTT": "You are muted, hold down SPACE-BAR to talk", "me.mutedPTT": "You are muted, hold down SPACE-BAR to talk",
@ -146,5 +148,10 @@
"devices.screenSharingError": "An error occured while accessing your screen", "devices.screenSharingError": "An error occured while accessing your screen",
"devices.cameraDisconnected": "Camera disconnected", "devices.cameraDisconnected": "Camera disconnected",
"devices.cameraError": "An error occured while accessing your camera" "devices.cameraError": "An error occured while accessing your camera",
"moderator.clearChat": "Moderator cleared the chat",
"moderator.clearFiles": "Moderator cleared the files",
"moderator.muteAudio": "Moderator muted your audio",
"moderator.muteVideo": "Moderator muted your video"
} }

View File

@ -52,6 +52,8 @@
"room.muteAll": null, "room.muteAll": null,
"room.stopAllVideo": null, "room.stopAllVideo": null,
"room.closeMeeting": null, "room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -146,5 +148,10 @@
"devices.screenSharingError": "Hubo un error al acceder a su pantalla", "devices.screenSharingError": "Hubo un error al acceder a su pantalla",
"devices.cameraDisconnected": "Cámara desconectada", "devices.cameraDisconnected": "Cámara desconectada",
"devices.cameraError": "Hubo un error al acceder a su cámara" "devices.cameraError": "Hubo un error al acceder a su cámara",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null
} }

View File

@ -52,6 +52,8 @@
"room.muteAll": null, "room.muteAll": null,
"room.stopAllVideo": null, "room.stopAllVideo": null,
"room.closeMeeting": null, "room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -145,5 +147,10 @@
"devices.screenSharingError": "Une erreur est apparue lors de l'accès à votre partage d'écran", "devices.screenSharingError": "Une erreur est apparue lors de l'accès à votre partage d'écran",
"devices.cameraDisconnected": "Caméra déconnectée", "devices.cameraDisconnected": "Caméra déconnectée",
"devices.cameraError": "Une erreur est apparue lors de l'accès à votre caméra" "devices.cameraError": "Une erreur est apparue lors de l'accès à votre caméra",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null
} }

View File

@ -52,6 +52,8 @@
"room.muteAll": "Utišaj sve", "room.muteAll": "Utišaj sve",
"room.stopAllVideo": "Ugasi sve kamere", "room.stopAllVideo": "Ugasi sve kamere",
"room.closeMeeting": "Završi sastanak", "room.closeMeeting": "Završi sastanak",
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": "Vaš preglednik ne podržava prepoznavanje govora", "room.speechUnsupported": "Vaš preglednik ne podržava prepoznavanje govora",
"me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor", "me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor",
@ -146,5 +148,10 @@
"devices.screenSharingError": "Greška prilikom pristupa ekranu", "devices.screenSharingError": "Greška prilikom pristupa ekranu",
"devices.cameraDisconnected": "Kamera odspojena", "devices.cameraDisconnected": "Kamera odspojena",
"devices.cameraError": "Greška prilikom pristupa kameri" "devices.cameraError": "Greška prilikom pristupa kameri",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null
} }

View File

@ -52,6 +52,8 @@
"room.muteAll": null, "room.muteAll": null,
"room.stopAllVideo": null, "room.stopAllVideo": null,
"room.closeMeeting": null, "room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -146,5 +148,10 @@
"devices.screenSharingError": "Hiba történt a képernyőd megosztása során", "devices.screenSharingError": "Hiba történt a képernyőd megosztása során",
"devices.cameraDisconnected": "A kamera kapcsolata lebomlott", "devices.cameraDisconnected": "A kamera kapcsolata lebomlott",
"devices.cameraError": "Hiba történt a kamera elérése során" "devices.cameraError": "Hiba történt a kamera elérése során",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null
} }

View File

@ -52,6 +52,8 @@
"room.muteAll": null, "room.muteAll": null,
"room.stopAllVideo": null, "room.stopAllVideo": null,
"room.closeMeeting": null, "room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -145,5 +147,10 @@
"devices.screenSharingError": "Errore con l'accesso al tuo schermo", "devices.screenSharingError": "Errore con l'accesso al tuo schermo",
"devices.cameraDisconnected": "Videocamera scollegata", "devices.cameraDisconnected": "Videocamera scollegata",
"devices.cameraError": "Errore con l'accesso alla videocamera" "devices.cameraError": "Errore con l'accesso alla videocamera",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null
} }

View File

@ -52,6 +52,8 @@
"room.muteAll": "Demp alle", "room.muteAll": "Demp alle",
"room.stopAllVideo": "Stopp all video", "room.stopAllVideo": "Stopp all video",
"room.closeMeeting": "Avslutt møte", "room.closeMeeting": "Avslutt møte",
"room.clearChat": "Tøm chat",
"room.clearFileSharing": "Fjern filer",
"room.speechUnsupported": "Din nettleser støtter ikke stemmegjenkjenning", "room.speechUnsupported": "Din nettleser støtter ikke stemmegjenkjenning",
"me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke", "me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke",
@ -146,5 +148,10 @@
"devices.screenSharingError": "Det skjedde noe feil med skjermdelingen din", "devices.screenSharingError": "Det skjedde noe feil med skjermdelingen din",
"devices.cameraDisconnected": "Kamera koblet fra", "devices.cameraDisconnected": "Kamera koblet fra",
"devices.cameraError": "Det skjedde noe feil med kameraet ditt" "devices.cameraError": "Det skjedde noe feil med kameraet ditt",
"moderator.clearChat": "Moderator tømte chatten",
"moderator.clearFiles": "Moderator fjernet filer",
"moderator.muteAudio": "Moderator mutet lyden din",
"moderator.muteVideo": "Moderator mutet videoen din"
} }

View File

@ -52,6 +52,8 @@
"room.muteAll": null, "room.muteAll": null,
"room.stopAllVideo": null, "room.stopAllVideo": null,
"room.closeMeeting": null, "room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -146,5 +148,10 @@
"devices.screenSharingError": "Wystąpił błąd podczas uzyskiwania dostępu do ekranu", "devices.screenSharingError": "Wystąpił błąd podczas uzyskiwania dostępu do ekranu",
"devices.cameraDisconnected": "Kamera odłączona", "devices.cameraDisconnected": "Kamera odłączona",
"devices.cameraError": "Wystąpił błąd podczas uzyskiwania dostępu do kamery" "devices.cameraError": "Wystąpił błąd podczas uzyskiwania dostępu do kamery",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null
} }

View File

@ -52,6 +52,8 @@
"room.muteAll": null, "room.muteAll": null,
"room.stopAllVideo": null, "room.stopAllVideo": null,
"room.closeMeeting": null, "room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -146,5 +148,10 @@
"devices.screenSharingError": "Ocorreu um erro no acesso ao seu ecrã", "devices.screenSharingError": "Ocorreu um erro no acesso ao seu ecrã",
"devices.cameraDisconnected": "Câmara desconectada", "devices.cameraDisconnected": "Câmara desconectada",
"devices.cameraError": "Ocorreu um erro no acesso à sua câmara" "devices.cameraError": "Ocorreu um erro no acesso à sua câmara",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null
} }

View File

@ -52,6 +52,8 @@
"room.muteAll": null, "room.muteAll": null,
"room.stopAllVideo": null, "room.stopAllVideo": null,
"room.closeMeeting": null, "room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -146,5 +148,10 @@
"devices.screenSharingError": "A apărut o eroare la accesarea ecranului", "devices.screenSharingError": "A apărut o eroare la accesarea ecranului",
"devices.cameraDisconnected": "Camera video e disconectată", "devices.cameraDisconnected": "Camera video e disconectată",
"devices.cameraError": "A apărut o eroare la accesarea camerei video" "devices.cameraError": "A apărut o eroare la accesarea camerei video",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null
} }

View File

@ -1,143 +1,154 @@
{ {
"socket.disconnected": "Ви відключені", "socket.disconnected": "Ви відключені",
"socket.reconnecting": "Ви від'єдналися, намагаєтесь знову підключитися", "socket.reconnecting": "Ви від'єдналися, намагаєтесь знову підключитися",
"socket.reconnected": "Ви знову підключилися", "socket.reconnected": "Ви знову підключилися",
"socket.requestError": "Помилка при запиті сервера", "socket.requestError": "Помилка при запиті сервера",
"room.chooseRoom": "Виберіть назву кімнати, до якої хочете приєднатися", "room.chooseRoom": "Виберіть назву кімнати, до якої хочете приєднатися",
"room.cookieConsent": "Цей веб-сайт використовує файли cookie для поліпшення роботи користувачів", "room.cookieConsent": "Цей веб-сайт використовує файли cookie для поліпшення роботи користувачів",
"room.consentUnderstand": "Я розумію", "room.consentUnderstand": "Я розумію",
"room.joined": "Ви приєдналися до кімнати", "room.joined": "Ви приєдналися до кімнати",
"room.cantJoin": "Неможливо приєднатися до кімнати", "room.cantJoin": "Неможливо приєднатися до кімнати",
"room.youLocked": "Ви заблокували кімнату", "room.youLocked": "Ви заблокували кімнату",
"room.cantLock": "Не вдається заблокувати кімнату", "room.cantLock": "Не вдається заблокувати кімнату",
"room.youUnLocked": "Ви розблокували кімнату", "room.youUnLocked": "Ви розблокували кімнату",
"room.cantUnLock": "Не вдається розблокувати кімнату", "room.cantUnLock": "Не вдається розблокувати кімнату",
"room.locked": "Кімната зараз заблокована", "room.locked": "Кімната зараз заблокована",
"room.unlocked": "Кімната зараз розблокована", "room.unlocked": "Кімната зараз розблокована",
"room.newLobbyPeer": "Новий учасник увійшов у зал очікування", "room.newLobbyPeer": "Новий учасник увійшов у зал очікування",
"room.lobbyPeerLeft": "Учасник вийшов із зала очікування", "room.lobbyPeerLeft": "Учасник вийшов із зала очікування",
"room.lobbyPeerChangedDisplayName": "Учасник у залі очікування змінив ім'я на {displayName}", "room.lobbyPeerChangedDisplayName": "Учасник у залі очікування змінив ім'я на {displayName}",
"room.lobbyPeerChangedPicture": "Учасник залу очікування змінив зображення", "room.lobbyPeerChangedPicture": "Учасник залу очікування змінив зображення",
"room.setAccessCode": "Код доступу до кімнати оновлений", "room.setAccessCode": "Код доступу до кімнати оновлений",
"room.accessCodeOn": "Код доступу до кімнати зараз активований", "room.accessCodeOn": "Код доступу до кімнати зараз активований",
"room.accessCodeOff": "Код доступу до кімнати зараз відключений", "room.accessCodeOff": "Код доступу до кімнати зараз відключений",
"room.peerChangedDisplayName": "{oldDisplayName} змінив ім'я на {displayName}", "room.peerChangedDisplayName": "{oldDisplayName} змінив ім'я на {displayName}",
"room.newPeer": "{displayName} приєднався до кімнати", "room.newPeer": "{displayName} приєднався до кімнати",
"room.newFile": "Новий файл є у доступі", "room.newFile": "Новий файл є у доступі",
"room.toggleAdvancedMode": "Увімкнено розширений режим", "room.toggleAdvancedMode": "Увімкнено розширений режим",
"room.setDemocratView": "Змінено макет на демократичний вигляд", "room.setDemocratView": "Змінено макет на демократичний вигляд",
"room.setFilmStripView": "Змінено макет на вид фільму", "room.setFilmStripView": "Змінено макет на вид фільму",
"room.loggedIn": "Ви ввійшли в систему", "room.loggedIn": "Ви ввійшли в систему",
"room.loggedOut": "Ви вийшли з системи", "room.loggedOut": "Ви вийшли з системи",
"room.changedDisplayName": "Відображуване ім’я змінено на {displayName}", "room.changedDisplayName": "Відображуване ім’я змінено на {displayName}",
"room.changeDisplayNameError": "Сталася помилка під час зміни вашого відображуваного імені", "room.changeDisplayNameError": "Сталася помилка під час зміни вашого відображуваного імені",
"room.chatError": "Не вдається надіслати повідомлення в чаті", "room.chatError": "Не вдається надіслати повідомлення в чаті",
"room.aboutToJoin": "Ви збираєтесь приєднатися до зустрічі", "room.aboutToJoin": "Ви збираєтесь приєднатися до зустрічі",
"room.roomId": "Ідентифікатор кімнати: {roomName}", "room.roomId": "Ідентифікатор кімнати: {roomName}",
"room.setYourName": "Встановіть своє ім'я для участі та виберіть, як ви хочете приєднатися:", "room.setYourName": "Встановіть своє ім'я для участі та виберіть, як ви хочете приєднатися:",
"room.audioOnly": "Тільки аудіо", "room.audioOnly": "Тільки аудіо",
"room.audioVideo": "Аудіо та відео", "room.audioVideo": "Аудіо та відео",
"room.youAreReady": "Добре, ви готові", "room.youAreReady": "Добре, ви готові",
"room.emptyRequireLogin": "Кімната порожня! Ви можете увійти, щоб розпочати зустріч або чекати, поки хост приєднається", "room.emptyRequireLogin": "Кімната порожня! Ви можете увійти, щоб розпочати зустріч або чекати, поки хост приєднається",
"room.locketWait": "Кімната заблокована - дочекайтеся, поки хтось не впустить вас у ...", "room.locketWait": "Кімната заблокована - дочекайтеся, поки хтось не впустить вас у ...",
"room.lobbyAdministration": "Адміністрація залу очікування", "room.lobbyAdministration": "Адміністрація залу очікування",
"room.peersInLobby": "Учасники залу очікувань", "room.peersInLobby": "Учасники залу очікувань",
"room.lobbyEmpty": "Наразі у залі очікувань немає нікого", "room.lobbyEmpty": "Наразі у залі очікувань немає нікого",
"room.hiddenPeers": "{hiddenPeersCount, множина, один {учасник} інший {учасників}}", "room.hiddenPeers": "{hiddenPeersCount, множина, один {учасник} інший {учасників}}",
"room.me": "Я", "room.me": "Я",
"room.spotlights": "Учасники у центрі уваги", "room.spotlights": "Учасники у центрі уваги",
"room.passive": "Пасивні учасники", "room.passive": "Пасивні учасники",
"room.videoPaused": "Це відео призупинено", "room.videoPaused": "Це відео призупинено",
"room.muteAll": null,
"room.stopAllVideo": null,
"room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null,
"tooltip.login": "Увійти", "tooltip.login": "Увійти",
"tooltip.logout": "Вихід", "tooltip.logout": "Вихід",
"tooltip.admitFromLobby": "Вхід із залу очікувань", "tooltip.admitFromLobby": "Вхід із залу очікувань",
"tooltip.lockRoom": "Заблокувати кімнату", "tooltip.lockRoom": "Заблокувати кімнату",
"tooltip.unLockRoom": "Розблокувати кімнату", "tooltip.unLockRoom": "Розблокувати кімнату",
"tooltip.enterFullscreen": "Вивести повний екран", "tooltip.enterFullscreen": "Вивести повний екран",
"tooltip.leaveFullscreen": "Залишити повноекранний екран", "tooltip.leaveFullscreen": "Залишити повноекранний екран",
"tooltip.lobby": "Показати зал очікувань", "tooltip.lobby": "Показати зал очікувань",
"tooltip.settings": "Показати налаштування", "tooltip.settings": "Показати налаштування",
"tooltip.participants": "Показати учасників", "tooltip.participants": "Показати учасників",
"label.roomName": "Назва кімнати", "label.roomName": "Назва кімнати",
"label.chooseRoomButton": "Продовжити", "label.chooseRoomButton": "Продовжити",
"label.yourName": "Ваше ім'я", "label.yourName": "Ваше ім'я",
"label.newWindow": "Нове вікно", "label.newWindow": "Нове вікно",
"label.fullscreen": "Повний екран", "label.fullscreen": "Повний екран",
"label.openDrawer": "Відкрити ящик", "label.openDrawer": "Відкрити ящик",
"label.leave": "Залишити", "label.leave": "Залишити",
"label.chatInput": "Введіть повідомлення чату ...", "label.chatInput": "Введіть повідомлення чату ...",
"label.chat": "Чат", "label.chat": "Чат",
"label.filesharing": "Обмін файлами", "label.filesharing": "Обмін файлами",
"label.participants": "Учасники", "label.participants": "Учасники",
"label.shareFile": "Надіслати файл", "label.shareFile": "Надіслати файл",
"label.fileSharingUnsupported": "Обмін файлами не підтримується", "label.fileSharingUnsupported": "Обмін файлами не підтримується",
"label.unknown": "Невідомо", "label.unknown": "Невідомо",
"label.democrat": "Демократичний вигляд", "label.democrat": "Демократичний вигляд",
"label.filmstrip": "У вигляді кінострічки", "label.filmstrip": "У вигляді кінострічки",
"label.low": "Низький", "label.low": "Низький",
"label.medium": "Середній", "label.medium": "Середній",
"label.high": "Високий (HD)", "label.high": "Високий (HD)",
"label.veryHigh": "Дуже високий (FHD)", "label.veryHigh": "Дуже високий (FHD)",
"label.ultra": "Ультра (UHD)", "label.ultra": "Ультра (UHD)",
"label.close": "Закрити", "label.close": "Закрити",
"settings.settings": "Налаштування", "settings.settings": "Налаштування",
"settings.camera": "Камера", "settings.camera": "Камера",
"settings.selectCamera": "Вибрати відеопристрій", "settings.selectCamera": "Вибрати відеопристрій",
"settings.cantSelectCamera": "Неможливо вибрати відеопристрій", "settings.cantSelectCamera": "Неможливо вибрати відеопристрій",
"settings.audio": "Аудіопристрій", "settings.audio": "Аудіопристрій",
"settings.selectAudio": "Вибрати аудіопристрій", "settings.selectAudio": "Вибрати аудіопристрій",
"settings.cantSelectAudio": "Неможливо вибрати аудіопристрій", "settings.cantSelectAudio": "Неможливо вибрати аудіопристрій",
"settings.audioOutput": "Пристрій аудіовиходу", "settings.audioOutput": "Пристрій аудіовиходу",
"settings.selectAudioOutput": "Виберіть пристрій аудіовиходу", "settings.selectAudioOutput": "Виберіть пристрій аудіовиходу",
"settings.cantSelectAudioOutput": "Неможливо вибрати аудіо вихідний пристрій", "settings.cantSelectAudioOutput": "Неможливо вибрати аудіо вихідний пристрій",
"settings.resolution": "Виберіть роздільну здатність відео", "settings.resolution": "Виберіть роздільну здатність відео",
"settings.layout": "Розміщення кімнати", "settings.layout": "Розміщення кімнати",
"settings.selectRoomLayout": "Вибір розташування кімнати", "settings.selectRoomLayout": "Вибір розташування кімнати",
"settings.advancedMode": "Розширений режим", "settings.advancedMode": "Розширений режим",
"settings.permanentTopBar": "Постійний верхній рядок", "settings.permanentTopBar": "Постійний верхній рядок",
"settings.lastn": "Кількість видимих ​​відео", "settings.lastn": "Кількість видимих ​​відео",
"filesharing.saveFileError": "Неможливо зберегти файл", "filesharing.saveFileError": "Неможливо зберегти файл",
"filesharing.startingFileShare": "Спроба поділитися файлом", "filesharing.startingFileShare": "Спроба поділитися файлом",
"filesharing.successfulFileShare": "Файл готовий для обміну", "filesharing.successfulFileShare": "Файл готовий для обміну",
"filesharing.unableToShare": "Неможливо поділитися файлом", "filesharing.unableToShare": "Неможливо поділитися файлом",
"filesharing.error": "Виникла помилка обміну файлами", "filesharing.error": "Виникла помилка обміну файлами",
"filesharing.finished": "Завантаження файлу закінчено", "filesharing.finished": "Завантаження файлу закінчено",
"filesharing.save": "Зберегти", "filesharing.save": "Зберегти",
"filesharing.sharedFile": "{displayName} поділився файлом", "filesharing.sharedFile": "{displayName} поділився файлом",
"filesharing.download": "Завантажити", "filesharing.download": "Завантажити",
"filesharing.missingSeeds": "Якщо цей процес триває тривалий час, може не з’явиться хтось, хто роздає цей торрент. Спробуйте попросити когось перезавантажити потрібний файл.", "filesharing.missingSeeds": "Якщо цей процес триває тривалий час, може не з’явиться хтось, хто роздає цей торрент. Спробуйте попросити когось перезавантажити потрібний файл.",
"devices.devicesChanged": "Ваші пристрої змінилися, налаштуйте ваші пристрої в діалоговому вікні налаштувань", "devices.devicesChanged": "Ваші пристрої змінилися, налаштуйте ваші пристрої в діалоговому вікні налаштувань",
"device.audioUnsupported": "Аудіо не підтримується", "device.audioUnsupported": "Аудіо не підтримується",
"device.activateAudio": "Активувати звук", "device.activateAudio": "Активувати звук",
"device.muteAudio": "Вимкнути звук", "device.muteAudio": "Вимкнути звук",
"device.unMuteAudio": "Увімкнути звук", "device.unMuteAudio": "Увімкнути звук",
"device.videoUnsupported": "Відео не підтримується", "device.videoUnsupported": "Відео не підтримується",
"device.startVideo": "Запустити відео", "device.startVideo": "Запустити відео",
"device.stopVideo": "Зупинити відео", "device.stopVideo": "Зупинити відео",
"device.screenSharingUnsupported": "Обмін екраном не підтримується", "device.screenSharingUnsupported": "Обмін екраном не підтримується",
"device.startScreenSharing": "Початок спільного використання екрана", "device.startScreenSharing": "Початок спільного використання екрана",
"device.stopScreenSharing": "Зупинити спільний доступ до екрана", "device.stopScreenSharing": "Зупинити спільний доступ до екрана",
"devices.microphoneDisconnected": "Мікрофон відключений", "devices.microphoneDisconnected": "Мікрофон відключений",
"devices.microphoneError": "Сталася помилка під час доступу до мікрофона", "devices.microphoneError": "Сталася помилка під час доступу до мікрофона",
"devices.microPhoneMute": "Вимкнено ваш мікрофон", "devices.microPhoneMute": "Вимкнено ваш мікрофон",
"devices.micophoneUnMute": "Не ввімкнено ваш мікрофон", "devices.micophoneUnMute": "Не ввімкнено ваш мікрофон",
"devices.microphoneEnable": "Увімкнено мікрофон", "devices.microphoneEnable": "Увімкнено мікрофон",
"devices.microphoneMuteError": "Не вдається вимкнути мікрофон", "devices.microphoneMuteError": "Не вдається вимкнути мікрофон",
"devices.microphoneUnMuteError": "Неможливо ввімкнути мікрофон", "devices.microphoneUnMuteError": "Неможливо ввімкнути мікрофон",
"devices.screenSharingDisconnected": "Спільний доступ до екрана відключений", "devices.screenSharingDisconnected": "Спільний доступ до екрана відключений",
"devices.screenSharingError": "Сталася помилка під час доступу до екрану", "devices.screenSharingError": "Сталася помилка під час доступу до екрану",
"devices.cameraDisconnected": "Камера відключена", "devices.cameraDisconnected": "Камера відключена",
"devices.cameraError": "Під час доступу до камери сталася помилка" "devices.cameraError": "Під час доступу до камери сталася помилка",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null
} }

View File

@ -229,10 +229,14 @@ module.exports =
PROMOTE_PEER : [ userRoles.NORMAL ], PROMOTE_PEER : [ userRoles.NORMAL ],
// The role(s) have permission to send chat messages // The role(s) have permission to send chat messages
SEND_CHAT : [ userRoles.NORMAL ], SEND_CHAT : [ userRoles.NORMAL ],
// The role(s) have permission to moderate chat
MODERATE_CHAT : [ userRoles.MODERATOR ],
// The role(s) have permission to share screen // The role(s) have permission to share screen
SHARE_SCREEN : [ userRoles.NORMAL ], SHARE_SCREEN : [ userRoles.NORMAL ],
// The role(s) have permission to share files // The role(s) have permission to share files
SHARE_FILE : [ userRoles.NORMAL ], SHARE_FILE : [ userRoles.NORMAL ],
// The role(s) have permission to moderate files
MODERATE_FILES : [ userRoles.MODERATOR ],
// The role(s) have permission to moderate room (e.g. kick user) // The role(s) have permission to moderate room (e.g. kick user)
MODERATE_ROOM : [ userRoles.MODERATOR ] MODERATE_ROOM : [ userRoles.MODERATOR ]
}, },

View File

@ -1005,6 +1005,24 @@ class Room extends EventEmitter
break; break;
} }
case 'moderator:clearChat':
{
if (
!peer.roles.some((role) => config.permissionsFromRoles.MODERATE_CHAT.includes(role))
)
throw new Error('peer not authorized');
this._chatHistory = [];
// Spread to others
this._notification(peer.socket, 'moderator:clearChat', null, true);
// Return no error
cb();
break;
}
case 'serverHistory': case 'serverHistory':
{ {
// Return to sender // Return to sender
@ -1160,6 +1178,24 @@ class Room extends EventEmitter
break; break;
} }
case 'moderator:clearFileSharing':
{
if (
!peer.roles.some((role) => config.permissionsFromRoles.MODERATE_FILES.includes(role))
)
throw new Error('peer not authorized');
this._fileHistory = [];
// Spread to others
this._notification(peer.socket, 'moderator:clearFileSharing', null, true);
// Return no error
cb();
break;
}
case 'raiseHand': case 'raiseHand':
{ {
const { raisedHand } = request.data; const { raisedHand } = request.data;
@ -1186,9 +1222,7 @@ class Room extends EventEmitter
throw new Error('peer not authorized'); throw new Error('peer not authorized');
// Spread to others // Spread to others
this._notification(peer.socket, 'moderator:mute', { this._notification(peer.socket, 'moderator:mute', null, true);
peerId : peer.id
}, true);
cb(); cb();
@ -1203,9 +1237,7 @@ class Room extends EventEmitter
throw new Error('peer not authorized'); throw new Error('peer not authorized');
// Spread to others // Spread to others
this._notification(peer.socket, 'moderator:stopVideo', { this._notification(peer.socket, 'moderator:stopVideo', null, true);
peerId : peer.id
}, true);
cb(); cb();
@ -1219,12 +1251,7 @@ class Room extends EventEmitter
) )
throw new Error('peer not authorized'); throw new Error('peer not authorized');
this._notification( this._notification(peer.socket, 'moderator:kick', null, true);
peer.socket,
'moderator:kick',
null,
true
);
cb(); cb();
@ -1248,10 +1275,7 @@ class Room extends EventEmitter
if (!kickPeer) if (!kickPeer)
throw new Error(`peer with id "${peerId}" not found`); throw new Error(`peer with id "${peerId}" not found`);
this._notification( this._notification(kickPeer.socket, 'moderator:kick');
kickPeer.socket,
'moderator:kick'
);
kickPeer.close(); kickPeer.close();

View File

@ -37,8 +37,8 @@ const interactiveServer = require('./lib/interactiveServer');
/* eslint-disable no-console */ /* eslint-disable no-console */
console.log('- process.env.DEBUG:', process.env.DEBUG); console.log('- process.env.DEBUG:', process.env.DEBUG);
console.log('- config.mediasoup.logLevel:', config.mediasoup.logLevel); console.log('- config.mediasoup.worker.logLevel:', config.mediasoup.worker.logLevel);
console.log('- config.mediasoup.logTags:', config.mediasoup.logTags); console.log('- config.mediasoup.worker.logTags:', config.mediasoup.worker.logTags);
/* eslint-enable no-console */ /* eslint-enable no-console */
const logger = new Logger(); const logger = new Logger();