From fcb15e706ddc17e134a1d5337871b6b42a1e4bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 15 Jun 2018 23:19:26 +0200 Subject: [PATCH] Added support for setting audio input device, not working on linux at the moment. Updated webcam selection. --- app/lib/RoomClient.js | 156 +++++++++++++++++++++++--- app/lib/components/Me.jsx | 4 +- app/lib/components/Settings.jsx | 92 ++++++++++----- app/lib/redux/reducers/me.js | 26 ++++- app/lib/redux/requestActions.js | 8 ++ app/lib/redux/roomClientMiddleware.js | 9 ++ app/lib/redux/stateActions.js | 26 ++++- app/stylus/components/Settings.styl | 4 + 8 files changed, 278 insertions(+), 47 deletions(-) diff --git a/app/lib/RoomClient.js b/app/lib/RoomClient.js index 8c5d0fe..8e824ba 100644 --- a/app/lib/RoomClient.js +++ b/app/lib/RoomClient.js @@ -79,7 +79,9 @@ export default class RoomClient // Map of webcam MediaDeviceInfos indexed by deviceId. // @type {Map} - this._webcams = new Map(); + this._webcams = {}; + + this._audioDevices = {}; // Local Webcam. Object with: // - {MediaDeviceInfo} [device] @@ -89,6 +91,10 @@ export default class RoomClient resolution : 'hd' }; + this._audioDevice = { + device : null + }; + this._screenSharing = ScreenShare.create(); this._screenSharingProducer = null; @@ -366,6 +372,72 @@ export default class RoomClient }); } + changeAudioDevice(deviceId) + { + logger.debug('changeAudioDevice() [deviceId: %s]', deviceId); + + this._dispatch( + stateActions.setAudioInProgress(true)); + + return Promise.resolve() + .then(() => + { + this._audioDevice.device = this._audioDevices[deviceId]; + + logger.debug( + 'changeAudioDevice() | new selected webcam [device:%o]', + this._audioDevice.device); + }) + .then(() => + { + const { device } = this._audioDevice; + + if (!device) + throw new Error('no audio devices'); + + logger.debug('changeAudioDevice() | calling getUserMedia()'); + + return navigator.mediaDevices.getUserMedia( + { + audio : + { + deviceId : { exact: device.deviceId } + } + }); + }) + .then((stream) => + { + const track = stream.getAudioTracks()[0]; + + return this._micProducer.replaceTrack(track) + .then((newTrack) => + { + track.stop(); + + return newTrack; + }); + }) + .then((newTrack) => + { + this._dispatch( + stateActions.setProducerTrack(this._micProducer.id, newTrack)); + + return this._updateAudioDevices(); + }) + .then(() => + { + this._dispatch( + stateActions.setAudioInProgress(false)); + }) + .catch((error) => + { + logger.error('changeAudioDevice() failed: %o', error); + + this._dispatch( + stateActions.setAudioInProgress(false)); + }); + } + changeWebcam(deviceId) { logger.debug('changeWebcam() [deviceId: %s]', deviceId); @@ -376,20 +448,7 @@ export default class RoomClient return Promise.resolve() .then(() => { - logger.debug('changeWebcam() | calling enumerateDevices()'); - - return navigator.mediaDevices.enumerateDevices(); - }) - .then((devices) => - { - for (const device of devices) - { - if (device.kind !== 'videoinput') - continue; - - if (device.deviceId == deviceId) - this._webcam.device = device; - } + this._webcam.device = this._webcams[deviceId]; logger.debug( 'changeWebcam() | new selected webcam [device:%o]', @@ -433,6 +492,10 @@ export default class RoomClient this._dispatch( stateActions.setProducerTrack(this._webcamProducer.id, newTrack)); + return this._updateWebcams(); + }) + .then(() => + { this._dispatch( stateActions.setWebcamInProgress(false)); }) @@ -1161,6 +1224,12 @@ export default class RoomClient let producer; return Promise.resolve() + .then(() => + { + logger.debug('_setMicProducer() | calling _updateAudioDevices()'); + + return this._updateAudioDevices(); + }) .then(() => { logger.debug('_setMicProducer() | calling getUserMedia()'); @@ -1480,6 +1549,58 @@ export default class RoomClient }); } + _updateAudioDevices() + { + logger.debug('_updateAudioDevices()'); + + // Reset the list. + this._audioDevices = {}; + + return Promise.resolve() + .then(() => + { + logger.debug('_updateAudioDevices() | calling enumerateDevices()'); + + return navigator.mediaDevices.enumerateDevices(); + }) + .then((devices) => + { + for (const device of devices) + { + if (device.kind !== 'audioinput') + continue; + + this._audioDevices[device.deviceId] = { + value : device.deviceId, + label : device.label, + deviceId : device.deviceId + }; + } + }) + .then(() => + { + const currentAudioDeviceId = + this._audioDevice.device ? this._audioDevice.device.deviceId : undefined; + + logger.debug('_updateAudioDevices() [audiodevices:%o]', this._audioDevices); + + const len = Object.keys(this._audioDevices).length; + + if (len === 0) + this._audioDevice.device = null; + else if (!this._audioDevices[currentAudioDeviceId]) + for (this._audioDevice.device in this._audioDevices) + if (this._audioDevices.hasOwnProperty(this._audioDevice.device)) + break; + + this._dispatch( + stateActions.setCanChangeAudioDevice(len >= 2)); + if (len >= 1) + this._dispatch( + stateActions.setAudioDevices(this._audioDevices)); + }); + } + _updateWebcams() { logger.debug('_updateWebcams()'); @@ -1502,8 +1623,9 @@ export default class RoomClient continue; this._webcams[device.deviceId] = { - value : device.deviceId, - label : device.label + value : device.deviceId, + label : device.label, + deviceId : device.deviceId }; } }) diff --git a/app/lib/components/Me.jsx b/app/lib/components/Me.jsx index e8286b1..41b6231 100644 --- a/app/lib/components/Me.jsx +++ b/app/lib/components/Me.jsx @@ -80,7 +80,9 @@ class Me extends React.Component {connected ?
{ micState === 'on' ? onMuteMic() : onUnmuteMic(); diff --git a/app/lib/components/Settings.jsx b/app/lib/components/Settings.jsx index d8e4802..9e130a1 100644 --- a/app/lib/components/Settings.jsx +++ b/app/lib/components/Settings.jsx @@ -3,6 +3,7 @@ import { connect } from 'react-redux'; import * as appPropTypes from './appPropTypes'; import * as requestActions from '../redux/requestActions'; import PropTypes from 'prop-types'; +import { Appear } from './transitions'; import Dropdown from 'react-dropdown'; class Settings extends React.Component @@ -18,49 +19,82 @@ class Settings extends React.Component room, me, handleChangeWebcam, + handleChangeAudioDevice, onToggleSettings } = this.props; if (!room.showSettings) return null; - const webcams = Object.values(me.webcamDevices); + let webcams; + let webcamText; + + if (me.canChangeWebcam) + webcamText = 'Select camera'; + else + webcamText = 'Unable to select camera'; + + if (me.webcamDevices) + webcams = Object.values(me.webcamDevices); + else + webcams = []; + + let audioDevices; + let audioDevicesText; + + if (me.canChangeAudioDevice) + audioDevicesText = 'Select audio input device'; + else + audioDevicesText = 'Unable to select audio input device'; + + if (me.audioDevices) + audioDevices = Object.values(me.audioDevices); + else + audioDevices = []; return ( -
-
-
- Settings -
-
- -
-
- onToggleSettings()} - > - Close - + +
+
+
+ Settings +
+
+ + +
+
+ onToggleSettings()} + > + Close + +
-
+ ); } } Settings.propTypes = { - me : appPropTypes.Me.isRequired, - room : appPropTypes.Room.isRequired, - onToggleSettings : PropTypes.func.isRequired, - handleChangeWebcam : PropTypes.func.isRequired + me : appPropTypes.Me.isRequired, + room : appPropTypes.Room.isRequired, + onToggleSettings : PropTypes.func.isRequired, + handleChangeWebcam : PropTypes.func.isRequired, + handleChangeAudioDevice : PropTypes.func.isRequired }; const mapStateToProps = (state) => @@ -77,6 +111,10 @@ const mapDispatchToProps = (dispatch) => handleChangeWebcam : (device) => { dispatch(requestActions.changeWebcam(device.value)); + }, + handleChangeAudioDevice : (device) => + { + dispatch(requestActions.changeAudioDevice(device.value)); } }; }; diff --git a/app/lib/redux/reducers/me.js b/app/lib/redux/reducers/me.js index 785acd2..ca47a7e 100644 --- a/app/lib/redux/reducers/me.js +++ b/app/lib/redux/reducers/me.js @@ -8,9 +8,12 @@ const initialState = canSendWebcam : false, canShareScreen : false, needExtension : false, + canChangeAudioDevice : false, + audioDevices : null, canChangeWebcam : false, webcamDevices : null, webcamInProgress : false, + audioInProgress : false, screenShareInProgress : false, audioOnly : false, audioOnlyInProgress : false, @@ -44,6 +47,20 @@ const me = (state = initialState, action) => return { ...state, canShareScreen, needExtension }; } + case 'SET_CAN_CHANGE_AUDIO_DEVICE': + { + const canChangeAudioDevice = action.payload; + + return { ...state, canChangeAudioDevice }; + } + + case 'SET_AUDIO_DEVICES': + { + const { devices } = action.payload; + + return { ...state, audioDevices: devices }; + } + case 'SET_CAN_CHANGE_WEBCAM': { const canChangeWebcam = action.payload; @@ -53,11 +70,18 @@ const me = (state = initialState, action) => case 'SET_WEBCAM_DEVICES': { - const devices = action.payload; + const { devices } = action.payload; return { ...state, webcamDevices: devices }; } + case 'SET_AUDIO_IN_PROGRESS': + { + const { flag } = action.payload; + + return { ...state, audioInProgress: flag }; + } + case 'SET_WEBCAM_IN_PROGRESS': { const { flag } = action.payload; diff --git a/app/lib/redux/requestActions.js b/app/lib/redux/requestActions.js index 073464b..59ffb7a 100644 --- a/app/lib/redux/requestActions.js +++ b/app/lib/redux/requestActions.js @@ -65,6 +65,14 @@ export const changeWebcam = (deviceId) => }; }; +export const changeAudioDevice = (deviceId) => +{ + return { + type : 'CHANGE_AUDIO_DEVICE', + payload : { deviceId } + }; +}; + export const enableAudioOnly = () => { return { diff --git a/app/lib/redux/roomClientMiddleware.js b/app/lib/redux/roomClientMiddleware.js index 09df186..60a8502 100644 --- a/app/lib/redux/roomClientMiddleware.js +++ b/app/lib/redux/roomClientMiddleware.js @@ -90,6 +90,15 @@ export default ({ dispatch, getState }) => (next) => break; } + case 'CHANGE_AUDIO_DEVICE': + { + const { deviceId } = action.payload; + + client.changeAudioDevice(deviceId); + + break; + } + case 'ENABLE_AUDIO_ONLY': { client.enableAudioOnly(); diff --git a/app/lib/redux/stateActions.js b/app/lib/redux/stateActions.js index 1106dac..72809f6 100644 --- a/app/lib/redux/stateActions.js +++ b/app/lib/redux/stateActions.js @@ -54,6 +54,22 @@ export const setScreenCapabilities = ({ canShareScreen, needExtension }) => }; }; +export const setCanChangeAudioDevice = (flag) => +{ + return { + type : 'SET_CAN_CHANGE_AUDIO_DEVICE', + payload : flag + }; +}; + +export const setAudioDevices = (devices) => +{ + return { + type : 'SET_AUDIO_DEVICES', + payload : { devices } + }; +}; + export const setCanChangeWebcam = (flag) => { return { @@ -66,7 +82,7 @@ export const setWebcamDevices = (devices) => { return { type : 'SET_WEBCAM_DEVICES', - payload : devices + payload : { devices } }; }; @@ -189,6 +205,14 @@ export const setProducerTrack = (producerId, track) => }; }; +export const setAudioInProgress = (flag) => +{ + return { + type : 'SET_AUDIO_IN_PROGRESS', + payload : { flag } + }; +}; + export const setWebcamInProgress = (flag) => { return { diff --git a/app/stylus/components/Settings.styl b/app/stylus/components/Settings.styl index f1eab49..ef21430 100644 --- a/app/stylus/components/Settings.styl +++ b/app/stylus/components/Settings.styl @@ -7,6 +7,8 @@ z-index: 19999; background-color: rgba(000, 000, 000, 0.5); + AppearFadeIn(500ms); + > .dialog { position: absolute; height: 50vmin; @@ -29,6 +31,8 @@ > .settings { height: 100%; width: 100%; + padding-top: 1vmin; + padding-bottom: 1vmin; .Dropdown-root { position: relative;