Merge pull request #945 from edumeet/feat-localrecording-disclaimer-v2

Localrecording Disclaimer
This commit is contained in:
Stefan Otto 2022-01-31 22:42:28 +01:00 committed by GitHub
commit 22b18ff3b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 529 additions and 167 deletions

View file

@ -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`` |

View file

@ -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',

View file

@ -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
}
}
}
}
export const recorder = new BrowserRecorder();

View file

@ -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()');

View file

@ -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 }
});

View file

@ -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 }
});

View file

@ -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();
export const setLocalRecordingState = (status) =>
({
type : 'SET_LOCAL_RECORDING_STATE',
payload : { status }
});
export const setLocalRecordingConsent = (agreed) =>
({
type : 'SET_LOCAL_RECORDING_CONSENT',
payload : { agreed }
});

View file

@ -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) */}
<VideoView
localRecordingState={localRecordingState}
recordingConsents={recordingConsents}
isMe
isMirrored={settings.mirrorOwnVideo}
VideoView
@ -938,6 +943,8 @@ const Me = (props) =>
</div>
<VideoView
localRecordingState={localRecordingState}
recordingConsents={recordingConsents}
isMe
isMirrored={settings.mirrorOwnVideo}
isExtraVideo
@ -998,6 +1005,8 @@ const Me = (props) =>
</p>
<VideoView
localRecordingState={localRecordingState}
recordingConsents={recordingConsents}
isMe
isScreen
advancedMode={advancedMode}
@ -1032,7 +1041,9 @@ Me.propTypes =
noiseVolume : PropTypes.number,
classes : PropTypes.object.isRequired,
theme : PropTypes.object.isRequired,
transports : PropTypes.object.isRequired
transports : PropTypes.object.isRequired,
localRecordingState : PropTypes.string,
recordingConsents : PropTypes.array
};
const makeMapStateToProps = () =>
@ -1067,7 +1078,9 @@ const makeMapStateToProps = () =>
hasVideoPermission : canShareVideo(state),
hasScreenPermission : canShareScreen(state),
noiseVolume : noise,
transports : state.transports
transports : state.transports,
localRecordingState : state.recorderReducer.localRecordingState.status,
recordingConsents : recordingConsentsPeersSelector(state)
};
};
@ -1089,7 +1102,10 @@ export default withRoomContext(connect(
prev.peers === next.peers &&
prev.producers === next.producers &&
prev.settings === next.settings &&
prev.transports === next.transports
prev.transports === next.transports &&
prev.recorderReducer.localRecordingState.status ===
next.recorderReducer.localRecordingState.status &&
recordingConsentsPeersSelector(prev)===recordingConsentsPeersSelector(next)
);
}
}

View file

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { makePeerConsumerSelector } from '../Selectors';
import { makePeerConsumerSelector, recordingConsentsPeersSelector } from '../Selectors';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import * as appPropTypes from '../appPropTypes';
@ -156,7 +156,9 @@ const Peer = (props) =>
enableLayersSwitch,
isSelected,
mode,
theme
theme,
localRecordingState,
recordingConsents
} = props;
const micEnabled = (
@ -658,6 +660,8 @@ const Peer = (props) =>
</div>
<VideoView
localRecordingState={localRecordingState}
recordingConsents={recordingConsents}
showQuality
advancedMode={advancedMode}
peer={peer}
@ -832,6 +836,8 @@ const Peer = (props) =>
</div>
<VideoView
localRecordingState={localRecordingState}
recordingConsents={recordingConsents}
showQuality
advancedMode={advancedMode}
peer={peer}
@ -994,6 +1000,8 @@ const Peer = (props) =>
</Tooltip>
</div>
<VideoView
localRecordingState={localRecordingState}
recordingConsents={recordingConsents}
showQuality
advancedMode={advancedMode}
videoContain
@ -1051,7 +1059,9 @@ Peer.propTypes =
theme : PropTypes.object.isRequired,
enableLayersSwitch : PropTypes.bool,
isSelected : PropTypes.bool,
mode : PropTypes.string.isRequired
mode : PropTypes.string.isRequired,
localRecordingState : PropTypes.string,
recordingConsents : PropTypes.array
};
const makeMapStateToProps = (initialState, { id }) =>
@ -1061,14 +1071,16 @@ const makeMapStateToProps = (initialState, { id }) =>
const mapStateToProps = (state) =>
{
return {
peer : state.peers[id],
peer : state.peers[id],
...getPeerConsumers(state, id),
windowConsumer : state.room.windowConsumer,
fullScreenConsumer : state.room.fullScreenConsumer,
activeSpeaker : id === state.room.activeSpeakerId,
browser : state.me.browser,
isSelected : state.room.selectedPeers.includes(id),
mode : state.room.mode
windowConsumer : state.room.windowConsumer,
fullScreenConsumer : state.room.fullScreenConsumer,
activeSpeaker : id === state.room.activeSpeakerId,
browser : state.me.browser,
isSelected : state.room.selectedPeers.includes(id),
mode : state.room.mode,
localRecordingState : state.recorderReducer.localRecordingState.status,
recordingConsents : recordingConsentsPeersSelector(state)
};
};
@ -1107,7 +1119,10 @@ export default withRoomContext(connect(
prev.room.mode === next.room.mode &&
prev.room.selectedPeers === next.room.selectedPeers &&
prev.me.browser === next.me.browser &&
prev.enableLayersSwitch === next.enableLayersSwitch
prev.enableLayersSwitch === next.enableLayersSwitch &&
prev.recorderReducer.localRecordingState.status ===
next.recorderReducer.localRecordingState.status &&
recordingConsentsPeersSelector(prev)===recordingConsentsPeersSelector(next)
);
}
}

View file

@ -6,7 +6,9 @@ import {
peersLengthSelector,
raisedHandsSelector,
makePermissionSelector,
recordingInProgressSelector
recordingInProgressSelector,
recordingInProgressPeersSelector,
recordingConsentsPeersSelector
} from '../Selectors';
import { permissions } from '../../permissions';
import * as appPropTypes from '../appPropTypes';
@ -48,11 +50,8 @@ import PauseCircleOutlineIcon from '@material-ui/icons/PauseCircleOutline';
import PauseCircleFilledIcon from '@material-ui/icons/PauseCircleFilled';
import StopIcon from '@material-ui/icons/Stop';
import randomString from 'random-string';
import { recorder, RECORDING_START, RECORDING_PAUSE, RECORDING_RESUME } from '../../actions/recorderActions';
import * as meActions from '../../actions/meActions';
import { store } from '../../store';
// import producers from '../../reducers/producers';
// import logger from 'redux-logger';
import { recorder } from './../../BrowserRecorder';
import Logger from '../../Logger';
import { config } from '../../config';
@ -215,8 +214,8 @@ const TopBar = (props) =>
const [ mobileMoreAnchorEl, setMobileMoreAnchorEl ] = useState(null);
const [ anchorEl, setAnchorEl ] = useState(null);
const [ currentMenu, setCurrentMenu ] = useState(null);
const [ recordingConsentNotificationId,
setRecordingConsentNotificationId ] = useState(null);
const [ recordingNotificationsId,
setRecordingNotificationsId ] = useState(null);
const handleExited = () =>
{
@ -255,6 +254,7 @@ const TopBar = (props) =>
drawerOverlayed,
toolAreaOpen,
isSafari,
meId,
isMobile,
loggedIn,
loginEnabled,
@ -275,29 +275,48 @@ const TopBar = (props) =>
unread,
canProduceExtraVideo,
canLock,
canRecord,
canPromote,
classes,
locale,
localesList,
localRecordingState,
recordingInProgress,
recordingPeers,
recordingMimeType,
producers,
consumers
consumers,
recordingConsents
} = props;
// did it change?
recorder.checkMicProducer(producers);
recorder.checkAudioConsumer(consumers);
recorder.checkAudioConsumer(consumers, recordingConsents);
useEffect(() =>
{
// someone else is recording (need consent) or only me(dont need consent notif)
const hasConsent = (
(
(
localRecordingState === undefined ||
localRecordingState.consent!=='init'
)
|| (
recordingPeers.includes(meId) && recordingPeers.length === 1
)
)
);
if (
recordingInProgress &&
!recordingConsentNotificationId)
!recordingNotificationsId &&
!hasConsent
)
{
const notificationId = randomString({ length: 6 }).toLowerCase();
setRecordingConsentNotificationId(notificationId);
setRecordingNotificationsId(notificationId);
addNotification(
{
id : notificationId,
@ -309,21 +328,24 @@ const TopBar = (props) =>
defaultMessage : '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'
}
),
persist : true
peerid : meId,
roomClient : roomClient,
persist : true
}
);
}
if (
!recordingInProgress
&& recordingConsentNotificationId)
&& recordingNotificationsId)
{
closeNotification(recordingConsentNotificationId);
setRecordingConsentNotificationId(null);
closeNotification(recordingNotificationsId);
setRecordingNotificationsId(null);
}
},
[
localRecordingState, recordingInProgress, recordingConsentNotificationId,
addNotification, closeNotification, intl
localRecordingState, recordingInProgress, recordingNotificationsId,
addNotification, closeNotification, intl, meId, recordingPeers, roomClient,
room
]);
const isMenuOpen = Boolean(anchorEl);
@ -340,8 +362,8 @@ const TopBar = (props) =>
defaultMessage : 'Lock room'
});
const recordingTooltip = (localRecordingState === RECORDING_START ||
localRecordingState === RECORDING_RESUME) ?
const recordingTooltip = (localRecordingState.status === 'start' ||
localRecordingState.status === 'resume') ?
intl.formatMessage({
id : 'tooltip.stopLocalRecording',
defaultMessage : 'Stop local recording'
@ -352,7 +374,7 @@ const TopBar = (props) =>
defaultMessage : 'Start local recording'
});
const recordingPausedTooltip = localRecordingState === RECORDING_PAUSE ?
const recordingPausedTooltip = localRecordingState.status === 'pause' ?
intl.formatMessage({
id : 'tooltip.resumeLocalRecording',
defaultMessage : 'Resume local recording'
@ -676,9 +698,9 @@ const TopBar = (props) =>
<Paper>
{
(
localRecordingState === RECORDING_START ||
localRecordingState === RECORDING_RESUME ||
localRecordingState === RECORDING_PAUSE
localRecordingState.status === 'start' ||
localRecordingState.status === 'resume' ||
localRecordingState.status === 'pause'
)
&&
<MenuItem
@ -686,7 +708,7 @@ const TopBar = (props) =>
onClick={() =>
{
handleMenuClose();
if (localRecordingState === RECORDING_PAUSE)
if (localRecordingState.status === 'pause')
{
recorder.resumeLocalRecording();
}
@ -700,13 +722,13 @@ const TopBar = (props) =>
<Badge
color='primary'
>
{ localRecordingState === RECORDING_PAUSE ?
{ localRecordingState.status === 'pause' ?
<PauseCircleFilledIcon />
:
<PauseCircleOutlineIcon />
}
</Badge>
{ localRecordingState === RECORDING_PAUSE ?
{ localRecordingState.status === 'pause' ?
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.resumeLocalRecording'
@ -724,38 +746,39 @@ const TopBar = (props) =>
</MenuItem>
}
{ isSafari &&
{ config.localRecordingEnabled && isSafari
&& canRecord &&
<MenuItem
aria-label={recordingTooltip}
onClick={async () =>
{
handleMenuClose();
if (localRecordingState === RECORDING_START ||
localRecordingState === RECORDING_PAUSE ||
localRecordingState === RECORDING_RESUME)
if (localRecordingState.status === 'start' ||
localRecordingState.status === 'pause' ||
localRecordingState.status === 'resume')
{
recorder.stopLocalRecording();
recorder.stopLocalRecording(meId);
}
else
{
try
{
const recordingMimeType =
store.getState().settings.recorderPreferredMimeType;
const additionalAudioTracks = [];
const micProducer = Object.values(producers).find((p) => p.source === 'mic');
if (micProducer) additionalAudioTracks.push(micProducer.track);
await recorder.startLocalRecording({
const roomname = room.name;
recorder.startLocalRecording({
roomClient,
additionalAudioTracks,
recordingMimeType
recordingMimeType,
roomname
});
recorder.checkAudioConsumer(consumers);
meActions.setLocalRecordingState(RECORDING_START);
}
catch (err)
{
@ -770,9 +793,9 @@ const TopBar = (props) =>
color='primary'
>
{
(localRecordingState === RECORDING_START ||
localRecordingState === RECORDING_PAUSE ||
localRecordingState === RECORDING_RESUME) ?
(localRecordingState.status === 'start' ||
localRecordingState.status === 'pause' ||
localRecordingState.status === 'resume') ?
<StopIcon />
:
<FiberManualRecordIcon />
@ -780,9 +803,9 @@ const TopBar = (props) =>
</Badge>
{
(localRecordingState === RECORDING_START ||
localRecordingState === RECORDING_PAUSE ||
localRecordingState === RECORDING_RESUME) ?
(localRecordingState.status === 'start' ||
localRecordingState.status === 'pause' ||
localRecordingState.status === 'resume') ?
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.stopLocalRecording'
@ -959,9 +982,9 @@ const TopBar = (props) =>
}
{
(
localRecordingState === RECORDING_PAUSE ||
localRecordingState === RECORDING_RESUME ||
localRecordingState === RECORDING_START
localRecordingState.status === 'pause' ||
localRecordingState.status === 'resume' ||
localRecordingState.status === 'start'
)
&&
<MenuItem
@ -970,7 +993,7 @@ const TopBar = (props) =>
onClick={() =>
{
handleMenuClose();
if (localRecordingState === RECORDING_PAUSE)
if (localRecordingState.status === 'pause')
{
recorder.resumeLocalRecording();
}
@ -984,14 +1007,14 @@ const TopBar = (props) =>
<Badge
color='primary'
>
{ localRecordingState === RECORDING_PAUSE ?
{ localRecordingState.status === 'pause' ?
<PauseCircleFilledIcon />
:
<PauseCircleOutlineIcon />
}
</Badge>
{ localRecordingState === RECORDING_PAUSE ?
{ localRecordingState.status === 'pause' ?
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.resumeLocalRecording'
@ -1225,6 +1248,7 @@ TopBar.propTypes =
roomClient : PropTypes.object.isRequired,
room : appPropTypes.Room.isRequired,
isSafari : PropTypes.bool,
meId : PropTypes.string,
isMobile : PropTypes.bool.isRequired,
peersLength : PropTypes.number,
lobbyPeers : PropTypes.array,
@ -1251,6 +1275,7 @@ TopBar.propTypes =
unread : PropTypes.number.isRequired,
canProduceExtraVideo : PropTypes.bool.isRequired,
canLock : PropTypes.bool.isRequired,
canRecord : PropTypes.bool.isRequired,
canPromote : PropTypes.bool.isRequired,
classes : PropTypes.object.isRequired,
theme : PropTypes.object.isRequired,
@ -1259,9 +1284,11 @@ TopBar.propTypes =
localesList : PropTypes.array.isRequired,
localRecordingState : PropTypes.string,
recordingInProgress : PropTypes.bool,
recordingPeers : PropTypes.array,
recordingMimeType : PropTypes.string,
producers : PropTypes.object,
consumers : PropTypes.object
consumers : PropTypes.object,
recordingConsents : PropTypes.array
};
const makeMapStateToProps = () =>
@ -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)
);
}
}

View file

@ -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) => (
<Fragment>
<Button
variant='contained'
size='small'
color='default'
startIcon={<VerifiedUserIcon />}
onClick={() => { this.props.closeSnackbar(key); }}
>
<FormattedMessage id='room.consentUnderstand' defaultMessage='I understand' />
</Button>
<ButtonGroup>
<Button
variant='contained'
size='small'
color='primary'
startIcon={<VerifiedUserIcon />}
onClick={() =>
{
notification.roomClient.addConsentForRecording(
'agreed'
);
this.props.closeSnackbar(key);
}}
>
<FormattedMessage id='room.recordingConsentAccept' defaultMessage='I Accept' />
</Button>
<Button
variant='contained'
size='small'
color='secondary'
startIcon={<Lock />}
onClick={() =>
{
notification.roomClient.addConsentForRecording(
'denied'
);
this.props.closeSnackbar(key);
}}
>
<FormattedMessage id='room.recordingConsentDeny' defaultMessage='Deny' />
</Button>
</ButtonGroup>
</Fragment>
);
@ -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 : {

View file

@ -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;
}
);

View file

@ -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
</React.Fragment>
:
<span className={classes.displayNameStatic}>
{displayName}
{
(
(
localRecordingState==='start' ||
localRecordingState==='resume'
)&&
(
!recordingConsents.includes(peer.id)
)
) ? '':displayName
}
</span>
}
</div>
@ -438,7 +451,18 @@ class VideoView extends React.PureComponent
<video
ref='videoElement'
className={classnames(classes.video, {
hidden : !videoVisible,
hidden : (!videoVisible ||
(
!isMe &&
(
localRecordingState==='start' ||
localRecordingState==='resume'
)&&
(
!recordingConsents.includes(peer.id)
)
)
),
'isMirrored' : isMirrored,
contain : videoContain
})}
@ -637,7 +661,11 @@ VideoView.propTypes =
netInfo : PropTypes.object,
width : PropTypes.number,
height : PropTypes.number,
opusConfig : PropTypes.string
opusConfig : PropTypes.string,
localRecordingState : PropTypes.string,
recordingConsents : PropTypes.array,
peer : PropTypes.string
};
export default withStyles(styles)(VideoView);

View file

@ -202,6 +202,13 @@ This value must match exactly one of the values defined in aspectRatios.`,
default : 0.75
},
localRecordingEnabled :
{
doc : 'If set to true Local Recording feature will be enabled.',
format : 'Boolean',
default : false
},
/**
* White listing browsers that support audio output device selection.
* It is not yet fully implemented in Firefox.

View file

@ -28,7 +28,7 @@ import { SnackbarProvider } from 'notistack';
import * as serviceWorker from './serviceWorker';
import { LazyPreload } from './components/Loader/LazyPreload';
import { detectDevice } from 'mediasoup-client';
import { recorder } from './actions/recorderActions';
import { recorder } from './BrowserRecorder';
import './index.css';

View file

@ -1,26 +1,28 @@
export const permissions = {
// The role(s) have permission to lock/unlock a room
CHANGE_ROOM_LOCK : 'CHANGE_ROOM_LOCK',
CHANGE_ROOM_LOCK : 'CHANGE_ROOM_LOCK',
// The role(s) have permission to promote a peer from the lobby
PROMOTE_PEER : 'PROMOTE_PEER',
PROMOTE_PEER : 'PROMOTE_PEER',
// The role(s) have permission to give/remove other peers roles
MODIFY_ROLE : 'MODIFY_ROLE',
MODIFY_ROLE : 'MODIFY_ROLE',
// The role(s) have permission to send chat messages
SEND_CHAT : 'SEND_CHAT',
SEND_CHAT : 'SEND_CHAT',
// The role(s) have permission to moderate chat
MODERATE_CHAT : 'MODERATE_CHAT',
MODERATE_CHAT : 'MODERATE_CHAT',
// The role(s) have permission to share audio
SHARE_AUDIO : 'SHARE_AUDIO',
SHARE_AUDIO : 'SHARE_AUDIO',
// The role(s) have permission to share video
SHARE_VIDEO : 'SHARE_VIDEO',
SHARE_VIDEO : 'SHARE_VIDEO',
// The role(s) have permission to share screen
SHARE_SCREEN : 'SHARE_SCREEN',
SHARE_SCREEN : 'SHARE_SCREEN',
// The role(s) have permission to produce extra video
EXTRA_VIDEO : 'EXTRA_VIDEO',
EXTRA_VIDEO : 'EXTRA_VIDEO',
// The role(s) have permission to share files
SHARE_FILE : 'SHARE_FILE',
SHARE_FILE : 'SHARE_FILE',
// The role(s) have permission to moderate files
MODERATE_FILES : 'MODERATE_FILES',
MODERATE_FILES : 'MODERATE_FILES',
// The role(s) have permission to moderate room (e.g. kick user)
MODERATE_ROOM : 'MODERATE_ROOM'
MODERATE_ROOM : 'MODERATE_ROOM',
// The role(s) have permission to start room recording localy
LOCAL_RECORD_ROOM : 'LOCAL_RECORD_ROOM'
};

View file

@ -19,8 +19,7 @@ const initialState =
raisedHandInProgress : false,
loggedIn : false,
isSpeaking : false,
isAutoMuted : true,
localRecordingState : null
isAutoMuted : true
};
const me = (state = initialState, action) =>
@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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}",

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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?",

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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",

View file

@ -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,

View file

@ -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,

View file

@ -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`` |

View file

@ -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))