diff --git a/dist/cordova-plugin-iosrtc.js b/dist/cordova-plugin-iosrtc.js index 4923b1ea..9e3c2569 100644 --- a/dist/cordova-plugin-iosrtc.js +++ b/dist/cordova-plugin-iosrtc.js @@ -1,5 +1,5 @@ /* - * cordova-plugin-iosrtc v3.0.1 + * cordova-plugin-iosrtc v3.0.2-pre * Cordova iOS plugin exposing the full WebRTC W3C JavaScript APIs * Copyright 2015-2016 IƱaki Baz Castillo at eFace2Face, inc. (https://eface2face.com) * License MIT @@ -461,7 +461,7 @@ function onEvent(data) { } } -},{"./MediaStreamTrack":5,"cordova/exec":undefined,"debug":15,"yaeti":20}],4:[function(require,module,exports){ +},{"./MediaStreamTrack":5,"cordova/exec":undefined,"debug":16,"yaeti":21}],4:[function(require,module,exports){ /** * Expose the MediaStreamRenderer class. */ @@ -813,7 +813,7 @@ function getElementPositionAndSize() { }; } -},{"./MediaStream":3,"cordova/exec":undefined,"debug":15,"random-number":19,"yaeti":20}],5:[function(require,module,exports){ +},{"./MediaStream":3,"cordova/exec":undefined,"debug":16,"random-number":20,"yaeti":21}],5:[function(require,module,exports){ /** * Expose the MediaStreamTrack class. */ @@ -933,7 +933,139 @@ function onEvent(data) { } } -},{"./enumerateDevices":10,"cordova/exec":undefined,"debug":15,"yaeti":20}],6:[function(require,module,exports){ +},{"./enumerateDevices":11,"cordova/exec":undefined,"debug":16,"yaeti":21}],6:[function(require,module,exports){ +/** + * Expose the RTCDTMFSender class. + */ +module.exports = RTCDTMFSender; + + +/** + * Dependencies. + */ +var + debug = require('debug')('iosrtc:RTCDTMFSender'), + debugerror = require('debug')('iosrtc:ERROR:RTCDTMFSender'), + exec = require('cordova/exec'), + randomNumber = require('random-number').generator({min: 10000, max: 99999, integer: true}), + EventTarget = require('yaeti').EventTarget; + + +debugerror.log = console.warn.bind(console); + + +function RTCDTMFSender(peerConnection, track) { + var self = this; + + // Make this an EventTarget. + EventTarget.call(this); + + debug('new() | [track:%o]', track); + + // Public atributes (accessed as read-only properties) + this._track = track; + // TODO: read these from the properties exposed in Swift? + this._duration = 100; + this._interToneGap = 70; + this._toneBuffer = ''; + + // Private attributes. + this.peerConnection = peerConnection; + this.dsId = randomNumber(); + + function onResultOK(data) { + onEvent.call(self, data); + } + + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_createDTMFSender', [this.peerConnection.pcId, this.dsId, this._track.id]); + +} + + +Object.defineProperty(RTCDTMFSender.prototype, 'canInsertDTMF', { + get: function () { + // TODO: check if it's muted or stopped? + return this._track && this._track.kind === 'audio' && this._track.enabled; + } +}); + + +Object.defineProperty(RTCDTMFSender.prototype, 'track', { + get: function () { + return this._track; + } +}); + + +Object.defineProperty(RTCDTMFSender.prototype, 'duration', { + get: function () { + return this._duration; + } +}); + + +Object.defineProperty(RTCDTMFSender.prototype, 'interToneGap', { + get: function () { + return this._interToneGap; + } +}); + + +Object.defineProperty(RTCDTMFSender.prototype, 'toneBuffer', { + get: function () { + return this._toneBuffer; + } +}); + + +RTCDTMFSender.prototype.insertDTMF = function (tones, duration, interToneGap) { + if (isClosed.call(this)) { + return; + } + + debug('insertDTMF() | [tones:%o, duration:%o, interToneGap:%o]', tones, duration, interToneGap); + + if (!tones) { + return; + } + + this._duration = duration || 100; + this._interToneGap = interToneGap || 70; + + var self = this; + + function onResultOK(data) { + onEvent.call(self, data); + } + + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCDTMFSender_insertDTMF', [this.peerConnection.pcId, this.dsId, tones, this._duration, this._interToneGap]); +}; + + +/** + * Private API. + */ + + +function isClosed() { + return this.peerConnection.signalingState === 'closed'; +} + + +function onEvent(data) { + var type = data.type, + event; + + debug('onEvent() | [type:%s, data:%o]', type, data); + + if (type === 'tonechange') { + event = new Event('tonechange'); + event.tone = data.tone; + this.dispatchEvent(event); + } +} + +},{"cordova/exec":undefined,"debug":16,"random-number":20,"yaeti":21}],7:[function(require,module,exports){ /** * Expose the RTCDataChannel class. */ @@ -1157,7 +1289,7 @@ function onEvent(data) { } } -},{"cordova/exec":undefined,"debug":15,"random-number":19,"yaeti":20}],7:[function(require,module,exports){ +},{"cordova/exec":undefined,"debug":16,"random-number":20,"yaeti":21}],8:[function(require,module,exports){ /** * Expose the RTCIceCandidate class. */ @@ -1173,7 +1305,7 @@ function RTCIceCandidate(data) { this.candidate = data.candidate; } -},{}],8:[function(require,module,exports){ +},{}],9:[function(require,module,exports){ (function (global){ /** * Expose the RTCPeerConnection class. @@ -1193,6 +1325,7 @@ var RTCSessionDescription = require('./RTCSessionDescription'), RTCIceCandidate = require('./RTCIceCandidate'), RTCDataChannel = require('./RTCDataChannel'), + RTCDTMFSender = require('./RTCDTMFSender'), MediaStream = require('./MediaStream'), Errors = require('./Errors'); @@ -1762,6 +1895,17 @@ RTCPeerConnection.prototype.createDataChannel = function (label, options) { }; +RTCPeerConnection.prototype.createDTMFSender = function (track) { + if (isClosed.call(this)) { + throw new Errors.InvalidStateError('peerconnection is closed'); + } + + debug('createDTMFSender() [track:%o]', track); + + return new RTCDTMFSender(this, track); +}; + + RTCPeerConnection.prototype.close = function () { if (isClosed.call(this)) { return; @@ -1895,7 +2039,7 @@ function onEvent(data) { } }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./Errors":1,"./MediaStream":3,"./RTCDataChannel":6,"./RTCIceCandidate":7,"./RTCSessionDescription":9,"cordova/exec":undefined,"debug":15,"random-number":19,"yaeti":20}],9:[function(require,module,exports){ +},{"./Errors":1,"./MediaStream":3,"./RTCDTMFSender":6,"./RTCDataChannel":7,"./RTCIceCandidate":8,"./RTCSessionDescription":10,"cordova/exec":undefined,"debug":16,"random-number":20,"yaeti":21}],10:[function(require,module,exports){ /** * Expose the RTCSessionDescription class. */ @@ -1910,7 +2054,7 @@ function RTCSessionDescription(data) { this.sdp = data.sdp; } -},{}],10:[function(require,module,exports){ +},{}],11:[function(require,module,exports){ /** * Expose the enumerateDevices function. */ @@ -1979,7 +2123,7 @@ function getMediaDeviceInfos(devices) { return mediaDeviceInfos; } -},{"./MediaDeviceInfo":2,"cordova/exec":undefined,"debug":15}],11:[function(require,module,exports){ +},{"./MediaDeviceInfo":2,"cordova/exec":undefined,"debug":16}],12:[function(require,module,exports){ /** * Expose the getUserMedia function. */ @@ -2150,7 +2294,7 @@ function getUserMedia(constraints) { exec(onResultOK, onResultError, 'iosrtcPlugin', 'getUserMedia', [newConstraints]); } -},{"./Errors":1,"./MediaStream":3,"cordova/exec":undefined,"debug":15}],12:[function(require,module,exports){ +},{"./Errors":1,"./MediaStream":3,"cordova/exec":undefined,"debug":16}],13:[function(require,module,exports){ (function (global){ /** * Variables. @@ -2287,7 +2431,7 @@ function dump() { } }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./MediaStream":3,"./MediaStreamTrack":5,"./RTCIceCandidate":7,"./RTCPeerConnection":8,"./RTCSessionDescription":9,"./enumerateDevices":10,"./getUserMedia":11,"./rtcninjaPlugin":13,"./videoElementsHandler":14,"cordova/exec":undefined,"debug":15,"domready":18}],13:[function(require,module,exports){ +},{"./MediaStream":3,"./MediaStreamTrack":5,"./RTCIceCandidate":8,"./RTCPeerConnection":9,"./RTCSessionDescription":10,"./enumerateDevices":11,"./getUserMedia":12,"./rtcninjaPlugin":14,"./videoElementsHandler":15,"cordova/exec":undefined,"debug":16,"domready":18}],14:[function(require,module,exports){ /** * Expose the rtcninjaPlugin object. */ @@ -2319,7 +2463,7 @@ function attachMediaStream(element, stream) { return element; } -},{"./MediaStreamTrack":5,"./RTCIceCandidate":7,"./RTCPeerConnection":8,"./RTCSessionDescription":9,"./enumerateDevices":10,"./getUserMedia":11}],14:[function(require,module,exports){ +},{"./MediaStreamTrack":5,"./RTCIceCandidate":8,"./RTCPeerConnection":9,"./RTCSessionDescription":10,"./enumerateDevices":11,"./getUserMedia":12}],15:[function(require,module,exports){ (function (global){ /** * Expose a function that must be called when the library is loaded. @@ -2657,7 +2801,7 @@ function releaseMediaStreamRenderer(video) { } }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./MediaStreamRenderer":4,"debug":15}],15:[function(require,module,exports){ +},{"./MediaStreamRenderer":4,"debug":16}],16:[function(require,module,exports){ /** * This is the web browser implementation of `debug()`. @@ -2827,7 +2971,7 @@ function localstorage(){ } catch (e) {} } -},{"./debug":16}],16:[function(require,module,exports){ +},{"./debug":17}],17:[function(require,module,exports){ /** * This is the common logic for both the Node.js and web browser @@ -3026,7 +3170,39 @@ function coerce(val) { return val; } -},{"ms":17}],17:[function(require,module,exports){ +},{"ms":19}],18:[function(require,module,exports){ +/*! + * domready (c) Dustin Diaz 2014 - License MIT + */ +!function (name, definition) { + + if (typeof module != 'undefined') module.exports = definition() + else if (typeof define == 'function' && typeof define.amd == 'object') define(definition) + else this[name] = definition() + +}('domready', function () { + + var fns = [], listener + , doc = document + , hack = doc.documentElement.doScroll + , domContentLoaded = 'DOMContentLoaded' + , loaded = (hack ? /^loaded|^c/ : /^loaded|^i|^c/).test(doc.readyState) + + + if (!loaded) + doc.addEventListener(domContentLoaded, listener = function () { + doc.removeEventListener(domContentLoaded, listener) + loaded = 1 + while (listener = fns.shift()) listener() + }) + + return function (fn) { + loaded ? setTimeout(fn, 0) : fns.push(fn) + } + +}); + +},{}],19:[function(require,module,exports){ /** * Helpers. */ @@ -3153,39 +3329,7 @@ function plural(ms, n, name) { return Math.ceil(ms / n) + ' ' + name + 's'; } -},{}],18:[function(require,module,exports){ -/*! - * domready (c) Dustin Diaz 2014 - License MIT - */ -!function (name, definition) { - - if (typeof module != 'undefined') module.exports = definition() - else if (typeof define == 'function' && typeof define.amd == 'object') define(definition) - else this[name] = definition() - -}('domready', function () { - - var fns = [], listener - , doc = document - , hack = doc.documentElement.doScroll - , domContentLoaded = 'DOMContentLoaded' - , loaded = (hack ? /^loaded|^c/ : /^loaded|^i|^c/).test(doc.readyState) - - - if (!loaded) - doc.addEventListener(domContentLoaded, listener = function () { - doc.removeEventListener(domContentLoaded, listener) - loaded = 1 - while (listener = fns.shift()) listener() - }) - - return function (fn) { - loaded ? setTimeout(fn, 0) : fns.push(fn) - } - -}); - -},{}],19:[function(require,module,exports){ +},{}],20:[function(require,module,exports){ void function(root){ function defaults(options){ @@ -3231,13 +3375,13 @@ void function(root){ module.exports.defaults = defaults }(this) -},{}],20:[function(require,module,exports){ +},{}],21:[function(require,module,exports){ module.exports = { EventTarget : require('./lib/EventTarget'), Event : require('./lib/Event') }; -},{"./lib/Event":21,"./lib/EventTarget":22}],21:[function(require,module,exports){ +},{"./lib/Event":22,"./lib/EventTarget":23}],22:[function(require,module,exports){ (function (global){ /** * In browsers export the native Event interface. @@ -3246,7 +3390,7 @@ module.exports = { module.exports = global.Event; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],22:[function(require,module,exports){ +},{}],23:[function(require,module,exports){ /** * Expose the _EventTarget class. */ @@ -3367,5 +3511,5 @@ function _dispatchEvent(event) { return !event.defaultPrevented; } -},{}]},{},[12])(12) +},{}]},{},[13])(13) }); \ No newline at end of file diff --git a/docs/BuildingLibWebRTC.md b/docs/BuildingLibWebRTC.md index f2252340..6aa32c8e 100644 --- a/docs/BuildingLibWebRTC.md +++ b/docs/BuildingLibWebRTC.md @@ -14,13 +14,13 @@ $ cd webrtc-build-scripts * Follow the project steps and fetch the *libwebrtc* source code: ```bash $ source ios/build.sh -$ WEBRTC_RELEASE=true +$ export WEBRTC_RELEASE=true $ get_webrtc ``` * Set a specific *libwebrtc* version: ```bash -$ update2Revision 12558 +$ update2Revision 13311 ``` @@ -34,11 +34,30 @@ Since we live in the present we need those two events to properly implement the * Apply the patch provided at `extra/libwebrtc-objc-iosrtc.patch` into the Objective-C source code of *libwebrtc*: ```bash -$ cd ios/webrtc/src/talk/app/webrtc/objc +$ cd ios/webrtc/src $ patch -p1 < $PATH_TO_CORDOVA_PLUGIN_IOSRTC/extra/libwebrtc-objc-iosrtc.patch ``` -* If desired, enable native H264 support by setting `'use_objc_h264%': 1` in `webrtc-build-scripts/ios/webrtc/src/webrtc/build/common.gypi`. + +### Apply `libwebrtc-objc-iosrtc-dtmf.patch` + +Google's *libwebrtc* Objective-C wrapper does not implement a `RTCPeerConnection.createDTMF` function, which makes it impossible to send DTMF from the Objective-C wrapper, and in turn this plugin. Google [will not implement this](https://bugs.chromium.org/p/webrtc/issues/detail?id=4180) +because a new API was devised. Since we need to use this today, **AG Projects** created this patch exposing an API analogous to that on the C++ layer. + +* Apply the patch provided at `extra/libwebrtc-objc-iosrtc-dtmf.patch` into the Objective-C source code of *libwebrtc*: +```bash +$ cd ios/webrtc/src +$ patch -p1 < $PATH_TO_CORDOVA_PLUGIN_IOSRTC/extra/libwebrtc-objc-iosrtc-dtmf.patch +``` + + +### Misc + +In order to enable H.264 support and compile libWebRTC properly, apply `libwebrtc-objc-iosrtc-misc.patch`. + +```bash +$ cd ios/webrtc/src +$ patch -p1 < $PATH_TO_CORDOVA_PLUGIN_IOSRTC/extra/libwebrtc-objc-iosrtc-misc.patch ### Build *libwebrtc* diff --git a/extra/libwebrtc-objc-iosrtc-dtmf.patch b/extra/libwebrtc-objc-iosrtc-dtmf.patch new file mode 100644 index 00000000..79ea693f --- /dev/null +++ b/extra/libwebrtc-objc-iosrtc-dtmf.patch @@ -0,0 +1,336 @@ +--- a/talk/app/webrtc/legacy_objc_api.gyp ++++ b/talk/app/webrtc/legacy_objc_api.gyp +@@ -41,6 +41,8 @@ + 'objc/RTCAudioTrack.mm', + 'objc/RTCDataChannel+Internal.h', + 'objc/RTCDataChannel.mm', ++ 'objc/RTCDTMFSender+Internal.h', ++ 'objc/RTCDTMFSender.mm', + 'objc/RTCEnumConverter.h', + 'objc/RTCEnumConverter.mm', + 'objc/RTCI420Frame+Internal.h', +@@ -84,6 +86,7 @@ + 'objc/public/RTCAudioSource.h', + 'objc/public/RTCAudioTrack.h', + 'objc/public/RTCDataChannel.h', ++ 'objc/public/RTCDTMFSender.h', + 'objc/public/RTCFileLogger.h', + 'objc/public/RTCI420Frame.h', + 'objc/public/RTCICECandidate.h', +--- /dev/null ++++ b/talk/app/webrtc/objc/RTCDTMFSender+Internal.h +@@ -0,0 +1,42 @@ ++/* ++ * libjingle ++ * Copyright 2014 Google Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright notice, ++ * this list of conditions and the following disclaimer in the documentation ++ * and/or other materials provided with the distribution. ++ * 3. The name of the author may not be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED ++ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO ++ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, ++ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; ++ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, ++ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR ++ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ++ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#import "RTCDTMFSender.h" ++ ++#include "webrtc/api/dtmfsenderinterface.h" ++#include "webrtc/base/scoped_ref_ptr.h" ++ ++ ++@interface RTCDTMFSender (Internal) ++ ++@property(nonatomic, readonly) ++ rtc::scoped_refptr dtmfSender; ++ ++- (instancetype)initWithDtmfSender: ++ (rtc::scoped_refptr)dtmfSender; ++ ++@end +--- /dev/null ++++ b/talk/app/webrtc/objc/RTCDTMFSender.mm +@@ -0,0 +1,150 @@ ++/* ++ * libjingle ++ * Copyright 2014 Google Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright notice, ++ * this list of conditions and the following disclaimer in the documentation ++ * and/or other materials provided with the distribution. ++ * 3. The name of the author may not be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED ++ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO ++ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, ++ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; ++ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, ++ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR ++ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ++ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#if !defined(__has_feature) || !__has_feature(objc_arc) ++#error "This file requires ARC support." ++#endif ++ ++#include "RTCDTMFSender.h" ++#include "RTCAudioTrack+Internal.h" ++#include "RTCMediaStreamTrack+Internal.h" ++ ++#include ++ ++#include "webrtc/api/dtmfsenderinterface.h" ++ ++ ++// utilities ++static NSString* NSStringFromStdString(const std::string& stdString) { ++ // std::string may contain null termination character so we construct ++ // using length. ++ return [[NSString alloc] initWithBytes:stdString.data() ++ length:stdString.length() ++ encoding:NSUTF8StringEncoding]; ++} ++ ++static std::string StdStringFromNSString(NSString* nsString) { ++ NSData* charData = [nsString dataUsingEncoding:NSUTF8StringEncoding]; ++ return std::string(reinterpret_cast([charData bytes]), ++ [charData length]); ++} ++ ++ ++namespace webrtc { ++ ++class RTCDTMFSenderObserver : public DtmfSenderObserverInterface { ++ public: ++ RTCDTMFSenderObserver(RTCDTMFSender* sender) { _sender = sender; } ++ ++ void OnToneChange(const std::string& tone) override { ++ if (!_sender.delegate) { ++ return; ++ } ++ [_sender.delegate toneChange:NSStringFromStdString(tone)]; ++ } ++ ++ private: ++ __weak RTCDTMFSender* _sender; ++}; ++} ++ ++ ++@implementation RTCDTMFSender { ++ rtc::scoped_refptr _dtmfSender; ++ std::unique_ptr _observer; ++ BOOL _isObserverRegistered; ++} ++ ++- (void)dealloc { ++ // Handles unregistering the observer properly. ++ self.delegate = nil; ++} ++ ++- (NSString*)toneBuffer { ++ return NSStringFromStdString(_dtmfSender->tones()); ++} ++ ++- (BOOL)canInsertDTMF { ++ return _dtmfSender->CanInsertDtmf(); ++} ++ ++- (NSInteger)duration { ++ return _dtmfSender->duration(); ++} ++ ++- (NSInteger)interToneGap { ++ return _dtmfSender->inter_tone_gap(); ++} ++ ++- (RTCAudioTrack*)track { ++ rtc::scoped_refptr audioTrack( ++ const_cast(_dtmfSender->track())); ++ if (audioTrack) { ++ return [[RTCAudioTrack alloc] initWithMediaTrack:audioTrack]; ++ } ++ return nil; ++} ++ ++- (void)setDelegate:(id)delegate { ++ if (_delegate == delegate) { ++ return; ++ } ++ if (_isObserverRegistered) { ++ _dtmfSender->UnregisterObserver(); ++ _isObserverRegistered = NO; ++ } ++ _delegate = delegate; ++ if (_delegate) { ++ _dtmfSender->RegisterObserver(_observer.get()); ++ _isObserverRegistered = YES; ++ } ++} ++ ++- (BOOL)insertDTMF:(NSString*)tones withDuration:(NSInteger)duration andInterToneGap:(NSInteger)interToneGap { ++ return _dtmfSender->InsertDtmf(StdStringFromNSString(tones), duration, interToneGap); ++} ++ ++@end ++ ++@implementation RTCDTMFSender (Internal) ++ ++- (instancetype)initWithDtmfSender: ++ (rtc::scoped_refptr) ++ dtmfSender { ++ NSAssert(dtmfSender != NULL, @"dtmfSender cannot be NULL"); ++ if (self = [super init]) { ++ _dtmfSender = dtmfSender; ++ _observer.reset(new webrtc::RTCDTMFSenderObserver(self)); ++ } ++ return self; ++} ++ ++- (rtc::scoped_refptr)dtmfSender { ++ return _dtmfSender; ++} ++ ++@end +--- a/talk/app/webrtc/objc/RTCPeerConnection.mm ++++ b/talk/app/webrtc/objc/RTCPeerConnection.mm +@@ -31,7 +31,9 @@ + + #import "RTCPeerConnection+Internal.h" + ++#import "RTCAudioTrack+Internal.h" + #import "RTCDataChannel+Internal.h" ++#import "RTCDTMFSender+Internal.h" + #import "RTCEnumConverter.h" + #import "RTCICECandidate+Internal.h" + #import "RTCICEServer+Internal.h" +@@ -171,6 +173,13 @@ - (RTCDataChannel*)createDataChannelWithLabel:(NSString*)label + return [[RTCDataChannel alloc] initWithDataChannel:dataChannel]; + } + ++- (RTCDTMFSender*)createDTMFSenderForTrack:(RTCAudioTrack*)track { ++ rtc::scoped_refptr dtmfSender = ++ self.peerConnection->CreateDtmfSender(track.audioTrack); ++ return dtmfSender ? [[RTCDTMFSender alloc] initWithDtmfSender:dtmfSender] ++ : nil; ++} ++ + - (void)createAnswerWithDelegate:(id)delegate + constraints:(RTCMediaConstraints*)constraints { + rtc::scoped_refptr +--- /dev/null ++++ b/talk/app/webrtc/objc/public/RTCDTMFSender.h +@@ -0,0 +1,66 @@ ++/* ++ * libjingle ++ * Copyright 2014 Google Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright notice, ++ * this list of conditions and the following disclaimer in the documentation ++ * and/or other materials provided with the distribution. ++ * 3. The name of the author may not be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED ++ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO ++ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, ++ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; ++ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, ++ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR ++ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ++ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#import ++ ++#import "RTCAudioTrack.h" ++ ++NS_ASSUME_NONNULL_BEGIN ++ ++@class RTCDTMFSender; ++// Protocol for receving tone change events. ++@protocol RTCDTMFSenderDelegate ++ ++// Called when a DTMF tone is played out. ++- (void)toneChange:(NSString*)tone; ++ ++@end ++ ++// ObjectiveC wrapper for a DtmfSender object. ++// See webrtc/api/dtmfsenderinterface.h ++@interface RTCDTMFSender : NSObject ++ ++@property(nonatomic, readonly) BOOL canInsertDTMF; ++@property(nonatomic, readonly) NSString* toneBuffer; ++@property(nonatomic, readonly) NSInteger duration; ++@property(nonatomic, readonly) NSInteger interToneGap; ++@property(nonatomic, weak) id delegate; ++// The track associated with this DTMF sender. This property ++// returns a copy of the RTCMediaStreamTrack ++@property(nonatomic, copy, nullable) RTCAudioTrack *track; ++ ++- (BOOL)insertDTMF:(NSString*)tones withDuration:(NSInteger)duration andInterToneGap:(NSInteger)interToneGap; ++ ++#ifndef DOXYGEN_SHOULD_SKIP_THIS ++// Disallow init and don't add to documentation ++- (id)init __attribute__(( ++ unavailable("init is not a supported initializer for this class."))); ++#endif /* DOXYGEN_SHOULD_SKIP_THIS */ ++ ++@end ++ ++NS_ASSUME_NONNULL_END +--- a/talk/app/webrtc/objc/public/RTCPeerConnection.h ++++ b/talk/app/webrtc/objc/public/RTCPeerConnection.h +@@ -27,9 +27,11 @@ + + #import "RTCPeerConnectionDelegate.h" + ++@class RTCAudioTrack; + @class RTCConfiguration; + @class RTCDataChannel; + @class RTCDataChannelInit; ++@class RTCDTMFSender; + @class RTCICECandidate; + @class RTCICEServers; + @class RTCMediaConstraints; +@@ -76,6 +78,9 @@ + - (RTCDataChannel*)createDataChannelWithLabel:(NSString*)label + config:(RTCDataChannelInit*)config; + ++// Create a DTMF sender. ++- (RTCDTMFSender*)createDTMFSenderForTrack:(RTCAudioTrack*)audioTrack; ++ + // Create a new offer. + // Success or failure will be reported via RTCSessionDescriptionDelegate. + - (void)createOfferWithDelegate:(id)delegate diff --git a/extra/libwebrtc-objc-iosrtc-misc.patch b/extra/libwebrtc-objc-iosrtc-misc.patch new file mode 100644 index 00000000..31446e6b --- /dev/null +++ b/extra/libwebrtc-objc-iosrtc-misc.patch @@ -0,0 +1,22 @@ +--- a/webrtc/build/common.gypi ++++ b/webrtc/build/common.gypi +@@ -155,7 +155,7 @@ + + # Enable this to use HW H.264 encoder/decoder on iOS/Mac PeerConnections. + # Enabling this may break interop with Android clients that support H264. +- 'use_objc_h264%': 0, ++ 'use_objc_h264%': 1, + + # Enable this to prevent extern symbols from being hidden on iOS builds. + # The chromium settings we inherit hide symbols by default on Release +--- a/webrtc/system_wrappers/system_wrappers.gyp ++++ b/webrtc/system_wrappers/system_wrappers.gyp +@@ -76,6 +76,8 @@ + 'source/trace_posix.h', + 'source/trace_win.cc', + 'source/trace_win.h', ++ 'include/metrics_default.h', ++ 'source/metrics_default.cc', + ], + 'conditions': [ + ['enable_data_logging==1', { diff --git a/extra/libwebrtc-objc-iosrtc.patch b/extra/libwebrtc-objc-iosrtc.patch index 5b357c96..57953817 100644 --- a/extra/libwebrtc-objc-iosrtc.patch +++ b/extra/libwebrtc-objc-iosrtc.patch @@ -1,10 +1,9 @@ -diff -ruN objc/RTCMediaStream+Internal.h objc.iosrtc/RTCMediaStream+Internal.h ---- objc/RTCMediaStream+Internal.h 2016-02-16 18:13:43.000000000 +0100 -+++ objc.iosrtc/RTCMediaStream+Internal.h 2016-02-16 18:23:22.000000000 +0100 +--- a/talk/app/webrtc/objc/RTCMediaStream+Internal.h ++++ b/talk/app/webrtc/objc/RTCMediaStream+Internal.h @@ -37,4 +37,12 @@ - (id)initWithMediaStream: (rtc::scoped_refptr)mediaStream; - + +- (void)update; +- (BOOL)hasNativeAudioTrack:(RTCAudioTrack *)objcTrack; +- (BOOL)hasNativeVideoTrack:(RTCVideoTrack *)objcTrack; @@ -14,20 +13,19 @@ diff -ruN objc/RTCMediaStream+Internal.h objc.iosrtc/RTCMediaStream+Internal.h + (rtc::scoped_refptr)nativeTrack; + @end -diff -ruN objc/RTCMediaStream.mm objc.iosrtc/RTCMediaStream.mm ---- objc/RTCMediaStream.mm 2016-02-16 18:13:43.000000000 +0100 -+++ objc.iosrtc/RTCMediaStream.mm 2016-02-16 18:34:04.000000000 +0100 +--- a/talk/app/webrtc/objc/RTCMediaStream.mm ++++ b/talk/app/webrtc/objc/RTCMediaStream.mm @@ -31,16 +31,31 @@ - + #import "RTCMediaStream+Internal.h" - + -#import "RTCAudioTrack+Internal.h" #import "RTCMediaStreamTrack+Internal.h" +#import "RTCAudioTrack+Internal.h" #import "RTCVideoTrack+Internal.h" - + #include "webrtc/api/mediastreaminterface.h" - + +namespace webrtc { + class RTCMediaStreamObserver : public ObserverInterface { + public: @@ -46,27 +44,27 @@ diff -ruN objc/RTCMediaStream.mm objc.iosrtc/RTCMediaStream.mm NSMutableArray* _audioTracks; NSMutableArray* _videoTracks; rtc::scoped_refptr _mediaStream; -+ rtc::scoped_ptr _observer; ++ std::unique_ptr _observer; } - + - (NSString*)description { -@@ -64,7 +79,6 @@ - +@@ -64,7 +79,6 @@ - (NSString*)label { + - (BOOL)addAudioTrack:(RTCAudioTrack*)track { if (self.mediaStream->AddTrack(track.audioTrack)) { - [_audioTracks addObject:track]; return YES; } return NO; -@@ -72,7 +86,6 @@ - +@@ -72,7 +86,6 @@ - (BOOL)addAudioTrack:(RTCAudioTrack*)track { + - (BOOL)addVideoTrack:(RTCVideoTrack*)track { if (self.mediaStream->AddTrack(track.nativeVideoTrack)) { - [_videoTracks addObject:track]; return YES; } return NO; -@@ -83,7 +96,6 @@ +@@ -83,7 +96,6 @@ - (BOOL)removeAudioTrack:(RTCAudioTrack*)track { NSAssert(index != NSNotFound, @"|removeAudioTrack| called on unexpected RTCAudioTrack"); if (index != NSNotFound && self.mediaStream->RemoveTrack(track.audioTrack)) { @@ -74,7 +72,7 @@ diff -ruN objc/RTCMediaStream.mm objc.iosrtc/RTCMediaStream.mm return YES; } return NO; -@@ -93,9 +105,7 @@ +@@ -93,9 +105,7 @@ - (BOOL)removeVideoTrack:(RTCVideoTrack*)track { NSUInteger index = [_videoTracks indexOfObjectIdenticalTo:track]; NSAssert(index != NSNotFound, @"|removeAudioTrack| called on unexpected RTCVideoTrack"); @@ -85,19 +83,19 @@ diff -ruN objc/RTCMediaStream.mm objc.iosrtc/RTCMediaStream.mm return YES; } return NO; -@@ -119,6 +129,8 @@ +@@ -119,6 +129,8 @@ - (id)initWithMediaStream: _audioTracks = [NSMutableArray arrayWithCapacity:audio_tracks.size()]; _videoTracks = [NSMutableArray arrayWithCapacity:video_tracks.size()]; _mediaStream = mediaStream; + _observer.reset(new webrtc::RTCMediaStreamObserver(self)); + _mediaStream->RegisterObserver(_observer.get()); - + for (size_t i = 0; i < audio_tracks.size(); ++i) { rtc::scoped_refptr track = -@@ -139,8 +151,157 @@ +@@ -139,8 +151,157 @@ - (id)initWithMediaStream: return self; } - + +- (void)dealloc { + _mediaStream->UnregisterObserver(_observer.get()); +} @@ -105,7 +103,7 @@ diff -ruN objc/RTCMediaStream.mm objc.iosrtc/RTCMediaStream.mm - (rtc::scoped_refptr)mediaStream { return _mediaStream; } - + +- (void)update { + size_t i; + webrtc::AudioTrackVector native_audio_tracks = _mediaStream->GetAudioTracks(); @@ -252,11 +250,10 @@ diff -ruN objc/RTCMediaStream.mm objc.iosrtc/RTCMediaStream.mm +} + @end -diff -ruN objc/public/RTCMediaStream.h objc.iosrtc/public/RTCMediaStream.h ---- objc/public/RTCMediaStream.h 2016-02-16 18:13:43.000000000 +0100 -+++ objc.iosrtc/public/RTCMediaStream.h 2016-02-16 18:24:45.000000000 +0100 +--- a/talk/app/webrtc/objc/public/RTCMediaStream.h ++++ b/talk/app/webrtc/objc/public/RTCMediaStream.h @@ -29,6 +29,21 @@ - + @class RTCAudioTrack; @class RTCVideoTrack; +@class RTCMediaStream; @@ -274,7 +271,7 @@ diff -ruN objc/public/RTCMediaStream.h objc.iosrtc/public/RTCMediaStream.h + (RTCMediaStream*)mediaStream track:(RTCMediaStreamTrack*)track; + +@end - + // RTCMediaStream is an ObjectiveC wrapper for MediaStreamInterface. @interface RTCMediaStream : NSObject @@ -36,6 +51,7 @@ @@ -282,6 +279,6 @@ diff -ruN objc/public/RTCMediaStream.h objc.iosrtc/public/RTCMediaStream.h @property(nonatomic, strong, readonly) NSArray *videoTracks; @property(nonatomic, strong, readonly) NSString *label; +@property(nonatomic, weak) id delegate; - + - (BOOL)addAudioTrack:(RTCAudioTrack *)track; - (BOOL)addVideoTrack:(RTCVideoTrack *)track; diff --git a/js/RTCDTMFSender.js b/js/RTCDTMFSender.js new file mode 100644 index 00000000..5810e35e --- /dev/null +++ b/js/RTCDTMFSender.js @@ -0,0 +1,130 @@ +/** + * Expose the RTCDTMFSender class. + */ +module.exports = RTCDTMFSender; + + +/** + * Dependencies. + */ +var + debug = require('debug')('iosrtc:RTCDTMFSender'), + debugerror = require('debug')('iosrtc:ERROR:RTCDTMFSender'), + exec = require('cordova/exec'), + randomNumber = require('random-number').generator({min: 10000, max: 99999, integer: true}), + EventTarget = require('yaeti').EventTarget; + + +debugerror.log = console.warn.bind(console); + + +function RTCDTMFSender(peerConnection, track) { + var self = this; + + // Make this an EventTarget. + EventTarget.call(this); + + debug('new() | [track:%o]', track); + + // Public atributes (accessed as read-only properties) + this._track = track; + // TODO: read these from the properties exposed in Swift? + this._duration = 100; + this._interToneGap = 70; + this._toneBuffer = ''; + + // Private attributes. + this.peerConnection = peerConnection; + this.dsId = randomNumber(); + + function onResultOK(data) { + onEvent.call(self, data); + } + + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_createDTMFSender', [this.peerConnection.pcId, this.dsId, this._track.id]); + +} + + +Object.defineProperty(RTCDTMFSender.prototype, 'canInsertDTMF', { + get: function () { + // TODO: check if it's muted or stopped? + return this._track && this._track.kind === 'audio' && this._track.enabled; + } +}); + + +Object.defineProperty(RTCDTMFSender.prototype, 'track', { + get: function () { + return this._track; + } +}); + + +Object.defineProperty(RTCDTMFSender.prototype, 'duration', { + get: function () { + return this._duration; + } +}); + + +Object.defineProperty(RTCDTMFSender.prototype, 'interToneGap', { + get: function () { + return this._interToneGap; + } +}); + + +Object.defineProperty(RTCDTMFSender.prototype, 'toneBuffer', { + get: function () { + return this._toneBuffer; + } +}); + + +RTCDTMFSender.prototype.insertDTMF = function (tones, duration, interToneGap) { + if (isClosed.call(this)) { + return; + } + + debug('insertDTMF() | [tones:%o, duration:%o, interToneGap:%o]', tones, duration, interToneGap); + + if (!tones) { + return; + } + + this._duration = duration || 100; + this._interToneGap = interToneGap || 70; + + var self = this; + + function onResultOK(data) { + onEvent.call(self, data); + } + + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCDTMFSender_insertDTMF', [this.peerConnection.pcId, this.dsId, tones, this._duration, this._interToneGap]); +}; + + +/** + * Private API. + */ + + +function isClosed() { + return this.peerConnection.signalingState === 'closed'; +} + + +function onEvent(data) { + var type = data.type, + event; + + debug('onEvent() | [type:%s, data:%o]', type, data); + + if (type === 'tonechange') { + event = new Event('tonechange'); + event.tone = data.tone; + this.dispatchEvent(event); + } +} diff --git a/js/RTCPeerConnection.js b/js/RTCPeerConnection.js index 77706bd2..0dc3fe57 100644 --- a/js/RTCPeerConnection.js +++ b/js/RTCPeerConnection.js @@ -16,6 +16,7 @@ var RTCSessionDescription = require('./RTCSessionDescription'), RTCIceCandidate = require('./RTCIceCandidate'), RTCDataChannel = require('./RTCDataChannel'), + RTCDTMFSender = require('./RTCDTMFSender'), MediaStream = require('./MediaStream'), Errors = require('./Errors'); @@ -585,6 +586,17 @@ RTCPeerConnection.prototype.createDataChannel = function (label, options) { }; +RTCPeerConnection.prototype.createDTMFSender = function (track) { + if (isClosed.call(this)) { + throw new Errors.InvalidStateError('peerconnection is closed'); + } + + debug('createDTMFSender() [track:%o]', track); + + return new RTCDTMFSender(this, track); +}; + + RTCPeerConnection.prototype.close = function () { if (isClosed.call(this)) { return; diff --git a/lib/libWebRTC-LATEST-Universal-Release.a b/lib/libWebRTC-LATEST-Universal-Release.a index d364a59b..be44c38f 100644 Binary files a/lib/libWebRTC-LATEST-Universal-Release.a and b/lib/libWebRTC-LATEST-Universal-Release.a differ diff --git a/plugin.xml b/plugin.xml index f0fc1201..aef76df2 100644 --- a/plugin.xml +++ b/plugin.xml @@ -35,6 +35,7 @@ + @@ -72,6 +73,7 @@ + diff --git a/src/PluginRTCDTMFSender.swift b/src/PluginRTCDTMFSender.swift new file mode 100644 index 00000000..edea9280 --- /dev/null +++ b/src/PluginRTCDTMFSender.swift @@ -0,0 +1,65 @@ +import Foundation + + +class PluginRTCDTMFSender : NSObject, RTCDTMFSenderDelegate { + var rtcDTMFSender: RTCDTMFSender? + var eventListener: ((data: NSDictionary) -> Void)? + + + /** + * Constructor for pc.createDTMFSender(). + */ + init( + rtcPeerConnection: RTCPeerConnection, + track: RTCMediaStreamTrack, + eventListener: (data: NSDictionary) -> Void + ) { + NSLog("PluginRTCDTMFSender#init()") + + self.eventListener = eventListener + self.rtcDTMFSender = rtcPeerConnection.createDTMFSenderForTrack(track as? RTCAudioTrack) + + if self.rtcDTMFSender == nil { + NSLog("PluginRTCDTMFSender#init() | rtcPeerConnection.createDTMFSenderForTrack() failed") + return + } + } + + + deinit { + NSLog("PluginRTCDTMFSender#deinit()") + } + + + func run() { + NSLog("PluginRTCDTMFSender#run()") + + self.rtcDTMFSender!.delegate = self + } + + + func insertDTMF(tones: String, duration: Int, interToneGap: Int) { + NSLog("PluginRTCDTMFSender#insertDTMF()") + + let result = self.rtcDTMFSender!.insertDTMF(tones, withDuration: duration, andInterToneGap: interToneGap) + if !result { + NSLog("PluginRTCDTMFSender#indertDTMF() | RTCDTMFSender#indertDTMF() failed") + } + } + + + /** + * Methods inherited from RTCDTMFSenderDelegate. + */ + + func toneChange(tone: String) { + NSLog("PluginRTCDTMFSender | tone change [tone:%@]", tone) + + if self.eventListener != nil { + self.eventListener!(data: [ + "type": "tonechange", + "tone": tone + ]) + } + } +} diff --git a/src/PluginRTCPeerConnection.swift b/src/PluginRTCPeerConnection.swift index 8dd418f4..3ba2ef4d 100644 --- a/src/PluginRTCPeerConnection.swift +++ b/src/PluginRTCPeerConnection.swift @@ -8,6 +8,8 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate, RTCSessionD var pluginRTCPeerConnectionConstraints: PluginRTCPeerConnectionConstraints // PluginRTCDataChannel dictionary. var pluginRTCDataChannels: [Int : PluginRTCDataChannel] = [:] + // PluginRTCDTMFSender dictionary. + var pluginRTCDTMFSenders: [Int : PluginRTCDTMFSender] = [:] var eventListener: (data: NSDictionary) -> Void var eventListenerForAddStream: (pluginMediaStream: PluginMediaStream) -> Void var eventListenerForRemoveStream: (id: String) -> Void @@ -38,6 +40,7 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate, RTCSessionD deinit { NSLog("PluginRTCPeerConnection#deinit()") + self.pluginRTCDTMFSenders = [:] } @@ -313,6 +316,31 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate, RTCSessionD } + func createDTMFSender( + dsId: Int, + track: PluginMediaStreamTrack, + eventListener: (data: NSDictionary) -> Void + ) { + NSLog("PluginRTCPeerConnection#createDTMFSender()") + + if self.rtcPeerConnection.signalingState.rawValue == RTCSignalingClosed.rawValue { + return + } + + let pluginRTCDTMFSender = PluginRTCDTMFSender( + rtcPeerConnection: rtcPeerConnection, + track: track.rtcMediaStreamTrack, + eventListener: eventListener + ) + + // Store the pluginRTCDTMFSender into the dictionary. + self.pluginRTCDTMFSenders[dsId] = pluginRTCDTMFSender + + // Run it. + pluginRTCDTMFSender.run() + } + + func close() { NSLog("PluginRTCPeerConnection#close()") @@ -386,6 +414,27 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate, RTCSessionD } + func RTCDTMFSender_insertDTMF( + dsId: Int, + tones: String, + duration: Int, + interToneGap: Int + ) { + NSLog("PluginRTCPeerConnection#RTCDTMFSender_insertDTMF()") + + if self.rtcPeerConnection.signalingState.rawValue == RTCSignalingClosed.rawValue { + return + } + + let pluginRTCDTMFSender = self.pluginRTCDTMFSenders[dsId] + if pluginRTCDTMFSender == nil { + return + } + + pluginRTCDTMFSender!.insertDTMF(tones, duration: duration, interToneGap: interToneGap) + } + + /** * Methods inherited from RTCPeerConnectionDelegate. */ diff --git a/src/cordova-plugin-iosrtc-Bridging-Header.h b/src/cordova-plugin-iosrtc-Bridging-Header.h index dc9de490..3ba34031 100644 --- a/src/cordova-plugin-iosrtc-Bridging-Header.h +++ b/src/cordova-plugin-iosrtc-Bridging-Header.h @@ -8,6 +8,7 @@ // #import "RTCAudioSource.h" #import "RTCAudioTrack.h" #import "RTCDataChannel.h" +#import "RTCDTMFSender.h" #import "RTCEAGLVideoView.h" // #import "RTCI420Frame.h" #import "RTCICECandidate.h" diff --git a/src/iosrtcPlugin.swift b/src/iosrtcPlugin.swift index b1f532f0..8647e3f8 100644 --- a/src/iosrtcPlugin.swift +++ b/src/iosrtcPlugin.swift @@ -24,7 +24,7 @@ class iosrtcPlugin : CDVPlugin { override func pluginInitialize() { NSLog("iosrtcPlugin#pluginInitialize()") - // Make the web view transparent + // Make the web view transparent self.webView!.opaque = false self.webView!.backgroundColor = UIColor.clearColor() @@ -465,6 +465,66 @@ class iosrtcPlugin : CDVPlugin { } + func RTCPeerConnection_createDTMFSender(command: CDVInvokedUrlCommand) { + NSLog("iosrtcPlugin#RTCPeerConnection_createDTMFSender()") + + let pcId = command.argumentAtIndex(0) as! Int + let dsId = command.argumentAtIndex(1) as! Int + let trackId = command.argumentAtIndex(2) as! String + let pluginRTCPeerConnection = self.pluginRTCPeerConnections[pcId] + let pluginMediaStreamTrack = self.pluginMediaStreamTracks[trackId] + + if pluginRTCPeerConnection == nil { + NSLog("iosrtcPlugin#RTCPeerConnection_createDTMFSender() | ERROR: pluginRTCPeerConnection with pcId=%@ does not exist", String(pcId)) + return; + } + + if pluginMediaStreamTrack == nil { + NSLog("iosrtcPlugin#RTCPeerConnection_createDTMFSender() | ERROR: pluginMediaStreamTrack with id=%@ does not exist", String(trackId)) + return; + } + + + dispatch_async(self.queue) { [weak pluginRTCPeerConnection] in + pluginRTCPeerConnection?.createDTMFSender(dsId, + track: pluginMediaStreamTrack!, + eventListener: { (data: NSDictionary) -> Void in + let result = CDVPluginResult(status: CDVCommandStatus_OK, messageAsDictionary: data as [NSObject : AnyObject]) + + // Allow more callbacks. + result.setKeepCallbackAsBool(true); + self.emit(command.callbackId, result: result) + } + ) + } + } + + + func RTCPeerConnection_RTCDTMFSender_insertDTMF(command: CDVInvokedUrlCommand) { + NSLog("iosrtcPlugin#RTCPeerConnection_RTCDTMFSender_insertDTMF()") + + let pcId = command.argumentAtIndex(0) as! Int + let dsId = command.argumentAtIndex(1) as! Int + let tones = command.argumentAtIndex(2) as! String + let duration = command.argumentAtIndex(3) as! Int + let interToneGap = command.argumentAtIndex(4) as! Int + let pluginRTCPeerConnection = self.pluginRTCPeerConnections[pcId] + + if pluginRTCPeerConnection == nil { + NSLog("iosrtcPlugin#RTCPeerConnection_RTCDTMFSender_insertDTMF() | ERROR: pluginRTCPeerConnection with pcId=%@ does not exist", String(pcId)) + return; + } + + dispatch_async(self.queue) { [weak pluginRTCPeerConnection] in + pluginRTCPeerConnection?.RTCDTMFSender_insertDTMF(dsId, + tones: tones, + duration: duration, + interToneGap: interToneGap + ) + } + } + + func MediaStream_setListener(command: CDVInvokedUrlCommand) { NSLog("iosrtcPlugin#MediaStream_setListener()") diff --git a/src/webrtc-headers/RTCDTMFSender.h b/src/webrtc-headers/RTCDTMFSender.h new file mode 100644 index 00000000..7e1f675d --- /dev/null +++ b/src/webrtc-headers/RTCDTMFSender.h @@ -0,0 +1,66 @@ +/* + * libjingle + * Copyright 2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +#import "RTCAudioTrack.h" + +NS_ASSUME_NONNULL_BEGIN + +@class RTCDTMFSender; +// Protocol for receving tone change events. +@protocol RTCDTMFSenderDelegate + +// Called when a DTMF tone is played out. +- (void)toneChange:(NSString*)tone; + +@end + +// ObjectiveC wrapper for a DtmfSender object. +// See webrtc/api/dtmfsenderinterface.h +@interface RTCDTMFSender : NSObject + +@property(nonatomic, readonly) BOOL canInsertDTMF; +@property(nonatomic, readonly) NSString* toneBuffer; +@property(nonatomic, readonly) NSInteger duration; +@property(nonatomic, readonly) NSInteger interToneGap; +@property(nonatomic, weak) id delegate; +// The track associated with this DTMF sender. This property +// returns a copy of the RTCMediaStreamTrack +@property(nonatomic, copy, nullable) RTCAudioTrack *track; + +- (BOOL)insertDTMF:(NSString*)tones withDuration:(NSInteger)duration andInterToneGap:(NSInteger)interToneGap; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +// Disallow init and don't add to documentation +- (id)init __attribute__(( + unavailable("init is not a supported initializer for this class."))); +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/webrtc-headers/RTCI420Frame.h b/src/webrtc-headers/RTCI420Frame.h index 890d4aa6..1d145ee7 100644 --- a/src/webrtc-headers/RTCI420Frame.h +++ b/src/webrtc-headers/RTCI420Frame.h @@ -28,6 +28,8 @@ #import // RTCI420Frame is an ObjectiveC version of cricket::VideoFrame. +// TODO(nisse): It appears it doesn't support any VideoFrame methods, +// so let it wrap an webrtc::VideoFrameBuffer instead? @interface RTCI420Frame : NSObject @property(nonatomic, readonly) NSUInteger width; diff --git a/src/webrtc-headers/RTCPeerConnection.h b/src/webrtc-headers/RTCPeerConnection.h index a13ed3ec..481bbd52 100644 --- a/src/webrtc-headers/RTCPeerConnection.h +++ b/src/webrtc-headers/RTCPeerConnection.h @@ -27,9 +27,11 @@ #import "RTCPeerConnectionDelegate.h" +@class RTCAudioTrack; @class RTCConfiguration; @class RTCDataChannel; @class RTCDataChannelInit; +@class RTCDTMFSender; @class RTCICECandidate; @class RTCICEServers; @class RTCMediaConstraints; @@ -76,6 +78,9 @@ - (RTCDataChannel*)createDataChannelWithLabel:(NSString*)label config:(RTCDataChannelInit*)config; +// Create a DTMF sender. +- (RTCDTMFSender*)createDTMFSenderForTrack:(RTCAudioTrack*)audioTrack; + // Create a new offer. // Success or failure will be reported via RTCSessionDescriptionDelegate. - (void)createOfferWithDelegate:(id)delegate