From 387b11c429af0229b72de77bf2badef9d4a65332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kal=C3=A1bek?= Date: Tue, 9 Jan 2018 16:10:11 +0100 Subject: [PATCH 01/10] Add settings --- lib/components/Settings.jsx | 119 +++++++++++++++++++++++++++++++++++- lib/settingsManager.js | 7 +++ 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/lib/components/Settings.jsx b/lib/components/Settings.jsx index da82646..60d26d3 100644 --- a/lib/components/Settings.jsx +++ b/lib/components/Settings.jsx @@ -24,13 +24,34 @@ 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.enumerateDevices && + (window.AudioContext || window.webkitAudioContext) + ) { + // TODO: Detect device change + 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 +216,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 => ( + + ))} + +
+ +
+
Date: Tue, 9 Jan 2018 16:12:15 +0100 Subject: [PATCH 02/10] Modify input devices --- lib/components/Phone.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/components/Phone.jsx b/lib/components/Phone.jsx index c99258b..434c791 100644 --- a/lib/components/Phone.jsx +++ b/lib/components/Phone.jsx @@ -351,13 +351,14 @@ 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 : { From 1b706c1f42cdebc1f4bccf8f91d2c758d27d51e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kal=C3=A1bek?= Date: Tue, 9 Jan 2018 16:14:23 +0100 Subject: [PATCH 03/10] Modify output devices TODO: Detect old/disconnected devices TODO: Switch devices on fly? --- lib/audioPlayer.js | 18 +++++++++++------ lib/components/Phone.jsx | 11 +++++++---- lib/components/Session.jsx | 40 +++++++++++++++++++++++++++++++------- 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/lib/audioPlayer.js b/lib/audioPlayer.js index eadb35f..a700d85 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 434c791..e85606a 100644 --- a/lib/components/Phone.jsx +++ b/lib/components/Phone.jsx @@ -100,6 +100,7 @@ export default class Phone extends React.Component
{state.session ? @@ -352,6 +354,7 @@ 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: [] }, @@ -374,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( @@ -400,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..68fe256 100644 --- a/lib/components/Session.jsx +++ b/lib/components/Session.jsx @@ -111,7 +111,9 @@ export default class Session extends React.Component this._mounted = true; let localVideo = this.refs.localVideo; - let session = this.props.session; + 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 }; From 5de5be336693b784ec5145c30b1acac4cd245745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kal=C3=A1bek?= Date: Wed, 10 Jan 2018 14:10:05 +0100 Subject: [PATCH 04/10] Make linter happy --- lib/audioPlayer.js | 2 +- lib/components/Phone.jsx | 4 ++-- lib/components/Session.jsx | 20 ++++++++--------- lib/components/Settings.jsx | 44 ++++++++++++++++++------------------- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/lib/audioPlayer.js b/lib/audioPlayer.js index a700d85..0d234a1 100644 --- a/lib/audioPlayer.js +++ b/lib/audioPlayer.js @@ -65,7 +65,7 @@ module.exports = sound.audio.volume = (sound.volume || 1.0) * relativeVolume; if (deviceId && sound.audio.setSinkId) { - sound.audio.setSinkId(deviceId) + sound.audio.setSinkId(deviceId); } sound.audio.play(); diff --git a/lib/components/Phone.jsx b/lib/components/Phone.jsx index e85606a..7937230 100644 --- a/lib/components/Phone.jsx +++ b/lib/components/Phone.jsx @@ -130,7 +130,7 @@ export default class Phone extends React.Component let settings = this.props.settings; let socket = new JsSIP.WebSocketInterface(settings.socket.uri); - let mediaSettings = settings.media + let mediaSettings = settings.media; if (settings.socket.via_transport !== 'auto') socket.via_transport = settings.socket.via_transport; @@ -353,7 +353,7 @@ export default class Phone extends React.Component { logger.debug('handleOutgoingCall() [uri:"%s"]', uri); - const mediaSettings = this.props.settings.media + const mediaSettings = this.props.settings.media; let session = this._ua.call(uri, { diff --git a/lib/components/Session.jsx b/lib/components/Session.jsx index 68fe256..59c252e 100644 --- a/lib/components/Session.jsx +++ b/lib/components/Session.jsx @@ -111,8 +111,8 @@ export default class Session extends React.Component this._mounted = true; let localVideo = this.refs.localVideo; - let session = this.props.session - let mediaSettings = this.props.settings.media + let session = this.props.session; + let mediaSettings = this.props.settings.media; let peerconnection = session.connection; let localStream = peerconnection.getLocalStreams()[0]; @@ -125,7 +125,7 @@ export default class Session extends React.Component this._localClonedStream = localStream.clone(); // Display local stream - this._attachStreamToElement(localVideo, this._localClonedStream, mediaSettings.audioOutput) + this._attachStreamToElement(localVideo, this._localClonedStream, mediaSettings.audioOutput); setTimeout(() => { @@ -142,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()) @@ -311,10 +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 + let mediaSettings = this.props.settings.media; // Display remote stream - this._attachStreamToElement(remoteVideo, stream, mediaSettings.audioOutput) + this._attachStreamToElement(remoteVideo, stream, mediaSettings.audioOutput); this._checkRemoteVideo(stream); @@ -328,7 +328,7 @@ export default class Session extends React.Component logger.debug('remote stream "addtrack" event [track:%o]', track); // Refresh remote video - this._attachStreamToElement(remoteVideo, stream, mediaSettings.audioOutput) + this._attachStreamToElement(remoteVideo, stream, mediaSettings.audioOutput); this._checkRemoteVideo(stream); @@ -346,7 +346,7 @@ export default class Session extends React.Component logger.debug('remote stream "removetrack" event'); // Refresh remote video - this._attachStreamToElement(remoteVideo, stream, mediaSettings.audioOutput) + this._attachStreamToElement(remoteVideo, stream, mediaSettings.audioOutput); this._checkRemoteVideo(stream); }); @@ -378,14 +378,14 @@ export default class Session extends React.Component // Redirect audio output to exact device if (deviceId && element.setSinkId) { - element.setSinkId(deviceId) + element.setSinkId(deviceId); } // ReAttach stream element.srcObject = stream; // Continue on new device - element.play() + element.play(); } } diff --git a/lib/components/Settings.jsx b/lib/components/Settings.jsx index 60d26d3..76c2757 100644 --- a/lib/components/Settings.jsx +++ b/lib/components/Settings.jsx @@ -38,11 +38,11 @@ export default class Settings extends React.Component ) { // TODO: Detect device change navigator.mediaDevices.enumerateDevices().then(devices => { - console.log('Loaded devices', devices) - this.setState({devices}) - }) + console.log('Loaded devices', devices); + this.setState({devices}); + }); } else { - console.warn('MediaDevices API is missing!') + console.warn('MediaDevices API is missing!'); } } @@ -51,7 +51,7 @@ export default class Settings extends React.Component const { devices, settings - } = this.state + } = this.state; return ( @@ -216,19 +216,19 @@ 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 => ( + ))}
@@ -252,8 +252,8 @@ export default class Settings extends React.Component onChange={this.handleChangeAudioOutputRinging.bind(this)} > - {devices.filter(x => x.kind === 'audiooutput').map(x => ( - + {devices.filter(x => x.kind === 'audiooutput').map(x => ( + ))}
@@ -266,8 +266,8 @@ export default class Settings extends React.Component onChange={this.handleChangeVideoInput.bind(this)} > - {devices.filter(x => x.kind === 'videoinput').map(x => ( - + {devices.filter(x => x.kind === 'videoinput').map(x => ( + ))}
@@ -314,7 +314,7 @@ export default class Settings extends React.Component { let settings = this.state.settings; - settings.socket.uri = event.target.value;; + settings.socket.uri = event.target.value; this.setState({ settings }); } From de1000c685ecf2fbd472ad84354d3150fcb8fbd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kal=C3=A1bek?= Date: Wed, 10 Jan 2018 15:27:40 +0100 Subject: [PATCH 05/10] Request permissions first --- lib/components/Settings.jsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/components/Settings.jsx b/lib/components/Settings.jsx index 76c2757..2adc565 100644 --- a/lib/components/Settings.jsx +++ b/lib/components/Settings.jsx @@ -33,14 +33,19 @@ export default class Settings extends React.Component if ( navigator && navigator.mediaDevices && + navigator.mediaDevices.getUserMedia && navigator.mediaDevices.enumerateDevices && (window.AudioContext || window.webkitAudioContext) ) { // TODO: Detect device change - navigator.mediaDevices.enumerateDevices().then(devices => { - console.log('Loaded devices', devices); - this.setState({devices}); - }); + 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!'); } From 561e731676ffd86fff3ee915ec2feddffa919e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kal=C3=A1bek?= Date: Tue, 23 Jan 2018 17:22:37 +0100 Subject: [PATCH 06/10] * Add DevicesWatcher * Add bascit device notifications --- lib/components/App.jsx | 5 ++ lib/components/DevicesWatcher.js | 79 ++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 lib/components/DevicesWatcher.js diff --git a/lib/components/App.jsx b/lib/components/App.jsx index c23c46e..eddf744 100644 --- a/lib/components/App.jsx +++ b/lib/components/App.jsx @@ -9,6 +9,7 @@ import Snackbar from 'material-ui/Snackbar'; import Notifier from './Notifier'; import Login from './Login'; import Phone from './Phone'; +import DevicesWatcher from './DevicesWatcher'; const logger = new Logger('App'); @@ -71,6 +72,10 @@ export default class App extends React.Component
+ + {component} { + const devicesOld = localStorage.getItem('devices') + ? JSON.parse(localStorage.getItem('devices')) + : devices + + devicesOld.map((item) => { + if (!devices.find(x => x.deviceId === item.deviceId)) { + this.deviceRemoved(item) + } + }) + + devices.map((item) => { + if (!devicesOld.find(x => x.deviceId === item.deviceId)) { + this.deviceAdded(item) + } + }) + + localStorage.setItem('devices', JSON.stringify(devices)) + }) + } + + deviceAdded(device) { + logger.debug('Device added', device) + + this.props.onNotify({ + level: 'success', + title: 'Device connected', + message: device.label + }) + } + + deviceRemoved(device) { + logger.debug('Device removed', device) + + this.props.onNotify({ + level : 'error', + title : 'Device disconnected', + message : device.label + }) + } + + componentWillMount() { + this.reloadDevices() + navigator.mediaDevices.ondevicechange = () => this.reloadDevices() + } + + render() + { + // let state = this.state; + // let props = this.props; + + return null; + } +} + +Dialer.propTypes = + { + onNotify: PropTypes.func.isRequired + }; From 0efb742c88bb79d0e699386565a276ad4877021a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kal=C3=A1bek?= Date: Wed, 24 Jan 2018 13:50:48 +0100 Subject: [PATCH 07/10] Change devices in settings when added/removed --- lib/components/App.jsx | 11 +++ lib/components/DevicesWatcher.js | 119 ++++++++++++++++++++++--------- lib/components/Settings.jsx | 43 ++++------- 3 files changed, 113 insertions(+), 60 deletions(-) diff --git a/lib/components/App.jsx b/lib/components/App.jsx index eddf744..da1131a 100644 --- a/lib/components/App.jsx +++ b/lib/components/App.jsx @@ -73,7 +73,9 @@ export default class App extends React.Component {component} @@ -156,6 +158,15 @@ export default class App extends React.Component }); } + handleSettingsUpdate(settings) + { + logger.debug('handleSettingsUpdate() [settings:%o]', settings) + + settingsManager.set(settings) + + this.setState({ settings }) + } + handlePhoneExit() { logger.debug('handlePhoneExit()'); diff --git a/lib/components/DevicesWatcher.js b/lib/components/DevicesWatcher.js index 8b37cd1..a9033ef 100644 --- a/lib/components/DevicesWatcher.js +++ b/lib/components/DevicesWatcher.js @@ -2,11 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import TextField from 'material-ui/TextField'; -import RaisedButton from 'material-ui/RaisedButton'; -import classnames from 'classnames'; import Logger from '../Logger'; -import utils from '../utils'; const logger = new Logger('DevicesWatcher'); @@ -18,45 +14,105 @@ export default class Dialer extends React.Component } reloadDevices() { - navigator.mediaDevices.enumerateDevices().then(devices => { - const devicesOld = localStorage.getItem('devices') - ? JSON.parse(localStorage.getItem('devices')) - : devices - - devicesOld.map((item) => { - if (!devices.find(x => x.deviceId === item.deviceId)) { - this.deviceRemoved(item) - } - }) + navigator.mediaDevices.enumerateDevices({audio:true,video:true}) + .then(devices => { + const devicesOld = localStorage.getItem('devices') + ? JSON.parse(localStorage.getItem('devices')) + : devices - devices.map((item) => { - if (!devicesOld.find(x => x.deviceId === item.deviceId)) { - this.deviceAdded(item) - } - }) + devicesOld.map((item) => { + if (!devices.find(x => x.deviceId === item.deviceId)) { + this.deviceRemoved(item, devices) + } + }) - localStorage.setItem('devices', JSON.stringify(devices)) - }) + devices.map((item) => { + if (!devicesOld.find(x => x.deviceId === item.deviceId)) { + this.deviceAdded(item, devices) + } + }) + + localStorage.setItem('devices', JSON.stringify(devices)); + }) } deviceAdded(device) { - logger.debug('Device added', device) + const settings = this.props.settings + + logger.debug('deviceAdded() [device:%o]', device); this.props.onNotify({ - level: 'success', - title: 'Device connected', - message: device.label + level : 'success', + title : 'Device added', + message : device.label }) + + if (device.kind === 'audioinput') { + localStorage.setItem('audioInputOld', settings.media.audioInput) + settings.media.audioInput = device.deviceId + } else if (device.kind === 'audiooutput') { + localStorage.setItem('audioOutputOld', settings.media.audioOutput) + settings.media.audioOutput = device.deviceId + } else if (device.kind === 'videoinput') { + localStorage.setItem('videoInputOld', settings.media.videoInput) + settings.media.videoInput = device.deviceId + } + + this.props.onSettingsUpdate(settings) } - deviceRemoved(device) { - logger.debug('Device removed', device) + deviceRemoved(device, devices) { + const settings = this.props.settings + + logger.debug('deviceRemoved() [device:%o]', device); this.props.onNotify({ level : 'error', - title : 'Device disconnected', + title : 'Device removed', message : device.label }) + + let updateNeeded = false + + if (device.deviceId === settings.media.audioInput) { + const audioInputOld = localStorage.audioInputOld + if (audioInputOld && devices.find(x => x.deviceId === audioInputOld)) { + settings.media.audioInput = audioInputOld + localStorage.removeItem('audioInputOld') + } else { + settings.media.audioInput = null + } + + updateNeeded = true + } + + if (device.deviceId === settings.media.audioOutput) { + const audioOutputOld = localStorage.audioOutputOld + if (audioOutputOld && devices.find(x => x.deviceId === audioOutputOld)) { + settings.media.audioOutput = audioOutputOld + localStorage.removeItem('audioOutputOld') + } else { + settings.media.audioOutput = null + } + + updateNeeded = true + } + + if (device.deviceId === settings.media.videoInput) { + const videoInputOld = localStorage.videoInputOld + if (videoInputOld && devices.find(x => x.deviceId === videoInputOld)) { + settings.media.videoInput = videoInputOld + localStorage.removeItem('videoInputOld') + } else { + settings.media.videoInput = null + } + + updateNeeded = true + } + + if (updateNeeded) { + this.props.onSettingsUpdate(settings) + } } componentWillMount() { @@ -66,14 +122,13 @@ export default class Dialer extends React.Component render() { - // let state = this.state; - // let props = this.props; - return null; } } Dialer.propTypes = { - onNotify: PropTypes.func.isRequired + settings: PropTypes.object.isRequired, + onNotify: PropTypes.func.isRequired, + onSettingsUpdate: PropTypes.func.isRequired }; diff --git a/lib/components/Settings.jsx b/lib/components/Settings.jsx index 2adc565..5aebdfb 100644 --- a/lib/components/Settings.jsx +++ b/lib/components/Settings.jsx @@ -22,41 +22,28 @@ export default class Settings extends React.Component let settings = props.settings; - this.state = - { + this.state = { settings : clone(settings, false), - devices: [] - }; + devices : JSON.parse(localStorage.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!'); - } + componentDidMount() + { + const self = this; + window.addEventListener('storage', function(e) { + logger.debug('StorageEvent [event:%o]', e); + + if (e.key === 'devices') { + self.setState({devices: JSON.parse(e.newValue)}); + } + }); } render() { - const { - devices, - settings - } = this.state; + const settings = this.state.settings; + const devices = JSON.parse(localStorage.devices); return ( From 84d2a0b621478213b84c2f26ebeca30a3463d611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kal=C3=A1bek?= Date: Tue, 30 Jan 2018 12:04:51 +0100 Subject: [PATCH 08/10] Switch devices on fly --- lib/components/App.jsx | 17 +++-- lib/components/DevicesWatcher.js | 101 +++++++++++++++++++----------- lib/components/Phone.jsx | 42 ++++++++----- lib/components/Session.jsx | 103 ++++++++++++++++++++++++------- lib/components/Settings.jsx | 4 ++ 5 files changed, 187 insertions(+), 80 deletions(-) diff --git a/lib/components/App.jsx b/lib/components/App.jsx index da1131a..8fc7d8c 100644 --- a/lib/components/App.jsx +++ b/lib/components/App.jsx @@ -10,6 +10,7 @@ import Notifier from './Notifier'; import Login from './Login'; import Phone from './Phone'; import DevicesWatcher from './DevicesWatcher'; +import clone from 'clone' const logger = new Logger('App'); @@ -73,9 +74,9 @@ export default class App extends React.Component {component} @@ -158,13 +159,19 @@ export default class App extends React.Component }); } - handleSettingsUpdate(settings) + handleMediaSettingsChange(key, deviceId) { - logger.debug('handleSettingsUpdate() [settings:%o]', settings) + const settings = clone(this.state.settings) + + logger.debug('handleMediaSettingsChange() [key:%s] [old:%s] [new:%s]', key, settings.media[key], deviceId) + // Merge media settings without mutating + settings.media[key] = deviceId settingsManager.set(settings) - this.setState({ settings }) + this.setState({ + settings: settingsManager.get() + }) } handlePhoneExit() diff --git a/lib/components/DevicesWatcher.js b/lib/components/DevicesWatcher.js index a9033ef..5805b73 100644 --- a/lib/components/DevicesWatcher.js +++ b/lib/components/DevicesWatcher.js @@ -37,81 +37,108 @@ export default class Dialer extends React.Component } deviceAdded(device) { - const settings = this.props.settings + const { + mediaDevices: { + audioInput, + audioOutput, + videoInput + }, + onMediaSettingsChange, + onNotify + } = this.props logger.debug('deviceAdded() [device:%o]', device); - this.props.onNotify({ + onNotify({ level : 'success', title : 'Device added', message : device.label }) if (device.kind === 'audioinput') { - localStorage.setItem('audioInputOld', settings.media.audioInput) - settings.media.audioInput = device.deviceId + localStorage.setItem('audioInputOld', audioInput) + onMediaSettingsChange('audioInput', device.deviceId) } else if (device.kind === 'audiooutput') { - localStorage.setItem('audioOutputOld', settings.media.audioOutput) - settings.media.audioOutput = device.deviceId + localStorage.setItem('audioOutputOld', audioOutput) + onMediaSettingsChange('audioOutput', device.deviceId) } else if (device.kind === 'videoinput') { - localStorage.setItem('videoInputOld', settings.media.videoInput) - settings.media.videoInput = device.deviceId + localStorage.setItem('videoInputOld', videoInput) + onMediaSettingsChange('videoInput', device.deviceId) } - - this.props.onSettingsUpdate(settings) } deviceRemoved(device, devices) { - const settings = this.props.settings + const { + mediaDevices: { + audioInput, + audioOutput, + videoInput + }, + onMediaSettingsChange, + onNotify + } = this.props logger.debug('deviceRemoved() [device:%o]', device); - this.props.onNotify({ + onNotify({ level : 'error', title : 'Device removed', message : device.label }) - let updateNeeded = false - - if (device.deviceId === settings.media.audioInput) { - const audioInputOld = localStorage.audioInputOld + // if (device.deviceId === audioInput) { + // if (audioInput && devices.find(x => x.deviceId === audioInput)) { + // onMediaSettingsChange('audioInput', audioInput) + // } else { + // onMediaSettingsChange('audioInput', 'default') + // } + // } + // + // if (device.deviceId === audioOutput) { + // if (audioOutput && devices.find(x => x.deviceId === audioOutput)) { + // onMediaSettingsChange('audioOutput', audioOutput) + // } else { + // onMediaSettingsChange('audioOutput', 'default') + // } + // } + // + // if (device.deviceId === videoInput) { + // const videoInput = localStorage.getItem('videoInput') + // if (videoInput && devices.find(x => x.deviceId === videoInput)) { + // onMediaSettingsChange('videoInput', videoInput) + // } else { + // onMediaSettingsChange('videoInput', null) + // } + // } + + if (device.deviceId === audioInput) { + const audioInputOld = localStorage.getItem('audioInputOld') if (audioInputOld && devices.find(x => x.deviceId === audioInputOld)) { - settings.media.audioInput = audioInputOld + onMediaSettingsChange('audioInput', audioInputOld) localStorage.removeItem('audioInputOld') } else { - settings.media.audioInput = null + onMediaSettingsChange('audioInput', 'default') } - - updateNeeded = true } - if (device.deviceId === settings.media.audioOutput) { - const audioOutputOld = localStorage.audioOutputOld + if (device.deviceId === audioOutput) { + const audioOutputOld = localStorage.getItem('audioOutputOld') if (audioOutputOld && devices.find(x => x.deviceId === audioOutputOld)) { - settings.media.audioOutput = audioOutputOld + onMediaSettingsChange('audioOutput', audioOutputOld) localStorage.removeItem('audioOutputOld') } else { - settings.media.audioOutput = null + onMediaSettingsChange('audioOutput', 'default') } - - updateNeeded = true } - if (device.deviceId === settings.media.videoInput) { - const videoInputOld = localStorage.videoInputOld + if (device.deviceId === videoInput) { + const videoInputOld = localStorage.getItem('videoInputOld') if (videoInputOld && devices.find(x => x.deviceId === videoInputOld)) { - settings.media.videoInput = videoInputOld + onMediaSettingsChange('videoInput', videoInputOld) localStorage.removeItem('videoInputOld') } else { - settings.media.videoInput = null + onMediaSettingsChange('videoInput', null) } - - updateNeeded = true - } - - if (updateNeeded) { - this.props.onSettingsUpdate(settings) } } @@ -130,5 +157,5 @@ Dialer.propTypes = { settings: PropTypes.object.isRequired, onNotify: PropTypes.func.isRequired, - onSettingsUpdate: PropTypes.func.isRequired + onMediaSettingsChange: PropTypes.func.isRequired }; diff --git a/lib/components/Phone.jsx b/lib/components/Phone.jsx index 7937230..919a152 100644 --- a/lib/components/Phone.jsx +++ b/lib/components/Phone.jsx @@ -100,10 +100,10 @@ export default class Phone extends React.Component
{state.session ? : null @@ -130,7 +130,6 @@ export default class Phone extends React.Component let settings = this.props.settings; let socket = new JsSIP.WebSocketInterface(settings.socket.uri); - let mediaSettings = settings.media; if (settings.socket.via_transport !== 'auto') socket.via_transport = settings.socket.via_transport; @@ -268,7 +267,7 @@ export default class Phone extends React.Component return; } - audioPlayer.play('ringing', mediaSettings.ringing); + audioPlayer.play('ringing', settings.media.audioRinging); this.setState({ incomingSession: session }); session.on('failed', () => @@ -351,17 +350,27 @@ export default class Phone extends React.Component handleOutgoingCall(uri) { - logger.debug('handleOutgoingCall() [uri:"%s"]', uri); + const { + settings: { + media: { + audioInput, + audioOutput, + videoInput + }, + pcConfig + }, + onNotify + } = this.props - const mediaSettings = this.props.settings.media; + logger.debug('handleOutgoingCall() [uri:"%s"]', uri); let session = this._ua.call(uri, { - pcConfig : this.props.settings.pcConfig || { iceServers: [] }, + pcConfig : pcConfig || { iceServers: [] }, mediaConstraints : { - audio: mediaSettings.audioInput ? {deviceId: {exact: mediaSettings.audioInput}} : true, - video : mediaSettings.videoInput ? {deviceId: {exact: mediaSettings.videoInput}} : true + audio: audioInput ? {deviceId: {exact: audioInput}} : true, + video : videoInput ? {deviceId: {exact: videoInput}} : true }, rtcOfferConstraints : { @@ -377,21 +386,20 @@ export default class Phone extends React.Component session.on('progress', () => { - audioPlayer.play('ringback', mediaSettings.audioOutput); + audioPlayer.play('ringback', audioOutput); }); session.on('failed', (data) => { audioPlayer.stop('ringback'); - audioPlayer.play('rejected', mediaSettings.audioOutput); + audioPlayer.play('rejected', audioOutput); this.setState({ session: null }); - this.props.onNotify( - { - level : 'error', - title : 'Call failed', - message : data.cause - }); + onNotify({ + level : 'error', + title : 'Call failed', + message : data.cause + }); }); session.on('ended', () => @@ -403,7 +411,7 @@ export default class Phone extends React.Component session.on('accepted', () => { audioPlayer.stop('ringback'); - audioPlayer.play('answered', mediaSettings.audioOutput); + audioPlayer.play('answered', audioOutput); }); } diff --git a/lib/components/Session.jsx b/lib/components/Session.jsx index 59c252e..8b4a147 100644 --- a/lib/components/Session.jsx +++ b/lib/components/Session.jsx @@ -32,6 +32,7 @@ export default class Session extends React.Component this._mounted = false; // Local cloned stream this._localClonedStream = null; + this._remoteStream = null; } render() @@ -53,6 +54,8 @@ export default class Session extends React.Component else if (!state.remoteHasVideo) noRemoteVideo =
no remote video
; + logger.debug('render() [output:%s]', props.mediaDevices.audioOutput) + return (
@@ -104,15 +107,32 @@ export default class Session extends React.Component ); } + componentDidUpdate(prevProps) { + if (prevProps.mediaDevices.audioOutput !== this.props.mediaDevices.audioOutput) { + logger.debug('componentDidUpdate() audioOutput [old:%s] [device:%s]', prevProps.mediaDevices.audioOutput, this.props.mediaDevices.audioOutput) + this._attachStreamToElement(this.refs.remoteVideo, this.props.mediaDevices.audioOutput, this._remoteStream) + } + + if (prevProps.mediaDevices.audioInput !== this.props.mediaDevices.audioInput) { + logger.debug('componentDidUpdate() audioInput [old:%s] [device:%s]', prevProps.mediaDevices.audioInput, this.props.mediaDevices.audioInput) + this._replaceLocalMedia() + } + } + componentDidMount() { - logger.debug('componentDidMount()'); + logger.debug('componentDidMount()', this.props.mediaDevices.audioOutput); this._mounted = true; let localVideo = this.refs.localVideo; - let session = this.props.session; - let mediaSettings = this.props.settings.media; + const { + session, + mediaDevices: { + audioOutput, + audioInput + } + } = this.props; let peerconnection = session.connection; let localStream = peerconnection.getLocalStreams()[0]; @@ -125,7 +145,7 @@ export default class Session extends React.Component this._localClonedStream = localStream.clone(); // Display local stream - this._attachStreamToElement(localVideo, this._localClonedStream, mediaSettings.audioOutput); + this._attachStreamToElement(localVideo, audioOutput, this._localClonedStream); setTimeout(() => { @@ -310,11 +330,14 @@ export default class Session extends React.Component { logger.debug('_handleRemoteStream() [stream:%o]', stream); + this._remoteStream = stream + let remoteVideo = this.refs.remoteVideo; - let mediaSettings = this.props.settings.media; + + const audioOutput = this.props.mediaDevices.audioOutput // Display remote stream - this._attachStreamToElement(remoteVideo, stream, mediaSettings.audioOutput); + this._attachStreamToElement(remoteVideo, audioOutput, stream); this._checkRemoteVideo(stream); @@ -328,7 +351,7 @@ export default class Session extends React.Component logger.debug('remote stream "addtrack" event [track:%o]', track); // Refresh remote video - this._attachStreamToElement(remoteVideo, stream, mediaSettings.audioOutput); + this._attachStreamToElement(remoteVideo, audioOutput, stream); this._checkRemoteVideo(stream); @@ -346,7 +369,7 @@ export default class Session extends React.Component logger.debug('remote stream "removetrack" event'); // Refresh remote video - this._attachStreamToElement(remoteVideo, stream, mediaSettings.audioOutput); + this._attachStreamToElement(remoteVideo, audioOutput, stream); this._checkRemoteVideo(stream); }); @@ -372,27 +395,65 @@ export default class Session extends React.Component * @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(); + _attachStreamToElement(element, deviceId, stream) { + console.log('Re-attach stream', element, deviceId, stream) - // Redirect audio output to exact device - if (deviceId && element.setSinkId) { - element.setSinkId(deviceId); - } + /* + 1. Pause stream before change + 2. Redirect to device + 3. Replace stream source if needed + 4. Play stream + */ - // ReAttach stream - element.srcObject = stream; + element.pause(); + element.setSinkId(deviceId) + .then(() => logger.debug('Stream directed to device [device:%s]', deviceId)) + .catch(err => logger.error(err, deviceId)) + + if (element.srcObject !== stream) { + element.srcObject = stream + } - // Continue on new device element.play(); } + + /** + * Update local input media + */ + _replaceLocalMedia() { + const { + session, + mediaDevices + } = this.props + + /* + 1. Get new stream with proper constrains + 2. Remove current local streams + 3. Add new stream to connection + 4. Send update + */ + + navigator.mediaDevices.getUserMedia({ + audio: mediaDevices.audioInput ? {deviceId: {exact: mediaDevices.audioInput}} : true, + video: mediaDevices.videoInput ? {deviceId: {exact: mediaDevices.videoInput}} : true + }) + .then(streamNew => { + session.connection.getLocalStreams().forEach(stream => { + // Shouldn't we end tracks manually? + session.connection.removeStream(stream) + }) + + return session.connection.addStream(streamNew) + }) + .then(() => session.renegotiate({ useUpdate: true })) + } } Session.propTypes = { - settings : PropTypes.object.isRequired, - session : PropTypes.object.isRequired, + onHideNotification : PropTypes.func.isRequired, onNotify : PropTypes.func.isRequired, - onHideNotification : PropTypes.func.isRequired + audioOutput : PropTypes.string, + session : PropTypes.object.isRequired, + mediaDevices : PropTypes.object.isRequired }; diff --git a/lib/components/Settings.jsx b/lib/components/Settings.jsx index 5aebdfb..50dc07b 100644 --- a/lib/components/Settings.jsx +++ b/lib/components/Settings.jsx @@ -395,6 +395,7 @@ export default class Settings extends React.Component let settings = this.state.settings; settings.media.audioInput = value; + localStorage.removeItem('audioInputOld') this.setState({ settings }); } @@ -404,6 +405,7 @@ export default class Settings extends React.Component let settings = this.state.settings; settings.media.audioOutput = value; + localStorage.removeItem('audioOutputOld') this.setState({ settings }); } @@ -413,6 +415,7 @@ export default class Settings extends React.Component let settings = this.state.settings; settings.media.audioRinging = value; + localStorage.removeItem('audioRingingOld') this.setState({ settings }); } @@ -422,6 +425,7 @@ export default class Settings extends React.Component let settings = this.state.settings; settings.media.videoInput = value; + localStorage.removeItem('videoInputOld') this.setState({ settings }); } From e484a9dd66838e01370a70afd595430f2a2e530e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kal=C3=A1bek?= Date: Wed, 31 Jan 2018 12:43:04 +0100 Subject: [PATCH 09/10] Make linter happy --- lib/components/App.jsx | 12 +++--- lib/components/DevicesWatcher.js | 65 ++++++++++++++++---------------- lib/components/Phone.jsx | 2 +- lib/components/Session.jsx | 39 ++++++++----------- lib/components/Settings.jsx | 10 ++--- 5 files changed, 61 insertions(+), 67 deletions(-) diff --git a/lib/components/App.jsx b/lib/components/App.jsx index 8fc7d8c..bf508d0 100644 --- a/lib/components/App.jsx +++ b/lib/components/App.jsx @@ -10,7 +10,7 @@ import Notifier from './Notifier'; import Login from './Login'; import Phone from './Phone'; import DevicesWatcher from './DevicesWatcher'; -import clone from 'clone' +import clone from 'clone'; const logger = new Logger('App'); @@ -161,17 +161,17 @@ export default class App extends React.Component handleMediaSettingsChange(key, deviceId) { - const settings = clone(this.state.settings) + const settings = clone(this.state.settings); - logger.debug('handleMediaSettingsChange() [key:%s] [old:%s] [new:%s]', key, settings.media[key], deviceId) + logger.debug('handleMediaSettingsChange() [key:%s] [old:%s] [new:%s]', key, settings.media[key], deviceId); // Merge media settings without mutating - settings.media[key] = deviceId - settingsManager.set(settings) + settings.media[key] = deviceId; + settingsManager.set(settings); this.setState({ settings: settingsManager.get() - }) + }); } handlePhoneExit() diff --git a/lib/components/DevicesWatcher.js b/lib/components/DevicesWatcher.js index 5805b73..8f35b74 100644 --- a/lib/components/DevicesWatcher.js +++ b/lib/components/DevicesWatcher.js @@ -14,26 +14,26 @@ export default class Dialer extends React.Component } reloadDevices() { - navigator.mediaDevices.enumerateDevices({audio:true,video:true}) + navigator.mediaDevices.enumerateDevices({audio:true, video:true}) .then(devices => { const devicesOld = localStorage.getItem('devices') ? JSON.parse(localStorage.getItem('devices')) - : devices + : devices; devicesOld.map((item) => { if (!devices.find(x => x.deviceId === item.deviceId)) { - this.deviceRemoved(item, devices) + this.deviceRemoved(item, devices); } - }) + }); devices.map((item) => { if (!devicesOld.find(x => x.deviceId === item.deviceId)) { - this.deviceAdded(item, devices) + this.deviceAdded(item, devices); } - }) + }); localStorage.setItem('devices', JSON.stringify(devices)); - }) + }); } deviceAdded(device) { @@ -45,7 +45,7 @@ export default class Dialer extends React.Component }, onMediaSettingsChange, onNotify - } = this.props + } = this.props; logger.debug('deviceAdded() [device:%o]', device); @@ -53,17 +53,17 @@ export default class Dialer extends React.Component level : 'success', title : 'Device added', message : device.label - }) + }); if (device.kind === 'audioinput') { - localStorage.setItem('audioInputOld', audioInput) - onMediaSettingsChange('audioInput', device.deviceId) + localStorage.setItem('audioInputOld', audioInput); + onMediaSettingsChange('audioInput', device.deviceId); } else if (device.kind === 'audiooutput') { - localStorage.setItem('audioOutputOld', audioOutput) - onMediaSettingsChange('audioOutput', device.deviceId) + localStorage.setItem('audioOutputOld', audioOutput); + onMediaSettingsChange('audioOutput', device.deviceId); } else if (device.kind === 'videoinput') { - localStorage.setItem('videoInputOld', videoInput) - onMediaSettingsChange('videoInput', device.deviceId) + localStorage.setItem('videoInputOld', videoInput); + onMediaSettingsChange('videoInput', device.deviceId); } } @@ -76,7 +76,7 @@ export default class Dialer extends React.Component }, onMediaSettingsChange, onNotify - } = this.props + } = this.props; logger.debug('deviceRemoved() [device:%o]', device); @@ -84,7 +84,7 @@ export default class Dialer extends React.Component level : 'error', title : 'Device removed', message : device.label - }) + }); // if (device.deviceId === audioInput) { // if (audioInput && devices.find(x => x.deviceId === audioInput)) { @@ -112,39 +112,39 @@ export default class Dialer extends React.Component // } if (device.deviceId === audioInput) { - const audioInputOld = localStorage.getItem('audioInputOld') + const audioInputOld = localStorage.getItem('audioInputOld'); if (audioInputOld && devices.find(x => x.deviceId === audioInputOld)) { - onMediaSettingsChange('audioInput', audioInputOld) - localStorage.removeItem('audioInputOld') + onMediaSettingsChange('audioInput', audioInputOld); + localStorage.removeItem('audioInputOld'); } else { - onMediaSettingsChange('audioInput', 'default') + onMediaSettingsChange('audioInput', 'default'); } } if (device.deviceId === audioOutput) { - const audioOutputOld = localStorage.getItem('audioOutputOld') + const audioOutputOld = localStorage.getItem('audioOutputOld'); if (audioOutputOld && devices.find(x => x.deviceId === audioOutputOld)) { - onMediaSettingsChange('audioOutput', audioOutputOld) - localStorage.removeItem('audioOutputOld') + onMediaSettingsChange('audioOutput', audioOutputOld); + localStorage.removeItem('audioOutputOld'); } else { - onMediaSettingsChange('audioOutput', 'default') + onMediaSettingsChange('audioOutput', 'default'); } } if (device.deviceId === videoInput) { - const videoInputOld = localStorage.getItem('videoInputOld') + const videoInputOld = localStorage.getItem('videoInputOld'); if (videoInputOld && devices.find(x => x.deviceId === videoInputOld)) { - onMediaSettingsChange('videoInput', videoInputOld) - localStorage.removeItem('videoInputOld') + onMediaSettingsChange('videoInput', videoInputOld); + localStorage.removeItem('videoInputOld'); } else { - onMediaSettingsChange('videoInput', null) + onMediaSettingsChange('videoInput', null); } } } componentWillMount() { - this.reloadDevices() - navigator.mediaDevices.ondevicechange = () => this.reloadDevices() + this.reloadDevices(); + navigator.mediaDevices.ondevicechange = () => this.reloadDevices(); } render() @@ -157,5 +157,6 @@ Dialer.propTypes = { settings: PropTypes.object.isRequired, onNotify: PropTypes.func.isRequired, - onMediaSettingsChange: PropTypes.func.isRequired + onMediaSettingsChange: PropTypes.func.isRequired, + mediaDevices: PropTypes.object.isRequired }; diff --git a/lib/components/Phone.jsx b/lib/components/Phone.jsx index 919a152..adbb43b 100644 --- a/lib/components/Phone.jsx +++ b/lib/components/Phone.jsx @@ -360,7 +360,7 @@ export default class Phone extends React.Component pcConfig }, onNotify - } = this.props + } = this.props; logger.debug('handleOutgoingCall() [uri:"%s"]', uri); diff --git a/lib/components/Session.jsx b/lib/components/Session.jsx index 8b4a147..2c92d95 100644 --- a/lib/components/Session.jsx +++ b/lib/components/Session.jsx @@ -54,8 +54,6 @@ export default class Session extends React.Component else if (!state.remoteHasVideo) noRemoteVideo =
no remote video
; - logger.debug('render() [output:%s]', props.mediaDevices.audioOutput) - return (
@@ -109,28 +107,25 @@ export default class Session extends React.Component componentDidUpdate(prevProps) { if (prevProps.mediaDevices.audioOutput !== this.props.mediaDevices.audioOutput) { - logger.debug('componentDidUpdate() audioOutput [old:%s] [device:%s]', prevProps.mediaDevices.audioOutput, this.props.mediaDevices.audioOutput) - this._attachStreamToElement(this.refs.remoteVideo, this.props.mediaDevices.audioOutput, this._remoteStream) + logger.debug('componentDidUpdate() audioOutput [old:%s] [device:%s]', prevProps.mediaDevices.audioOutput, this.props.mediaDevices.audioOutput); + this._attachStreamToElement(this.refs.remoteVideo, this.props.mediaDevices.audioOutput, this._remoteStream); } if (prevProps.mediaDevices.audioInput !== this.props.mediaDevices.audioInput) { - logger.debug('componentDidUpdate() audioInput [old:%s] [device:%s]', prevProps.mediaDevices.audioInput, this.props.mediaDevices.audioInput) - this._replaceLocalMedia() + logger.debug('componentDidUpdate() audioInput [old:%s] [device:%s]', prevProps.mediaDevices.audioInput, this.props.mediaDevices.audioInput); + this._replaceLocalMedia(); } } componentDidMount() { - logger.debug('componentDidMount()', this.props.mediaDevices.audioOutput); - this._mounted = true; let localVideo = this.refs.localVideo; const { session, mediaDevices: { - audioOutput, - audioInput + audioOutput } } = this.props; @@ -330,11 +325,11 @@ export default class Session extends React.Component { logger.debug('_handleRemoteStream() [stream:%o]', stream); - this._remoteStream = stream + this._remoteStream = stream; let remoteVideo = this.refs.remoteVideo; - const audioOutput = this.props.mediaDevices.audioOutput + const audioOutput = this.props.mediaDevices.audioOutput; // Display remote stream this._attachStreamToElement(remoteVideo, audioOutput, stream); @@ -396,8 +391,6 @@ export default class Session extends React.Component * @param {String} deviceId - DeviceId to use for audio output */ _attachStreamToElement(element, deviceId, stream) { - console.log('Re-attach stream', element, deviceId, stream) - /* 1. Pause stream before change 2. Redirect to device @@ -408,10 +401,10 @@ export default class Session extends React.Component element.pause(); element.setSinkId(deviceId) .then(() => logger.debug('Stream directed to device [device:%s]', deviceId)) - .catch(err => logger.error(err, deviceId)) + .catch(err => logger.error(err, deviceId)); if (element.srcObject !== stream) { - element.srcObject = stream + element.srcObject = stream; } element.play(); @@ -424,7 +417,7 @@ export default class Session extends React.Component const { session, mediaDevices - } = this.props + } = this.props; /* 1. Get new stream with proper constrains @@ -439,13 +432,13 @@ export default class Session extends React.Component }) .then(streamNew => { session.connection.getLocalStreams().forEach(stream => { - // Shouldn't we end tracks manually? - session.connection.removeStream(stream) - }) + stream.getTracks().forEach(track => track.stop()); + session.connection.removeStream(stream); + }); - return session.connection.addStream(streamNew) - }) - .then(() => session.renegotiate({ useUpdate: true })) + return session.connection.addStream(streamNew); + }) + .then(() => session.renegotiate({ useUpdate: true })); } } diff --git a/lib/components/Settings.jsx b/lib/components/Settings.jsx index 50dc07b..84eb675 100644 --- a/lib/components/Settings.jsx +++ b/lib/components/Settings.jsx @@ -25,7 +25,7 @@ export default class Settings extends React.Component this.state = { settings : clone(settings, false), devices : JSON.parse(localStorage.devices) - } + }; } componentDidMount() @@ -395,7 +395,7 @@ export default class Settings extends React.Component let settings = this.state.settings; settings.media.audioInput = value; - localStorage.removeItem('audioInputOld') + localStorage.removeItem('audioInputOld'); this.setState({ settings }); } @@ -405,7 +405,7 @@ export default class Settings extends React.Component let settings = this.state.settings; settings.media.audioOutput = value; - localStorage.removeItem('audioOutputOld') + localStorage.removeItem('audioOutputOld'); this.setState({ settings }); } @@ -415,7 +415,7 @@ export default class Settings extends React.Component let settings = this.state.settings; settings.media.audioRinging = value; - localStorage.removeItem('audioRingingOld') + localStorage.removeItem('audioRingingOld'); this.setState({ settings }); } @@ -425,7 +425,7 @@ export default class Settings extends React.Component let settings = this.state.settings; settings.media.videoInput = value; - localStorage.removeItem('videoInputOld') + localStorage.removeItem('videoInputOld'); this.setState({ settings }); } From c6fcb238af21a1382605668023ae79584baedf30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kal=C3=A1bek?= Date: Fri, 2 Feb 2018 11:49:15 +0100 Subject: [PATCH 10/10] Remove empty options --- lib/components/Settings.jsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/components/Settings.jsx b/lib/components/Settings.jsx index 84eb675..9ba065c 100644 --- a/lib/components/Settings.jsx +++ b/lib/components/Settings.jsx @@ -215,7 +215,6 @@ export default class Settings extends React.Component fullWidth onChange={this.handleChangeAudioInput.bind(this)} > - {devices.filter(x => x.kind === 'audioinput').map(x => ( ))} @@ -229,7 +228,6 @@ export default class Settings extends React.Component fullWidth onChange={this.handleChangeAudioOutput.bind(this)} > - {devices.filter(x => x.kind === 'audiooutput').map(x => ( ))} @@ -243,7 +241,6 @@ export default class Settings extends React.Component fullWidth onChange={this.handleChangeAudioOutputRinging.bind(this)} > - {devices.filter(x => x.kind === 'audiooutput').map(x => ( ))} @@ -257,7 +254,6 @@ export default class Settings extends React.Component fullWidth onChange={this.handleChangeVideoInput.bind(this)} > - {devices.filter(x => x.kind === 'videoinput').map(x => ( ))}