From ffb722bc4cc2bdd8de4119e85985fb322b78b33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mai=20G=C3=A1bor?= Date: Wed, 26 Jan 2022 22:38:17 +0100 Subject: [PATCH 1/3] Localrecording Disclaimer --- app/src/BrowserRecorder.js | 65 +++++--- app/src/RoomClient.js | 36 ++++- app/src/actions/meActions.js | 6 - app/src/actions/peerActions.js | 6 + app/src/actions/recorderActions.js | 20 +-- app/src/components/Containers/Me.js | 26 +++- app/src/components/Containers/Peer.js | 37 +++-- app/src/components/Controls/TopBar.js | 145 +++++++++++------- .../components/Notifications/Notifications.js | 47 ++++-- app/src/components/Selectors.js | 87 +++++++++-- .../components/VideoContainers/VideoView.js | 36 ++++- app/src/config.ts | 7 + app/src/index.js | 2 +- app/src/permissions.js | 26 ++-- app/src/reducers/me.js | 10 +- app/src/reducers/notifications.js | 9 ++ app/src/reducers/peers.js | 15 ++ app/src/reducers/recorder.js | 44 ++++++ app/src/reducers/rootReducer.js | 2 + server/lib/Room.js | 15 ++ 20 files changed, 478 insertions(+), 163 deletions(-) create mode 100644 app/src/reducers/recorder.js diff --git a/app/src/BrowserRecorder.js b/app/src/BrowserRecorder.js index befadf0f..07e3965b 100644 --- a/app/src/BrowserRecorder.js +++ b/app/src/BrowserRecorder.js @@ -2,10 +2,9 @@ import Logger from './Logger'; import streamSaver from 'streamsaver'; import { WritableStream } from 'web-streams-polyfill/ponyfill'; import { openDB, deleteDB } from 'idb'; -import * as meActions from './actions/meActions'; import { store } from './store'; import * as requestActions from './actions/requestActions'; -import { RECORDING_PAUSE, RECORDING_RESUME, RECORDING_STOP, RECORDING_START } from './actions/recorderActions'; +import * as recorderActions from './actions/recorderActions'; export default class BrowserRecorder { @@ -21,7 +20,7 @@ export default class BrowserRecorder this.recorderStream = null; this.gdmStream = null; this.roomClient = null; - this.fileName = 'apple.webm'; + this.fileName = 'default.webm'; this.logger = new Logger('Recorder'); // IndexedDB @@ -45,7 +44,7 @@ export default class BrowserRecorder displaySurface : 'browser', width : { ideal: 1920 } }, - audio : true, + audio : false, advanced : [ { width: 1920, height: 1080 }, { width: 1280, height: 720 } @@ -81,7 +80,7 @@ export default class BrowserRecorder async startLocalRecording( { - roomClient, additionalAudioTracks, recordingMimeType + roomClient, additionalAudioTracks, recordingMimeType, roomname }) { this.roomClient = roomClient; @@ -106,6 +105,8 @@ export default class BrowserRecorder try { + store.dispatch(recorderActions.setLocalRecordingState('start')); + // Screensharing this.gdmStream = await navigator.mediaDevices.getDisplayMedia( this.RECORDING_CONSTRAINTS @@ -135,10 +136,17 @@ export default class BrowserRecorder this.recorderStream = this.mixer(null, this.gdmStream); } + const dt = new Date(); + const rdt = `${dt.getFullYear() }-${ (`0${ dt.getMonth()+1}`).slice(-2) }-${ (`0${ dt.getDate()}`).slice(-2) }_${dt.getHours() }_${(`0${ dt.getMinutes()}`).slice(-2) }_${dt.getSeconds()}`; + this.recorder = new MediaRecorder( this.recorderStream, { mimeType: this.recordingMimeType } ); + const ext = this.recorder.mimeType.split(';')[0].split('/')[1]; + + this.fileName = `${roomname}-recording-${rdt}.${ext}`; + if (typeof indexedDB === 'undefined' || typeof indexedDB.open === 'undefined') { this.logger.warn('IndexedDB API is not available in this browser. Fallback to '); @@ -195,7 +203,7 @@ export default class BrowserRecorder this.recorder.onerror = (error) => { - this.logger.err(`Recorder onerror: ${error}`); + this.logger.error(`Recorder onerror: ${error}`); switch (error.name) { case 'SecurityError': @@ -271,7 +279,18 @@ export default class BrowserRecorder }; this.recorder.start(this.RECORDING_SLICE_SIZE); - meActions.setLocalRecordingState(RECORDING_START); + // abort so it dose not look stuck + + window.onbeforeunload = () => + { + if (this.recorder !== null) + { + this.stopLocalRecording(); + + // evt.returnValue = 'Are you sure you want to leave? Recording in process'; + } + }; + recorderActions.setLocalRecordingState('start'); } } @@ -288,7 +307,7 @@ export default class BrowserRecorder this.logger.error('startLocalRecording() [error:"%o"]', error); if (this.recorder) this.recorder.stop(); - store.dispatch(meActions.setLocalRecordingState(RECORDING_STOP)); + store.dispatch(recorderActions.setLocalRecordingState('stop')); if (typeof this.gdmStream !== 'undefined' && this.gdmStream && typeof this.gdmStream.getTracks === 'function') { this.gdmStream.getTracks().forEach((track) => track.stop()); @@ -303,9 +322,8 @@ export default class BrowserRecorder try { - await this.roomClient.sendRequest('setLocalRecording', { localRecordingState: RECORDING_START }); - store.dispatch(meActions.setLocalRecordingState(RECORDING_START)); + await this.roomClient.sendRequest('setLocalRecording', { localRecordingState: 'start' }); store.dispatch(requestActions.notify( { @@ -329,6 +347,7 @@ export default class BrowserRecorder } } + // eslint-disable-next-line no-unused-vars async stopLocalRecording() { this.logger.debug('stopLocalRecording()'); @@ -344,9 +363,8 @@ export default class BrowserRecorder }) })); - store.dispatch(meActions.setLocalRecordingState(RECORDING_STOP)); - - await this.roomClient.sendRequest('setLocalRecording', { localRecordingState: RECORDING_STOP }); + store.dispatch(recorderActions.setLocalRecordingState('stop')); + await this.roomClient.sendRequest('setLocalRecording', { localRecordingState: 'stop' }); } catch (error) @@ -367,14 +385,14 @@ export default class BrowserRecorder async pauseLocalRecording() { this.recorder.pause(); - store.dispatch(meActions.setLocalRecordingState(RECORDING_PAUSE)); - await this.roomClient.sendRequest('setLocalRecording', { localRecordingState: RECORDING_PAUSE }); + store.dispatch(recorderActions.setLocalRecordingState('pause')); + await this.roomClient.sendRequest('setLocalRecording', { localRecordingState: 'pause' }); } async resumeLocalRecording() { this.recorder.resume(); - store.dispatch(meActions.setLocalRecordingState(RECORDING_RESUME)); - await this.roomClient.sendRequest('setLocalRecording', { localRecordingState: RECORDING_RESUME }); + store.dispatch(recorderActions.setLocalRecordingState('resume')); + await this.roomClient.sendRequest('setLocalRecording', { localRecordingState: 'resume' }); } invokeSaveAsDialog(blob) { @@ -520,12 +538,10 @@ export default class BrowserRecorder // is it already appended to stream? if (this.recorder != null && (this.recorder.state === 'recording' || this.recorder.state === 'paused')) { - - const micProducer = Object.values(producers).find((p) => p.source === 'mic'); + const micProducer = Object.values(producers).find((p) => p.kind === 'audio'); if (micProducer && this.micProducerId !== micProducer.id) { - // delete/dc previous one if (this.micProducerStreamSource) { @@ -542,11 +558,11 @@ export default class BrowserRecorder } } } - checkAudioConsumer(consumers) + checkAudioConsumer(consumers, recordingConsents) { - if (this.recorder != null && (this.recorder.state === 'recording' || this.recorder.state === 'paused')) + if (this.recorder != null && (this.recorder.state === 'recording' || this.recorder.state === 'paused') && recordingConsents!==undefined) { - const audioConsumers = Object.values(consumers).filter((p) => p.kind === 'audio'); + const audioConsumers = Object.values(consumers).filter((p) => p.kind === 'audio' && recordingConsents.includes(p.peerId)); for (let i = 0; i < audioConsumers.length; i++) { @@ -572,4 +588,5 @@ export default class BrowserRecorder } } -} \ No newline at end of file +} +export const recorder = new BrowserRecorder(); \ No newline at end of file diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index d585d2c2..9247ef42 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -20,7 +20,7 @@ import Spotlights from './Spotlights'; import { permissions } from './permissions'; import * as locales from './translations/locales'; import { createIntl } from 'react-intl'; -import { RECORDING_START, RECORDING_PAUSE, RECORDING_RESUME, RECORDING_STOP } from './actions/recorderActions'; +import * as recorderActions from './actions/recorderActions'; import { directReceiverTransform, opusReceiverTransform } from './transforms/receiver'; import { config } from './config'; @@ -3593,7 +3593,16 @@ export default class RoomClient break; } + case 'addConsentForRecording': + { + // eslint-disable-next-line no-unused-vars + const { peerId, consent } = notification.data; + store.dispatch( + peerActions.setPeerLocalRecordingConsent(peerId, consent)); + + break; + } case 'setLocalRecording': { const { peerId, localRecordingState } = notification.data; @@ -3616,7 +3625,7 @@ export default class RoomClient switch (localRecordingState) { - case RECORDING_START: + case 'start': store.dispatch(requestActions.notify( { text : intl.formatMessage({ @@ -3627,7 +3636,7 @@ export default class RoomClient }) })); break; - case RECORDING_RESUME: + case 'resume': store.dispatch(requestActions.notify( { text : intl.formatMessage({ @@ -3638,7 +3647,7 @@ export default class RoomClient }) })); break; - case RECORDING_PAUSE: + case 'pause': { store.dispatch(requestActions.notify( { @@ -3651,7 +3660,7 @@ export default class RoomClient })); break; } - case RECORDING_STOP: + case 'stop': store.dispatch(requestActions.notify( { text : intl.formatMessage({ @@ -4174,6 +4183,23 @@ export default class RoomClient } } + async addConsentForRecording(consent) + { + logger.debug('addConsentForRecording()'); + + try + { + store.dispatch( + recorderActions.setLocalRecordingConsent(consent)); + await this.sendRequest('addConsentForRecording', { consent }); + } + catch (error) + { + + logger.error('addConsentForRecording() [error:"%o"]', error); + } + } + async setAccessCode(code) { logger.debug('setAccessCode()'); diff --git a/app/src/actions/meActions.js b/app/src/actions/meActions.js index 39795745..14b12136 100644 --- a/app/src/actions/meActions.js +++ b/app/src/actions/meActions.js @@ -116,9 +116,3 @@ export const setAutoMuted = (flag) => type : 'SET_AUTO_MUTED', payload : { flag } }); - -export const setLocalRecordingState = (localRecordingState) => - ({ - type : 'SET_LOCAL_RECORDING_STATE', - payload : { localRecordingState } - }); \ No newline at end of file diff --git a/app/src/actions/peerActions.js b/app/src/actions/peerActions.js index 3d39634f..648a24ee 100644 --- a/app/src/actions/peerActions.js +++ b/app/src/actions/peerActions.js @@ -104,3 +104,9 @@ export const setPeerLocalRecordingState = (peerId, localRecordingState) => type : 'SET_PEER_LOCAL_RECORDING_STATE', payload : { peerId, localRecordingState } }); + +export const setPeerLocalRecordingConsent = (peerId, consent) => + ({ + type : 'SET_PEER_LOCAL_RECORDING_CONSENT', + payload : { peerId, consent } + }); diff --git a/app/src/actions/recorderActions.js b/app/src/actions/recorderActions.js index 3cf8b893..9b403650 100644 --- a/app/src/actions/recorderActions.js +++ b/app/src/actions/recorderActions.js @@ -1,10 +1,10 @@ -// Recoding STATE -import BrowserRecorder from '../BrowserRecorder'; - -export const RECORDING_START = 'start'; -export const RECORDING_STOP = 'stop'; -export const RECORDING_PAUSE = 'pause'; -export const RECORDING_RESUME = 'resume'; -export const RECORDING_INIT = null; - -export const recorder = new BrowserRecorder(); \ No newline at end of file +export const setLocalRecordingState = (status) => + ({ + type : 'SET_LOCAL_RECORDING_STATE', + payload : { status } + }); +export const setLocalRecordingConsent = (agreed) => + ({ + type : 'SET_LOCAL_RECORDING_CONSENT', + payload : { agreed } + }); diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index 9ab73bcc..350e2268 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -2,7 +2,8 @@ import React, { useState, useEffect } from 'react'; import { connect } from 'react-redux'; import { meProducersSelector, - makePermissionSelector + makePermissionSelector, + recordingConsentsPeersSelector } from '../Selectors'; import { permissions } from '../../permissions'; import { withRoomContext } from '../../RoomContext'; @@ -169,7 +170,9 @@ const Me = (props) => transports, noiseVolume, classes, - theme + theme, + recordingConsents, + localRecordingState } = props; // const width = style.width; @@ -812,6 +815,8 @@ const Me = (props) => {/* /CONTROLS BUTTONS (inside) */}
} { ( - localRecordingState === RECORDING_PAUSE || - localRecordingState === RECORDING_RESUME || - localRecordingState === RECORDING_START + localRecordingState.status === 'pause' || + localRecordingState.status === 'resume' || + localRecordingState.status === 'start' ) && onClick={() => { handleMenuClose(); - if (localRecordingState === RECORDING_PAUSE) + if (localRecordingState.status === 'pause') { recorder.resumeLocalRecording(); } @@ -984,14 +1007,14 @@ const TopBar = (props) => - { localRecordingState === RECORDING_PAUSE ? + { localRecordingState.status === 'pause' ? : } - { localRecordingState === RECORDING_PAUSE ? + { localRecordingState.status === 'pause' ? @@ -1272,6 +1299,8 @@ const makeMapStateToProps = () => const hasLockPermission = makePermissionSelector(permissions.CHANGE_ROOM_LOCK); + const hasRecordPermission = + makePermissionSelector(permissions.LOCAL_RECORD_ROOM); const hasPromotionPermission = makePermissionSelector(permissions.PROMOTE_PEER); @@ -1279,6 +1308,7 @@ const makeMapStateToProps = () => ({ room : state.room, isSafari : state.me.browser.name !== 'safari', + meId : state.me.id, isMobile : state.me.browser.platform === 'mobile', peersLength : peersLengthSelector(state), lobbyPeers : lobbyPeersKeySelector(state), @@ -1287,16 +1317,19 @@ const makeMapStateToProps = () => toolAreaOpen : state.toolarea.toolAreaOpen, loggedIn : state.me.loggedIn, loginEnabled : state.me.loginEnabled, - localRecordingState : state.me.localRecordingState, + localRecordingState : state.recorderReducer.localRecordingState, recordingInProgress : recordingInProgressSelector(state), + recordingPeers : recordingInProgressPeersSelector(state), + recordingConsents : recordingConsentsPeersSelector(state), unread : state.toolarea.unreadMessages + state.toolarea.unreadFiles + raisedHandsSelector(state), canProduceExtraVideo : hasExtraVideoPermission(state), canLock : hasLockPermission(state), + canRecord : hasRecordPermission(state), canPromote : hasPromotionPermission(state), locale : state.intl.locale, localesList : state.intl.list, - recordingMimeType : state.settings.recordingMimeType, + recordingMimeType : state.settings.recorderPreferredMimeType, producers : state.producers, consumers : state.consumers }); @@ -1375,14 +1408,18 @@ export default withRoomContext(connect( prev.me.loginEnabled === next.me.loginEnabled && prev.me.picture === next.me.picture && prev.me.roles === next.me.roles && - prev.me.localRecordingState === next.me.localRecordingState && + prev.recorderReducer.localRecordingState.status === + next.recorderReducer.localRecordingState.status && prev.toolarea.unreadMessages === next.toolarea.unreadMessages && prev.toolarea.unreadFiles === next.toolarea.unreadFiles && prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen && prev.intl.locale === next.intl.locale && prev.intl.localesList === next.intl.localesList && prev.producers === next.producers && - prev.consumers === next.consumers + prev.consumers === next.consumers && + prev.settings.recorderPreferredMimeType === + next.settings.recorderPreferredMimeType && + recordingConsentsPeersSelector(prev)===recordingConsentsPeersSelector(next) ); } } diff --git a/app/src/components/Notifications/Notifications.js b/app/src/components/Notifications/Notifications.js index 9d743a50..226a6532 100644 --- a/app/src/components/Notifications/Notifications.js +++ b/app/src/components/Notifications/Notifications.js @@ -5,9 +5,10 @@ import { withSnackbar } from 'notistack'; import * as notificationActions from '../../actions/notificationActions'; import { config } from '../../config'; import Button from '@material-ui/core/Button'; +import ButtonGroup from '@material-ui/core/ButtonGroup'; import VerifiedUserIcon from '@material-ui/icons/VerifiedUser'; import { FormattedMessage } from 'react-intl'; - +import Lock from '@material-ui/icons/Lock'; class Notifications extends Component { displayed = []; @@ -63,15 +64,39 @@ class Notifications extends Component // customized const okAction = (key) => ( - } - onClick={() => { this.props.closeSnackbar(key); }} - > - - + + } + onClick={() => + { + notification.roomClient.addConsentForRecording( + 'agreed' + ); + this.props.closeSnackbar(key); + }} + > + + + } + onClick={() => + { + notification.roomClient.addConsentForRecording( + 'denied' + ); + this.props.closeSnackbar(key); + }} + > + + + + ); @@ -81,6 +106,8 @@ class Notifications extends Component variant : notification.type, autoHideDuration : notification.timeout, persist : notification.persist, + peerid : notification.peerid, + roomClient : notification.roomClient, key : notification.id, action : notification.persist? okAction: null, anchorOrigin : { diff --git a/app/src/components/Selectors.js b/app/src/components/Selectors.js index eeab10e1..70489f9a 100644 --- a/app/src/components/Selectors.js +++ b/app/src/components/Selectors.js @@ -1,5 +1,4 @@ import { createSelector } from 'reselect'; -import { RECORDING_INIT, RECORDING_START, RECORDING_STOP, RECORDING_PAUSE, RECORDING_RESUME } from '../actions/recorderActions'; const meRolesSelect = (state) => state.me.roles; const userRolesSelect = (state) => state.room.userRoles; @@ -10,6 +9,7 @@ const consumersSelect = (state) => state.consumers; const spotlightsSelector = (state) => state.room.spotlights; const peersSelector = (state) => state.peers; const meSelector = (state) => state.me; +const recorderReducerSelect = (state) => state.recorderReducer; const lobbyPeersSelector = (state) => state.lobbyPeers; const getPeerConsumers = (state, id) => (state.peers[id] ? state.peers[id].consumers : null); @@ -304,30 +304,93 @@ export const makePermissionSelector = (permission) => export const recordingInProgressSelector = createSelector( peersValueSelector, - meSelector, - (peers, me) => + recorderReducerSelect, + (peers, recorderReducer) => { if ( - me.localRecordingState === RECORDING_START || - me.localRecordingState === RECORDING_RESUME || + recorderReducer.localRecordingState.status === 'start' || + recorderReducer.localRecordingState.status === 'resume' || peers.findIndex((e) => - e.localRecordingState === RECORDING_START || - e.localRecordingState === RECORDING_RESUME + e.localRecordingState !== undefined && + ( + e.localRecordingState === 'start' || + e.localRecordingState === 'resume' + ) ) !== -1 ) { return true; } else if ( - me.localRecordingState === RECORDING_INIT || - me.localRecordingState === RECORDING_STOP || - me.localRecordingState === RECORDING_PAUSE || + recorderReducer.localRecordingState.status === 'init' || + recorderReducer.localRecordingState.status === 'stop' || + recorderReducer.localRecordingState.status === 'pause' || peers.findIndex((e) => - e.localRecordingState === RECORDING_START || - e.localRecordingState === RECORDING_RESUME + e.localRecordingState !== undefined && + ( + e.localRecordingState === 'start' || + e.localRecordingState === 'resume' + ) ) === -1 ) return false; } ); + +export const recordingConsentsPeersSelector = createSelector( + peersValueSelector, + (peers) => + { + const recordingconsents = []; + + peers.forEach((e) => + { + if ( + e.localRecordingConsent !== undefined && + e.localRecordingConsent === 'agreed' + ) + { + recordingconsents.push(e.id); + } + }); + + return recordingconsents; + } +); + +export const recordingInProgressPeersSelector = createSelector( + peersValueSelector, + meSelector, + recorderReducerSelect, + (peers, me, recorderReducer) => + { + const recordingpeers = []; + + if ( + recorderReducer !== undefined && + ( + recorderReducer.localRecordingState.status === 'start' || + recorderReducer.localRecordingState.status === 'resume' + ) + ) + { + recordingpeers.push(me.id); + } + peers.forEach((e) => + { + if ( + e.recorderReducer !== undefined && + ( + e.recorderReducer.localRecordingState.status === 'start' || + e.recorderReducer.localRecordingState.status === 'resume' + ) + ) + { + recordingpeers.push(e.id); + } + }); + + return recordingpeers; + } +); diff --git a/app/src/components/VideoContainers/VideoView.js b/app/src/components/VideoContainers/VideoView.js index 4421fa34..b72effec 100644 --- a/app/src/components/VideoContainers/VideoView.js +++ b/app/src/components/VideoContainers/VideoView.js @@ -231,7 +231,10 @@ class VideoView extends React.PureComponent netInfo, width, height, - opusConfig + opusConfig, + localRecordingState, + recordingConsents, + peer } = this.props; const { @@ -427,7 +430,17 @@ class VideoView extends React.PureComponent : - {displayName} + { + ( + ( + localRecordingState==='start' || + localRecordingState==='resume' + )&& + ( + !recordingConsents.includes(peer.id) + ) + ) ? '':displayName + } } @@ -438,7 +451,18 @@ class VideoView extends React.PureComponent @@ -168,13 +167,6 @@ const me = (state = initialState, action) => return { ...state, isAutoMuted: flag }; } - case 'SET_LOCAL_RECORDING_STATE': - { - const { localRecordingState } = action.payload; - - return { ...state, localRecordingState }; - } - default: return state; } diff --git a/app/src/reducers/notifications.js b/app/src/reducers/notifications.js index 79969661..baa084b0 100644 --- a/app/src/reducers/notifications.js +++ b/app/src/reducers/notifications.js @@ -11,6 +11,15 @@ const notifications = (state = [], action) => return [ ...state, notification ]; } + case 'ADD_CONSENT_NOTIFICATION': + { + const { notification } = action.payload; + + notification.toBeClosed=false; + + return [ ...state, notification ]; + } + case 'REMOVE_NOTIFICATION': { const { notificationId } = action.payload; diff --git a/app/src/reducers/peers.js b/app/src/reducers/peers.js index c850d8a1..89397315 100644 --- a/app/src/reducers/peers.js +++ b/app/src/reducers/peers.js @@ -97,6 +97,12 @@ const peer = (state = initialState, action) => localRecordingState : action.payload.localRecordingState }; + case 'SET_PEER_LOCAL_RECORDING_CONSENT': + return { + ...state, + localRecordingConsent : action.payload.consent + }; + default: return state; } @@ -147,7 +153,16 @@ const peers = (state = initialState, action) => return { ...state, [oldPeer.id]: peer(oldPeer, action) }; } + case 'SET_PEER_LOCAL_RECORDING_CONSENT': + { + const oldPeer = state[action.payload.peerId]; + // NOTE: This means that the Peer was closed before, so it's ok. + if (!oldPeer) + return state; + + return { ...state, [oldPeer.id]: peer(oldPeer, action) }; + } case 'CLEAR_PEERS': { return initialState; diff --git a/app/src/reducers/recorder.js b/app/src/reducers/recorder.js new file mode 100644 index 00000000..f6e3b4eb --- /dev/null +++ b/app/src/reducers/recorder.js @@ -0,0 +1,44 @@ +const initialState = { + localRecordingState : { + status : 'init', + consent : 'init' + } +} + +; + +const recorderReducer = (state = initialState, action) => +{ + switch (action.type) + { + case 'SET_LOCAL_RECORDING_STATE': + { + const { status } = action.payload; + + const localRecordingState = state['localRecordingState']; + + localRecordingState.status = status; + + return { ...state, + localRecordingState : localRecordingState + }; + } + + case 'SET_LOCAL_RECORDING_CONSENT': + { + const { agreed } = action.payload; + const localRecordingState = state['localRecordingState']; + + localRecordingState.consent = agreed; + + return { ...state, + localRecordingState : localRecordingState + }; + } + + default: + return state; + } +}; + +export default recorderReducer; diff --git a/app/src/reducers/rootReducer.js b/app/src/reducers/rootReducer.js index d837c7f9..fd5065c8 100644 --- a/app/src/reducers/rootReducer.js +++ b/app/src/reducers/rootReducer.js @@ -8,6 +8,7 @@ import consumers from './consumers'; import peerVolumes from './peerVolumes'; import notifications from './notifications'; import chat from './chat'; +import recorderReducer from './recorder'; import toolarea from './toolarea'; import files from './files'; import settings from './settings'; @@ -30,6 +31,7 @@ export default combineReducers({ toolarea, files, settings, + recorderReducer, // intl : intlReducer intl, config diff --git a/server/lib/Room.js b/server/lib/Room.js index eee67c4e..e7cac6e6 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -1561,6 +1561,21 @@ class Room extends EventEmitter break; } + case 'addConsentForRecording': + { + const {consent} = request.data; + // Spread to others + this._notification(peer.socket, 'addConsentForRecording', { + peerId : peer.id, + consent: consent + }, true); + + // Return no error + cb(); + + break; + } + case 'unlockRoom': { if (!this._hasPermission(peer, CHANGE_ROOM_LOCK)) From bea537f1621de207d7b0e7e7cc6e6c2cd9f5675a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mai=20G=C3=A1bor?= Date: Fri, 28 Jan 2022 13:32:00 +0000 Subject: [PATCH 2/3] yarn gen-config --- app/README.md | 1 + app/public/config/config.example.js | 3 +++ server/README.md | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/README.md b/app/README.md index 9e10c9f6..43efa9f9 100644 --- a/app/README.md +++ b/app/README.md @@ -35,6 +35,7 @@ can be found here: [config.example.js](public/config/config.example.js). | simulcastSharing | Enable or disable simulcast for screen sharing video. | `"boolean"` | ``false`` | | simulcastProfiles | Define different encodings for various resolutions of the video. | `"object"` | ``{ "320": [ { "scaleResolutionDownBy": 1, "maxBitRate": 150000 } ], "640": [ { "scaleResolutionDownBy": 2, "maxBitRate": 150000 }, { "scaleResolutionDownBy": 1, "maxBitRate": 500000 } ], "1280": [ { "scaleResolutionDownBy": 4, "maxBitRate": 150000 }, { "scaleResolutionDownBy": 2, "maxBitRate": 500000 }, { "scaleResolutionDownBy": 1, "maxBitRate": 1200000 } ], "1920": [ { "scaleResolutionDownBy": 6, "maxBitRate": 150000 }, { "scaleResolutionDownBy": 3, "maxBitRate": 500000 }, { "scaleResolutionDownBy": 1, "maxBitRate": 3500000 } ], "3840": [ { "scaleResolutionDownBy": 12, "maxBitRate": 150000 }, { "scaleResolutionDownBy": 6, "maxBitRate": 500000 }, { "scaleResolutionDownBy": 1, "maxBitRate": 10000000 } ]}`` | | adaptiveScalingFactor | The adaptive spatial layer selection scaling factor in the range [0.5, 1.0]. | | ``0.75`` | +| localRecordingEnabled | If set to true Local Recording feature will be enabled. | `"boolean"` | ``false`` | | audioOutputSupportedBrowsers | White listing browsers that support audio output device selection. | `"array"` | ``[ "chrome", "opera"]`` | | requestTimeout | The Socket.io request timeout. | `"nat"` | ``20000`` | | requestRetries | The Socket.io request maximum retries. | `"nat"` | ``3`` | diff --git a/app/public/config/config.example.js b/app/public/config/config.example.js index d2d5e7c6..8cfe7c86 100644 --- a/app/public/config/config.example.js +++ b/app/public/config/config.example.js @@ -143,6 +143,9 @@ var config = { // The adaptive spatial layer selection scaling factor in the range [0.5, 1.0]. adaptiveScalingFactor : 0.75, + // If set to true Local Recording feature will be enabled. + localRecordingEnabled : false, + // White listing browsers that support audio output device selection. audioOutputSupportedBrowsers : [ 'chrome', diff --git a/server/README.md b/server/README.md index e464be4b..19c48556 100644 --- a/server/README.md +++ b/server/README.md @@ -54,13 +54,13 @@ Look at the default `config/config.example.js` file for documentation. | routerScaleSize | Room size before spreading to a new router. | `"nat"` | ``40`` | | requestTimeout | Socket timeout value (ms). | `"nat"` | ``20000`` | | requestRetries | Socket retries when a timeout occurs. | `"nat"` | ``3`` | -| mediasoup.numWorkers | The number of Mediasoup workers to spawn. Defaults to the available CPUs count. | `"nat"` | ``4`` | +| mediasoup.numWorkers | The number of Mediasoup workers to spawn. Defaults to the available CPUs count. | `"nat"` | ``6`` | | mediasoup.worker.logLevel | The Mediasoup log level. | `"string"` | ``"warn"`` | | mediasoup.worker.logTags | The Mediasoup log tags. | `"array"` | ``[ "info", "ice", "dtls", "rtp", "srtp", "rtcp"]`` | | mediasoup.worker.rtcMinPort | The Mediasoup start listening port number. | `"port"` | ``40000`` | | mediasoup.worker.rtcMaxPort | The Mediasoup end listening port number. | `"port"` | ``49999`` | | mediasoup.router.mediaCodecs | The Mediasoup codecs settings. [supportedRtpCapabilities](https://github.com/versatica/mediasoup/blob/v3/src/supportedRtpCapabilities.ts) | `"*"` | ``[ { "kind": "audio", "mimeType": "audio/opus", "clockRate": 48000, "channels": 2 }, { "kind": "video", "mimeType": "video/VP8", "clockRate": 90000, "parameters": { "x-google-start-bitrate": 1000 } }, { "kind": "video", "mimeType": "video/VP9", "clockRate": 90000, "parameters": { "profile-id": 2, "x-google-start-bitrate": 1000 } }, { "kind": "video", "mimeType": "video/h264", "clockRate": 90000, "parameters": { "packetization-mode": 1, "profile-level-id": "4d0032", "level-asymmetry-allowed": 1, "x-google-start-bitrate": 1000 } }, { "kind": "video", "mimeType": "video/h264", "clockRate": 90000, "parameters": { "packetization-mode": 1, "profile-level-id": "42e01f", "level-asymmetry-allowed": 1, "x-google-start-bitrate": 1000 } }]`` | -| mediasoup.webRtcTransport.listenIps | The Mediasoup listen IPs. [TransportListenIp](https://mediasoup.org/documentation/v3/mediasoup/api/#TransportListenIp) | `"array"` | ``[ { "ip": "192.168.246.104", "announcedIp": null }]`` | +| mediasoup.webRtcTransport.listenIps | The Mediasoup listen IPs. [TransportListenIp](https://mediasoup.org/documentation/v3/mediasoup/api/#TransportListenIp) | `"array"` | ``[ { "ip": "10.0.0.1", "announcedIp": null }, { "ip": "db19:25c4:5f01:9683:cc5a:bcac:fd6e:b38d", "announcedIp": null }]`` | | mediasoup.webRtcTransport.initialAvailableOutgoingBitrate | The Mediasoup initial available outgoing bitrate (in bps). [WebRtcTransportOptions](https://mediasoup.org/documentation/v3/mediasoup/api/#WebRtcTransportOptions) | `"nat"` | ``1000000`` | | mediasoup.webRtcTransport.maxIncomingBitrate | The Mediasoup maximum incoming bitrate for each transport. (in bps). [setMaxIncomingBitrate](https://mediasoup.org/documentation/v3/mediasoup/api/#transport-setMaxIncomingBitrate) | `"nat"` | ``1500000`` | | prometheus.enabled | Enables the Prometheus metrics exporter. | `"boolean"` | ``false`` | From 79d31b23f1e2fd57cc9c985fd68ceed2fa6d3cf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mai=20G=C3=A1bor?= Date: Fri, 28 Jan 2022 13:43:58 +0000 Subject: [PATCH 3/3] Add translations for localrecording --- app/src/translations/cn.json | 2 ++ app/src/translations/cs.json | 2 ++ app/src/translations/de.json | 2 ++ app/src/translations/dk.json | 2 ++ app/src/translations/el.json | 2 ++ app/src/translations/en.json | 5 +++-- app/src/translations/es.json | 2 ++ app/src/translations/fr.json | 2 ++ app/src/translations/hi.json | 2 ++ app/src/translations/hr.json | 2 ++ app/src/translations/hu.json | 2 ++ app/src/translations/it.json | 2 ++ app/src/translations/kk.json | 2 ++ app/src/translations/lv.json | 2 ++ app/src/translations/nb.json | 2 ++ app/src/translations/pl.json | 2 ++ app/src/translations/pt.json | 2 ++ app/src/translations/ro.json | 2 ++ app/src/translations/ru.json | 2 ++ app/src/translations/tr.json | 2 ++ app/src/translations/tw.json | 2 ++ app/src/translations/uk.json | 2 ++ 22 files changed, 45 insertions(+), 2 deletions(-) diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index b5269495..0aa42952 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -75,6 +75,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index b4491d04..d1cdc62a 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/de.json b/app/src/translations/de.json index 034f701b..a071da6e 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index 49ec6c73..2d932761 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/el.json b/app/src/translations/el.json index e2174bc3..341d26dc 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 6cff495a..654c4fdf 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -75,11 +75,12 @@ "room.localRecordingPaused": "{displayName} paused local recording", "room.localRecordingResumed": "{displayName} resumed local recording", "room.localRecordingStopped": "{displayName} stopped local recording", - "room.recordingConsent": "When attending this meeting you agree and give your consent that the meeting will be audio and video recorded and/or live broadcasted through web streaming.", "room.localRecordingSecurityError": "Recording the specified source is not allowed due to security restrictions. Check you client settings!", "room.leavingTheRoom": "Leaving the room", "room.leaveConfirmationMessage": "Do you want to leave the room?", - + "room.recordingConsent": "When attending this meeting you agree and give your consent that the meeting will be audio and video recorded and/or live broadcasted through web streaming.", + "room.recordingConsentAccept" : "I Accept", + "room.recordingConsentDeny" : "Deny", "me.mutedPTT": "You are muted{br}hold down SPACE-BAR to talk", "roles.gotRole": "You got the role: {role}", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index 96f55338..edb30311 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index 7aec6a20..487de51e 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/hi.json b/app/src/translations/hi.json index 3ddf182a..b4240313 100644 --- a/app/src/translations/hi.json +++ b/app/src/translations/hi.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index b0ba9368..4d77432f 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index e562d5f7..074dc013 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": "{displayName} visszaindította a helyi rögzítést", "room.localRecordingStopped": "{displayName} leállította a helyi rögzítést", "room.recordingConsent": "A konferencián való részvételeddel beleegyezésed adod hogy a konferenciáról hang és videó felvétel, és/vagy élő közvetítés készüljön!", + "room.recordingConsentAccept" : "Elfogadom", + "room.recordingConsentDeny" : "Megtagadom", "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 94212eb7..c24be57f 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/kk.json b/app/src/translations/kk.json index 41103aa8..6c1ba589 100644 --- a/app/src/translations/kk.json +++ b/app/src/translations/kk.json @@ -74,6 +74,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/lv.json b/app/src/translations/lv.json index 5bb754ab..acb619db 100644 --- a/app/src/translations/lv.json +++ b/app/src/translations/lv.json @@ -75,6 +75,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 5157ae6d..a673382a 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 79e5facf..22dbb996 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": "Opuszczanie pokoju", "room.leaveConfirmationMessage": "Czy na pewno chcesz opuścić pokój?", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index 047b3a3b..9ba7996e 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index 0311577b..e5beccd1 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/ru.json b/app/src/translations/ru.json index 922dfdd9..e08d2e8a 100644 --- a/app/src/translations/ru.json +++ b/app/src/translations/ru.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json index ae292208..3f8c5540 100644 --- a/app/src/translations/tr.json +++ b/app/src/translations/tr.json @@ -74,6 +74,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.hideSelfView": "Kendi görüntümü gizle", "room.showSelfView": "Kendi görüntümü göster", diff --git a/app/src/translations/tw.json b/app/src/translations/tw.json index 781710b8..205d886e 100644 --- a/app/src/translations/tw.json +++ b/app/src/translations/tw.json @@ -75,6 +75,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index c7c68b51..833d9179 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null,
@@ -1272,6 +1299,8 @@ const makeMapStateToProps = () => const hasLockPermission = makePermissionSelector(permissions.CHANGE_ROOM_LOCK); + const hasRecordPermission = + makePermissionSelector(permissions.LOCAL_RECORD_ROOM); const hasPromotionPermission = makePermissionSelector(permissions.PROMOTE_PEER); @@ -1279,6 +1308,7 @@ const makeMapStateToProps = () => ({ room : state.room, isSafari : state.me.browser.name !== 'safari', + meId : state.me.id, isMobile : state.me.browser.platform === 'mobile', peersLength : peersLengthSelector(state), lobbyPeers : lobbyPeersKeySelector(state), @@ -1287,16 +1317,19 @@ const makeMapStateToProps = () => toolAreaOpen : state.toolarea.toolAreaOpen, loggedIn : state.me.loggedIn, loginEnabled : state.me.loginEnabled, - localRecordingState : state.me.localRecordingState, + localRecordingState : state.recorderReducer.localRecordingState, recordingInProgress : recordingInProgressSelector(state), + recordingPeers : recordingInProgressPeersSelector(state), + recordingConsents : recordingConsentsPeersSelector(state), unread : state.toolarea.unreadMessages + state.toolarea.unreadFiles + raisedHandsSelector(state), canProduceExtraVideo : hasExtraVideoPermission(state), canLock : hasLockPermission(state), + canRecord : hasRecordPermission(state), canPromote : hasPromotionPermission(state), locale : state.intl.locale, localesList : state.intl.list, - recordingMimeType : state.settings.recordingMimeType, + recordingMimeType : state.settings.recorderPreferredMimeType, producers : state.producers, consumers : state.consumers }); @@ -1375,14 +1408,18 @@ export default withRoomContext(connect( prev.me.loginEnabled === next.me.loginEnabled && prev.me.picture === next.me.picture && prev.me.roles === next.me.roles && - prev.me.localRecordingState === next.me.localRecordingState && + prev.recorderReducer.localRecordingState.status === + next.recorderReducer.localRecordingState.status && prev.toolarea.unreadMessages === next.toolarea.unreadMessages && prev.toolarea.unreadFiles === next.toolarea.unreadFiles && prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen && prev.intl.locale === next.intl.locale && prev.intl.localesList === next.intl.localesList && prev.producers === next.producers && - prev.consumers === next.consumers + prev.consumers === next.consumers && + prev.settings.recorderPreferredMimeType === + next.settings.recorderPreferredMimeType && + recordingConsentsPeersSelector(prev)===recordingConsentsPeersSelector(next) ); } } diff --git a/app/src/components/Notifications/Notifications.js b/app/src/components/Notifications/Notifications.js index 9d743a50..226a6532 100644 --- a/app/src/components/Notifications/Notifications.js +++ b/app/src/components/Notifications/Notifications.js @@ -5,9 +5,10 @@ import { withSnackbar } from 'notistack'; import * as notificationActions from '../../actions/notificationActions'; import { config } from '../../config'; import Button from '@material-ui/core/Button'; +import ButtonGroup from '@material-ui/core/ButtonGroup'; import VerifiedUserIcon from '@material-ui/icons/VerifiedUser'; import { FormattedMessage } from 'react-intl'; - +import Lock from '@material-ui/icons/Lock'; class Notifications extends Component { displayed = []; @@ -63,15 +64,39 @@ class Notifications extends Component // customized const okAction = (key) => ( - } - onClick={() => { this.props.closeSnackbar(key); }} - > - - + + } + onClick={() => + { + notification.roomClient.addConsentForRecording( + 'agreed' + ); + this.props.closeSnackbar(key); + }} + > + + + } + onClick={() => + { + notification.roomClient.addConsentForRecording( + 'denied' + ); + this.props.closeSnackbar(key); + }} + > + + + + ); @@ -81,6 +106,8 @@ class Notifications extends Component variant : notification.type, autoHideDuration : notification.timeout, persist : notification.persist, + peerid : notification.peerid, + roomClient : notification.roomClient, key : notification.id, action : notification.persist? okAction: null, anchorOrigin : { diff --git a/app/src/components/Selectors.js b/app/src/components/Selectors.js index eeab10e1..70489f9a 100644 --- a/app/src/components/Selectors.js +++ b/app/src/components/Selectors.js @@ -1,5 +1,4 @@ import { createSelector } from 'reselect'; -import { RECORDING_INIT, RECORDING_START, RECORDING_STOP, RECORDING_PAUSE, RECORDING_RESUME } from '../actions/recorderActions'; const meRolesSelect = (state) => state.me.roles; const userRolesSelect = (state) => state.room.userRoles; @@ -10,6 +9,7 @@ const consumersSelect = (state) => state.consumers; const spotlightsSelector = (state) => state.room.spotlights; const peersSelector = (state) => state.peers; const meSelector = (state) => state.me; +const recorderReducerSelect = (state) => state.recorderReducer; const lobbyPeersSelector = (state) => state.lobbyPeers; const getPeerConsumers = (state, id) => (state.peers[id] ? state.peers[id].consumers : null); @@ -304,30 +304,93 @@ export const makePermissionSelector = (permission) => export const recordingInProgressSelector = createSelector( peersValueSelector, - meSelector, - (peers, me) => + recorderReducerSelect, + (peers, recorderReducer) => { if ( - me.localRecordingState === RECORDING_START || - me.localRecordingState === RECORDING_RESUME || + recorderReducer.localRecordingState.status === 'start' || + recorderReducer.localRecordingState.status === 'resume' || peers.findIndex((e) => - e.localRecordingState === RECORDING_START || - e.localRecordingState === RECORDING_RESUME + e.localRecordingState !== undefined && + ( + e.localRecordingState === 'start' || + e.localRecordingState === 'resume' + ) ) !== -1 ) { return true; } else if ( - me.localRecordingState === RECORDING_INIT || - me.localRecordingState === RECORDING_STOP || - me.localRecordingState === RECORDING_PAUSE || + recorderReducer.localRecordingState.status === 'init' || + recorderReducer.localRecordingState.status === 'stop' || + recorderReducer.localRecordingState.status === 'pause' || peers.findIndex((e) => - e.localRecordingState === RECORDING_START || - e.localRecordingState === RECORDING_RESUME + e.localRecordingState !== undefined && + ( + e.localRecordingState === 'start' || + e.localRecordingState === 'resume' + ) ) === -1 ) return false; } ); + +export const recordingConsentsPeersSelector = createSelector( + peersValueSelector, + (peers) => + { + const recordingconsents = []; + + peers.forEach((e) => + { + if ( + e.localRecordingConsent !== undefined && + e.localRecordingConsent === 'agreed' + ) + { + recordingconsents.push(e.id); + } + }); + + return recordingconsents; + } +); + +export const recordingInProgressPeersSelector = createSelector( + peersValueSelector, + meSelector, + recorderReducerSelect, + (peers, me, recorderReducer) => + { + const recordingpeers = []; + + if ( + recorderReducer !== undefined && + ( + recorderReducer.localRecordingState.status === 'start' || + recorderReducer.localRecordingState.status === 'resume' + ) + ) + { + recordingpeers.push(me.id); + } + peers.forEach((e) => + { + if ( + e.recorderReducer !== undefined && + ( + e.recorderReducer.localRecordingState.status === 'start' || + e.recorderReducer.localRecordingState.status === 'resume' + ) + ) + { + recordingpeers.push(e.id); + } + }); + + return recordingpeers; + } +); diff --git a/app/src/components/VideoContainers/VideoView.js b/app/src/components/VideoContainers/VideoView.js index 4421fa34..b72effec 100644 --- a/app/src/components/VideoContainers/VideoView.js +++ b/app/src/components/VideoContainers/VideoView.js @@ -231,7 +231,10 @@ class VideoView extends React.PureComponent netInfo, width, height, - opusConfig + opusConfig, + localRecordingState, + recordingConsents, + peer } = this.props; const { @@ -427,7 +430,17 @@ class VideoView extends React.PureComponent : - {displayName} + { + ( + ( + localRecordingState==='start' || + localRecordingState==='resume' + )&& + ( + !recordingConsents.includes(peer.id) + ) + ) ? '':displayName + } } @@ -438,7 +451,18 @@ class VideoView extends React.PureComponent @@ -168,13 +167,6 @@ const me = (state = initialState, action) => return { ...state, isAutoMuted: flag }; } - case 'SET_LOCAL_RECORDING_STATE': - { - const { localRecordingState } = action.payload; - - return { ...state, localRecordingState }; - } - default: return state; } diff --git a/app/src/reducers/notifications.js b/app/src/reducers/notifications.js index 79969661..baa084b0 100644 --- a/app/src/reducers/notifications.js +++ b/app/src/reducers/notifications.js @@ -11,6 +11,15 @@ const notifications = (state = [], action) => return [ ...state, notification ]; } + case 'ADD_CONSENT_NOTIFICATION': + { + const { notification } = action.payload; + + notification.toBeClosed=false; + + return [ ...state, notification ]; + } + case 'REMOVE_NOTIFICATION': { const { notificationId } = action.payload; diff --git a/app/src/reducers/peers.js b/app/src/reducers/peers.js index c850d8a1..89397315 100644 --- a/app/src/reducers/peers.js +++ b/app/src/reducers/peers.js @@ -97,6 +97,12 @@ const peer = (state = initialState, action) => localRecordingState : action.payload.localRecordingState }; + case 'SET_PEER_LOCAL_RECORDING_CONSENT': + return { + ...state, + localRecordingConsent : action.payload.consent + }; + default: return state; } @@ -147,7 +153,16 @@ const peers = (state = initialState, action) => return { ...state, [oldPeer.id]: peer(oldPeer, action) }; } + case 'SET_PEER_LOCAL_RECORDING_CONSENT': + { + const oldPeer = state[action.payload.peerId]; + // NOTE: This means that the Peer was closed before, so it's ok. + if (!oldPeer) + return state; + + return { ...state, [oldPeer.id]: peer(oldPeer, action) }; + } case 'CLEAR_PEERS': { return initialState; diff --git a/app/src/reducers/recorder.js b/app/src/reducers/recorder.js new file mode 100644 index 00000000..f6e3b4eb --- /dev/null +++ b/app/src/reducers/recorder.js @@ -0,0 +1,44 @@ +const initialState = { + localRecordingState : { + status : 'init', + consent : 'init' + } +} + +; + +const recorderReducer = (state = initialState, action) => +{ + switch (action.type) + { + case 'SET_LOCAL_RECORDING_STATE': + { + const { status } = action.payload; + + const localRecordingState = state['localRecordingState']; + + localRecordingState.status = status; + + return { ...state, + localRecordingState : localRecordingState + }; + } + + case 'SET_LOCAL_RECORDING_CONSENT': + { + const { agreed } = action.payload; + const localRecordingState = state['localRecordingState']; + + localRecordingState.consent = agreed; + + return { ...state, + localRecordingState : localRecordingState + }; + } + + default: + return state; + } +}; + +export default recorderReducer; diff --git a/app/src/reducers/rootReducer.js b/app/src/reducers/rootReducer.js index d837c7f9..fd5065c8 100644 --- a/app/src/reducers/rootReducer.js +++ b/app/src/reducers/rootReducer.js @@ -8,6 +8,7 @@ import consumers from './consumers'; import peerVolumes from './peerVolumes'; import notifications from './notifications'; import chat from './chat'; +import recorderReducer from './recorder'; import toolarea from './toolarea'; import files from './files'; import settings from './settings'; @@ -30,6 +31,7 @@ export default combineReducers({ toolarea, files, settings, + recorderReducer, // intl : intlReducer intl, config diff --git a/server/lib/Room.js b/server/lib/Room.js index eee67c4e..e7cac6e6 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -1561,6 +1561,21 @@ class Room extends EventEmitter break; } + case 'addConsentForRecording': + { + const {consent} = request.data; + // Spread to others + this._notification(peer.socket, 'addConsentForRecording', { + peerId : peer.id, + consent: consent + }, true); + + // Return no error + cb(); + + break; + } + case 'unlockRoom': { if (!this._hasPermission(peer, CHANGE_ROOM_LOCK)) From bea537f1621de207d7b0e7e7cc6e6c2cd9f5675a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mai=20G=C3=A1bor?= Date: Fri, 28 Jan 2022 13:32:00 +0000 Subject: [PATCH 2/3] yarn gen-config --- app/README.md | 1 + app/public/config/config.example.js | 3 +++ server/README.md | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/README.md b/app/README.md index 9e10c9f6..43efa9f9 100644 --- a/app/README.md +++ b/app/README.md @@ -35,6 +35,7 @@ can be found here: [config.example.js](public/config/config.example.js). | simulcastSharing | Enable or disable simulcast for screen sharing video. | `"boolean"` | ``false`` | | simulcastProfiles | Define different encodings for various resolutions of the video. | `"object"` | ``{ "320": [ { "scaleResolutionDownBy": 1, "maxBitRate": 150000 } ], "640": [ { "scaleResolutionDownBy": 2, "maxBitRate": 150000 }, { "scaleResolutionDownBy": 1, "maxBitRate": 500000 } ], "1280": [ { "scaleResolutionDownBy": 4, "maxBitRate": 150000 }, { "scaleResolutionDownBy": 2, "maxBitRate": 500000 }, { "scaleResolutionDownBy": 1, "maxBitRate": 1200000 } ], "1920": [ { "scaleResolutionDownBy": 6, "maxBitRate": 150000 }, { "scaleResolutionDownBy": 3, "maxBitRate": 500000 }, { "scaleResolutionDownBy": 1, "maxBitRate": 3500000 } ], "3840": [ { "scaleResolutionDownBy": 12, "maxBitRate": 150000 }, { "scaleResolutionDownBy": 6, "maxBitRate": 500000 }, { "scaleResolutionDownBy": 1, "maxBitRate": 10000000 } ]}`` | | adaptiveScalingFactor | The adaptive spatial layer selection scaling factor in the range [0.5, 1.0]. | | ``0.75`` | +| localRecordingEnabled | If set to true Local Recording feature will be enabled. | `"boolean"` | ``false`` | | audioOutputSupportedBrowsers | White listing browsers that support audio output device selection. | `"array"` | ``[ "chrome", "opera"]`` | | requestTimeout | The Socket.io request timeout. | `"nat"` | ``20000`` | | requestRetries | The Socket.io request maximum retries. | `"nat"` | ``3`` | diff --git a/app/public/config/config.example.js b/app/public/config/config.example.js index d2d5e7c6..8cfe7c86 100644 --- a/app/public/config/config.example.js +++ b/app/public/config/config.example.js @@ -143,6 +143,9 @@ var config = { // The adaptive spatial layer selection scaling factor in the range [0.5, 1.0]. adaptiveScalingFactor : 0.75, + // If set to true Local Recording feature will be enabled. + localRecordingEnabled : false, + // White listing browsers that support audio output device selection. audioOutputSupportedBrowsers : [ 'chrome', diff --git a/server/README.md b/server/README.md index e464be4b..19c48556 100644 --- a/server/README.md +++ b/server/README.md @@ -54,13 +54,13 @@ Look at the default `config/config.example.js` file for documentation. | routerScaleSize | Room size before spreading to a new router. | `"nat"` | ``40`` | | requestTimeout | Socket timeout value (ms). | `"nat"` | ``20000`` | | requestRetries | Socket retries when a timeout occurs. | `"nat"` | ``3`` | -| mediasoup.numWorkers | The number of Mediasoup workers to spawn. Defaults to the available CPUs count. | `"nat"` | ``4`` | +| mediasoup.numWorkers | The number of Mediasoup workers to spawn. Defaults to the available CPUs count. | `"nat"` | ``6`` | | mediasoup.worker.logLevel | The Mediasoup log level. | `"string"` | ``"warn"`` | | mediasoup.worker.logTags | The Mediasoup log tags. | `"array"` | ``[ "info", "ice", "dtls", "rtp", "srtp", "rtcp"]`` | | mediasoup.worker.rtcMinPort | The Mediasoup start listening port number. | `"port"` | ``40000`` | | mediasoup.worker.rtcMaxPort | The Mediasoup end listening port number. | `"port"` | ``49999`` | | mediasoup.router.mediaCodecs | The Mediasoup codecs settings. [supportedRtpCapabilities](https://github.com/versatica/mediasoup/blob/v3/src/supportedRtpCapabilities.ts) | `"*"` | ``[ { "kind": "audio", "mimeType": "audio/opus", "clockRate": 48000, "channels": 2 }, { "kind": "video", "mimeType": "video/VP8", "clockRate": 90000, "parameters": { "x-google-start-bitrate": 1000 } }, { "kind": "video", "mimeType": "video/VP9", "clockRate": 90000, "parameters": { "profile-id": 2, "x-google-start-bitrate": 1000 } }, { "kind": "video", "mimeType": "video/h264", "clockRate": 90000, "parameters": { "packetization-mode": 1, "profile-level-id": "4d0032", "level-asymmetry-allowed": 1, "x-google-start-bitrate": 1000 } }, { "kind": "video", "mimeType": "video/h264", "clockRate": 90000, "parameters": { "packetization-mode": 1, "profile-level-id": "42e01f", "level-asymmetry-allowed": 1, "x-google-start-bitrate": 1000 } }]`` | -| mediasoup.webRtcTransport.listenIps | The Mediasoup listen IPs. [TransportListenIp](https://mediasoup.org/documentation/v3/mediasoup/api/#TransportListenIp) | `"array"` | ``[ { "ip": "192.168.246.104", "announcedIp": null }]`` | +| mediasoup.webRtcTransport.listenIps | The Mediasoup listen IPs. [TransportListenIp](https://mediasoup.org/documentation/v3/mediasoup/api/#TransportListenIp) | `"array"` | ``[ { "ip": "10.0.0.1", "announcedIp": null }, { "ip": "db19:25c4:5f01:9683:cc5a:bcac:fd6e:b38d", "announcedIp": null }]`` | | mediasoup.webRtcTransport.initialAvailableOutgoingBitrate | The Mediasoup initial available outgoing bitrate (in bps). [WebRtcTransportOptions](https://mediasoup.org/documentation/v3/mediasoup/api/#WebRtcTransportOptions) | `"nat"` | ``1000000`` | | mediasoup.webRtcTransport.maxIncomingBitrate | The Mediasoup maximum incoming bitrate for each transport. (in bps). [setMaxIncomingBitrate](https://mediasoup.org/documentation/v3/mediasoup/api/#transport-setMaxIncomingBitrate) | `"nat"` | ``1500000`` | | prometheus.enabled | Enables the Prometheus metrics exporter. | `"boolean"` | ``false`` | From 79d31b23f1e2fd57cc9c985fd68ceed2fa6d3cf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mai=20G=C3=A1bor?= Date: Fri, 28 Jan 2022 13:43:58 +0000 Subject: [PATCH 3/3] Add translations for localrecording --- app/src/translations/cn.json | 2 ++ app/src/translations/cs.json | 2 ++ app/src/translations/de.json | 2 ++ app/src/translations/dk.json | 2 ++ app/src/translations/el.json | 2 ++ app/src/translations/en.json | 5 +++-- app/src/translations/es.json | 2 ++ app/src/translations/fr.json | 2 ++ app/src/translations/hi.json | 2 ++ app/src/translations/hr.json | 2 ++ app/src/translations/hu.json | 2 ++ app/src/translations/it.json | 2 ++ app/src/translations/kk.json | 2 ++ app/src/translations/lv.json | 2 ++ app/src/translations/nb.json | 2 ++ app/src/translations/pl.json | 2 ++ app/src/translations/pt.json | 2 ++ app/src/translations/ro.json | 2 ++ app/src/translations/ru.json | 2 ++ app/src/translations/tr.json | 2 ++ app/src/translations/tw.json | 2 ++ app/src/translations/uk.json | 2 ++ 22 files changed, 45 insertions(+), 2 deletions(-) diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index b5269495..0aa42952 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -75,6 +75,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index b4491d04..d1cdc62a 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/de.json b/app/src/translations/de.json index 034f701b..a071da6e 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index 49ec6c73..2d932761 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/el.json b/app/src/translations/el.json index e2174bc3..341d26dc 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 6cff495a..654c4fdf 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -75,11 +75,12 @@ "room.localRecordingPaused": "{displayName} paused local recording", "room.localRecordingResumed": "{displayName} resumed local recording", "room.localRecordingStopped": "{displayName} stopped local recording", - "room.recordingConsent": "When attending this meeting you agree and give your consent that the meeting will be audio and video recorded and/or live broadcasted through web streaming.", "room.localRecordingSecurityError": "Recording the specified source is not allowed due to security restrictions. Check you client settings!", "room.leavingTheRoom": "Leaving the room", "room.leaveConfirmationMessage": "Do you want to leave the room?", - + "room.recordingConsent": "When attending this meeting you agree and give your consent that the meeting will be audio and video recorded and/or live broadcasted through web streaming.", + "room.recordingConsentAccept" : "I Accept", + "room.recordingConsentDeny" : "Deny", "me.mutedPTT": "You are muted{br}hold down SPACE-BAR to talk", "roles.gotRole": "You got the role: {role}", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index 96f55338..edb30311 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index 7aec6a20..487de51e 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/hi.json b/app/src/translations/hi.json index 3ddf182a..b4240313 100644 --- a/app/src/translations/hi.json +++ b/app/src/translations/hi.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index b0ba9368..4d77432f 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index e562d5f7..074dc013 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": "{displayName} visszaindította a helyi rögzítést", "room.localRecordingStopped": "{displayName} leállította a helyi rögzítést", "room.recordingConsent": "A konferencián való részvételeddel beleegyezésed adod hogy a konferenciáról hang és videó felvétel, és/vagy élő közvetítés készüljön!", + "room.recordingConsentAccept" : "Elfogadom", + "room.recordingConsentDeny" : "Megtagadom", "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 94212eb7..c24be57f 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/kk.json b/app/src/translations/kk.json index 41103aa8..6c1ba589 100644 --- a/app/src/translations/kk.json +++ b/app/src/translations/kk.json @@ -74,6 +74,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/lv.json b/app/src/translations/lv.json index 5bb754ab..acb619db 100644 --- a/app/src/translations/lv.json +++ b/app/src/translations/lv.json @@ -75,6 +75,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 5157ae6d..a673382a 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 79e5facf..22dbb996 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": "Opuszczanie pokoju", "room.leaveConfirmationMessage": "Czy na pewno chcesz opuścić pokój?", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index 047b3a3b..9ba7996e 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index 0311577b..e5beccd1 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/ru.json b/app/src/translations/ru.json index 922dfdd9..e08d2e8a 100644 --- a/app/src/translations/ru.json +++ b/app/src/translations/ru.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json index ae292208..3f8c5540 100644 --- a/app/src/translations/tr.json +++ b/app/src/translations/tr.json @@ -74,6 +74,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.hideSelfView": "Kendi görüntümü gizle", "room.showSelfView": "Kendi görüntümü göster", diff --git a/app/src/translations/tw.json b/app/src/translations/tw.json index 781710b8..205d886e 100644 --- a/app/src/translations/tw.json +++ b/app/src/translations/tw.json @@ -75,6 +75,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null, diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index c7c68b51..833d9179 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -76,6 +76,8 @@ "room.localRecordingResumed": null, "room.localRecordingStopped": null, "room.recordingConsent": null, + "room.recordingConsentAccept" : null, + "room.recordingConsentDeny" : null, "room.localRecordingSecurityError": null, "room.leavingTheRoom": null, "room.leaveConfirmationMessage": null,