Added support for setting audio input device, not working on linux at the moment. Updated webcam selection.

master
Håvar Aambø Fosstveit 2018-06-15 23:19:26 +02:00
parent 7e1b391fe1
commit fcb15e706d
8 changed files with 278 additions and 47 deletions

View File

@ -79,7 +79,9 @@ export default class RoomClient
// Map of webcam MediaDeviceInfos indexed by deviceId.
// @type {Map<String, MediaDeviceInfos>}
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
};
}
})

View File

@ -80,7 +80,9 @@ class Me extends React.Component
{connected ?
<div className='controls'>
<div
className={classnames('button', 'mic', micState)}
className={classnames('button', 'mic', micState, {
disabled : me.audioInProgress
})}
onClick={() =>
{
micState === 'on' ? onMuteMic() : onUnmuteMic();

View File

@ -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 (
<div data-component='Settings'>
<div className='dialog'>
<div className='header'>
<span>Settings</span>
</div>
<div className='settings'>
<Dropdown
disabled={!me.canChangeWebcam}
options={webcams}
onChange={handleChangeWebcam}
value={webcams[0]}
placeholder='No other cameras detected'
/>
</div>
<div className='footer'>
<span
className='button'
onClick={() => onToggleSettings()}
>
Close
</span>
<Appear duration={500}>
<div data-component='Settings'>
<div className='dialog'>
<div className='header'>
<span>Settings</span>
</div>
<div className='settings'>
<Dropdown
disabled={!me.canChangeWebcam}
options={webcams}
onChange={handleChangeWebcam}
placeholder={webcamText}
/>
<Dropdown
disabled={!me.canChangeAudioDevice}
options={audioDevices}
onChange={handleChangeAudioDevice}
placeholder={audioDevicesText}
/>
</div>
<div className='footer'>
<span
className='button'
onClick={() => onToggleSettings()}
>
Close
</span>
</div>
</div>
</div>
</div>
</Appear>
);
}
}
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));
}
};
};

View File

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

View File

@ -65,6 +65,14 @@ export const changeWebcam = (deviceId) =>
};
};
export const changeAudioDevice = (deviceId) =>
{
return {
type : 'CHANGE_AUDIO_DEVICE',
payload : { deviceId }
};
};
export const enableAudioOnly = () =>
{
return {

View File

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

View File

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

View File

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