From 44f30405be19f5a732f339dc99501e1bc5fef53b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Cle=CC=81ment?= Date: Mon, 7 Oct 2019 15:58:02 +0200 Subject: [PATCH 1/5] Added RFC-4733 DTMF support --- src/components/SipProvider/index.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/components/SipProvider/index.ts b/src/components/SipProvider/index.ts index 6c99403..bf12437 100644 --- a/src/components/SipProvider/index.ts +++ b/src/components/SipProvider/index.ts @@ -55,6 +55,7 @@ export default class SipProvider extends React.Component< callStatus: CallStatus; callDirection: CallDirection | null; callCounterpart: string | null; + dtmfSender: RTCDTMFSender | null; rtcSession; } > { @@ -67,6 +68,7 @@ export default class SipProvider extends React.Component< answerCall: PropTypes.func, startCall: PropTypes.func, stopCall: PropTypes.func, + sendDTMF: PropTypes.func, }; public static propTypes = { @@ -119,6 +121,7 @@ export default class SipProvider extends React.Component< callStatus: CALL_STATUS_IDLE, callDirection: null, callCounterpart: null, + dtmfSender: null, }; this.ua = null; @@ -137,6 +140,7 @@ export default class SipProvider extends React.Component< status: this.state.callStatus, direction: this.state.callDirection, counterpart: this.state.callCounterpart, + dtmfSender: this.state.dtmfSender, }, registerSip: this.registerSip, unregisterSip: this.unregisterSip, @@ -144,6 +148,7 @@ export default class SipProvider extends React.Component< answerCall: this.answerCall, startCall: this.startCall, stopCall: this.stopCall, + sendDTMF: this.sendDTMF, }; } @@ -291,6 +296,15 @@ export default class SipProvider extends React.Component< this.ua.terminateSessions(); }; + public sendDTMF = ( value ) => { + if( this.state.callStatus === "callStatus/ACTIVE" && this.state.dtmfSender ) { + this.state.dtmfSender.insertDTMF(value); + } + else { + this.logger.debug("Warning:", "You are attempting to send DTMF, but there is no active call.") + } + }; + public reconfigureDebug() { const { debug } = this.props; @@ -471,6 +485,7 @@ export default class SipProvider extends React.Component< callStatus: CALL_STATUS_IDLE, callDirection: null, callCounterpart: null, + dtmfSender: null, }); }); @@ -484,6 +499,7 @@ export default class SipProvider extends React.Component< callStatus: CALL_STATUS_IDLE, callDirection: null, callCounterpart: null, + dtmfSender: null, }); }); @@ -495,6 +511,10 @@ export default class SipProvider extends React.Component< [ this.remoteAudio.srcObject, ] = rtcSession.connection.getRemoteStreams(); + + // Set up DTMF + this.setState({dtmfSender: rtcSession.connection.getSenders()[0].dtmf}); + // const played = this.remoteAudio.play(); const played = this.remoteAudio.play(); @@ -508,6 +528,7 @@ export default class SipProvider extends React.Component< this.remoteAudio.play(); }, 2000); }); + // this.setState({ dtmfSender: rtcSession.connection.createDTMFSender(rtcSession.getAudioTracks()[0])}); this.setState({ callStatus: CALL_STATUS_ACTIVE }); return; } From c78efda38065fc108a535ac7415960d6aae09732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Cle=CC=81ment?= Date: Tue, 8 Oct 2019 10:38:36 +0200 Subject: [PATCH 2/5] Ran "prettier" for consistent codestyle --- src/components/SipProvider/index.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/components/SipProvider/index.ts b/src/components/SipProvider/index.ts index bf12437..a721bc9 100644 --- a/src/components/SipProvider/index.ts +++ b/src/components/SipProvider/index.ts @@ -296,12 +296,17 @@ export default class SipProvider extends React.Component< this.ua.terminateSessions(); }; - public sendDTMF = ( value ) => { - if( this.state.callStatus === "callStatus/ACTIVE" && this.state.dtmfSender ) { + public sendDTMF = (value) => { + if ( + this.state.callStatus === "callStatus/ACTIVE" && + this.state.dtmfSender + ) { this.state.dtmfSender.insertDTMF(value); - } - else { - this.logger.debug("Warning:", "You are attempting to send DTMF, but there is no active call.") + } else { + this.logger.debug( + "Warning:", + "You are attempting to send DTMF, but there is no active call.", + ); } }; @@ -513,7 +518,9 @@ export default class SipProvider extends React.Component< ] = rtcSession.connection.getRemoteStreams(); // Set up DTMF - this.setState({dtmfSender: rtcSession.connection.getSenders()[0].dtmf}); + this.setState({ + dtmfSender: rtcSession.connection.getSenders()[0].dtmf, + }); // const played = this.remoteAudio.play(); const played = this.remoteAudio.play(); From dc3f671856d8054bdcbdb65435f97135f453246c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Cle=CC=81ment?= Date: Wed, 9 Oct 2019 15:05:30 +0200 Subject: [PATCH 3/5] Updated README with an explanation of sendDTMF() --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 9e95237..4237606 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ Child components get access to this context: answerCall: PropTypes.func, startCall: PropTypes.func, stopCall: PropTypes.func, + sendDTMF: PropTypes.func, } ``` @@ -120,6 +121,12 @@ To make calls, simply use these functions: The value for `destination` argument equals to the target SIP user without the host part (e.g. `+441234567890` or `bob`). The omitted host part is equal to host you’ve defined in `SipProvider` props (e.g. `sip.example.com`). +To send DTMF tones while in-call, you can use this function: + +`sendDTMF(value)` + +The DTMF implementation is **not** SIP INFO, but [RFC-4733](https://tools.ietf.org/html/rfc4733). + --- The values for `sip.status`, `sip.errorType`, `call.status` and `call.direction` can be imported as constants to make typos easier to detect: From 8685cc5e3126973bb59daa75ca1d31e95bf88c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Cle=CC=81ment?= Date: Wed, 16 Oct 2019 09:52:27 +0200 Subject: [PATCH 4/5] Added duration and interToneGap parameters to sendDTMF() --- README.md | 5 ++++- src/components/SipProvider/index.ts | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4237606..aa3093a 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,10 @@ The omitted host part is equal to host you’ve defined in `SipProvider` props ( To send DTMF tones while in-call, you can use this function: -`sendDTMF(value)` +`sendDTMF(tones)` + +You can pass as many tones as you want in a `string` (e.g. `sendDTMF("1234")`). +You may also specify `duration` and `interToneGap` in milliseconds, as `sendDTMF("1234", 100, 70)`. See [the MDN docs for `RTCDTMFSender.insertDTMF()`](https://developer.mozilla.org/en-US/docs/Web/API/RTCDTMFSender/insertDTMF) for further details. The DTMF implementation is **not** SIP INFO, but [RFC-4733](https://tools.ietf.org/html/rfc4733). diff --git a/src/components/SipProvider/index.ts b/src/components/SipProvider/index.ts index a721bc9..729c362 100644 --- a/src/components/SipProvider/index.ts +++ b/src/components/SipProvider/index.ts @@ -296,12 +296,12 @@ export default class SipProvider extends React.Component< this.ua.terminateSessions(); }; - public sendDTMF = (value) => { + public sendDTMF = (tones, duration = 100, interToneGap = 70) => { if ( this.state.callStatus === "callStatus/ACTIVE" && this.state.dtmfSender ) { - this.state.dtmfSender.insertDTMF(value); + this.state.dtmfSender.insertDTMF(tones, duration, interToneGap); } else { this.logger.debug( "Warning:", From 27cb1b4bf918e3587f02db03cd621326084b350a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Cle=CC=81ment?= Date: Wed, 16 Oct 2019 11:51:31 +0200 Subject: [PATCH 5/5] Fixed a bug where senders weren't close if the other party hangs up the call --- src/components/SipProvider/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/SipProvider/index.ts b/src/components/SipProvider/index.ts index 729c362..0b022b2 100644 --- a/src/components/SipProvider/index.ts +++ b/src/components/SipProvider/index.ts @@ -499,6 +499,11 @@ export default class SipProvider extends React.Component< return; } + // 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,