From 4031226bed98f937090ac516192026d0a9c98758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Cle=CC=81ment?= Date: Tue, 8 Oct 2019 13:02:13 +0200 Subject: [PATCH 1/6] Added properties for setting inbound/outbound audio devices --- src/components/SipProvider/index.ts | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/components/SipProvider/index.ts b/src/components/SipProvider/index.ts index 6c99403..4cea54c 100644 --- a/src/components/SipProvider/index.ts +++ b/src/components/SipProvider/index.ts @@ -47,6 +47,8 @@ export default class SipProvider extends React.Component< extraHeaders: ExtraHeaders; iceServers: IceServers; debug: boolean; + incomingAudioDeviceId: string; + outboundAudioDeviceId: string; }, { sipStatus: SipStatus; @@ -82,6 +84,8 @@ export default class SipProvider extends React.Component< extraHeaders: extraHeadersPropType, iceServers: iceServersPropType, debug: PropTypes.bool, + incomingAudioDeviceId: PropTypes.string, + outboundAudioDeviceId: PropTypes.string, children: PropTypes.node, }; @@ -99,6 +103,8 @@ export default class SipProvider extends React.Component< extraHeaders: { register: [], invite: [] }, iceServers: [], debug: false, + incomingAudioDeviceId: "", + outboundAudioDeviceId: "", children: null, }; @@ -158,6 +164,9 @@ export default class SipProvider extends React.Component< this.remoteAudio = window.document.createElement("audio"); this.remoteAudio.id = "sip-provider-audio"; + if (this.props.incomingAudioDeviceId) { + this.remoteAudio.setSinkId(this.props.incomingAudioDeviceId); + } window.document.body.appendChild(this.remoteAudio); this.reconfigureDebug(); @@ -492,6 +501,34 @@ export default class SipProvider extends React.Component< return; } + // Set outbound device, if provided + if (this.props.outboundAudioDeviceId) { + // Get the appropriate device and set the new stream + const constraints = { + audio: { + deviceId: { + exact: this.props.outboundAudioDeviceId, + }, + }, + }; + navigator.mediaDevices + .getUserMedia(constraints) + .then((stream) => { + rtcSession.connection + .getRemoteStreams() + .forEach((remoteStream) => { + rtcSession.connection.removeStream(remoteStream); + }); + rtcSession.connection.addStream(stream); + }) + .catch((e) => { + this.logger.warn( + "Warning: Invalid audio device passed. Caught error:", + ); + this.logger.warn(e); + }); + } + [ this.remoteAudio.srcObject, ] = rtcSession.connection.getRemoteStreams(); From acb8cc09a13d94866eb9bd9f1c0981148e47a183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Cle=CC=81ment?= Date: Wed, 9 Oct 2019 13:21:48 +0200 Subject: [PATCH 2/6] Added the ability to change audio device without reloading Without these changes, the browser will throw a DOMException when changing devices if one or more calls have been made already. I narrowed it down the remoteAudio.srcObject not being reset post-call. --- src/components/SipProvider/index.ts | 30 ++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/components/SipProvider/index.ts b/src/components/SipProvider/index.ts index 4cea54c..8a3a048 100644 --- a/src/components/SipProvider/index.ts +++ b/src/components/SipProvider/index.ts @@ -164,9 +164,6 @@ export default class SipProvider extends React.Component< this.remoteAudio = window.document.createElement("audio"); this.remoteAudio.id = "sip-provider-audio"; - if (this.props.incomingAudioDeviceId) { - this.remoteAudio.setSinkId(this.props.incomingAudioDeviceId); - } window.document.body.appendChild(this.remoteAudio); this.reconfigureDebug(); @@ -183,7 +180,9 @@ export default class SipProvider extends React.Component< this.props.pathname !== prevProps.pathname || this.props.user !== prevProps.user || this.props.password !== prevProps.password || - this.props.autoRegister !== prevProps.autoRegister + this.props.autoRegister !== prevProps.autoRegister || + this.props.incomingAudioDeviceId !== prevProps.incomingAudioDeviceId || + this.props.outboundAudioDeviceId !== prevProps.outboundAudioDeviceId ) { this.reinitializeJsSIP(); } @@ -318,7 +317,16 @@ export default class SipProvider extends React.Component< this.ua = null; } - const { host, port, pathname, user, password, autoRegister } = this.props; + const { + host, + port, + pathname, + user, + password, + autoRegister, + incomingAudioDeviceId, + outboundAudioDeviceId, + } = this.props; if (!host || !port || !user) { this.setState({ @@ -329,6 +337,10 @@ export default class SipProvider extends React.Component< return; } + if (incomingAudioDeviceId) { + this.remoteAudio.setSinkId(incomingAudioDeviceId); + } + try { const socket = new JsSIP.WebSocketInterface( `wss://${host}:${port}${pathname}`, @@ -475,6 +487,8 @@ export default class SipProvider extends React.Component< return; } + this.remoteAudio.srcObject = null; + this.setState({ rtcSession: null, callStatus: CALL_STATUS_IDLE, @@ -488,6 +502,8 @@ export default class SipProvider extends React.Component< return; } + this.remoteAudio.srcObject = null; + this.setState({ rtcSession: null, callStatus: CALL_STATUS_IDLE, @@ -502,12 +518,12 @@ export default class SipProvider extends React.Component< } // Set outbound device, if provided - if (this.props.outboundAudioDeviceId) { + if (outboundAudioDeviceId) { // Get the appropriate device and set the new stream const constraints = { audio: { deviceId: { - exact: this.props.outboundAudioDeviceId, + exact: outboundAudioDeviceId, }, }, }; From 2e18423fad31058167125342c8a5fbdd9d23376f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Cle=CC=81ment?= Date: Wed, 9 Oct 2019 14:57:43 +0200 Subject: [PATCH 3/6] Properly close microphone on call end --- src/components/SipProvider/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/SipProvider/index.ts b/src/components/SipProvider/index.ts index 8a3a048..e04fc73 100644 --- a/src/components/SipProvider/index.ts +++ b/src/components/SipProvider/index.ts @@ -296,6 +296,12 @@ export default class SipProvider extends React.Component< public stopCall = () => { this.setState({ callStatus: CALL_STATUS_STOPPING }); + + // Close senders, as these keep the microphone open according to browsers (and that keeps Bluetooth headphones from exiting headset mode) + this.state.rtcSession.connection.getSenders().forEach((sender) => { + sender.track.stop(); + }); + this.ua.terminateSessions(); }; @@ -487,8 +493,6 @@ export default class SipProvider extends React.Component< return; } - this.remoteAudio.srcObject = null; - this.setState({ rtcSession: null, callStatus: CALL_STATUS_IDLE, @@ -502,8 +506,6 @@ export default class SipProvider extends React.Component< return; } - this.remoteAudio.srcObject = null; - this.setState({ rtcSession: null, callStatus: CALL_STATUS_IDLE, From 6a2ab16afad0a5c1f0bc1405001d3a90375eb9cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Cle=CC=81ment?= Date: Wed, 9 Oct 2019 15:00:38 +0200 Subject: [PATCH 4/6] Updated README with new props --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9e95237..961f3b6 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ ReactDOM.render( { urls: 'turn:example.com', username: 'foo', credential: '1234' } ]} debug={false} // whether to output events to console; false by default + incomingAudioDeviceId={"default"} // default, or a deviceId obtained from navigator.mediaDevices.enumerateDevices() + outboundAudioDeviceId={"default"} // default, or a deviceId obtained from navigator.mediaDevices.enumerateDevices() > From 54a34e9a50ce78311ac1693cb4d7f5109b3ec95f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Cle=CC=81ment?= Date: Thu, 24 Oct 2019 10:02:12 +0200 Subject: [PATCH 5/6] Check if rtcSession.connection is set before removing tracks --- src/components/SipProvider/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/SipProvider/index.ts b/src/components/SipProvider/index.ts index e04fc73..b3398f8 100644 --- a/src/components/SipProvider/index.ts +++ b/src/components/SipProvider/index.ts @@ -297,10 +297,12 @@ export default class SipProvider extends React.Component< public stopCall = () => { this.setState({ callStatus: CALL_STATUS_STOPPING }); - // Close senders, as these keep the microphone open according to browsers (and that keeps Bluetooth headphones from exiting headset mode) - this.state.rtcSession.connection.getSenders().forEach((sender) => { - sender.track.stop(); - }); + if (this.state.rtcSession.connection) { + // Close senders, as these keep the microphone open according to browsers (and that keeps Bluetooth headphones from exiting headset mode) + this.state.rtcSession.connection.getSenders().forEach((sender) => { + sender.track.stop(); + }); + } this.ua.terminateSessions(); }; From 8697c12cb84a7d17559a77feaaef396111bd78e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Cle=CC=81ment?= Date: Thu, 24 Oct 2019 10:10:23 +0200 Subject: [PATCH 6/6] Actually check rtcSession.connection in the right places - oops --- src/components/SipProvider/index.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/components/SipProvider/index.ts b/src/components/SipProvider/index.ts index b3398f8..af14eea 100644 --- a/src/components/SipProvider/index.ts +++ b/src/components/SipProvider/index.ts @@ -296,14 +296,6 @@ export default class SipProvider extends React.Component< public stopCall = () => { this.setState({ callStatus: CALL_STATUS_STOPPING }); - - if (this.state.rtcSession.connection) { - // Close senders, as these keep the microphone open according to browsers (and that keeps Bluetooth headphones from exiting headset mode) - this.state.rtcSession.connection.getSenders().forEach((sender) => { - sender.track.stop(); - }); - } - this.ua.terminateSessions(); }; @@ -495,6 +487,13 @@ export default class SipProvider extends React.Component< return; } + if (this.state.rtcSession.connection) { + // Close senders, as these keep the microphone open according to browsers (and that keeps Bluetooth headphones from exiting headset mode) + this.state.rtcSession.connection.getSenders().forEach((sender) => { + sender.track.stop(); + }); + } + this.setState({ rtcSession: null, callStatus: CALL_STATUS_IDLE, @@ -508,6 +507,13 @@ export default class SipProvider extends React.Component< return; } + if (this.state.rtcSession.connection) { + // Close senders, as these keep the microphone open according to browsers (and that keeps Bluetooth headphones from exiting headset mode) + this.state.rtcSession.connection.getSenders().forEach((sender) => { + sender.track.stop(); + }); + } + this.setState({ rtcSession: null, callStatus: CALL_STATUS_IDLE,