diff --git a/lib/audioPlayer.js b/lib/audioPlayer.js
index eadb35f..0d234a1 100644
--- a/lib/audioPlayer.js
+++ b/lib/audioPlayer.js
@@ -38,12 +38,13 @@ module.exports =
initialized = true;
},
- /**
- * Play a sound
- * @param {String} name - Sound name
- * @param {[Float]} relativeVolume - Relative volume (0.0 - 1.0)
- */
- play(name, relativeVolume)
+ /**
+ * Play a sound
+ * @param {String} name - Sound name
+ * @param {String} deviceId - DeviceId to use for audio output
+ * @param {[Float]} relativeVolume - Relative volume (0.0 - 1.0)
+ */
+ play(name, deviceId, relativeVolume)
{
this.initialize();
@@ -62,6 +63,11 @@ module.exports =
sound.audio.pause();
sound.audio.currentTime = 0.0;
sound.audio.volume = (sound.volume || 1.0) * relativeVolume;
+
+ if (deviceId && sound.audio.setSinkId) {
+ sound.audio.setSinkId(deviceId);
+ }
+
sound.audio.play();
}
catch (error)
diff --git a/lib/components/Phone.jsx b/lib/components/Phone.jsx
index c99258b..7937230 100644
--- a/lib/components/Phone.jsx
+++ b/lib/components/Phone.jsx
@@ -100,6 +100,7 @@ export default class Phone extends React.Component
{state.session ?
@@ -351,13 +353,15 @@ export default class Phone extends React.Component
{
logger.debug('handleOutgoingCall() [uri:"%s"]', uri);
+ const mediaSettings = this.props.settings.media;
+
let session = this._ua.call(uri,
{
pcConfig : this.props.settings.pcConfig || { iceServers: [] },
mediaConstraints :
{
- audio : true,
- video : true
+ audio: mediaSettings.audioInput ? {deviceId: {exact: mediaSettings.audioInput}} : true,
+ video : mediaSettings.videoInput ? {deviceId: {exact: mediaSettings.videoInput}} : true
},
rtcOfferConstraints :
{
@@ -373,13 +377,13 @@ export default class Phone extends React.Component
session.on('progress', () =>
{
- audioPlayer.play('ringback');
+ audioPlayer.play('ringback', mediaSettings.audioOutput);
});
session.on('failed', (data) =>
{
audioPlayer.stop('ringback');
- audioPlayer.play('rejected');
+ audioPlayer.play('rejected', mediaSettings.audioOutput);
this.setState({ session: null });
this.props.onNotify(
@@ -399,7 +403,7 @@ export default class Phone extends React.Component
session.on('accepted', () =>
{
audioPlayer.stop('ringback');
- audioPlayer.play('answered');
+ audioPlayer.play('answered', mediaSettings.audioOutput);
});
}
diff --git a/lib/components/Session.jsx b/lib/components/Session.jsx
index 76315a0..59c252e 100644
--- a/lib/components/Session.jsx
+++ b/lib/components/Session.jsx
@@ -112,6 +112,8 @@ export default class Session extends React.Component
let localVideo = this.refs.localVideo;
let session = this.props.session;
+ let mediaSettings = this.props.settings.media;
+
let peerconnection = session.connection;
let localStream = peerconnection.getLocalStreams()[0];
let remoteStream = peerconnection.getRemoteStreams()[0];
@@ -123,7 +125,7 @@ export default class Session extends React.Component
this._localClonedStream = localStream.clone();
// Display local stream
- localVideo.srcObject = this._localClonedStream;
+ this._attachStreamToElement(localVideo, this._localClonedStream, mediaSettings.audioOutput);
setTimeout(() =>
{
@@ -140,7 +142,7 @@ export default class Session extends React.Component
{
logger.debug('already have a remote stream');
- this._handleRemoteStream(remoteStream);
+ this._handleRemoteStream(remoteStream);
}
if (session.isEstablished())
@@ -309,9 +311,10 @@ export default class Session extends React.Component
logger.debug('_handleRemoteStream() [stream:%o]', stream);
let remoteVideo = this.refs.remoteVideo;
+ let mediaSettings = this.props.settings.media;
// Display remote stream
- remoteVideo.srcObject = stream;
+ this._attachStreamToElement(remoteVideo, stream, mediaSettings.audioOutput);
this._checkRemoteVideo(stream);
@@ -325,7 +328,7 @@ export default class Session extends React.Component
logger.debug('remote stream "addtrack" event [track:%o]', track);
// Refresh remote video
- remoteVideo.srcObject = stream;
+ this._attachStreamToElement(remoteVideo, stream, mediaSettings.audioOutput);
this._checkRemoteVideo(stream);
@@ -343,7 +346,7 @@ export default class Session extends React.Component
logger.debug('remote stream "removetrack" event');
// Refresh remote video
- remoteVideo.srcObject = stream;
+ this._attachStreamToElement(remoteVideo, stream, mediaSettings.audioOutput);
this._checkRemoteVideo(stream);
});
@@ -362,11 +365,34 @@ export default class Session extends React.Component
this.setState({ remoteHasVideo: !!videoTrack });
}
+
+ /**
+ * Re/Attach stream to element with deviceId
+ * @param {HTMLMediaElement} element - Target audio/video element
+ * @param {Stream} stream - Stream to attach
+ * @param {String} deviceId - DeviceId to use for audio output
+ */
+ _attachStreamToElement(element, stream, deviceId) {
+ // Pause stream before device change
+ element.pause();
+
+ // Redirect audio output to exact device
+ if (deviceId && element.setSinkId) {
+ element.setSinkId(deviceId);
+ }
+
+ // ReAttach stream
+ element.srcObject = stream;
+
+ // Continue on new device
+ element.play();
+ }
}
Session.propTypes =
{
+ settings : PropTypes.object.isRequired,
session : PropTypes.object.isRequired,
onNotify : PropTypes.func.isRequired,
- onHideNotification : PropTypes.func.isRequired,
+ onHideNotification : PropTypes.func.isRequired
};
diff --git a/lib/components/Settings.jsx b/lib/components/Settings.jsx
index da82646..2adc565 100644
--- a/lib/components/Settings.jsx
+++ b/lib/components/Settings.jsx
@@ -24,13 +24,39 @@ export default class Settings extends React.Component
this.state =
{
- settings : clone(settings, false)
+ settings : clone(settings, false),
+ devices: []
};
}
+ componentDidMount() {
+ if (
+ navigator &&
+ navigator.mediaDevices &&
+ navigator.mediaDevices.getUserMedia &&
+ navigator.mediaDevices.enumerateDevices &&
+ (window.AudioContext || window.webkitAudioContext)
+ ) {
+ // TODO: Detect device change
+ navigator.mediaDevices.getUserMedia({audio:true,video:true})
+ .then(() => {
+ navigator.mediaDevices.enumerateDevices().then(devices => {
+ console.log('Loaded devices', devices);
+ this.setState({devices});
+ })
+ });
+
+ } else {
+ console.warn('MediaDevices API is missing!');
+ }
+ }
+
render()
{
- let settings = this.state.settings;
+ const {
+ devices,
+ settings
+ } = this.state;
return (
@@ -195,6 +221,64 @@ export default class Settings extends React.Component
+
+
+
+ {devices.filter(x => x.kind === 'audioinput').map(x => (
+
+ ))}
+
+
+
+
+
+
+ {devices.filter(x => x.kind === 'audiooutput').map(x => (
+
+ ))}
+
+
+
+
+
+
+ {devices.filter(x => x.kind === 'audiooutput').map(x => (
+
+ ))}
+
+
+
+
+
+
+ {devices.filter(x => x.kind === 'videoinput').map(x => (
+
+ ))}
+
+
+
+
+