Merge branch 'feat-audio-settings' into develop
commit
57bb55764f
|
|
@ -42,6 +42,17 @@ var config =
|
|||
{
|
||||
tcp : true
|
||||
},
|
||||
defaultAudio :
|
||||
{
|
||||
sampleRate : 48000,
|
||||
channelCount : 1,
|
||||
volume : 1.0,
|
||||
autoGainControl : true,
|
||||
echoCancellation : true,
|
||||
noiseSuppression : true,
|
||||
sampleSize : 16
|
||||
},
|
||||
background : 'images/background.jpg',
|
||||
defaultLayout : 'democratic', // democratic, filmstrip
|
||||
lastN : 4,
|
||||
mobileLastN : 1,
|
||||
|
|
@ -49,7 +60,6 @@ var config =
|
|||
maxLastN : 5,
|
||||
// If truthy, users can NOT change number of speakers visible
|
||||
lockLastN : false,
|
||||
background : 'images/background.jpg',
|
||||
// Add file and uncomment for adding logo to appbar
|
||||
// logo : 'images/logo.svg',
|
||||
title : 'Multiparty meeting',
|
||||
|
|
|
|||
|
|
@ -953,22 +953,60 @@ export default class RoomClient
|
|||
}
|
||||
}
|
||||
|
||||
async getAudioTrack()
|
||||
{
|
||||
await navigator.mediaDevices.getUserMedia(
|
||||
{
|
||||
audio : true, video : false
|
||||
});
|
||||
disconnectLocalHark() {
|
||||
logger.debug('disconnectLocalHark() | Stopping harkStream.');
|
||||
if (this._harkStream != null) {
|
||||
this._harkStream.getAudioTracks()[0].stop();
|
||||
this._harkStream = null;
|
||||
}
|
||||
|
||||
if (this._hark != null) {
|
||||
logger.debug('disconnectLocalHark() Stopping hark.');
|
||||
this._hark.stop();
|
||||
}
|
||||
}
|
||||
|
||||
async getVideoTrack()
|
||||
{
|
||||
await navigator.mediaDevices.getUserMedia(
|
||||
{
|
||||
audio : false, video : true
|
||||
});
|
||||
connectLocalHark(track) {
|
||||
logger.debug('connectLocalHark() | Track:%o', track);
|
||||
this._harkStream = new MediaStream();
|
||||
|
||||
this._harkStream.addTrack(track.clone());
|
||||
this._harkStream.getAudioTracks()[0].enabled = true;
|
||||
|
||||
if (!this._harkStream.getAudioTracks()[0])
|
||||
throw new Error('getMicStream():something went wrong with hark');
|
||||
|
||||
this._hark = hark(this._harkStream, { play: false });
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
this._hark.on('volume_change', (dBs, threshold) => {
|
||||
// The exact formula to convert from dBs (-100..0) to linear (0..1) is:
|
||||
// Math.pow(10, dBs / 20)
|
||||
// However it does not produce a visually useful output, so let exagerate
|
||||
// it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to
|
||||
// minimize component renderings.
|
||||
let volume = Math.round(Math.pow(10, dBs / 85) * 10);
|
||||
|
||||
if (volume === 1)
|
||||
volume = 0;
|
||||
|
||||
volume = Math.round(volume);
|
||||
|
||||
if (this._micProducer && volume !== this._micProducer.volume) {
|
||||
this._micProducer.volume = volume;
|
||||
|
||||
store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume));
|
||||
}
|
||||
});
|
||||
this._hark.on('speaking', function () {
|
||||
store.dispatch(meActions.setIsSpeaking(true));
|
||||
});
|
||||
this._hark.on('stopped_speaking', function () {
|
||||
store.dispatch(meActions.setIsSpeaking(false));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async changeAudioDevice(deviceId)
|
||||
{
|
||||
logger.debug('changeAudioDevice() [deviceId: %s]', deviceId);
|
||||
|
|
@ -987,29 +1025,30 @@ export default class RoomClient
|
|||
'changeAudioDevice() | new selected webcam [device:%o]',
|
||||
device);
|
||||
|
||||
if (this._hark != null)
|
||||
this._hark.stop();
|
||||
|
||||
if (this._harkStream != null)
|
||||
{
|
||||
logger.debug('Stopping hark.');
|
||||
this._harkStream.getAudioTracks()[0].stop();
|
||||
this._harkStream = null;
|
||||
}
|
||||
this.disconnectLocalHark();
|
||||
|
||||
if (this._micProducer && this._micProducer.track)
|
||||
this._micProducer.track.stop();
|
||||
|
||||
logger.debug('changeAudioDevice() | calling getUserMedia()');
|
||||
logger.debug('changeAudioDevice() | calling getUserMedia() %o', store.getState().settings);
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia(
|
||||
{
|
||||
audio :
|
||||
{
|
||||
deviceId : { exact: device.deviceId }
|
||||
deviceId : { ideal: device.deviceId },
|
||||
sampleRate : store.getState().settings.sampleRate,
|
||||
channelCount : store.getState().settings.channelCount,
|
||||
volume : store.getState().settings.volume,
|
||||
autoGainControl : store.getState().settings.autoGainControl,
|
||||
echoCancellation : store.getState().settings.echoCancellation,
|
||||
noiseSuppression : store.getState().settings.noiseSuppression,
|
||||
sampleSize : store.getState().settings.sampleSize
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
logger.debug('Constraints: %o', stream.getAudioTracks()[0].getConstraints());
|
||||
const track = stream.getAudioTracks()[0];
|
||||
|
||||
if (this._micProducer)
|
||||
|
|
@ -1017,47 +1056,8 @@ export default class RoomClient
|
|||
|
||||
if (this._micProducer)
|
||||
this._micProducer.volume = 0;
|
||||
this.connectLocalHark(track);
|
||||
|
||||
this._harkStream = new MediaStream();
|
||||
|
||||
this._harkStream.addTrack(track.clone());
|
||||
this._harkStream.getAudioTracks()[0].enabled = true;
|
||||
|
||||
if (!this._harkStream.getAudioTracks()[0])
|
||||
throw new Error('changeAudioDevice(): given stream has no audio track');
|
||||
|
||||
this._hark = hark(this._harkStream, { play: false });
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
this._hark.on('volume_change', (dBs, threshold) =>
|
||||
{
|
||||
// The exact formula to convert from dBs (-100..0) to linear (0..1) is:
|
||||
// Math.pow(10, dBs / 20)
|
||||
// However it does not produce a visually useful output, so let exaggerate
|
||||
// it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to
|
||||
// minimize component renderings.
|
||||
let volume = Math.round(Math.pow(10, dBs / 85) * 10);
|
||||
|
||||
if (volume === 1)
|
||||
volume = 0;
|
||||
|
||||
volume = Math.round(volume);
|
||||
|
||||
if (this._micProducer && volume !== this._micProducer.volume)
|
||||
{
|
||||
this._micProducer.volume = volume;
|
||||
|
||||
store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume));
|
||||
}
|
||||
});
|
||||
this._hark.on('speaking', function()
|
||||
{
|
||||
store.dispatch(meActions.setIsSpeaking(true));
|
||||
});
|
||||
this._hark.on('stopped_speaking', function()
|
||||
{
|
||||
store.dispatch(meActions.setIsSpeaking(false));
|
||||
});
|
||||
if (this._micProducer && this._micProducer.id)
|
||||
store.dispatch(
|
||||
producerActions.setProducerTrack(this._micProducer.id, track));
|
||||
|
|
@ -1106,6 +1106,37 @@ export default class RoomClient
|
|||
meActions.setAudioOutputInProgress(false));
|
||||
}
|
||||
|
||||
async changeAudioOutputDevice(deviceId)
|
||||
{
|
||||
logger.debug('changeAudioOutputDevice() [deviceId: %s]', deviceId);
|
||||
|
||||
store.dispatch(
|
||||
meActions.setAudioOutputInProgress(true));
|
||||
|
||||
try
|
||||
{
|
||||
const device = this._audioOutputDevices[deviceId];
|
||||
|
||||
if (!device)
|
||||
throw new Error('Selected audio output device no longer avaibale');
|
||||
|
||||
logger.debug(
|
||||
'changeAudioOutputDevice() | new selected [audio output device:%o]',
|
||||
device);
|
||||
|
||||
store.dispatch(settingsActions.setSelectedAudioOutputDevice(deviceId));
|
||||
|
||||
await this._updateAudioOutputDevices();
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('changeAudioOutputDevice() failed: %o', error);
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
meActions.setAudioOutputInProgress(false));
|
||||
}
|
||||
|
||||
async changeVideoResolution(resolution)
|
||||
{
|
||||
logger.debug('changeVideoResolution() [resolution: %s]', resolution);
|
||||
|
|
@ -1795,6 +1826,49 @@ export default class RoomClient
|
|||
this._recvTransport = null;
|
||||
}
|
||||
|
||||
store.dispatch(roomActions.setRoomState('connecting'));
|
||||
});
|
||||
|
||||
store.dispatch(
|
||||
producerActions.removeProducer(this._screenSharingProducer.id));
|
||||
|
||||
this._screenSharingProducer = null;
|
||||
}
|
||||
|
||||
if (this._webcamProducer)
|
||||
{
|
||||
this._webcamProducer.close();
|
||||
|
||||
store.dispatch(
|
||||
producerActions.removeProducer(this._webcamProducer.id));
|
||||
|
||||
this._webcamProducer = null;
|
||||
}
|
||||
|
||||
if (this._micProducer)
|
||||
{
|
||||
this._micProducer.close();
|
||||
|
||||
store.dispatch(
|
||||
producerActions.removeProducer(this._micProducer.id));
|
||||
|
||||
this._micProducer = null;
|
||||
}
|
||||
|
||||
if (this._sendTransport)
|
||||
{
|
||||
this._sendTransport.close();
|
||||
|
||||
this._sendTransport = null;
|
||||
}
|
||||
|
||||
if (this._recvTransport)
|
||||
{
|
||||
this._recvTransport.close();
|
||||
|
||||
this._recvTransport = null;
|
||||
}
|
||||
|
||||
store.dispatch(roomActions.setRoomState('connecting'));
|
||||
});
|
||||
|
||||
|
|
@ -3262,11 +3336,20 @@ export default class RoomClient
|
|||
const stream = await navigator.mediaDevices.getUserMedia(
|
||||
{
|
||||
audio : {
|
||||
deviceId : { ideal: deviceId }
|
||||
deviceId : { ideal: device.deviceId },
|
||||
sampleRate : store.getState().settings.sampleRate,
|
||||
channelCount : store.getState().settings.channelCount,
|
||||
volume : store.getState().settings.volume,
|
||||
autoGainControl : store.getState().settings.autoGainControl,
|
||||
echoCancellation : store.getState().settings.echoCancellation,
|
||||
noiseSuppression : store.getState().settings.noiseSuppression,
|
||||
sampleSize : store.getState().settings.sampleSize
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
logger.debug('Constraints: %o', stream.getAudioTracks()[0].getConstraints());
|
||||
|
||||
track = stream.getAudioTracks()[0];
|
||||
|
||||
this._micProducer = await this._sendTransport.produce(
|
||||
|
|
@ -3320,51 +3403,8 @@ export default class RoomClient
|
|||
|
||||
this._micProducer.volume = 0;
|
||||
|
||||
if (this._hark != null)
|
||||
this._hark.stop();
|
||||
this.connectLocalHark(track);
|
||||
|
||||
if (this._harkStream != null)
|
||||
this._harkStream.getAudioTracks()[0].stop();
|
||||
|
||||
this._harkStream = new MediaStream();
|
||||
|
||||
this._harkStream.addTrack(track.clone());
|
||||
|
||||
if (!this._harkStream.getAudioTracks()[0])
|
||||
throw new Error('enableMic(): given stream has no audio track');
|
||||
|
||||
this._hark = hark(this._harkStream, { play: false });
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
this._hark.on('volume_change', (dBs, threshold) =>
|
||||
{
|
||||
// The exact formula to convert from dBs (-100..0) to linear (0..1) is:
|
||||
// Math.pow(10, dBs / 20)
|
||||
// However it does not produce a visually useful output, so let exaggerate
|
||||
// it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to
|
||||
// minimize component renderings.
|
||||
let volume = Math.round(Math.pow(10, dBs / 85) * 10);
|
||||
|
||||
if (volume === 1)
|
||||
volume = 0;
|
||||
|
||||
volume = Math.round(volume);
|
||||
|
||||
if (this._micProducer && volume !== this._micProducer.volume)
|
||||
{
|
||||
this._micProducer.volume = volume;
|
||||
|
||||
store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume));
|
||||
}
|
||||
});
|
||||
this._hark.on('speaking', function()
|
||||
{
|
||||
store.dispatch(meActions.setIsSpeaking(true));
|
||||
});
|
||||
this._hark.on('stopped_speaking', function()
|
||||
{
|
||||
store.dispatch(meActions.setIsSpeaking(false));
|
||||
});
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -38,6 +38,45 @@ export const togglePermanentTopBar = () =>
|
|||
type : 'TOGGLE_PERMANENT_TOPBAR'
|
||||
});
|
||||
|
||||
export const setEchoCancellation = (echoCancellation) =>
|
||||
({
|
||||
type : 'SET_ECHO_CANCELLATION',
|
||||
payload : { echoCancellation }
|
||||
});
|
||||
|
||||
export const setAutoGainControl = (autoGainControl) =>
|
||||
({
|
||||
type : 'SET_AUTO_GAIN_CONTROL',
|
||||
payload : { autoGainControl }
|
||||
});
|
||||
|
||||
export const setNoiseSuppression = (noiseSuppression) =>
|
||||
({
|
||||
type : 'SET_NOISE_SUPPRESSION',
|
||||
payload : { noiseSuppression }
|
||||
});
|
||||
|
||||
export const setDefaultAudio = (audio) =>
|
||||
({
|
||||
type : 'SET_DEFAULT_AUDIO',
|
||||
payload : { audio }
|
||||
});
|
||||
|
||||
export const toggleEchoCancellation = () =>
|
||||
({
|
||||
type : 'TOGGLE_ECHO_CANCELLATION'
|
||||
});
|
||||
|
||||
export const toggleAutoGainControl = () =>
|
||||
({
|
||||
type : 'TOGGLE_AUTO_GAIN_CONTROL'
|
||||
});
|
||||
|
||||
export const toggleNoiseSuppression = () =>
|
||||
({
|
||||
type : 'TOGGLE_NOISE_SUPPRESSION'
|
||||
});
|
||||
|
||||
export const toggleHiddenControls = () =>
|
||||
({
|
||||
type : 'TOGGLE_HIDDEN_CONTROLS'
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ const peersKeySelector = createSelector(
|
|||
peersSelector,
|
||||
(peers) => Object.keys(peers)
|
||||
);
|
||||
const peersValueSelector = createSelector(
|
||||
peersSelector,
|
||||
(peers) => Object.values(peers)
|
||||
);
|
||||
|
||||
export const peersValueSelector = createSelector(
|
||||
peersSelector,
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@ import { connect } from 'react-redux';
|
|||
import * as appPropTypes from '../appPropTypes';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../RoomContext';
|
||||
import * as settingsActions from '../../actions/settingsActions';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
|
|
@ -23,6 +26,9 @@ const styles = (theme) =>
|
|||
});
|
||||
|
||||
const MediaSettings = ({
|
||||
setEchoCancellation,
|
||||
setAutoGainControl,
|
||||
setNoiseSuppression,
|
||||
roomClient,
|
||||
me,
|
||||
settings,
|
||||
|
|
@ -247,6 +253,48 @@ const MediaSettings = ({
|
|||
/>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControlLabel
|
||||
className={classes.setting}
|
||||
control={
|
||||
<Checkbox checked={settings.echoCancellation} onChange={
|
||||
(event) => {
|
||||
setEchoCancellation(event.target.checked);
|
||||
roomClient.changeAudioDevice(settings.selectedAudioDevice);
|
||||
}}
|
||||
/>}
|
||||
label={intl.formatMessage({
|
||||
id : 'settings.echoCancellation',
|
||||
defaultMessage : 'Echo Cancellation'
|
||||
})}
|
||||
/>
|
||||
<FormControlLabel
|
||||
className={classes.setting}
|
||||
control={
|
||||
<Checkbox checked={settings.autoGainControl} onChange={
|
||||
(event) => {
|
||||
setAutoGainControl(event.target.checked);
|
||||
roomClient.changeAudioDevice(settings.selectedAudioDevice);
|
||||
}}
|
||||
/>}
|
||||
label={intl.formatMessage({
|
||||
id : 'settings.autoGainControl',
|
||||
defaultMessage : 'Auto Gain Control'
|
||||
})}
|
||||
/>
|
||||
<FormControlLabel
|
||||
className={classes.setting}
|
||||
control={
|
||||
<Checkbox checked={settings.noiseSuppression} onChange={
|
||||
(event) => {
|
||||
setNoiseSuppression(event.target.checked);
|
||||
roomClient.changeAudioDevice(settings.selectedAudioDevice);
|
||||
}}
|
||||
/>}
|
||||
label={intl.formatMessage({
|
||||
id : 'settings.noiseSuppression',
|
||||
defaultMessage : 'Noise Suppression'
|
||||
})}
|
||||
/>
|
||||
</form>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
@ -254,10 +302,13 @@ const MediaSettings = ({
|
|||
|
||||
MediaSettings.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
settings : PropTypes.object.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
setEchoCancellation : PropTypes.func.isRequired,
|
||||
setAutoGainControl : PropTypes.func.isRequired,
|
||||
setNoiseSuppression : PropTypes.func.isRequired,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
settings : PropTypes.object.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
|
|
@ -268,9 +319,15 @@ const mapStateToProps = (state) =>
|
|||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setEchoCancellation : settingsActions.setEchoCancellation,
|
||||
setAutoGainControl : settingsActions.toggleAutoGainControl,
|
||||
setNoiseSuppression : settingsActions.toggleNoiseSuppression
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,20 @@ const initialState =
|
|||
selectedWebcam : null,
|
||||
selectedAudioDevice : null,
|
||||
advancedMode : false,
|
||||
sampleRate : 48000,
|
||||
channelCount : 1,
|
||||
volume : 1.0,
|
||||
autoGainControl : true,
|
||||
echoCancellation : true,
|
||||
noiseSuppression : true,
|
||||
sampleSize : 16,
|
||||
// low, medium, high, veryhigh, ultra
|
||||
resolution : window.config.defaultResolution || 'medium',
|
||||
lastN : 4,
|
||||
permanentTopBar : true,
|
||||
hiddenControls : false,
|
||||
notificationSounds : true
|
||||
notificationSounds : true,
|
||||
...window.config.defaultAudio
|
||||
};
|
||||
|
||||
const settings = (state = initialState, action) =>
|
||||
|
|
@ -45,6 +53,83 @@ const settings = (state = initialState, action) =>
|
|||
return { ...state, advancedMode };
|
||||
}
|
||||
|
||||
case 'SET_SAMPLE_RATE':
|
||||
{
|
||||
const { sampleRate } = action.payload;
|
||||
|
||||
return { ...state, sampleRate };
|
||||
}
|
||||
|
||||
case 'SET_CHANNEL_COUNT':
|
||||
{
|
||||
const { channelCount } = action.payload;
|
||||
|
||||
return { ...state, channelCount };
|
||||
}
|
||||
|
||||
case 'SET_VOLUME':
|
||||
{
|
||||
const { volume } = action.payload;
|
||||
|
||||
return { ...state, volume };
|
||||
}
|
||||
|
||||
case 'SET_AUTO_GAIN_CONTROL':
|
||||
{
|
||||
const { autoGainControl } = action.payload;
|
||||
|
||||
return { ...state, autoGainControl };
|
||||
}
|
||||
|
||||
case 'SET_ECHO_CANCELLATION':
|
||||
{
|
||||
const { echoCancellation } = action.payload;
|
||||
|
||||
return { ...state, echoCancellation };
|
||||
}
|
||||
|
||||
case 'SET_NOISE_SUPPRESSION':
|
||||
{
|
||||
const { noiseSuppression } = action.payload;
|
||||
|
||||
return { ...state, noiseSuppression };
|
||||
}
|
||||
|
||||
case 'SET_DEFAULT_AUDIO':
|
||||
{
|
||||
const { audio } = action.payload;
|
||||
|
||||
return { ...state, audio };
|
||||
}
|
||||
|
||||
case 'TOGGLE_AUTO_GAIN_CONTROL':
|
||||
{
|
||||
const autoGainControl = !state.autoGainControl;
|
||||
|
||||
return { ...state, autoGainControl };
|
||||
}
|
||||
|
||||
case 'TOGGLE_ECHO_CANCELLATION':
|
||||
{
|
||||
const echoCancellation = !state.echoCancellation;
|
||||
|
||||
return { ...state, echoCancellation };
|
||||
}
|
||||
|
||||
case 'TOGGLE_NOISE_SUPPRESSION':
|
||||
{
|
||||
const noiseSuppression = !state.noiseSuppression;
|
||||
|
||||
return { ...state, noiseSuppression };
|
||||
}
|
||||
|
||||
case 'SET_SAMPLE_SIZE':
|
||||
{
|
||||
const { sampleSize } = action.payload;
|
||||
|
||||
return { ...state, sampleSize };
|
||||
}
|
||||
|
||||
case 'SET_LAST_N':
|
||||
{
|
||||
const { lastN } = action.payload;
|
||||
|
|
|
|||
Loading…
Reference in New Issue