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/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) */}