From b7dbce068ca70c67cb7bb4124c540d95b09e7e9d Mon Sep 17 00:00:00 2001 From: Dev1 MacBook Pro Date: Fri, 8 Jan 2021 17:47:16 +0900 Subject: [PATCH 1/9] Basic transceiver scaffolding. * Create native class for transceivers. * Extend js class for transceivers. * Add RTCPeerConnection.addTransceiver. * Set up basic callback for sending native state to js. --- js/RTCPeerConnection.js | 26 +++++- js/RTCRtpTransceiver.js | 113 ++++++++++++++++++++++- js/iosrtc.js | 4 +- plugin.xml | 1 + src/PluginRTCPeerConnection.swift | 26 +++++- src/PluginRTCRtpTransceiver.swift | 73 +++++++++++++++ src/iosrtcPlugin.swift | 48 ++++++++++ www/cordova-plugin-iosrtc.js | 147 ++++++++++++++++++++++++++++-- 8 files changed, 418 insertions(+), 20 deletions(-) create mode 100644 src/PluginRTCRtpTransceiver.swift diff --git a/js/RTCPeerConnection.js b/js/RTCPeerConnection.js index 025fa29d..4456d160 100644 --- a/js/RTCPeerConnection.js +++ b/js/RTCPeerConnection.js @@ -55,6 +55,7 @@ function RTCPeerConnection(pcConfig, pcConstraints) { 'getLocalStreams', RTCPeerConnection.prototype_descriptor.getLocalStreams ); + Object.defineProperty(this, 'addTransceiver', RTCPeerConnection.prototype_descriptor.addTransceiver); // Public atributes. this._localDescription = null; @@ -468,9 +469,10 @@ RTCPeerConnection.prototype.getSenders = function () { RTCPeerConnection.prototype.getTransceivers = function () { var transceivers = []; + // TODO: Retrieve actual transceivers (passing data like before for compiling to work temporarily) this.getReceivers().map(function (receiver) { transceivers.push( - new RTCRtpTransceiver({ + new RTCRtpTransceiver(this, null, null, { receiver: receiver }) ); @@ -478,7 +480,7 @@ RTCPeerConnection.prototype.getTransceivers = function () { this.getSenders().map(function (sender) { transceivers.push( - new RTCRtpTransceiver({ + new RTCRtpTransceiver(this, null, null, { sender: sender }) ); @@ -580,6 +582,22 @@ RTCPeerConnection.prototype.removeTrack = function (sender) { } }; +RTCPeerConnection.prototype.addTransceiver = function (trackOrKind, init) { + if (isClosed.call(this)) { + throw new Errors.InvalidStateError('peerconnection is closed'); + } + + if (trackOrKind instanceof String) { + if (!(trackOrKind === "audio" || trackOrKind === "video")) { + throw new TypeError("A string was specified as trackOrKind which is not valid. The string must be either \"audio\" or \"video\"."); + } + } + + debug('addTransceiver() [trackOrKind:%o, init:%o]', trackOrKind, init); + + return new RTCRtpTransceiver(this, trackOrKind, init); +}; + RTCPeerConnection.prototype.getStreamById = function (id) { debug('getStreamById()'); @@ -822,7 +840,9 @@ function onEvent(data) { case 'track': var track = (event.track = new MediaStreamTrack(data.track)); event.receiver = new RTCRtpReceiver({ track: track }); - event.transceiver = new RTCRtpTransceiver({ receiver: event.receiver }); + + // TODO: Ensure this transceiver instance is associated with native API as well. + event.transceiver = new RTCRtpTransceiver(this, null, null, { receiver: event.receiver }); event.streams = []; // Add stream only if available in case of Unified-Plan of track event without stream diff --git a/js/RTCRtpTransceiver.js b/js/RTCRtpTransceiver.js index 3a7b05cf..9c9b9ee1 100644 --- a/js/RTCRtpTransceiver.js +++ b/js/RTCRtpTransceiver.js @@ -3,11 +3,116 @@ */ module.exports = RTCRtpTransceiver; -function RTCRtpTransceiver(data) { - data = data || {}; +/** + * Dependencies. + */ +var + debug = require('debug')('iosrtc:RTCRtpTransceiver'), + debugerror = require('debug')('iosrtc:ERROR:RTCRtpTransceiver'), + exec = require('cordova/exec'), + randomNumber = require('random-number').generator({min: 10000, max: 99999, integer: true}), + EventTarget = require('./EventTarget'); + +debugerror.log = console.warn.bind(console); + +function RTCRtpTransceiver(peerConnection, trackOrKind, init, data) { + var self = this; + + // Created using RTCPeerConnection.addTransceiver + if (!data) { + // Make this an EventTarget. + EventTarget.call(this); + + // Private attributes. + this.peerConnection = peerConnection; + this._currentDirection = "sendrecv"; + this._direction = "sendrecv"; + this._mid = null; + this._receiver = null; + this._sender = null; + this.tcId = randomNumber(); + + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', [this.peerConnection.pcId, this.tcId, trackOrKind.id, init]); + + // Created using RTCPeerConnection.getTransceivers + } else { + this._receiver = data.receiver; + this._sender = data.sender; + } + + function onResultOK(data) { + onEvent.call(self, data); + } +} + +RTCRtpTransceiver.prototype = Object.create(EventTarget.prototype); +RTCRtpTransceiver.prototype.constructor = RTCRtpTransceiver; + +Object.defineProperties(RTCRtpTransceiver.prototype, { + currentDirection: { + get: function () { + return this._currentDirection; + } + }, + direction: { + get: function () { + return this._direction; + }, + set: function (direction) { + this._direction = direction; + // TODO: Invoke native api + } + }, + mid: { + get: function () { + return this._mid; + } + }, + receiver: { + get: function() { + return this._receiver; + } + }, + sender: { + get: function() { + return this._sender; + } + }, +}); + +RTCRtpTransceiver.prototype.stop = function () { + // TODO: Implement stop function + + exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_close', [this.peerConnection.pcId, this.tcId]); +}; + +function onEvent(data) { + var type = data.type; + + debug('onEvent() | [type:%s, data:%o]', type, data); + + switch (type) { + case 'direction': + this._direction = data.direction; + this._currentDirection = data.direction; + + break; + + case 'mid': + this._mid = data.mid; + + break; + + case 'receiver': + this._receiver = data.receiver; + + break; + + case 'sender': + this._sender = data.sender; - this.receiver = data.receiver; - this.sender = data.sender; + break; + } } // TODO diff --git a/js/iosrtc.js b/js/iosrtc.js index d53f633d..44addf05 100644 --- a/js/iosrtc.js +++ b/js/iosrtc.js @@ -24,7 +24,8 @@ var // Dictionary of MediaStreamRenderers. MediaDevices = require('./MediaDevices'), MediaStream = require('./MediaStream'), MediaStreamTrack = require('./MediaStreamTrack'), - videoElementsHandler = require('./videoElementsHandler'); + videoElementsHandler = require('./videoElementsHandler'), + RTCRtpTransceiver = require('./RTCRtpTransceiver'); /** * Expose the iosrtc object. @@ -205,6 +206,7 @@ function registerGlobals(doNotRestoreCallbacksSupport) { window.MediaStream = MediaStream; window.webkitMediaStream = MediaStream; window.MediaStreamTrack = MediaStreamTrack; + window.RTCRtpTransceiver = RTCRtpTransceiver; // Apply CanvasRenderingContext2D.drawImage monkey patch var drawImage = CanvasRenderingContext2D.prototype.drawImage; diff --git a/plugin.xml b/plugin.xml index 43f35179..80bf9bcf 100644 --- a/plugin.xml +++ b/plugin.xml @@ -72,6 +72,7 @@ + diff --git a/src/PluginRTCPeerConnection.swift b/src/PluginRTCPeerConnection.swift index 7565ae94..5e4288f1 100644 --- a/src/PluginRTCPeerConnection.swift +++ b/src/PluginRTCPeerConnection.swift @@ -9,9 +9,9 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { // PluginRTCDataChannel dictionary. var pluginRTCDataChannels: [Int : PluginRTCDataChannel] = [:] // PluginRTCDTMFSender dictionary. - var pluginRTCDTMFSenders: [Int : PluginRTCDTMFSender] = [:] - + // PluginRTCRtpTransceiver dictionary. + var pluginRTCRtpTransceivers: [Int : PluginRTCRtpTransceiver] = [:] var eventListener: (_ data: NSDictionary) -> Void var eventListenerForAddStream: (_ pluginMediaStream: PluginMediaStream) -> Void var eventListenerForRemoveStream: (_ pluginMediaStream: PluginMediaStream) -> Void @@ -381,6 +381,28 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { } } + func addTransceiver( + _ tcId: Int, + pluginMediaTrack: PluginMediaStreamTrack, + options: NSDictionary?, + eventListener: @escaping (_ data: NSDictionary) -> Void + ) { + NSLog("PluginRTCPeerConnection#addTransceiver()") + + if self.rtcPeerConnection.signalingState == RTCSignalingState.closed { + return + } + + let pluginRtcRtpTransceiver = PluginRTCRtpTransceiver( + rtcPeerConnection: self.rtcPeerConnection, + mediaStreamTrack: pluginMediaTrack.rtcMediaStreamTrack, + options: options, + eventListener: eventListener + ) + + self.pluginRTCRtpTransceivers[tcId] = pluginRtcRtpTransceiver + } + func createDataChannel( _ dcId: Int, label: String, diff --git a/src/PluginRTCRtpTransceiver.swift b/src/PluginRTCRtpTransceiver.swift new file mode 100644 index 00000000..7a0ada82 --- /dev/null +++ b/src/PluginRTCRtpTransceiver.swift @@ -0,0 +1,73 @@ +import Foundation + +class PluginRTCRtpTransceiver : NSObject { + var rtcRtpTransceiver: RTCRtpTransceiver? + var eventListener: ((_ data: NSDictionary) -> Void)? + + init( + rtcPeerConnection: RTCPeerConnection, + mediaStreamTrack: RTCMediaStreamTrack, + options: NSDictionary?, + eventListener: @escaping (_ data: NSDictionary) -> Void + ) { + NSLog("PluginRTCRtpTransceiver#init()") + + self.eventListener = eventListener + + let rtcRtpTransceiverInit = RTCRtpTransceiverInit(); + + if options?.object(forKey: "direction") != nil { + let direction = options!.object(forKey: "direction") as! String + + if direction == "inactive" { + rtcRtpTransceiverInit.direction = RTCRtpTransceiverDirection.inactive + } else if direction == "recvonly" { + rtcRtpTransceiverInit.direction = RTCRtpTransceiverDirection.recvOnly + } else if direction == "rendonly" { + rtcRtpTransceiverInit.direction = RTCRtpTransceiverDirection.sendOnly + } else if direction == "sendrecv" { + rtcRtpTransceiverInit.direction = RTCRtpTransceiverDirection.sendRecv + } else if direction == "stopped" { + rtcRtpTransceiverInit.direction = RTCRtpTransceiverDirection.stopped + } + } + + if options?.object(forKey: "streams") != nil { + let streams = options!.object(forKey: "streams") as! [NSDictionary] + let streamIds = streams.compactMap({$0["_id"] as! String}) + + rtcRtpTransceiverInit.streamIds = streamIds + } + + // TODO: Implement sendEncodings configuration + if options?.object(forKey: "sendEncodings") != nil { + NSLog("iosrtcPlugin#RTCPeerConnection_addTransceiver() | ERROR: init.sendEncodings not supported yet") + return; + } + + self.rtcRtpTransceiver = rtcPeerConnection.addTransceiver(with: mediaStreamTrack, init: rtcRtpTransceiverInit) + + // TODO: Implement: + // addTransceiverOfType(mediaType) + // addTransceiverOfType(mediaType, init) + + if self.rtcRtpTransceiver == nil { + NSLog("PluginRTCRtpTransceiver#init() | rtcPeerConnection.addTransceiver() failed") + return + } + + // TODO: Notify self.eventListener + self.eventListener!([ + "type": "new", + "transceiver": [] + ]) + } + + deinit { + NSLog("PluginRTCRtpTransceiver#deinit()") + } + + func stop() { + self.rtcRtpTransceiver!.stop() + } +} diff --git a/src/iosrtcPlugin.swift b/src/iosrtcPlugin.swift index 5270f409..3059b058 100644 --- a/src/iosrtcPlugin.swift +++ b/src/iosrtcPlugin.swift @@ -401,6 +401,54 @@ class iosrtcPlugin : CDVPlugin { } } + @objc(RTCPeerConnection_addTransceiver:) func RTCPeerConnection_addTransceiver(_ command: CDVInvokedUrlCommand) { + NSLog("iosrtcPlugin#RTCPeerConnection_addTransceiver()") + + let pcId = command.argument(at: 0) as! Int + let tcId = command.argument(at: 1) as! Int + let trackId = command.argument(at: 2) as! String + + // TODO: Handle cases of trackId representing a track type. + + let pluginRTCPeerConnection = self.pluginRTCPeerConnections[pcId] + let pluginMediaStreamTrack = self.pluginMediaStreamTracks[trackId] + var options: NSDictionary? = nil + + var rtcRtpTransceiverInit: RTCRtpTransceiverInit? = nil + + if command.argument(at: 3) != nil { + options = command.argument(at: 3) as? NSDictionary + } + + if pluginRTCPeerConnection == nil { + NSLog("iosrtcPlugin#RTCPeerConnection_addTransceiver() | ERROR: pluginRTCPeerConnection with pcId=%@ does not exist", String(pcId)) + return; + } + + if pluginMediaStreamTrack == nil { + NSLog("iosrtcPlugin#RTCPeerConnection_addTransceiver() | ERROR: pluginMediaStreamTrack with id=\(trackId) does not exist") + return; + } + + self.queue.async { [weak pluginRTCPeerConnection, weak pluginMediaStreamTrack] in + pluginRTCPeerConnection?.addTransceiver( + tcId, + pluginMediaTrack: pluginMediaStreamTrack!, + options: options, + eventListener: { (data: NSDictionary) -> Void in + let result = CDVPluginResult( + status: CDVCommandStatus_OK, + messageAs: data as? [AnyHashable: Any] + ) + + result!.setKeepCallbackAs(true); + self.emit(command.callbackId, result: result!) + } + ) + // TODO: Return the resulting transceiver object. + } + } + @objc(RTCPeerConnection_createDataChannel:) func RTCPeerConnection_createDataChannel(_ command: CDVInvokedUrlCommand) { NSLog("iosrtcPlugin#RTCPeerConnection_createDataChannel()") diff --git a/www/cordova-plugin-iosrtc.js b/www/cordova-plugin-iosrtc.js index a57fd7b9..706d36f9 100644 --- a/www/cordova-plugin-iosrtc.js +++ b/www/cordova-plugin-iosrtc.js @@ -1950,6 +1950,7 @@ function RTCPeerConnection(pcConfig, pcConstraints) { 'getLocalStreams', RTCPeerConnection.prototype_descriptor.getLocalStreams ); + Object.defineProperty(this, 'addTransceiver', RTCPeerConnection.prototype_descriptor.addTransceiver); // Public atributes. this._localDescription = null; @@ -2363,9 +2364,10 @@ RTCPeerConnection.prototype.getSenders = function () { RTCPeerConnection.prototype.getTransceivers = function () { var transceivers = []; + // TODO: Retrieve actual transceivers (passing data like before for compiling to work temporarily) this.getReceivers().map(function (receiver) { transceivers.push( - new RTCRtpTransceiver({ + new RTCRtpTransceiver(this, null, null, { receiver: receiver }) ); @@ -2373,7 +2375,7 @@ RTCPeerConnection.prototype.getTransceivers = function () { this.getSenders().map(function (sender) { transceivers.push( - new RTCRtpTransceiver({ + new RTCRtpTransceiver(this, null, null, { sender: sender }) ); @@ -2475,6 +2477,22 @@ RTCPeerConnection.prototype.removeTrack = function (sender) { } }; +RTCPeerConnection.prototype.addTransceiver = function (trackOrKind, init) { + if (isClosed.call(this)) { + throw new Errors.InvalidStateError('peerconnection is closed'); + } + + if (trackOrKind instanceof String) { + if (!(trackOrKind === "audio" || trackOrKind === "video")) { + throw new TypeError("A string was specified as trackOrKind which is not valid. The string must be either \"audio\" or \"video\"."); + } + } + + debug('addTransceiver() [trackOrKind:%o, init:%o]', trackOrKind, init); + + return new RTCRtpTransceiver(this, trackOrKind, init); +}; + RTCPeerConnection.prototype.getStreamById = function (id) { debug('getStreamById()'); @@ -2717,7 +2735,9 @@ function onEvent(data) { case 'track': var track = (event.track = new MediaStreamTrack(data.track)); event.receiver = new RTCRtpReceiver({ track: track }); - event.transceiver = new RTCRtpTransceiver({ receiver: event.receiver }); + + // TODO: Ensure this transceiver instance is associated with native API as well. + event.transceiver = new RTCRtpTransceiver(this, null, null, { receiver: event.receiver }); event.streams = []; // Add stream only if available in case of Unified-Plan of track event without stream @@ -2834,11 +2854,116 @@ RTCRtpSender.prototype.replaceTrack = function (withTrack) { */ module.exports = RTCRtpTransceiver; -function RTCRtpTransceiver(data) { - data = data || {}; +/** + * Dependencies. + */ +var + debug = _dereq_('debug')('iosrtc:RTCRtpTransceiver'), + debugerror = _dereq_('debug')('iosrtc:ERROR:RTCRtpTransceiver'), + exec = _dereq_('cordova/exec'), + randomNumber = _dereq_('random-number').generator({min: 10000, max: 99999, integer: true}), + EventTarget = _dereq_('./EventTarget'); + +debugerror.log = console.warn.bind(console); + +function RTCRtpTransceiver(peerConnection, trackOrKind, init, data) { + var self = this; + + // Created using RTCPeerConnection.addTransceiver + if (!data) { + // Make this an EventTarget. + EventTarget.call(this); + + // Private attributes. + this.peerConnection = peerConnection; + this._currentDirection = "sendrecv"; + this._direction = "sendrecv"; + this._mid = null; + this._receiver = null; + this._sender = null; + this.tcId = randomNumber(); + + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', [this.peerConnection.pcId, this.tcId, trackOrKind.id, init]); + + // Created using RTCPeerConnection.getTransceivers + } else { + this._receiver = data.receiver; + this._sender = data.sender; + } - this.receiver = data.receiver; - this.sender = data.sender; + function onResultOK(data) { + onEvent.call(self, data); + } +} + +RTCRtpTransceiver.prototype = Object.create(EventTarget.prototype); +RTCRtpTransceiver.prototype.constructor = RTCRtpTransceiver; + +Object.defineProperties(RTCRtpTransceiver.prototype, { + currentDirection: { + get: function () { + return this._currentDirection; + } + }, + direction: { + get: function () { + return this._direction; + }, + set: function (direction) { + this._direction = direction; + // TODO: Invoke native api + } + }, + mid: { + get: function () { + return this._mid; + } + }, + receiver: { + get: function() { + return this._receiver; + } + }, + sender: { + get: function() { + return this._sender; + } + }, +}); + +RTCRtpTransceiver.prototype.stop = function () { + // TODO: Implement stop function + + exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_close', [this.peerConnection.pcId, this.tcId]); +}; + +function onEvent(data) { + var type = data.type; + + debug('onEvent() | [type:%s, data:%o]', type, data); + + switch (type) { + case 'direction': + this._direction = data.direction; + this._currentDirection = data.direction; + + break; + + case 'mid': + this._mid = data.mid; + + break; + + case 'receiver': + this._receiver = data.receiver; + + break; + + case 'sender': + this._sender = data.sender; + + break; + } } // TODO @@ -2847,7 +2972,7 @@ function RTCRtpTransceiver(data) { // https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/mid // https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/stop -},{}],17:[function(_dereq_,module,exports){ +},{"./EventTarget":2,"cordova/exec":undefined,"debug":24,"random-number":29}],17:[function(_dereq_,module,exports){ /** * Expose the RTCSessionDescription class. */ @@ -3446,7 +3571,8 @@ var // Dictionary of MediaStreamRenderers. MediaDevices = _dereq_('./MediaDevices'), MediaStream = _dereq_('./MediaStream'), MediaStreamTrack = _dereq_('./MediaStreamTrack'), - videoElementsHandler = _dereq_('./videoElementsHandler'); + videoElementsHandler = _dereq_('./videoElementsHandler'), + RTCRtpTransceiver = _dereq_('./RTCRtpTransceiver'); /** * Expose the iosrtc object. @@ -3627,6 +3753,7 @@ function registerGlobals(doNotRestoreCallbacksSupport) { window.MediaStream = MediaStream; window.webkitMediaStream = MediaStream; window.MediaStreamTrack = MediaStreamTrack; + window.RTCRtpTransceiver = RTCRtpTransceiver; // Apply CanvasRenderingContext2D.drawImage monkey patch var drawImage = CanvasRenderingContext2D.prototype.drawImage; @@ -3654,7 +3781,7 @@ function dump() { } }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./MediaDevices":4,"./MediaStream":5,"./MediaStreamTrack":7,"./RTCIceCandidate":12,"./RTCPeerConnection":13,"./RTCSessionDescription":17,"./enumerateDevices":20,"./getUserMedia":21,"./videoElementsHandler":23,"cordova/exec":undefined,"debug":24,"domready":26}],23:[function(_dereq_,module,exports){ +},{"./MediaDevices":4,"./MediaStream":5,"./MediaStreamTrack":7,"./RTCIceCandidate":12,"./RTCPeerConnection":13,"./RTCRtpTransceiver":16,"./RTCSessionDescription":17,"./enumerateDevices":20,"./getUserMedia":21,"./videoElementsHandler":23,"cordova/exec":undefined,"debug":24,"domready":26}],23:[function(_dereq_,module,exports){ /** * Expose a function that must be called when the library is loaded. * And also a helper function. From 2db54be39ce4dc0efb5be95c14e371e931edf40a Mon Sep 17 00:00:00 2001 From: Dev1 MacBook Pro Date: Mon, 11 Jan 2021 13:46:07 +0900 Subject: [PATCH 2/9] Add support for stop and direction. * Invoke the native transceiver stop function. * Allow setting direction from JS. * State event for updating the JS state. --- js/RTCRtpTransceiver.js | 40 ++++++++----------- src/PluginRTCPeerConnection.swift | 1 + src/PluginRTCRtpTransceiver.swift | 64 +++++++++++++++++++++++++++++-- src/iosrtcPlugin.swift | 50 ++++++++++++++++++++++-- www/cordova-plugin-iosrtc.js | 40 ++++++++----------- 5 files changed, 142 insertions(+), 53 deletions(-) diff --git a/js/RTCRtpTransceiver.js b/js/RTCRtpTransceiver.js index 9c9b9ee1..6baadb17 100644 --- a/js/RTCRtpTransceiver.js +++ b/js/RTCRtpTransceiver.js @@ -60,7 +60,8 @@ Object.defineProperties(RTCRtpTransceiver.prototype, { }, set: function (direction) { this._direction = direction; - // TODO: Invoke native api + + exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_setDirection', [this.peerConnection.pcId, this.tcId, direction]); } }, mid: { @@ -81,9 +82,7 @@ Object.defineProperties(RTCRtpTransceiver.prototype, { }); RTCRtpTransceiver.prototype.stop = function () { - // TODO: Implement stop function - - exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_close', [this.peerConnection.pcId, this.tcId]); + exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_stop', [this.peerConnection.pcId, this.tcId]); }; function onEvent(data) { @@ -91,27 +90,22 @@ function onEvent(data) { debug('onEvent() | [type:%s, data:%o]', type, data); - switch (type) { - case 'direction': - this._direction = data.direction; - this._currentDirection = data.direction; - - break; - - case 'mid': - this._mid = data.mid; - - break; - - case 'receiver': - this._receiver = data.receiver; - - break; + if (type !== 'state') { + return; + } - case 'sender': - this._sender = data.sender; + var transceiver = data.transceiver; - break; + if (transceiver) { + if (transceiver.direction) { + this._direction = transceiver.direction; + } + if (transceiver.currentDirection) { + this._currentDirection = transceiver.currentDirection; + } + if (transceiver.mid) { + this._mid = transceiver.mid; + } } } diff --git a/src/PluginRTCPeerConnection.swift b/src/PluginRTCPeerConnection.swift index 5e4288f1..57bd9ca8 100644 --- a/src/PluginRTCPeerConnection.swift +++ b/src/PluginRTCPeerConnection.swift @@ -61,6 +61,7 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { deinit { NSLog("PluginRTCPeerConnection#deinit()") self.pluginRTCDTMFSenders = [:] + self.pluginRTCRtpTransceivers = [:] } func run() { diff --git a/src/PluginRTCRtpTransceiver.swift b/src/PluginRTCRtpTransceiver.swift index 7a0ada82..66384242 100644 --- a/src/PluginRTCRtpTransceiver.swift +++ b/src/PluginRTCRtpTransceiver.swift @@ -34,7 +34,7 @@ class PluginRTCRtpTransceiver : NSObject { if options?.object(forKey: "streams") != nil { let streams = options!.object(forKey: "streams") as! [NSDictionary] - let streamIds = streams.compactMap({$0["_id"] as! String}) + let streamIds = streams.compactMap({$0["_id"] as? String}) rtcRtpTransceiverInit.streamIds = streamIds } @@ -55,11 +55,22 @@ class PluginRTCRtpTransceiver : NSObject { NSLog("PluginRTCRtpTransceiver#init() | rtcPeerConnection.addTransceiver() failed") return } + + // TODO: Add support for senders and receivers. + //self.rtcRtpTransceiver?.sender + //self.rtcRtpTransceiver?.receiver + + var currentDirection = RTCRtpTransceiverDirection.inactive + self.rtcRtpTransceiver!.currentDirection(¤tDirection) - // TODO: Notify self.eventListener self.eventListener!([ - "type": "new", - "transceiver": [] + "type": "state", + "transceiver": [ + "mid": self.rtcRtpTransceiver!.mid, + "currentDirection": PluginRTCRtpTransceiver.directionToString(direction: currentDirection), + "direction": PluginRTCRtpTransceiver.directionToString(direction: self.rtcRtpTransceiver!.direction), + "stopped": self.rtcRtpTransceiver!.isStopped + ] ]) } @@ -69,5 +80,50 @@ class PluginRTCRtpTransceiver : NSObject { func stop() { self.rtcRtpTransceiver!.stop() + + self.eventListener!([ + "type": "state", + "transceiver": [ + "direction": PluginRTCRtpTransceiver.directionToString(direction: self.rtcRtpTransceiver!.direction), + "stopped": self.rtcRtpTransceiver!.isStopped + ] + ]) + } + + func setDirection(direction: String) { + if direction == "inactive" { + self.rtcRtpTransceiver!.direction = RTCRtpTransceiverDirection.inactive + } else if direction == "recvonly" { + self.rtcRtpTransceiver!.direction = RTCRtpTransceiverDirection.recvOnly + } else if direction == "rendonly" { + self.rtcRtpTransceiver!.direction = RTCRtpTransceiverDirection.sendOnly + } else if direction == "sendrecv" { + self.rtcRtpTransceiver!.direction = RTCRtpTransceiverDirection.sendRecv + } else if direction == "stopped" { + self.rtcRtpTransceiver!.direction = RTCRtpTransceiverDirection.stopped + } + + self.eventListener!([ + "type": "state", + "transceiver": [ + "direction": PluginRTCRtpTransceiver.directionToString(direction: self.rtcRtpTransceiver!.direction), + "stopped": self.rtcRtpTransceiver!.isStopped + ] + ]) + } + + static func directionToString(direction: RTCRtpTransceiverDirection) -> String { + switch direction { + case RTCRtpTransceiverDirection.inactive: + return "inactive" + case RTCRtpTransceiverDirection.recvOnly: + return "recvonly" + case RTCRtpTransceiverDirection.sendOnly: + return "sendonly" + case RTCRtpTransceiverDirection.sendRecv: + return "sendrecv" + case RTCRtpTransceiverDirection.stopped: + return "stopped" + } } } diff --git a/src/iosrtcPlugin.swift b/src/iosrtcPlugin.swift index 3059b058..b76c4f8f 100644 --- a/src/iosrtcPlugin.swift +++ b/src/iosrtcPlugin.swift @@ -413,8 +413,6 @@ class iosrtcPlugin : CDVPlugin { let pluginRTCPeerConnection = self.pluginRTCPeerConnections[pcId] let pluginMediaStreamTrack = self.pluginMediaStreamTracks[trackId] var options: NSDictionary? = nil - - var rtcRtpTransceiverInit: RTCRtpTransceiverInit? = nil if command.argument(at: 3) != nil { options = command.argument(at: 3) as? NSDictionary @@ -445,10 +443,56 @@ class iosrtcPlugin : CDVPlugin { self.emit(command.callbackId, result: result!) } ) - // TODO: Return the resulting transceiver object. } } + @objc(RTCPeerConnection_RTCRtpTransceiver_setDirection:) func RTCPeerConnection_RTCRtpTransceiver_setDirection(_ command: CDVInvokedUrlCommand) { + NSLog("iosrtcPlugin#RTCPeerConnection_RTCRtpTransceiver_setDirection()") + + let pcId = command.argument(at: 0) as! Int + let tcId = command.argument(at: 1) as! Int + let direction = command.argument(at: 2) as! String + + let pluginRTCPeerConnection = pluginRTCPeerConnections[pcId] + + if pluginRTCPeerConnection == nil { + NSLog("iosrtcPlugin#RTCPeerConnection_RTCRtpTransceiver_setDirection() | ERROR: pluginRTCPeerConnection with pcId=%@ does not exist", String(pcId)) + return; + } + + let pluginRTCRtpTransceiver = pluginRTCPeerConnection!.pluginRTCRtpTransceivers[tcId] + + if pluginRTCRtpTransceiver == nil { + NSLog("iosrtcPlugin#RTCPeerConnection_RTCRtpTransceiver_setDirection() | ERROR: pluginRTCRtpTransceiver with id=\(tcId) does not exist") + return; + } + + pluginRTCRtpTransceiver!.setDirection(direction: direction) + } + + @objc(RTCPeerConnection_RTCRtpTransceiver_stop:) func RTCPeerConnection_RTCRtpTransceiver_stop(_ command: CDVInvokedUrlCommand) { + NSLog("iosrtcPlugin#RTCPeerConnection_RTCRtpTransceiver_stop()") + + let pcId = command.argument(at: 0) as! Int + let tcId = command.argument(at: 1) as! Int + + let pluginRTCPeerConnection = pluginRTCPeerConnections[pcId] + + if pluginRTCPeerConnection == nil { + NSLog("iosrtcPlugin#RTCPeerConnection_RTCRtpTransceiver_stop() | ERROR: pluginRTCPeerConnection with pcId=%@ does not exist", String(pcId)) + return; + } + + let pluginRTCRtpTransceiver = pluginRTCPeerConnection!.pluginRTCRtpTransceivers[tcId] + + if pluginRTCRtpTransceiver == nil { + NSLog("iosrtcPlugin#RTCPeerConnection_RTCRtpTransceiver_stop() | ERROR: pluginRTCRtpTransceiver with id=\(tcId) does not exist") + return; + } + + pluginRTCRtpTransceiver!.stop() + } + @objc(RTCPeerConnection_createDataChannel:) func RTCPeerConnection_createDataChannel(_ command: CDVInvokedUrlCommand) { NSLog("iosrtcPlugin#RTCPeerConnection_createDataChannel()") diff --git a/www/cordova-plugin-iosrtc.js b/www/cordova-plugin-iosrtc.js index 706d36f9..8a96f7f7 100644 --- a/www/cordova-plugin-iosrtc.js +++ b/www/cordova-plugin-iosrtc.js @@ -2911,7 +2911,8 @@ Object.defineProperties(RTCRtpTransceiver.prototype, { }, set: function (direction) { this._direction = direction; - // TODO: Invoke native api + + exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_setDirection', [this.peerConnection.pcId, this.tcId, direction]); } }, mid: { @@ -2932,9 +2933,7 @@ Object.defineProperties(RTCRtpTransceiver.prototype, { }); RTCRtpTransceiver.prototype.stop = function () { - // TODO: Implement stop function - - exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_close', [this.peerConnection.pcId, this.tcId]); + exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_stop', [this.peerConnection.pcId, this.tcId]); }; function onEvent(data) { @@ -2942,27 +2941,22 @@ function onEvent(data) { debug('onEvent() | [type:%s, data:%o]', type, data); - switch (type) { - case 'direction': - this._direction = data.direction; - this._currentDirection = data.direction; - - break; - - case 'mid': - this._mid = data.mid; - - break; - - case 'receiver': - this._receiver = data.receiver; - - break; + if (type !== 'state') { + return; + } - case 'sender': - this._sender = data.sender; + var transceiver = data.transceiver; - break; + if (transceiver) { + if (transceiver.direction) { + this._direction = transceiver.direction; + } + if (transceiver.currentDirection) { + this._currentDirection = transceiver.currentDirection; + } + if (transceiver.mid) { + this._mid = transceiver.mid; + } } } From 4a463d57bf3c4b9bb3169faecf37cf27bc55bb61 Mon Sep 17 00:00:00 2001 From: Dev1 MacBook Pro Date: Tue, 12 Jan 2021 13:33:22 +0900 Subject: [PATCH 3/9] Add string to direction utility function. * Added PluginRTCRtpTransceiver#stringToDirection() * Fixed typo rendonly -> sendonly. --- src/PluginRTCRtpTransceiver.swift | 42 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/PluginRTCRtpTransceiver.swift b/src/PluginRTCRtpTransceiver.swift index 66384242..2bd8f627 100644 --- a/src/PluginRTCRtpTransceiver.swift +++ b/src/PluginRTCRtpTransceiver.swift @@ -19,17 +19,7 @@ class PluginRTCRtpTransceiver : NSObject { if options?.object(forKey: "direction") != nil { let direction = options!.object(forKey: "direction") as! String - if direction == "inactive" { - rtcRtpTransceiverInit.direction = RTCRtpTransceiverDirection.inactive - } else if direction == "recvonly" { - rtcRtpTransceiverInit.direction = RTCRtpTransceiverDirection.recvOnly - } else if direction == "rendonly" { - rtcRtpTransceiverInit.direction = RTCRtpTransceiverDirection.sendOnly - } else if direction == "sendrecv" { - rtcRtpTransceiverInit.direction = RTCRtpTransceiverDirection.sendRecv - } else if direction == "stopped" { - rtcRtpTransceiverInit.direction = RTCRtpTransceiverDirection.stopped - } + rtcRtpTransceiverInit.direction = PluginRTCRtpTransceiver.stringToDirection(direction: direction) } if options?.object(forKey: "streams") != nil { @@ -91,17 +81,7 @@ class PluginRTCRtpTransceiver : NSObject { } func setDirection(direction: String) { - if direction == "inactive" { - self.rtcRtpTransceiver!.direction = RTCRtpTransceiverDirection.inactive - } else if direction == "recvonly" { - self.rtcRtpTransceiver!.direction = RTCRtpTransceiverDirection.recvOnly - } else if direction == "rendonly" { - self.rtcRtpTransceiver!.direction = RTCRtpTransceiverDirection.sendOnly - } else if direction == "sendrecv" { - self.rtcRtpTransceiver!.direction = RTCRtpTransceiverDirection.sendRecv - } else if direction == "stopped" { - self.rtcRtpTransceiver!.direction = RTCRtpTransceiverDirection.stopped - } + self.rtcRtpTransceiver!.direction = PluginRTCRtpTransceiver.stringToDirection(direction: direction) self.eventListener!([ "type": "state", @@ -112,6 +92,24 @@ class PluginRTCRtpTransceiver : NSObject { ]) } + static func stringToDirection(direction: String) -> RTCRtpTransceiverDirection { + switch direction { + case "inactive": + return RTCRtpTransceiverDirection.inactive + case "recvonly": + return RTCRtpTransceiverDirection.recvOnly + case "sendonly": + return RTCRtpTransceiverDirection.sendOnly + case "sendrecv": + return RTCRtpTransceiverDirection.sendRecv + case "stopped": + return RTCRtpTransceiverDirection.stopped + default: + NSLog("PluginRTCRtpTransceiver#stringToDirection() | Unrecognized direction value: @%", direction) + return RTCRtpTransceiverDirection.inactive + } + } + static func directionToString(direction: RTCRtpTransceiverDirection) -> String { switch direction { case RTCRtpTransceiverDirection.inactive: From 6115210539db2d5558b5c47c7d96f4c9e2199c25 Mon Sep 17 00:00:00 2001 From: Dev1 MacBook Pro Date: Thu, 14 Jan 2021 10:11:20 +0900 Subject: [PATCH 4/9] Create native and js transceiver on pc event. --- js/RTCPeerConnection.js | 6 +++ js/RTCRtpTransceiver.js | 28 +++++++++---- src/PluginRTCPeerConnection.swift | 69 +++++++++++++++++++++++++++++++ src/PluginRTCRtpTransceiver.swift | 50 +++++++++++++++------- src/iosrtcPlugin.swift | 28 +++++++++++++ www/cordova-plugin-iosrtc.js | 34 +++++++++++---- 6 files changed, 185 insertions(+), 30 deletions(-) diff --git a/js/RTCPeerConnection.js b/js/RTCPeerConnection.js index 4456d160..1122093c 100644 --- a/js/RTCPeerConnection.js +++ b/js/RTCPeerConnection.js @@ -791,6 +791,7 @@ function onEvent(data) { self = this, event = new Event(type), dataChannel, + transceiver, id; Object.defineProperty(event, 'target', { value: self, enumerable: true }); @@ -886,6 +887,11 @@ function onEvent(data) { dataChannel = new RTCDataChannel(this, null, null, data.channel); event.channel = dataChannel; break; + + case 'transceiver': + transceiver = new RTCRtpTransceiver(this, null, null, data.transceiver); + event.transceiver = transceiver; + break; } this.dispatchEvent(event); diff --git a/js/RTCRtpTransceiver.js b/js/RTCRtpTransceiver.js index 6baadb17..15cbf91f 100644 --- a/js/RTCRtpTransceiver.js +++ b/js/RTCRtpTransceiver.js @@ -18,15 +18,15 @@ debugerror.log = console.warn.bind(console); function RTCRtpTransceiver(peerConnection, trackOrKind, init, data) { var self = this; + // Make this an EventTarget. + EventTarget.call(this); + + this.peerConnection = peerConnection; + // Created using RTCPeerConnection.addTransceiver if (!data) { - // Make this an EventTarget. - EventTarget.call(this); - - // Private attributes. - this.peerConnection = peerConnection; - this._currentDirection = "sendrecv"; - this._direction = "sendrecv"; + this._currentDirection = "inactive"; + this._direction = "inactive"; this._mid = null; this._receiver = null; this._sender = null; @@ -34,7 +34,19 @@ function RTCRtpTransceiver(peerConnection, trackOrKind, init, data) { exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', [this.peerConnection.pcId, this.tcId, trackOrKind.id, init]); - // Created using RTCPeerConnection.getTransceivers + // Created by event coming from native. + } else if(data.tcId) { + this.tcId = data.tcId; + this._mid = data.mid; + this._currentDirection = data.currentDirection; + this._direction = data.direction; + + this._receiver = data.receiver; + this._sender = data.sender; + + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_setListener', [this.peerConnection.pcId, this.tcId]); + + // Created using RTCPeerConnection.getTransceivers } else { this._receiver = data.receiver; this._sender = data.sender; diff --git a/src/PluginRTCPeerConnection.swift b/src/PluginRTCPeerConnection.swift index 57bd9ca8..f4a0f4da 100644 --- a/src/PluginRTCPeerConnection.swift +++ b/src/PluginRTCPeerConnection.swift @@ -189,6 +189,15 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { errback(error) } + // TODO: Would transceivers with mid=null get their mid's assigned after + // setLocalDescription call? Investigate... + // + // Reference: + // Something I overlooked last year is that transceiver.mid is null before setLocalDescription. We avoided + // that problem above by establishing the connection ahead of sending anything, but this makes mid useless + // for correlating in the initial negotiation. + // https://blog.mozilla.org/webrtc/rtcrtptransceiver-explored/ + self.rtcPeerConnection.setLocalDescription(rtcSessionDescription, completionHandler: { (error: Error?) in if (error == nil) { @@ -404,6 +413,22 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { self.pluginRTCRtpTransceivers[tcId] = pluginRtcRtpTransceiver } + func RTCRtpTransceiver_setListener( + _ tcId: Int, + eventListener: @escaping (_ data: NSDictionary) -> Void + ) { + NSLog("PluginRTCPeerConnection#RTCRtpTransceiver_setListener()") + + let pluginRTCRtpTransceiver = self.pluginRTCRtpTransceivers[tcId] + + if pluginRTCRtpTransceiver == nil { + return; + } + + // Set the eventListener. + pluginRTCRtpTransceiver!.setListener(eventListener) + } + func createDataChannel( _ dcId: Int, label: String, @@ -870,4 +895,48 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { ] ]) } + + /* Called when transceier will start receiving data. */ + func peerConnection(_ peerConnection: RTCPeerConnection, didStartReceivingOn transceiver: RTCRtpTransceiver) { + NSLog("PluginRTCPeerConnection | didStartReceivingOn") + + // TODO: Is this a new transceiver or was it created already? + var existingTransceiver: PluginRTCRtpTransceiver? = nil + + // TODO: Is it correct reusing transceiver instance with same mid? + for (_, pluginTransceiver) in pluginRTCRtpTransceivers { + if (transceiver.mid == pluginTransceiver.rtcRtpTransceiver!.mid) { + existingTransceiver = pluginTransceiver + break + } + } + + if (existingTransceiver == nil) { + NSLog("PluginRTCPeerConnection | Info: No existing transceiver matching mid: %@", transceiver.mid) + + let tcId = PluginUtils.randomInt(10000, max: 99999) + let pluginRTCRtpTransceiver = PluginRTCRtpTransceiver(transceiver) + + self.pluginRTCRtpTransceivers[tcId] = pluginRTCRtpTransceiver + + var currentDirection = RTCRtpTransceiverDirection.inactive + transceiver.currentDirection(¤tDirection) + + self.eventListener([ + "type": "transceiver", + "transceiver": [ + "tcId": tcId, + "mid": transceiver.mid, + "currentDirection": PluginRTCRtpTransceiver.directionToString(direction: currentDirection), + "direction": PluginRTCRtpTransceiver.directionToString(direction: transceiver.direction), + "stopped": transceiver.isStopped + ] + ]) + } else { + // TODO: Can this situation happen? + NSLog("PluginRTCPeerConnection | Info: Found existing transceiver matching mid: %@", transceiver.mid) + + existingTransceiver!.sendStateUpdate() + } + } } diff --git a/src/PluginRTCRtpTransceiver.swift b/src/PluginRTCRtpTransceiver.swift index 2bd8f627..7b7314a9 100644 --- a/src/PluginRTCRtpTransceiver.swift +++ b/src/PluginRTCRtpTransceiver.swift @@ -11,6 +11,7 @@ class PluginRTCRtpTransceiver : NSObject { eventListener: @escaping (_ data: NSDictionary) -> Void ) { NSLog("PluginRTCRtpTransceiver#init()") + super.init() self.eventListener = eventListener @@ -50,26 +51,32 @@ class PluginRTCRtpTransceiver : NSObject { //self.rtcRtpTransceiver?.sender //self.rtcRtpTransceiver?.receiver - var currentDirection = RTCRtpTransceiverDirection.inactive - self.rtcRtpTransceiver!.currentDirection(¤tDirection) - - self.eventListener!([ - "type": "state", - "transceiver": [ - "mid": self.rtcRtpTransceiver!.mid, - "currentDirection": PluginRTCRtpTransceiver.directionToString(direction: currentDirection), - "direction": PluginRTCRtpTransceiver.directionToString(direction: self.rtcRtpTransceiver!.direction), - "stopped": self.rtcRtpTransceiver!.isStopped - ] - ]) + self.sendStateUpdate() } + /** + * Constructor for pc.didStartReceivingOn event. + */ + init(_ rtcRtpTransceiver: RTCRtpTransceiver) { + NSLog("RTCRtpTransceiver#init()") + + self.rtcRtpTransceiver = rtcRtpTransceiver + } + deinit { NSLog("PluginRTCRtpTransceiver#deinit()") } + func setListener(_ eventListener: @escaping (_ data: NSDictionary) -> Void) { + NSLog("PluginRTCRtpTransceiver#setListener()") + + self.eventListener = eventListener + + // TODO: Emit any queued up state updates + } + func stop() { - self.rtcRtpTransceiver!.stop() + self.rtcRtpTransceiver!.stopInternal() self.eventListener!([ "type": "state", @@ -81,7 +88,7 @@ class PluginRTCRtpTransceiver : NSObject { } func setDirection(direction: String) { - self.rtcRtpTransceiver!.direction = PluginRTCRtpTransceiver.stringToDirection(direction: direction) + self.rtcRtpTransceiver?.setDirection(PluginRTCRtpTransceiver.stringToDirection(direction: direction), error: nil) self.eventListener!([ "type": "state", @@ -91,6 +98,21 @@ class PluginRTCRtpTransceiver : NSObject { ] ]) } + + func sendStateUpdate() { + var currentDirection = RTCRtpTransceiverDirection.inactive + self.rtcRtpTransceiver!.currentDirection(¤tDirection) + + self.eventListener!([ + "type": "state", + "transceiver": [ + "mid": self.rtcRtpTransceiver!.mid, + "currentDirection": PluginRTCRtpTransceiver.directionToString(direction: currentDirection), + "direction": PluginRTCRtpTransceiver.directionToString(direction: self.rtcRtpTransceiver!.direction), + "stopped": self.rtcRtpTransceiver!.isStopped + ] + ]) + } static func stringToDirection(direction: String) -> RTCRtpTransceiverDirection { switch direction { diff --git a/src/iosrtcPlugin.swift b/src/iosrtcPlugin.swift index b76c4f8f..51abe7f1 100644 --- a/src/iosrtcPlugin.swift +++ b/src/iosrtcPlugin.swift @@ -446,6 +446,34 @@ class iosrtcPlugin : CDVPlugin { } } + @objc(RTCPeerConnection_RTCRtpTransceiver_setListener:) func RTCPeerConnection_RTCRtpTransceiver_setListener(_ command: CDVInvokedUrlCommand) { + NSLog("iosrtcPlugin#RTCPeerConnection_RTCRtpTransceiver_setListener()") + + let pcId = command.argument(at: 0) as! Int + let tcId = command.argument(at: 1) as! Int + let pluginRTCPeerConnection = self.pluginRTCPeerConnections[pcId] + + if pluginRTCPeerConnection == nil { + NSLog("iosrtcPlugin#RTCPeerConnection_RTCRtpTransceiver_setListener() | ERROR: pluginRTCPeerConnection with pcId=%@ does not exist", String(pcId)) + return; + } + + self.queue.async { [weak pluginRTCPeerConnection] in + pluginRTCPeerConnection?.RTCRtpTransceiver_setListener(tcId, + eventListener: { (data: NSDictionary) -> Void in + let result = CDVPluginResult( + status: CDVCommandStatus_OK, + messageAs: data as? [AnyHashable: Any] + ) + + // Allow more callbacks. + result!.setKeepCallbackAs(true); + self.emit(command.callbackId, result: result!) + } + ) + } + } + @objc(RTCPeerConnection_RTCRtpTransceiver_setDirection:) func RTCPeerConnection_RTCRtpTransceiver_setDirection(_ command: CDVInvokedUrlCommand) { NSLog("iosrtcPlugin#RTCPeerConnection_RTCRtpTransceiver_setDirection()") diff --git a/www/cordova-plugin-iosrtc.js b/www/cordova-plugin-iosrtc.js index 8a96f7f7..1fa354b1 100644 --- a/www/cordova-plugin-iosrtc.js +++ b/www/cordova-plugin-iosrtc.js @@ -2686,6 +2686,7 @@ function onEvent(data) { self = this, event = new Event(type), dataChannel, + transceiver, id; Object.defineProperty(event, 'target', { value: self, enumerable: true }); @@ -2781,6 +2782,11 @@ function onEvent(data) { dataChannel = new RTCDataChannel(this, null, null, data.channel); event.channel = dataChannel; break; + + case 'transceiver': + transceiver = new RTCRtpTransceiver(this, null, null, data.transceiver); + event.transceiver = transceiver; + break; } this.dispatchEvent(event); @@ -2869,15 +2875,15 @@ debugerror.log = console.warn.bind(console); function RTCRtpTransceiver(peerConnection, trackOrKind, init, data) { var self = this; + // Make this an EventTarget. + EventTarget.call(this); + + this.peerConnection = peerConnection; + // Created using RTCPeerConnection.addTransceiver if (!data) { - // Make this an EventTarget. - EventTarget.call(this); - - // Private attributes. - this.peerConnection = peerConnection; - this._currentDirection = "sendrecv"; - this._direction = "sendrecv"; + this._currentDirection = "inactive"; + this._direction = "inactive"; this._mid = null; this._receiver = null; this._sender = null; @@ -2885,7 +2891,19 @@ function RTCRtpTransceiver(peerConnection, trackOrKind, init, data) { exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', [this.peerConnection.pcId, this.tcId, trackOrKind.id, init]); - // Created using RTCPeerConnection.getTransceivers + // Created by event coming from native. + } else if(data.tcId) { + this.tcId = data.tcId; + this._mid = data.mid; + this._currentDirection = data.currentDirection; + this._direction = data.direction; + + this._receiver = data.receiver; + this._sender = data.sender; + + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_setListener', [this.peerConnection.pcId, this.tcId]); + + // Created using RTCPeerConnection.getTransceivers } else { this._receiver = data.receiver; this._sender = data.sender; From 350aaf175a122634dc87eec469df771e688b0b32 Mon Sep 17 00:00:00 2001 From: Dev1 MacBook Pro Date: Thu, 14 Jan 2021 17:01:04 +0900 Subject: [PATCH 5/9] Add support for addTransceiver(of:) --- js/RTCRtpTransceiver.js | 10 +++- src/PluginRTCPeerConnection.swift | 32 +++++++++++-- src/PluginRTCRtpTransceiver.swift | 76 +++++++++++++++++++++---------- src/iosrtcPlugin.swift | 71 ++++++++++++++++++----------- www/cordova-plugin-iosrtc.js | 10 +++- 5 files changed, 142 insertions(+), 57 deletions(-) diff --git a/js/RTCRtpTransceiver.js b/js/RTCRtpTransceiver.js index 15cbf91f..6a4209eb 100644 --- a/js/RTCRtpTransceiver.js +++ b/js/RTCRtpTransceiver.js @@ -25,6 +25,14 @@ function RTCRtpTransceiver(peerConnection, trackOrKind, init, data) { // Created using RTCPeerConnection.addTransceiver if (!data) { + + var mediaStreamTrackIdOrKind; + if (trackOrKind.id) { + mediaStreamTrackIdOrKind = trackOrKind.id; + } else { + mediaStreamTrackIdOrKind = trackOrKind; + } + this._currentDirection = "inactive"; this._direction = "inactive"; this._mid = null; @@ -32,7 +40,7 @@ function RTCRtpTransceiver(peerConnection, trackOrKind, init, data) { this._sender = null; this.tcId = randomNumber(); - exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', [this.peerConnection.pcId, this.tcId, trackOrKind.id, init]); + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', [this.peerConnection.pcId, this.tcId, mediaStreamTrackIdOrKind, init]); // Created by event coming from native. } else if(data.tcId) { diff --git a/src/PluginRTCPeerConnection.swift b/src/PluginRTCPeerConnection.swift index f4a0f4da..13af18d7 100644 --- a/src/PluginRTCPeerConnection.swift +++ b/src/PluginRTCPeerConnection.swift @@ -393,7 +393,7 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { func addTransceiver( _ tcId: Int, - pluginMediaTrack: PluginMediaStreamTrack, + with: PluginMediaStreamTrack, options: NSDictionary?, eventListener: @escaping (_ data: NSDictionary) -> Void ) { @@ -403,15 +403,37 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { return } - let pluginRtcRtpTransceiver = PluginRTCRtpTransceiver( + let pluginRTCRtpTransceiver = PluginRTCRtpTransceiver( rtcPeerConnection: self.rtcPeerConnection, - mediaStreamTrack: pluginMediaTrack.rtcMediaStreamTrack, + mediaStreamTrack: with.rtcMediaStreamTrack, options: options, eventListener: eventListener - ) + ) - self.pluginRTCRtpTransceivers[tcId] = pluginRtcRtpTransceiver + self.pluginRTCRtpTransceivers[tcId] = pluginRTCRtpTransceiver } + + func addTransceiver( + _ tcId: Int, + of: RTCRtpMediaType, + options: NSDictionary?, + eventListener: @escaping (_ data: NSDictionary) -> Void + ) { + NSLog("PluginRTCPeerConnection#addTransceiver()") + + if self.rtcPeerConnection.signalingState == RTCSignalingState.closed { + return + } + + let pluginRTCRtpTransceiver = PluginRTCRtpTransceiver( + rtcPeerConnection: self.rtcPeerConnection, + mediaType: of, + options: options, + eventListener: eventListener + ) + + self.pluginRTCRtpTransceivers[tcId] = pluginRTCRtpTransceiver + } func RTCRtpTransceiver_setListener( _ tcId: Int, diff --git a/src/PluginRTCRtpTransceiver.swift b/src/PluginRTCRtpTransceiver.swift index 7b7314a9..b5dc1ada 100644 --- a/src/PluginRTCRtpTransceiver.swift +++ b/src/PluginRTCRtpTransceiver.swift @@ -5,45 +5,49 @@ class PluginRTCRtpTransceiver : NSObject { var eventListener: ((_ data: NSDictionary) -> Void)? init( - rtcPeerConnection: RTCPeerConnection, - mediaStreamTrack: RTCMediaStreamTrack, + rtcPeerConnection: RTCPeerConnection, + mediaType: RTCRtpMediaType, options: NSDictionary?, eventListener: @escaping (_ data: NSDictionary) -> Void ) { - NSLog("PluginRTCRtpTransceiver#init()") + NSLog("PluginRTCRtpTransceiver#init(mediaType)") super.init() - self.eventListener = eventListener + self.eventListener = eventListener - let rtcRtpTransceiverInit = RTCRtpTransceiverInit(); + let rtcRtpTransceiverInit = PluginRTCRtpTransceiver.initFromOptionsDictionary(options) - if options?.object(forKey: "direction") != nil { - let direction = options!.object(forKey: "direction") as! String + self.rtcRtpTransceiver = rtcPeerConnection.addTransceiver(of: mediaType, init: rtcRtpTransceiverInit) - rtcRtpTransceiverInit.direction = PluginRTCRtpTransceiver.stringToDirection(direction: direction) - } + if self.rtcRtpTransceiver == nil { + NSLog("PluginRTCRtpTransceiver#init(mediaType) | rtcPeerConnection.addTransceiver() failed") + return + } - if options?.object(forKey: "streams") != nil { - let streams = options!.object(forKey: "streams") as! [NSDictionary] - let streamIds = streams.compactMap({$0["_id"] as? String}) + // TODO: Add support for senders and receivers. + //self.rtcRtpTransceiver?.sender + //self.rtcRtpTransceiver?.receiver - rtcRtpTransceiverInit.streamIds = streamIds - } + self.sendStateUpdate() + } - // TODO: Implement sendEncodings configuration - if options?.object(forKey: "sendEncodings") != nil { - NSLog("iosrtcPlugin#RTCPeerConnection_addTransceiver() | ERROR: init.sendEncodings not supported yet") - return; - } + init( + rtcPeerConnection: RTCPeerConnection, + mediaStreamTrack: RTCMediaStreamTrack, + options: NSDictionary?, + eventListener: @escaping (_ data: NSDictionary) -> Void + ) { + NSLog("PluginRTCRtpTransceiver#init(mediaStreamTrack)") + super.init() - self.rtcRtpTransceiver = rtcPeerConnection.addTransceiver(with: mediaStreamTrack, init: rtcRtpTransceiverInit) + self.eventListener = eventListener + + let rtcRtpTransceiverInit = PluginRTCRtpTransceiver.initFromOptionsDictionary(options) - // TODO: Implement: - // addTransceiverOfType(mediaType) - // addTransceiverOfType(mediaType, init) + self.rtcRtpTransceiver = rtcPeerConnection.addTransceiver(with: mediaStreamTrack, init: rtcRtpTransceiverInit) if self.rtcRtpTransceiver == nil { - NSLog("PluginRTCRtpTransceiver#init() | rtcPeerConnection.addTransceiver() failed") + NSLog("PluginRTCRtpTransceiver#init(mediaStream) | rtcPeerConnection.addTransceiver() failed") return } @@ -146,4 +150,28 @@ class PluginRTCRtpTransceiver : NSObject { return "stopped" } } + + static func initFromOptionsDictionary(_ options: NSDictionary?) -> RTCRtpTransceiverInit { + let rtcRtpTransceiverInit = RTCRtpTransceiverInit(); + + if options?.object(forKey: "direction") != nil { + let direction = options!.object(forKey: "direction") as! String + + rtcRtpTransceiverInit.direction = PluginRTCRtpTransceiver.stringToDirection(direction: direction) + } + + if options?.object(forKey: "streams") != nil { + let streams = options!.object(forKey: "streams") as! [NSDictionary] + let streamIds = streams.compactMap({$0["_id"] as? String}) + + rtcRtpTransceiverInit.streamIds = streamIds + } + + // TODO: Implement sendEncodings configuration + if options?.object(forKey: "sendEncodings") != nil { + NSLog("iosrtcPlugin#RTCPeerConnection_addTransceiver() | ERROR: init.sendEncodings not supported yet") + } + + return rtcRtpTransceiverInit + } } diff --git a/src/iosrtcPlugin.swift b/src/iosrtcPlugin.swift index 51abe7f1..ee2e224c 100644 --- a/src/iosrtcPlugin.swift +++ b/src/iosrtcPlugin.swift @@ -406,43 +406,62 @@ class iosrtcPlugin : CDVPlugin { let pcId = command.argument(at: 0) as! Int let tcId = command.argument(at: 1) as! Int - let trackId = command.argument(at: 2) as! String - - // TODO: Handle cases of trackId representing a track type. + let trackIdOrMediaType = command.argument(at: 2) as? String let pluginRTCPeerConnection = self.pluginRTCPeerConnections[pcId] - let pluginMediaStreamTrack = self.pluginMediaStreamTracks[trackId] - var options: NSDictionary? = nil - - if command.argument(at: 3) != nil { - options = command.argument(at: 3) as? NSDictionary - } if pluginRTCPeerConnection == nil { NSLog("iosrtcPlugin#RTCPeerConnection_addTransceiver() | ERROR: pluginRTCPeerConnection with pcId=%@ does not exist", String(pcId)) return; } - if pluginMediaStreamTrack == nil { - NSLog("iosrtcPlugin#RTCPeerConnection_addTransceiver() | ERROR: pluginMediaStreamTrack with id=\(trackId) does not exist") - return; + var pluginMediaStreamTrack: PluginMediaStreamTrack? = nil + var mediaType: RTCRtpMediaType? = nil + + if trackIdOrMediaType == "video" { + mediaType = RTCRtpMediaType.video + } else if trackIdOrMediaType == "audio" { + mediaType = RTCRtpMediaType.audio + } else { + pluginMediaStreamTrack = self.pluginMediaStreamTracks[trackIdOrMediaType!] + + if pluginMediaStreamTrack == nil { + NSLog("iosrtcPlugin#RTCPeerConnection_addTransceiver() | ERROR: pluginMediaStreamTrack with id=\(trackIdOrMediaType!) does not exist") + return; + } + } + + var options: NSDictionary? = nil + if command.argument(at: 3) != nil { + options = command.argument(at: 3) as? NSDictionary } self.queue.async { [weak pluginRTCPeerConnection, weak pluginMediaStreamTrack] in - pluginRTCPeerConnection?.addTransceiver( - tcId, - pluginMediaTrack: pluginMediaStreamTrack!, - options: options, - eventListener: { (data: NSDictionary) -> Void in - let result = CDVPluginResult( - status: CDVCommandStatus_OK, - messageAs: data as? [AnyHashable: Any] - ) - - result!.setKeepCallbackAs(true); - self.emit(command.callbackId, result: result!) - } - ) + let eventListener = { (data: NSDictionary) -> Void in + let result = CDVPluginResult( + status: CDVCommandStatus_OK, + messageAs: data as? [AnyHashable: Any] + ) + + result!.setKeepCallbackAs(true); + self.emit(command.callbackId, result: result!) + } + + if (pluginMediaStreamTrack != nil) { + pluginRTCPeerConnection!.addTransceiver( + tcId, + with: pluginMediaStreamTrack!, + options: options, + eventListener: eventListener + ) + } else { + pluginRTCPeerConnection!.addTransceiver( + tcId, + of: mediaType!, + options: options, + eventListener: eventListener + ) + } } } diff --git a/www/cordova-plugin-iosrtc.js b/www/cordova-plugin-iosrtc.js index 1fa354b1..620593d1 100644 --- a/www/cordova-plugin-iosrtc.js +++ b/www/cordova-plugin-iosrtc.js @@ -2882,6 +2882,14 @@ function RTCRtpTransceiver(peerConnection, trackOrKind, init, data) { // Created using RTCPeerConnection.addTransceiver if (!data) { + + var mediaStreamTrackIdOrKind; + if (trackOrKind.id) { + mediaStreamTrackIdOrKind = trackOrKind.id; + } else { + mediaStreamTrackIdOrKind = trackOrKind; + } + this._currentDirection = "inactive"; this._direction = "inactive"; this._mid = null; @@ -2889,7 +2897,7 @@ function RTCRtpTransceiver(peerConnection, trackOrKind, init, data) { this._sender = null; this.tcId = randomNumber(); - exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', [this.peerConnection.pcId, this.tcId, trackOrKind.id, init]); + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', [this.peerConnection.pcId, this.tcId, mediaStreamTrackIdOrKind, init]); // Created by event coming from native. } else if(data.tcId) { From 9a7866357d6c789cc3005ae2e0b80cb2f149b8b0 Mon Sep 17 00:00:00 2001 From: Dev1 MacBook Pro Date: Wed, 20 Jan 2021 22:20:49 +0900 Subject: [PATCH 6/9] Associate transceivers with sender/receiver/track. --- js/MediaStream.js | 2 +- js/MediaStreamTrack.js | 8 +- js/RTCPeerConnection.js | 208 +++++++++-------- js/RTCRtpReceiver.js | 14 +- js/RTCRtpSender.js | 20 +- js/RTCRtpTransceiver.js | 100 ++++----- js/iosrtc.js | 2 +- src/PluginRTCPeerConnection.swift | 159 +++++++------ src/PluginRTCRtpTransceiver.swift | 79 ++----- src/iosrtcPlugin.swift | 54 +---- www/cordova-plugin-iosrtc.js | 356 ++++++++++++++++-------------- 11 files changed, 501 insertions(+), 501 deletions(-) diff --git a/js/MediaStream.js b/js/MediaStream.js index 1161e513..cb47b1f1 100644 --- a/js/MediaStream.js +++ b/js/MediaStream.js @@ -13,7 +13,7 @@ module.exports = MediaStream; var debug = require('debug')('iosrtc:MediaStream'), exec = require('cordova/exec'), EventTarget = require('./EventTarget'), - MediaStreamTrack = require('./MediaStreamTrack'), + { MediaStreamTrack } = require('./MediaStreamTrack'), /** * Local variables. */ diff --git a/js/MediaStreamTrack.js b/js/MediaStreamTrack.js index 16f68a91..52e413e5 100644 --- a/js/MediaStreamTrack.js +++ b/js/MediaStreamTrack.js @@ -1,7 +1,8 @@ /** * Expose the MediaStreamTrack class. */ -module.exports = MediaStreamTrack; +module.exports.MediaStreamTrack = MediaStreamTrack; +module.exports.newMediaStreamTrackId = newMediaStreamTrackId; /** * Spec: http://w3c.github.io/mediacapture-main/#mediastreamtrack @@ -48,6 +49,8 @@ function MediaStreamTrack(dataFromEvent) { this._enabled = dataFromEvent.enabled; this._ended = false; + this.dataFromEvent = dataFromEvent; + function onResultOK(data) { onEvent.call(self, data); } @@ -92,7 +95,8 @@ MediaStreamTrack.prototype.clone = function () { kind: this.kind, label: this.label, readyState: this.readyState, - enabled: this.enabled + enabled: this.enabled, + trackId: this.dataFromEvent.trackId }); }; diff --git a/js/RTCPeerConnection.js b/js/RTCPeerConnection.js index 1122093c..92deabdb 100644 --- a/js/RTCPeerConnection.js +++ b/js/RTCPeerConnection.js @@ -21,7 +21,7 @@ var debug = require('debug')('iosrtc:RTCPeerConnection'), RTCStatsResponse = require('./RTCStatsResponse'), RTCStatsReport = require('./RTCStatsReport'), MediaStream = require('./MediaStream'), - MediaStreamTrack = require('./MediaStreamTrack'), + { MediaStreamTrack, newMediaStreamTrackId } = require('./MediaStreamTrack'), Errors = require('./Errors'); debugerror.log = console.warn.bind(console); @@ -56,6 +56,7 @@ function RTCPeerConnection(pcConfig, pcConstraints) { RTCPeerConnection.prototype_descriptor.getLocalStreams ); Object.defineProperty(this, 'addTransceiver', RTCPeerConnection.prototype_descriptor.addTransceiver); + Object.defineProperty(this, 'getOrCreateTrack', RTCPeerConnection.prototype_descriptor.getOrCreateTrack); // Public atributes. this._localDescription = null; @@ -69,8 +70,9 @@ function RTCPeerConnection(pcConfig, pcConstraints) { this.pcId = randomNumber(); this.localStreams = {}; this.remoteStreams = {}; - this.localTracks = {}; - this.remoteTracks = {}; + + this.tracks = {}; + this.transceivers = []; function onResultOK(data) { onEvent.call(self, data); @@ -155,6 +157,10 @@ RTCPeerConnection.prototype.createOffer = function (options) { return; } + if (data.transceivers) { + self.updateTransceiversState(data.transceivers); + } + var desc = new RTCSessionDescription(data); debug('createOffer() | success [desc:%o]', desc); @@ -196,6 +202,10 @@ RTCPeerConnection.prototype.createAnswer = function (options) { return; } + if (data.transceivers) { + self.updateTransceiversState(data.transceivers); + } + var desc = new RTCSessionDescription(data); debug('createAnswer() | success [desc:%o]', desc); @@ -250,6 +260,10 @@ RTCPeerConnection.prototype.setLocalDescription = function (desc) { return; } + if (data.transceivers) { + self.updateTransceiversState(data.transceivers); + } + debug('setLocalDescription() | success'); // Update localDescription. self._localDescription = new RTCSessionDescription(data); @@ -308,6 +322,10 @@ RTCPeerConnection.prototype.setRemoteDescription = function (desc) { return; } + if (data.transceivers) { + self.updateTransceiversState(data.transceivers); + } + debug('setRemoteDescription() | success'); // Update remoteDescription. self.remoteDescription = new RTCSessionDescription(data); @@ -429,68 +447,23 @@ RTCPeerConnection.prototype.getRemoteStreams = function () { }; RTCPeerConnection.prototype.getReceivers = function () { - var self = this, - tracks = [], - id; - - for (id in this.remoteTracks) { - if (this.remoteTracks.hasOwnProperty(id)) { - tracks.push(this.remoteTracks[id]); - } - } - - return tracks.map(function (track) { - return new RTCRtpReceiver({ - pc: self, - track: track - }); - }); + return this.getTransceivers() + .filter((transceiver) => !transceiver.stopped) + .map((transceiver) => transceiver.receiver); }; RTCPeerConnection.prototype.getSenders = function () { - var self = this, - tracks = [], - id; - - for (id in this.localTracks) { - if (this.localTracks.hasOwnProperty(id)) { - tracks.push(this.localTracks[id]); - } - } - - return tracks.map(function (track) { - return new RTCRtpSender({ - pc: self, - track: track - }); - }); + return this.getTransceivers() + .filter((transceiver) => !transceiver.stopped) + .map((transceiver) => transceiver.sender); }; RTCPeerConnection.prototype.getTransceivers = function () { - var transceivers = []; - - // TODO: Retrieve actual transceivers (passing data like before for compiling to work temporarily) - this.getReceivers().map(function (receiver) { - transceivers.push( - new RTCRtpTransceiver(this, null, null, { - receiver: receiver - }) - ); - }); - - this.getSenders().map(function (sender) { - transceivers.push( - new RTCRtpTransceiver(this, null, null, { - sender: sender - }) - ); - }); - - return transceivers; + return this.transceivers; }; -RTCPeerConnection.prototype.addTrack = function (track, stream) { - var id; +RTCPeerConnection.prototype.addTrack = function (track, ...streams) { + var stream = streams[0]; if (isClosed.call(this)) { throw new Errors.InvalidStateError('peerconnection is closed'); @@ -505,16 +478,16 @@ RTCPeerConnection.prototype.addTrack = function (track, stream) { this.addStream(stream); } - for (id in this.localStreams) { - if (this.localStreams.hasOwnProperty(id)) { + for (var streamId in this.localStreams) { + if (this.localStreams.hasOwnProperty(streamId)) { // Target provided stream argument or first added stream to group track - if (!stream || (stream && stream.id === id)) { - stream = this.localStreams[id]; + if (!stream || (stream && stream.id === streamId)) { + stream = this.localStreams[streamId]; stream.addTrack(track); exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_addTrack', [ this.pcId, track.id, - id + streamId ]); break; } @@ -526,11 +499,9 @@ RTCPeerConnection.prototype.addTrack = function (track, stream) { exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_addTrack', [this.pcId, track.id, null]); } - this.localTracks[track.id] = track; + this.getOrCreateTrack(track); - return new RTCRtpSender({ - track: track - }); + return new RTCRtpSender(this, { track }); }; RTCPeerConnection.prototype.removeTrack = function (sender) { @@ -559,7 +530,7 @@ RTCPeerConnection.prototype.removeTrack = function (sender) { track.id, stream.id ]); - delete this.localTracks[track.id]; + delete this.tracks[track.id]; break; } } @@ -567,35 +538,97 @@ RTCPeerConnection.prototype.removeTrack = function (sender) { // No Stream matched remove track without stream if (!stream) { - for (id in this.localTracks) { - if (this.localTracks.hasOwnProperty(id)) { + for (id in this.tracks) { + if (this.tracks.hasOwnProperty(id)) { if (track.id === id) { exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_removeTrack', [ this.pcId, track.id, null ]); - delete this.localTracks[track.id]; + delete this.tracks[track.id]; } } } } }; +RTCPeerConnection.prototype.getOrCreateTrack = function (trackInput) { + var { id } = trackInput, existingTrack = this.tracks[id]; + + if (existingTrack) { + return existingTrack; + } + + var track; + if (trackInput instanceof MediaStreamTrack) { + track = trackInput; + } else { + track = new MediaStreamTrack(trackInput); + } + + this.tracks[id] = track; + track.addEventListener('ended', () => { + delete this.tracks[id]; + }); + + return track; +}; + RTCPeerConnection.prototype.addTransceiver = function (trackOrKind, init) { if (isClosed.call(this)) { throw new Errors.InvalidStateError('peerconnection is closed'); } - if (trackOrKind instanceof String) { + var kind, track = null; + if (trackOrKind instanceof MediaStreamTrack) { + kind = trackOrKind.kind; + track = trackOrKind; + } else { if (!(trackOrKind === "audio" || trackOrKind === "video")) { - throw new TypeError("A string was specified as trackOrKind which is not valid. The string must be either \"audio\" or \"video\"."); + throw new TypeError("An invalid string was specified as trackOrKind. The string must be either \"audio\" or \"video\"."); } - } + kind = trackOrKind; + } + + var receiverTrackID = newMediaStreamTrackId(); + var receiver = new RTCRtpReceiver(this, { + track: new MediaStreamTrack({ + id: receiverTrackID, + kind, + enabled: true, + readyState: 'live', + trackId: 'unknown' + }) + }); + + var sender = new RTCRtpSender(this, { + track + }); debug('addTransceiver() [trackOrKind:%o, init:%o]', trackOrKind, init); - return new RTCRtpTransceiver(this, trackOrKind, init); + var transceiver = new RTCRtpTransceiver(this, trackOrKind, init, { receiver, sender }, receiverTrackID); + + this.transceivers.push(transceiver); + + return transceiver; +}; + +RTCPeerConnection.prototype.updateTransceiversState = function(transceivers) { + debug('updateTransceiversState()', transceivers, this.transceivers); + this.transceivers = transceivers.map((transceiver) => { + const existingTransceiver = this.transceivers.find( + (localTransceiver) => transceiver.tcId === localTransceiver.tcId + ); + + if (existingTransceiver) { + existingTransceiver.update(transceiver); + return existingTransceiver; + } + + return new RTCRtpTransceiver(this, null, null, transceiver); + }); }; RTCPeerConnection.prototype.getStreamById = function (id) { @@ -627,10 +660,7 @@ RTCPeerConnection.prototype.addStream = function (stream) { stream.addedToConnection = true; stream.getTracks().forEach(function (track) { - self.localTracks[track.id] = track; - track.addEventListener('ended', function () { - delete self.localTracks[track.id]; - }); + self.getOrCreateTrack(track); }); exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_addStream', [this.pcId, stream.id]); @@ -657,7 +687,7 @@ RTCPeerConnection.prototype.removeStream = function (stream) { delete this.localStreams[stream.id]; stream.getTracks().forEach(function (track) { - delete self.localTracks[track.id]; + delete self.tracks[track.id]; }); exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_removeStream', [this.pcId, stream.id]); @@ -798,6 +828,10 @@ function onEvent(data) { debug('onEvent() | [type:%s, data:%o]', type, data); + if (data.transceivers) { + this.updateTransceiversState(data.transceivers); + } + switch (type) { case 'signalingstatechange': this.signalingState = data.signalingState; @@ -840,10 +874,10 @@ function onEvent(data) { case 'track': var track = (event.track = new MediaStreamTrack(data.track)); - event.receiver = new RTCRtpReceiver({ track: track }); + event.receiver = new RTCRtpReceiver(self, { track: track }); - // TODO: Ensure this transceiver instance is associated with native API as well. - event.transceiver = new RTCRtpTransceiver(this, null, null, { receiver: event.receiver }); + transceiver = this.transceivers.find(t => t.receiver.track.id === track.id); + event.transceiver = transceiver; event.streams = []; // Add stream only if available in case of Unified-Plan of track event without stream @@ -853,10 +887,7 @@ function onEvent(data) { } // Store remote track - this.remoteTracks[track.id] = track; - track.addEventListener('ended', function () { - delete self.remoteTracks[track.id]; - }); + this.getOrCreateTrack(track); break; @@ -887,11 +918,6 @@ function onEvent(data) { dataChannel = new RTCDataChannel(this, null, null, data.channel); event.channel = dataChannel; break; - - case 'transceiver': - transceiver = new RTCRtpTransceiver(this, null, null, data.transceiver); - event.transceiver = transceiver; - break; } this.dispatchEvent(event); diff --git a/js/RTCRtpReceiver.js b/js/RTCRtpReceiver.js index b6fd3933..5a7c16cc 100644 --- a/js/RTCRtpReceiver.js +++ b/js/RTCRtpReceiver.js @@ -3,9 +3,17 @@ */ module.exports = RTCRtpReceiver; -function RTCRtpReceiver(data) { +function RTCRtpReceiver(pc, data) { data = data || {}; - this._pc = data.pc; - this.track = data.track; + this._pc = pc; + this.track = pc.getOrCreateTrack(data.track); } + +RTCRtpReceiver.prototype.update = function ({ track }) { + if (track) { + this.track = this._pc.getOrCreateTrack(track); + } else { + this.track = null; + } +}; diff --git a/js/RTCRtpSender.js b/js/RTCRtpSender.js index 3e91ddc3..131dd227 100644 --- a/js/RTCRtpSender.js +++ b/js/RTCRtpSender.js @@ -3,11 +3,11 @@ */ module.exports = RTCRtpSender; -function RTCRtpSender(data) { +function RTCRtpSender(pc ,data) { data = data || {}; - this._pc = data.pc; - this.track = data.track; + this._pc = pc; + this.track = data.track ? pc.getOrCreateTrack(data.track) : null; this.params = data.params || {}; } @@ -26,8 +26,10 @@ RTCRtpSender.prototype.replaceTrack = function (withTrack) { return new Promise(function (resolve, reject) { pc.removeTrack(self); - pc.addTrack(withTrack); - self.track = withTrack; + + if (withTrack) { + pc.addTrack(withTrack); + } // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/negotiationneeded_event var event = new Event('negotiationneeded'); @@ -44,3 +46,11 @@ RTCRtpSender.prototype.replaceTrack = function (withTrack) { }); }); }; + +RTCRtpSender.prototype.update = function ({ track }) { + if (track) { + this.track = this._pc.getOrCreateTrack(track); + } else { + this.track = null; + } +}; diff --git a/js/RTCRtpTransceiver.js b/js/RTCRtpTransceiver.js index 6a4209eb..c42b3f34 100644 --- a/js/RTCRtpTransceiver.js +++ b/js/RTCRtpTransceiver.js @@ -1,3 +1,6 @@ +const RTCRtpSender = require('./RTCRtpSender'); +const RTCRtpReceiver = require('./RTCRtpReceiver'); + /** * Expose the RTCRtpTransceiver class. */ @@ -7,7 +10,6 @@ module.exports = RTCRtpTransceiver; * Dependencies. */ var - debug = require('debug')('iosrtc:RTCRtpTransceiver'), debugerror = require('debug')('iosrtc:ERROR:RTCRtpTransceiver'), exec = require('cordova/exec'), randomNumber = require('random-number').generator({min: 10000, max: 99999, integer: true}), @@ -15,17 +17,20 @@ var debugerror.log = console.warn.bind(console); -function RTCRtpTransceiver(peerConnection, trackOrKind, init, data) { - var self = this; - - // Make this an EventTarget. - EventTarget.call(this); +function RTCRtpTransceiver(peerConnection, trackOrKind, init, initialState, receiverTrackId) { + initialState = initialState || {}; this.peerConnection = peerConnection; - - // Created using RTCPeerConnection.addTransceiver - if (!data) { - + this._receiver = initialState.receiver instanceof RTCRtpReceiver ? initialState.receiver : new RTCRtpReceiver(peerConnection, initialState.receiver); + this._sender = initialState.sender instanceof RTCRtpSender ? initialState.sender : new RTCRtpSender(peerConnection, initialState.sender); + this._mid = initialState.mid; + this._stopped = false; + + if (initialState.tcId) { + this.tcId = initialState.tcId; + this._currentDirection = initialState.currentDirection; + this._direction = initialState.direction; + } else { var mediaStreamTrackIdOrKind; if (trackOrKind.id) { mediaStreamTrackIdOrKind = trackOrKind.id; @@ -34,34 +39,21 @@ function RTCRtpTransceiver(peerConnection, trackOrKind, init, data) { } this._currentDirection = "inactive"; - this._direction = "inactive"; - this._mid = null; - this._receiver = null; - this._sender = null; - this.tcId = randomNumber(); - - exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', [this.peerConnection.pcId, this.tcId, mediaStreamTrackIdOrKind, init]); - - // Created by event coming from native. - } else if(data.tcId) { - this.tcId = data.tcId; - this._mid = data.mid; - this._currentDirection = data.currentDirection; - this._direction = data.direction; - this._receiver = data.receiver; - this._sender = data.sender; + if (init && init.direction) { + this._direction = init.direction; + } else { + this._direction = "sendrecv"; + } - exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_setListener', [this.peerConnection.pcId, this.tcId]); + this.tcId = randomNumber(); - // Created using RTCPeerConnection.getTransceivers - } else { - this._receiver = data.receiver; - this._sender = data.sender; + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', + [this.peerConnection.pcId, this.tcId, mediaStreamTrackIdOrKind, init, receiverTrackId]); } function onResultOK(data) { - onEvent.call(self, data); + peerConnection.updateTransceiversState(data); } } @@ -99,38 +91,30 @@ Object.defineProperties(RTCRtpTransceiver.prototype, { return this._sender; } }, + stopped: { + get: function() { + return this._stopped; + } + } }); RTCRtpTransceiver.prototype.stop = function () { exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_stop', [this.peerConnection.pcId, this.tcId]); }; -function onEvent(data) { - var type = data.type; - - debug('onEvent() | [type:%s, data:%o]', type, data); - - if (type !== 'state') { - return; +RTCRtpTransceiver.prototype.update = function (data) { + if (data.direction) { + this._direction = data.direction; } - - var transceiver = data.transceiver; - - if (transceiver) { - if (transceiver.direction) { - this._direction = transceiver.direction; - } - if (transceiver.currentDirection) { - this._currentDirection = transceiver.currentDirection; - } - if (transceiver.mid) { - this._mid = transceiver.mid; - } + if (data.currentDirection) { + this._currentDirection = data.currentDirection; + } + if (data.mid) { + this._mid = data.mid; } -} -// TODO -// https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/currentDirection -// https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiverDirection -// https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/mid -// https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/stop + this._stopped = data.stopped; + + this.receiver.update(data.receiver); + this.sender.update(data.sender); +}; diff --git a/js/iosrtc.js b/js/iosrtc.js index 44addf05..83e9d658 100644 --- a/js/iosrtc.js +++ b/js/iosrtc.js @@ -23,7 +23,7 @@ var // Dictionary of MediaStreamRenderers. RTCIceCandidate = require('./RTCIceCandidate'), MediaDevices = require('./MediaDevices'), MediaStream = require('./MediaStream'), - MediaStreamTrack = require('./MediaStreamTrack'), + { MediaStreamTrack } = require('./MediaStreamTrack'), videoElementsHandler = require('./videoElementsHandler'), RTCRtpTransceiver = require('./RTCRtpTransceiver'); diff --git a/src/PluginRTCPeerConnection.swift b/src/PluginRTCPeerConnection.swift index 13af18d7..dc1b1661 100644 --- a/src/PluginRTCPeerConnection.swift +++ b/src/PluginRTCPeerConnection.swift @@ -93,7 +93,8 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { let data = [ "type": RTCSessionDescription.string(for: rtcSessionDescription.type), - "sdp": rtcSessionDescription.sdp + "sdp": rtcSessionDescription.sdp, + "transceivers": self.getTransceiversJSON() ] as [String : Any] callback(data as NSDictionary) @@ -134,7 +135,8 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { let data = [ "type": RTCSessionDescription.string(for: rtcSessionDescription.type), - "sdp": rtcSessionDescription.sdp + "sdp": rtcSessionDescription.sdp, + "transceivers": self.getTransceiversJSON() ] as [String : Any] callback(data as NSDictionary) @@ -176,7 +178,8 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { NSLog("PluginRTCPeerConnection#setLocalDescription() | success callback") let data = [ "type": RTCSessionDescription.string(for: self.rtcPeerConnection.localDescription!.type), - "sdp": self.rtcPeerConnection.localDescription!.sdp + "sdp": self.rtcPeerConnection.localDescription!.sdp, + "transceivers": self.getTransceiversJSON() ] as [String : Any] callback(data as NSDictionary) @@ -230,10 +233,11 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { let data = [ "type": RTCSessionDescription.string(for: self.rtcPeerConnection.remoteDescription!.type), - "sdp": self.rtcPeerConnection.remoteDescription!.sdp - ] + "sdp": self.rtcPeerConnection.remoteDescription!.sdp, + "transceivers": self.getTransceiversJSON() + ] as NSDictionary - callback(data as NSDictionary) + callback(data) } self.onSetDescriptionFailureCallback = { (error: Error) -> Void in @@ -393,9 +397,11 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { func addTransceiver( _ tcId: Int, - with: PluginMediaStreamTrack, - options: NSDictionary?, - eventListener: @escaping (_ data: NSDictionary) -> Void + with: PluginMediaStreamTrack?, + of: RTCRtpMediaType?, + options: NSDictionary?, + receiverTrackId: String, + callback: (_ data: NSDictionary) -> Void ) { NSLog("PluginRTCPeerConnection#addTransceiver()") @@ -403,52 +409,35 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { return } - let pluginRTCRtpTransceiver = PluginRTCRtpTransceiver( - rtcPeerConnection: self.rtcPeerConnection, - mediaStreamTrack: with.rtcMediaStreamTrack, - options: options, - eventListener: eventListener - ) - - self.pluginRTCRtpTransceivers[tcId] = pluginRTCRtpTransceiver - } - - func addTransceiver( - _ tcId: Int, - of: RTCRtpMediaType, - options: NSDictionary?, - eventListener: @escaping (_ data: NSDictionary) -> Void - ) { - NSLog("PluginRTCPeerConnection#addTransceiver()") - - if self.rtcPeerConnection.signalingState == RTCSignalingState.closed { - return - } - - let pluginRTCRtpTransceiver = PluginRTCRtpTransceiver( - rtcPeerConnection: self.rtcPeerConnection, - mediaType: of, - options: options, - eventListener: eventListener - ) - - self.pluginRTCRtpTransceivers[tcId] = pluginRTCRtpTransceiver - } + var pluginRTCRtpTransceiver: PluginRTCRtpTransceiver + + if with != nil { + pluginRTCRtpTransceiver = PluginRTCRtpTransceiver( + rtcPeerConnection: self.rtcPeerConnection, + mediaStreamTrack: with!.rtcMediaStreamTrack, + options: options + ) + } else if of != nil { + pluginRTCRtpTransceiver = PluginRTCRtpTransceiver( + rtcPeerConnection: self.rtcPeerConnection, + mediaType: of!, + options: options + ) + } else { + NSLog("PluginRTCPeerConnection#addTransceiver() | error: no valid track or type") + return + } - func RTCRtpTransceiver_setListener( - _ tcId: Int, - eventListener: @escaping (_ data: NSDictionary) -> Void - ) { - NSLog("PluginRTCPeerConnection#RTCRtpTransceiver_setListener()") + // NOTE: Creates native track in case it's not already existing. + self.getPluginMediaStreamTrack(pluginRTCRtpTransceiver.rtcRtpTransceiver!.receiver.track, trackId: receiverTrackId) - let pluginRTCRtpTransceiver = self.pluginRTCRtpTransceivers[tcId] + self.pluginRTCRtpTransceivers[tcId] = pluginRTCRtpTransceiver - if pluginRTCRtpTransceiver == nil { - return; - } + let response: NSDictionary = [ + "transceivers": self.getTransceiversJSON() + ] - // Set the eventListener. - pluginRTCRtpTransceiver!.setListener(eventListener) + callback(response) } func createDataChannel( @@ -690,16 +679,15 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { } - private func getPluginMediaStreamTrack(_ rtpReceiver: RTCRtpReceiver) -> PluginMediaStreamTrack? { - - if (rtpReceiver.track == nil) { + private func getPluginMediaStreamTrack(_ track: RTCMediaStreamTrack?, trackId: String?) -> PluginMediaStreamTrack? { + if (track == nil) { return nil; } var currentMediaStreamTrack : PluginMediaStreamTrack? = nil; for (_, pluginMediaTrack) in self.pluginMediaTracks { - if (pluginMediaTrack.rtcMediaStreamTrack.trackId == rtpReceiver.track!.trackId) { + if (pluginMediaTrack.rtcMediaStreamTrack.trackId == track!.trackId) { currentMediaStreamTrack = pluginMediaTrack; break; } @@ -707,13 +695,12 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { if (currentMediaStreamTrack == nil) { - currentMediaStreamTrack = PluginMediaStreamTrack(rtcMediaStreamTrack: rtpReceiver.track!) + currentMediaStreamTrack = PluginMediaStreamTrack(rtcMediaStreamTrack: track!, trackId: trackId) currentMediaStreamTrack!.run() // Let the plugin store it in its dictionary. self.pluginMediaTracks[currentMediaStreamTrack!.id] = currentMediaStreamTrack; - self.trackIdsToReceivers[currentMediaStreamTrack!.id] = rtpReceiver; // Fixes issue #576 self.eventListenerForAddTrack(currentMediaStreamTrack!) @@ -722,6 +709,40 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { return currentMediaStreamTrack; } + func getTransceiversJSON() -> [NSDictionary] { + return self.rtcPeerConnection.transceivers.map({ (transceiver: RTCRtpTransceiver) -> NSDictionary in + let receiverTrack = self.getPluginMediaStreamTrack(transceiver.receiver.track, trackId: nil); + let senderTrack = self.getPluginMediaStreamTrack(transceiver.sender.track, trackId: nil); + + var tcId: Int = -1 + for pluginTransceiver in pluginRTCRtpTransceivers { + if pluginTransceiver.value.rtcRtpTransceiver == transceiver { + tcId = pluginTransceiver.key + break + } + } + + if (tcId == -1) { + tcId = Int.random(in: 0...Int.max) + + pluginRTCRtpTransceivers[tcId] = PluginRTCRtpTransceiver(transceiver) + } + + var currentDirection = RTCRtpTransceiverDirection.inactive + transceiver.currentDirection(¤tDirection) + + return [ + "tcId": tcId, + "mid": transceiver.mid, + "currentDirection": PluginRTCRtpTransceiver.directionToString(currentDirection), + "direction": PluginRTCRtpTransceiver.directionToString(transceiver.direction), + "stopped": transceiver.isStopped, + "receiver": [ "track": receiverTrack!.getJSON() ], + "sender": [ "track": senderTrack!.getJSON() ] + ] + }) + } + /** Called when media is received on a new stream from remote peer. */ func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { NSLog("PluginRTCPeerConnection | onaddstream") @@ -732,7 +753,8 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { self.eventListener([ "type": "addstream", "streamId": pluginMediaStream!.id, - "stream": pluginMediaStream!.getJSON() + "stream": pluginMediaStream!.getJSON(), + "transceivers": self.getTransceiversJSON() ]) } @@ -749,7 +771,8 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { self.eventListener([ "type": "removestream", - "streamId": pluginMediaStream!.id + "streamId": pluginMediaStream!.id, + "transceivers": self.getTransceiversJSON() ]) } @@ -758,7 +781,7 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { NSLog("PluginRTCPeerConnection | onaddtrack") - let pluginMediaTrack = getPluginMediaStreamTrack(rtpReceiver); + let pluginMediaTrack = getPluginMediaStreamTrack(rtpReceiver.track, trackId: nil); // Add stream only if available in case of Unified-Plan of track event without stream // TODO investigate why no stream sometimes with Unified-Plan and confirm that expexted behavior. @@ -767,6 +790,7 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { self.eventListener([ "type": "track", "track": pluginMediaTrack!.getJSON(), + "transceivers": self.getTransceiversJSON() ]) } else { let pluginMediaStream = getPluginMediaStream(streams[0]); @@ -775,7 +799,8 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { "type": "track", "track": pluginMediaTrack!.getJSON(), "streamId": pluginMediaStream!.id, - "stream": pluginMediaStream!.getJSON() + "stream": pluginMediaStream!.getJSON(), + "transceivers": self.getTransceiversJSON() ]) } } @@ -919,10 +944,10 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { } /* Called when transceier will start receiving data. */ - func peerConnection(_ peerConnection: RTCPeerConnection, didStartReceivingOn transceiver: RTCRtpTransceiver) { + /* func peerConnection(_ peerConnection: RTCPeerConnection, didStartReceivingOn transceiver: RTCRtpTransceiver) { NSLog("PluginRTCPeerConnection | didStartReceivingOn") - // TODO: Is this a new transceiver or was it created already? + // NOTE: Is this a new transceiver or was it created already? var existingTransceiver: PluginRTCRtpTransceiver? = nil // TODO: Is it correct reusing transceiver instance with same mid? @@ -949,16 +974,14 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { "transceiver": [ "tcId": tcId, "mid": transceiver.mid, - "currentDirection": PluginRTCRtpTransceiver.directionToString(direction: currentDirection), - "direction": PluginRTCRtpTransceiver.directionToString(direction: transceiver.direction), + "currentDirection": PluginRTCRtpTransceiver.directionToString(currentDirection), + "direction": PluginRTCRtpTransceiver.directionToString(transceiver.direction), "stopped": transceiver.isStopped ] ]) } else { // TODO: Can this situation happen? NSLog("PluginRTCPeerConnection | Info: Found existing transceiver matching mid: %@", transceiver.mid) - - existingTransceiver!.sendStateUpdate() } - } + } */ } diff --git a/src/PluginRTCRtpTransceiver.swift b/src/PluginRTCRtpTransceiver.swift index b5dc1ada..3134804a 100644 --- a/src/PluginRTCRtpTransceiver.swift +++ b/src/PluginRTCRtpTransceiver.swift @@ -2,60 +2,46 @@ import Foundation class PluginRTCRtpTransceiver : NSObject { var rtcRtpTransceiver: RTCRtpTransceiver? - var eventListener: ((_ data: NSDictionary) -> Void)? + + init(rtcRtpTransceiver: RTCRtpTransceiver) { + NSLog("PluginRTCRtpTransceiver#init(rtcRtpTransceiver)") + super.init() + + self.rtcRtpTransceiver = rtcRtpTransceiver + } init( rtcPeerConnection: RTCPeerConnection, mediaType: RTCRtpMediaType, - options: NSDictionary?, - eventListener: @escaping (_ data: NSDictionary) -> Void + options: NSDictionary? ) { NSLog("PluginRTCRtpTransceiver#init(mediaType)") super.init() - self.eventListener = eventListener - let rtcRtpTransceiverInit = PluginRTCRtpTransceiver.initFromOptionsDictionary(options) self.rtcRtpTransceiver = rtcPeerConnection.addTransceiver(of: mediaType, init: rtcRtpTransceiverInit) if self.rtcRtpTransceiver == nil { NSLog("PluginRTCRtpTransceiver#init(mediaType) | rtcPeerConnection.addTransceiver() failed") - return } - - // TODO: Add support for senders and receivers. - //self.rtcRtpTransceiver?.sender - //self.rtcRtpTransceiver?.receiver - - self.sendStateUpdate() } init( rtcPeerConnection: RTCPeerConnection, mediaStreamTrack: RTCMediaStreamTrack, - options: NSDictionary?, - eventListener: @escaping (_ data: NSDictionary) -> Void + options: NSDictionary? ) { NSLog("PluginRTCRtpTransceiver#init(mediaStreamTrack)") super.init() - self.eventListener = eventListener - let rtcRtpTransceiverInit = PluginRTCRtpTransceiver.initFromOptionsDictionary(options) self.rtcRtpTransceiver = rtcPeerConnection.addTransceiver(with: mediaStreamTrack, init: rtcRtpTransceiverInit) if self.rtcRtpTransceiver == nil { NSLog("PluginRTCRtpTransceiver#init(mediaStream) | rtcPeerConnection.addTransceiver() failed") - return } - - // TODO: Add support for senders and receivers. - //self.rtcRtpTransceiver?.sender - //self.rtcRtpTransceiver?.receiver - - self.sendStateUpdate() } /** @@ -71,54 +57,15 @@ class PluginRTCRtpTransceiver : NSObject { NSLog("PluginRTCRtpTransceiver#deinit()") } - func setListener(_ eventListener: @escaping (_ data: NSDictionary) -> Void) { - NSLog("PluginRTCRtpTransceiver#setListener()") - - self.eventListener = eventListener - - // TODO: Emit any queued up state updates - } - func stop() { self.rtcRtpTransceiver!.stopInternal() - - self.eventListener!([ - "type": "state", - "transceiver": [ - "direction": PluginRTCRtpTransceiver.directionToString(direction: self.rtcRtpTransceiver!.direction), - "stopped": self.rtcRtpTransceiver!.isStopped - ] - ]) } func setDirection(direction: String) { - self.rtcRtpTransceiver?.setDirection(PluginRTCRtpTransceiver.stringToDirection(direction: direction), error: nil) - - self.eventListener!([ - "type": "state", - "transceiver": [ - "direction": PluginRTCRtpTransceiver.directionToString(direction: self.rtcRtpTransceiver!.direction), - "stopped": self.rtcRtpTransceiver!.isStopped - ] - ]) - } - - func sendStateUpdate() { - var currentDirection = RTCRtpTransceiverDirection.inactive - self.rtcRtpTransceiver!.currentDirection(¤tDirection) - - self.eventListener!([ - "type": "state", - "transceiver": [ - "mid": self.rtcRtpTransceiver!.mid, - "currentDirection": PluginRTCRtpTransceiver.directionToString(direction: currentDirection), - "direction": PluginRTCRtpTransceiver.directionToString(direction: self.rtcRtpTransceiver!.direction), - "stopped": self.rtcRtpTransceiver!.isStopped - ] - ]) + self.rtcRtpTransceiver?.setDirection(PluginRTCRtpTransceiver.stringToDirection(direction), error: nil) } - static func stringToDirection(direction: String) -> RTCRtpTransceiverDirection { + static func stringToDirection(_ direction: String) -> RTCRtpTransceiverDirection { switch direction { case "inactive": return RTCRtpTransceiverDirection.inactive @@ -136,7 +83,7 @@ class PluginRTCRtpTransceiver : NSObject { } } - static func directionToString(direction: RTCRtpTransceiverDirection) -> String { + static func directionToString(_ direction: RTCRtpTransceiverDirection) -> String { switch direction { case RTCRtpTransceiverDirection.inactive: return "inactive" @@ -157,7 +104,7 @@ class PluginRTCRtpTransceiver : NSObject { if options?.object(forKey: "direction") != nil { let direction = options!.object(forKey: "direction") as! String - rtcRtpTransceiverInit.direction = PluginRTCRtpTransceiver.stringToDirection(direction: direction) + rtcRtpTransceiverInit.direction = PluginRTCRtpTransceiver.stringToDirection(direction) } if options?.object(forKey: "streams") != nil { diff --git a/src/iosrtcPlugin.swift b/src/iosrtcPlugin.swift index ee2e224c..b5d316ef 100644 --- a/src/iosrtcPlugin.swift +++ b/src/iosrtcPlugin.swift @@ -407,6 +407,7 @@ class iosrtcPlugin : CDVPlugin { let pcId = command.argument(at: 0) as! Int let tcId = command.argument(at: 1) as! Int let trackIdOrMediaType = command.argument(at: 2) as? String + let receiverTrackId = command.argument(at: 4) as? String let pluginRTCPeerConnection = self.pluginRTCPeerConnections[pcId] @@ -437,7 +438,7 @@ class iosrtcPlugin : CDVPlugin { } self.queue.async { [weak pluginRTCPeerConnection, weak pluginMediaStreamTrack] in - let eventListener = { (data: NSDictionary) -> Void in + let callback = { (data: NSDictionary) -> Void in let result = CDVPluginResult( status: CDVCommandStatus_OK, messageAs: data as? [AnyHashable: Any] @@ -446,50 +447,15 @@ class iosrtcPlugin : CDVPlugin { result!.setKeepCallbackAs(true); self.emit(command.callbackId, result: result!) } - - if (pluginMediaStreamTrack != nil) { - pluginRTCPeerConnection!.addTransceiver( - tcId, - with: pluginMediaStreamTrack!, - options: options, - eventListener: eventListener - ) - } else { - pluginRTCPeerConnection!.addTransceiver( - tcId, - of: mediaType!, - options: options, - eventListener: eventListener - ) - } - } - } - - @objc(RTCPeerConnection_RTCRtpTransceiver_setListener:) func RTCPeerConnection_RTCRtpTransceiver_setListener(_ command: CDVInvokedUrlCommand) { - NSLog("iosrtcPlugin#RTCPeerConnection_RTCRtpTransceiver_setListener()") - - let pcId = command.argument(at: 0) as! Int - let tcId = command.argument(at: 1) as! Int - let pluginRTCPeerConnection = self.pluginRTCPeerConnections[pcId] - - if pluginRTCPeerConnection == nil { - NSLog("iosrtcPlugin#RTCPeerConnection_RTCRtpTransceiver_setListener() | ERROR: pluginRTCPeerConnection with pcId=%@ does not exist", String(pcId)) - return; - } - self.queue.async { [weak pluginRTCPeerConnection] in - pluginRTCPeerConnection?.RTCRtpTransceiver_setListener(tcId, - eventListener: { (data: NSDictionary) -> Void in - let result = CDVPluginResult( - status: CDVCommandStatus_OK, - messageAs: data as? [AnyHashable: Any] - ) - - // Allow more callbacks. - result!.setKeepCallbackAs(true); - self.emit(command.callbackId, result: result!) - } - ) + pluginRTCPeerConnection!.addTransceiver( + tcId, + with: pluginMediaStreamTrack, + of: mediaType, + options: options, + receiverTrackId: receiverTrackId!, + callback: callback + ) } } diff --git a/www/cordova-plugin-iosrtc.js b/www/cordova-plugin-iosrtc.js index 620593d1..98cf8044 100644 --- a/www/cordova-plugin-iosrtc.js +++ b/www/cordova-plugin-iosrtc.js @@ -218,7 +218,7 @@ module.exports = MediaStream; var debug = _dereq_('debug')('iosrtc:MediaStream'), exec = _dereq_('cordova/exec'), EventTarget = _dereq_('./EventTarget'), - MediaStreamTrack = _dereq_('./MediaStreamTrack'), + { MediaStreamTrack } = _dereq_('./MediaStreamTrack'), /** * Local variables. */ @@ -1128,7 +1128,8 @@ function getElementPositionAndSize() { /** * Expose the MediaStreamTrack class. */ -module.exports = MediaStreamTrack; +module.exports.MediaStreamTrack = MediaStreamTrack; +module.exports.newMediaStreamTrackId = newMediaStreamTrackId; /** * Spec: http://w3c.github.io/mediacapture-main/#mediastreamtrack @@ -1175,6 +1176,8 @@ function MediaStreamTrack(dataFromEvent) { this._enabled = dataFromEvent.enabled; this._ended = false; + this.dataFromEvent = dataFromEvent; + function onResultOK(data) { onEvent.call(self, data); } @@ -1219,7 +1222,8 @@ MediaStreamTrack.prototype.clone = function () { kind: this.kind, label: this.label, readyState: this.readyState, - enabled: this.enabled + enabled: this.enabled, + trackId: this.dataFromEvent.trackId }); }; @@ -1916,7 +1920,7 @@ var debug = _dereq_('debug')('iosrtc:RTCPeerConnection'), RTCStatsResponse = _dereq_('./RTCStatsResponse'), RTCStatsReport = _dereq_('./RTCStatsReport'), MediaStream = _dereq_('./MediaStream'), - MediaStreamTrack = _dereq_('./MediaStreamTrack'), + { MediaStreamTrack, newMediaStreamTrackId } = _dereq_('./MediaStreamTrack'), Errors = _dereq_('./Errors'); debugerror.log = console.warn.bind(console); @@ -1951,6 +1955,7 @@ function RTCPeerConnection(pcConfig, pcConstraints) { RTCPeerConnection.prototype_descriptor.getLocalStreams ); Object.defineProperty(this, 'addTransceiver', RTCPeerConnection.prototype_descriptor.addTransceiver); + Object.defineProperty(this, 'getOrCreateTrack', RTCPeerConnection.prototype_descriptor.getOrCreateTrack); // Public atributes. this._localDescription = null; @@ -1964,8 +1969,9 @@ function RTCPeerConnection(pcConfig, pcConstraints) { this.pcId = randomNumber(); this.localStreams = {}; this.remoteStreams = {}; - this.localTracks = {}; - this.remoteTracks = {}; + + this.tracks = {}; + this.transceivers = []; function onResultOK(data) { onEvent.call(self, data); @@ -2050,6 +2056,10 @@ RTCPeerConnection.prototype.createOffer = function (options) { return; } + if (data.transceivers) { + self.updateTransceiversState(data.transceivers); + } + var desc = new RTCSessionDescription(data); debug('createOffer() | success [desc:%o]', desc); @@ -2091,6 +2101,10 @@ RTCPeerConnection.prototype.createAnswer = function (options) { return; } + if (data.transceivers) { + self.updateTransceiversState(data.transceivers); + } + var desc = new RTCSessionDescription(data); debug('createAnswer() | success [desc:%o]', desc); @@ -2145,6 +2159,10 @@ RTCPeerConnection.prototype.setLocalDescription = function (desc) { return; } + if (data.transceivers) { + self.updateTransceiversState(data.transceivers); + } + debug('setLocalDescription() | success'); // Update localDescription. self._localDescription = new RTCSessionDescription(data); @@ -2203,6 +2221,10 @@ RTCPeerConnection.prototype.setRemoteDescription = function (desc) { return; } + if (data.transceivers) { + self.updateTransceiversState(data.transceivers); + } + debug('setRemoteDescription() | success'); // Update remoteDescription. self.remoteDescription = new RTCSessionDescription(data); @@ -2324,68 +2346,23 @@ RTCPeerConnection.prototype.getRemoteStreams = function () { }; RTCPeerConnection.prototype.getReceivers = function () { - var self = this, - tracks = [], - id; - - for (id in this.remoteTracks) { - if (this.remoteTracks.hasOwnProperty(id)) { - tracks.push(this.remoteTracks[id]); - } - } - - return tracks.map(function (track) { - return new RTCRtpReceiver({ - pc: self, - track: track - }); - }); + return this.getTransceivers() + .filter((transceiver) => !transceiver.stopped) + .map((transceiver) => transceiver.receiver); }; RTCPeerConnection.prototype.getSenders = function () { - var self = this, - tracks = [], - id; - - for (id in this.localTracks) { - if (this.localTracks.hasOwnProperty(id)) { - tracks.push(this.localTracks[id]); - } - } - - return tracks.map(function (track) { - return new RTCRtpSender({ - pc: self, - track: track - }); - }); + return this.getTransceivers() + .filter((transceiver) => !transceiver.stopped) + .map((transceiver) => transceiver.sender); }; RTCPeerConnection.prototype.getTransceivers = function () { - var transceivers = []; - - // TODO: Retrieve actual transceivers (passing data like before for compiling to work temporarily) - this.getReceivers().map(function (receiver) { - transceivers.push( - new RTCRtpTransceiver(this, null, null, { - receiver: receiver - }) - ); - }); - - this.getSenders().map(function (sender) { - transceivers.push( - new RTCRtpTransceiver(this, null, null, { - sender: sender - }) - ); - }); - - return transceivers; + return this.transceivers; }; -RTCPeerConnection.prototype.addTrack = function (track, stream) { - var id; +RTCPeerConnection.prototype.addTrack = function (track, ...streams) { + var stream = streams[0]; if (isClosed.call(this)) { throw new Errors.InvalidStateError('peerconnection is closed'); @@ -2400,16 +2377,16 @@ RTCPeerConnection.prototype.addTrack = function (track, stream) { this.addStream(stream); } - for (id in this.localStreams) { - if (this.localStreams.hasOwnProperty(id)) { + for (var streamId in this.localStreams) { + if (this.localStreams.hasOwnProperty(streamId)) { // Target provided stream argument or first added stream to group track - if (!stream || (stream && stream.id === id)) { - stream = this.localStreams[id]; + if (!stream || (stream && stream.id === streamId)) { + stream = this.localStreams[streamId]; stream.addTrack(track); exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_addTrack', [ this.pcId, track.id, - id + streamId ]); break; } @@ -2421,11 +2398,9 @@ RTCPeerConnection.prototype.addTrack = function (track, stream) { exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_addTrack', [this.pcId, track.id, null]); } - this.localTracks[track.id] = track; + this.getOrCreateTrack(track); - return new RTCRtpSender({ - track: track - }); + return new RTCRtpSender(this, { track }); }; RTCPeerConnection.prototype.removeTrack = function (sender) { @@ -2454,7 +2429,7 @@ RTCPeerConnection.prototype.removeTrack = function (sender) { track.id, stream.id ]); - delete this.localTracks[track.id]; + delete this.tracks[track.id]; break; } } @@ -2462,35 +2437,97 @@ RTCPeerConnection.prototype.removeTrack = function (sender) { // No Stream matched remove track without stream if (!stream) { - for (id in this.localTracks) { - if (this.localTracks.hasOwnProperty(id)) { + for (id in this.tracks) { + if (this.tracks.hasOwnProperty(id)) { if (track.id === id) { exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_removeTrack', [ this.pcId, track.id, null ]); - delete this.localTracks[track.id]; + delete this.tracks[track.id]; } } } } }; +RTCPeerConnection.prototype.getOrCreateTrack = function (trackInput) { + var { id } = trackInput, existingTrack = this.tracks[id]; + + if (existingTrack) { + return existingTrack; + } + + var track; + if (trackInput instanceof MediaStreamTrack) { + track = trackInput; + } else { + track = new MediaStreamTrack(trackInput); + } + + this.tracks[id] = track; + track.addEventListener('ended', () => { + delete this.tracks[id]; + }); + + return track; +}; + RTCPeerConnection.prototype.addTransceiver = function (trackOrKind, init) { if (isClosed.call(this)) { throw new Errors.InvalidStateError('peerconnection is closed'); } - if (trackOrKind instanceof String) { + var kind, track = null; + if (trackOrKind instanceof MediaStreamTrack) { + kind = trackOrKind.kind; + track = trackOrKind; + } else { if (!(trackOrKind === "audio" || trackOrKind === "video")) { - throw new TypeError("A string was specified as trackOrKind which is not valid. The string must be either \"audio\" or \"video\"."); + throw new TypeError("An invalid string was specified as trackOrKind. The string must be either \"audio\" or \"video\"."); } - } + kind = trackOrKind; + } + + var receiverTrackID = newMediaStreamTrackId(); + var receiver = new RTCRtpReceiver(this, { + track: new MediaStreamTrack({ + id: receiverTrackID, + kind, + enabled: true, + readyState: 'live', + trackId: 'unknown' + }) + }); + + var sender = new RTCRtpSender(this, { + track + }); debug('addTransceiver() [trackOrKind:%o, init:%o]', trackOrKind, init); - return new RTCRtpTransceiver(this, trackOrKind, init); + var transceiver = new RTCRtpTransceiver(this, trackOrKind, init, { receiver, sender }, receiverTrackID); + + this.transceivers.push(transceiver); + + return transceiver; +}; + +RTCPeerConnection.prototype.updateTransceiversState = function(transceivers) { + debug('updateTransceiversState()', transceivers, this.transceivers); + this.transceivers = transceivers.map((transceiver) => { + const existingTransceiver = this.transceivers.find( + (localTransceiver) => transceiver.tcId === localTransceiver.tcId + ); + + if (existingTransceiver) { + existingTransceiver.update(transceiver); + return existingTransceiver; + } + + return new RTCRtpTransceiver(this, null, null, transceiver); + }); }; RTCPeerConnection.prototype.getStreamById = function (id) { @@ -2522,10 +2559,7 @@ RTCPeerConnection.prototype.addStream = function (stream) { stream.addedToConnection = true; stream.getTracks().forEach(function (track) { - self.localTracks[track.id] = track; - track.addEventListener('ended', function () { - delete self.localTracks[track.id]; - }); + self.getOrCreateTrack(track); }); exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_addStream', [this.pcId, stream.id]); @@ -2552,7 +2586,7 @@ RTCPeerConnection.prototype.removeStream = function (stream) { delete this.localStreams[stream.id]; stream.getTracks().forEach(function (track) { - delete self.localTracks[track.id]; + delete self.tracks[track.id]; }); exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_removeStream', [this.pcId, stream.id]); @@ -2693,6 +2727,10 @@ function onEvent(data) { debug('onEvent() | [type:%s, data:%o]', type, data); + if (data.transceivers) { + this.updateTransceiversState(data.transceivers); + } + switch (type) { case 'signalingstatechange': this.signalingState = data.signalingState; @@ -2735,10 +2773,10 @@ function onEvent(data) { case 'track': var track = (event.track = new MediaStreamTrack(data.track)); - event.receiver = new RTCRtpReceiver({ track: track }); + event.receiver = new RTCRtpReceiver(self, { track: track }); - // TODO: Ensure this transceiver instance is associated with native API as well. - event.transceiver = new RTCRtpTransceiver(this, null, null, { receiver: event.receiver }); + transceiver = this.transceivers.find(t => t.receiver.track.id === track.id); + event.transceiver = transceiver; event.streams = []; // Add stream only if available in case of Unified-Plan of track event without stream @@ -2748,10 +2786,7 @@ function onEvent(data) { } // Store remote track - this.remoteTracks[track.id] = track; - track.addEventListener('ended', function () { - delete self.remoteTracks[track.id]; - }); + this.getOrCreateTrack(track); break; @@ -2782,11 +2817,6 @@ function onEvent(data) { dataChannel = new RTCDataChannel(this, null, null, data.channel); event.channel = dataChannel; break; - - case 'transceiver': - transceiver = new RTCRtpTransceiver(this, null, null, data.transceiver); - event.transceiver = transceiver; - break; } this.dispatchEvent(event); @@ -2799,24 +2829,32 @@ function onEvent(data) { */ module.exports = RTCRtpReceiver; -function RTCRtpReceiver(data) { +function RTCRtpReceiver(pc, data) { data = data || {}; - this._pc = data.pc; - this.track = data.track; + this._pc = pc; + this.track = pc.getOrCreateTrack(data.track); } +RTCRtpReceiver.prototype.update = function ({ track }) { + if (track) { + this.track = this._pc.getOrCreateTrack(track); + } else { + this.track = null; + } +}; + },{}],15:[function(_dereq_,module,exports){ /** * Expose the RTCRtpSender class. */ module.exports = RTCRtpSender; -function RTCRtpSender(data) { +function RTCRtpSender(pc ,data) { data = data || {}; - this._pc = data.pc; - this.track = data.track; + this._pc = pc; + this.track = data.track ? pc.getOrCreateTrack(data.track) : null; this.params = data.params || {}; } @@ -2835,8 +2873,10 @@ RTCRtpSender.prototype.replaceTrack = function (withTrack) { return new Promise(function (resolve, reject) { pc.removeTrack(self); - pc.addTrack(withTrack); - self.track = withTrack; + + if (withTrack) { + pc.addTrack(withTrack); + } // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/negotiationneeded_event var event = new Event('negotiationneeded'); @@ -2854,7 +2894,18 @@ RTCRtpSender.prototype.replaceTrack = function (withTrack) { }); }; +RTCRtpSender.prototype.update = function ({ track }) { + if (track) { + this.track = this._pc.getOrCreateTrack(track); + } else { + this.track = null; + } +}; + },{}],16:[function(_dereq_,module,exports){ +const RTCRtpSender = _dereq_('./RTCRtpSender'); +const RTCRtpReceiver = _dereq_('./RTCRtpReceiver'); + /** * Expose the RTCRtpTransceiver class. */ @@ -2864,7 +2915,6 @@ module.exports = RTCRtpTransceiver; * Dependencies. */ var - debug = _dereq_('debug')('iosrtc:RTCRtpTransceiver'), debugerror = _dereq_('debug')('iosrtc:ERROR:RTCRtpTransceiver'), exec = _dereq_('cordova/exec'), randomNumber = _dereq_('random-number').generator({min: 10000, max: 99999, integer: true}), @@ -2872,17 +2922,20 @@ var debugerror.log = console.warn.bind(console); -function RTCRtpTransceiver(peerConnection, trackOrKind, init, data) { - var self = this; - - // Make this an EventTarget. - EventTarget.call(this); +function RTCRtpTransceiver(peerConnection, trackOrKind, init, initialState, receiverTrackId) { + initialState = initialState || {}; this.peerConnection = peerConnection; - - // Created using RTCPeerConnection.addTransceiver - if (!data) { - + this._receiver = initialState.receiver instanceof RTCRtpReceiver ? initialState.receiver : new RTCRtpReceiver(peerConnection, initialState.receiver); + this._sender = initialState.sender instanceof RTCRtpSender ? initialState.sender : new RTCRtpSender(peerConnection, initialState.sender); + this._mid = initialState.mid; + this._stopped = false; + + if (initialState.tcId) { + this.tcId = initialState.tcId; + this._currentDirection = initialState.currentDirection; + this._direction = initialState.direction; + } else { var mediaStreamTrackIdOrKind; if (trackOrKind.id) { mediaStreamTrackIdOrKind = trackOrKind.id; @@ -2891,34 +2944,21 @@ function RTCRtpTransceiver(peerConnection, trackOrKind, init, data) { } this._currentDirection = "inactive"; - this._direction = "inactive"; - this._mid = null; - this._receiver = null; - this._sender = null; - this.tcId = randomNumber(); - - exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', [this.peerConnection.pcId, this.tcId, mediaStreamTrackIdOrKind, init]); - // Created by event coming from native. - } else if(data.tcId) { - this.tcId = data.tcId; - this._mid = data.mid; - this._currentDirection = data.currentDirection; - this._direction = data.direction; - - this._receiver = data.receiver; - this._sender = data.sender; + if (init && init.direction) { + this._direction = init.direction; + } else { + this._direction = "sendrecv"; + } - exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_setListener', [this.peerConnection.pcId, this.tcId]); + this.tcId = randomNumber(); - // Created using RTCPeerConnection.getTransceivers - } else { - this._receiver = data.receiver; - this._sender = data.sender; + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', + [this.peerConnection.pcId, this.tcId, mediaStreamTrackIdOrKind, init, receiverTrackId]); } function onResultOK(data) { - onEvent.call(self, data); + peerConnection.updateTransceiversState(data); } } @@ -2956,43 +2996,35 @@ Object.defineProperties(RTCRtpTransceiver.prototype, { return this._sender; } }, + stopped: { + get: function() { + return this._stopped; + } + } }); RTCRtpTransceiver.prototype.stop = function () { exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_stop', [this.peerConnection.pcId, this.tcId]); }; -function onEvent(data) { - var type = data.type; - - debug('onEvent() | [type:%s, data:%o]', type, data); - - if (type !== 'state') { - return; +RTCRtpTransceiver.prototype.update = function (data) { + if (data.direction) { + this._direction = data.direction; } - - var transceiver = data.transceiver; - - if (transceiver) { - if (transceiver.direction) { - this._direction = transceiver.direction; - } - if (transceiver.currentDirection) { - this._currentDirection = transceiver.currentDirection; - } - if (transceiver.mid) { - this._mid = transceiver.mid; - } + if (data.currentDirection) { + this._currentDirection = data.currentDirection; } -} + if (data.mid) { + this._mid = data.mid; + } + + this._stopped = data.stopped; -// TODO -// https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/currentDirection -// https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiverDirection -// https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/mid -// https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/stop + this.receiver.update(data.receiver); + this.sender.update(data.sender); +}; -},{"./EventTarget":2,"cordova/exec":undefined,"debug":24,"random-number":29}],17:[function(_dereq_,module,exports){ +},{"./EventTarget":2,"./RTCRtpReceiver":14,"./RTCRtpSender":15,"cordova/exec":undefined,"debug":24,"random-number":29}],17:[function(_dereq_,module,exports){ /** * Expose the RTCSessionDescription class. */ @@ -3590,7 +3622,7 @@ var // Dictionary of MediaStreamRenderers. RTCIceCandidate = _dereq_('./RTCIceCandidate'), MediaDevices = _dereq_('./MediaDevices'), MediaStream = _dereq_('./MediaStream'), - MediaStreamTrack = _dereq_('./MediaStreamTrack'), + { MediaStreamTrack } = _dereq_('./MediaStreamTrack'), videoElementsHandler = _dereq_('./videoElementsHandler'), RTCRtpTransceiver = _dereq_('./RTCRtpTransceiver'); From dc9802c4420c2c5c62796225e550a99eb3bc217c Mon Sep 17 00:00:00 2001 From: Dev1 MacBook Pro Date: Thu, 21 Jan 2021 23:03:51 +0900 Subject: [PATCH 7/9] Transient ID's for native transceivers. * Instead of having permanent ID's for the JS -> Transceiver association, just have transient ones until the next transceiver state update. Until better solution can be found. * Reduce complexity of transceiver constructor. --- js/RTCPeerConnection.js | 44 +++++++++-- js/RTCRtpTransceiver.js | 79 +++++++++++-------- js/iosrtc.js | 2 +- src/PluginRTCPeerConnection.swift | 27 +++---- src/PluginRTCRtpTransceiver.swift | 33 ++++---- src/iosrtcPlugin.swift | 61 +++++++++++--- www/cordova-plugin-iosrtc.js | 127 ++++++++++++++++++++---------- 7 files changed, 241 insertions(+), 132 deletions(-) diff --git a/js/RTCPeerConnection.js b/js/RTCPeerConnection.js index 92deabdb..65f95562 100644 --- a/js/RTCPeerConnection.js +++ b/js/RTCPeerConnection.js @@ -17,7 +17,7 @@ var debug = require('debug')('iosrtc:RTCPeerConnection'), RTCDTMFSender = require('./RTCDTMFSender'), RTCRtpReceiver = require('./RTCRtpReceiver'), RTCRtpSender = require('./RTCRtpSender'), - RTCRtpTransceiver = require('./RTCRtpTransceiver'), + { RTCRtpTransceiver, addTransceiverToPeerConnection } = require('./RTCRtpTransceiver'), RTCStatsResponse = require('./RTCStatsResponse'), RTCStatsReport = require('./RTCStatsReport'), MediaStream = require('./MediaStream'), @@ -580,18 +580,21 @@ RTCPeerConnection.prototype.addTransceiver = function (trackOrKind, init) { throw new Errors.InvalidStateError('peerconnection is closed'); } - var kind, track = null; + var kind, track = null, self = this, trackIdOrKind; if (trackOrKind instanceof MediaStreamTrack) { kind = trackOrKind.kind; track = trackOrKind; + trackIdOrKind = trackOrKind.id; } else { if (!(trackOrKind === "audio" || trackOrKind === "video")) { throw new TypeError("An invalid string was specified as trackOrKind. The string must be either \"audio\" or \"video\"."); } kind = trackOrKind; + trackIdOrKind = trackOrKind; } var receiverTrackID = newMediaStreamTrackId(); + var receiver = new RTCRtpReceiver(this, { track: new MediaStreamTrack({ id: receiverTrackID, @@ -605,21 +608,46 @@ RTCPeerConnection.prototype.addTransceiver = function (trackOrKind, init) { var sender = new RTCRtpSender(this, { track }); - - debug('addTransceiver() [trackOrKind:%o, init:%o]', trackOrKind, init); - var transceiver = new RTCRtpTransceiver(this, trackOrKind, init, { receiver, sender }, receiverTrackID); + var data = init; + data.sender = sender; + data.receiver = receiver; + + var transceiver = new RTCRtpTransceiver(this, data); this.transceivers.push(transceiver); + var initJson = {}; + if (init && init.direction) { + initJson.direction = init.direction; + } else { + initJson.direction = 'sendrecv'; + } + + if (init && init.sendEncodings) { + initJson.sendEncodings = init.sendEncodings; + } + + if (init && init.streams) { + initJson.streams = init.streams.map(stream => stream.id); + } else { + initJson.streams = []; + } + + debug('addTransceiver() [trackIdOrKind:%s, init:%o, initJson: %o]', trackIdOrKind, init, initJson); + + addTransceiverToPeerConnection(this, trackIdOrKind, initJson, receiverTrackID) + .then(update => { self.updateTransceiversState(update); }) + .catch(error => { debugerror("addTransceiver() | failure: %s", error); }); + return transceiver; }; RTCPeerConnection.prototype.updateTransceiversState = function(transceivers) { - debug('updateTransceiversState()', transceivers, this.transceivers); + debug('updateTransceiversState()'); this.transceivers = transceivers.map((transceiver) => { const existingTransceiver = this.transceivers.find( - (localTransceiver) => transceiver.tcId === localTransceiver.tcId + (localTransceiver) => localTransceiver.receiverId === transceiver.receiver.track.id ); if (existingTransceiver) { @@ -627,7 +655,7 @@ RTCPeerConnection.prototype.updateTransceiversState = function(transceivers) { return existingTransceiver; } - return new RTCRtpTransceiver(this, null, null, transceiver); + return new RTCRtpTransceiver(this, transceiver); }); }; diff --git a/js/RTCRtpTransceiver.js b/js/RTCRtpTransceiver.js index c42b3f34..6a4187d7 100644 --- a/js/RTCRtpTransceiver.js +++ b/js/RTCRtpTransceiver.js @@ -4,7 +4,7 @@ const RTCRtpReceiver = require('./RTCRtpReceiver'); /** * Expose the RTCRtpTransceiver class. */ -module.exports = RTCRtpTransceiver; +module.exports = { RTCRtpTransceiver, addTransceiverToPeerConnection }; /** * Dependencies. @@ -12,48 +12,43 @@ module.exports = RTCRtpTransceiver; var debugerror = require('debug')('iosrtc:ERROR:RTCRtpTransceiver'), exec = require('cordova/exec'), - randomNumber = require('random-number').generator({min: 10000, max: 99999, integer: true}), EventTarget = require('./EventTarget'); debugerror.log = console.warn.bind(console); -function RTCRtpTransceiver(peerConnection, trackOrKind, init, initialState, receiverTrackId) { - initialState = initialState || {}; +function addTransceiverToPeerConnection(peerConnection, trackIdOrKind, init, receiverTrackId) { + return new Promise((resolve, reject) => { + exec(onResultOK, reject, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', + [peerConnection.pcId, trackIdOrKind, init, receiverTrackId]); - this.peerConnection = peerConnection; - this._receiver = initialState.receiver instanceof RTCRtpReceiver ? initialState.receiver : new RTCRtpReceiver(peerConnection, initialState.receiver); - this._sender = initialState.sender instanceof RTCRtpSender ? initialState.sender : new RTCRtpSender(peerConnection, initialState.sender); - this._mid = initialState.mid; - this._stopped = false; - - if (initialState.tcId) { - this.tcId = initialState.tcId; - this._currentDirection = initialState.currentDirection; - this._direction = initialState.direction; - } else { - var mediaStreamTrackIdOrKind; - if (trackOrKind.id) { - mediaStreamTrackIdOrKind = trackOrKind.id; - } else { - mediaStreamTrackIdOrKind = trackOrKind; + function onResultOK(data) { + resolve(data); } + }); +} - this._currentDirection = "inactive"; +function RTCRtpTransceiver(peerConnection, data) { + data = data || {}; - if (init && init.direction) { - this._direction = init.direction; - } else { - this._direction = "sendrecv"; - } + this.peerConnection = peerConnection; - this.tcId = randomNumber(); + this._id = data.id; + this._receiver = data.receiver instanceof RTCRtpReceiver ? data.receiver : new RTCRtpReceiver(peerConnection, data.receiver); + this._sender = data.sender instanceof RTCRtpSender ? data.sender : new RTCRtpSender(peerConnection, data.sender); + this._stopped = false; - exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', - [this.peerConnection.pcId, this.tcId, mediaStreamTrackIdOrKind, init, receiverTrackId]); + if (data.mid) { + this._mid = data.mid; + } else { + this._mid = null; } - function onResultOK(data) { - peerConnection.updateTransceiversState(data); + if (data.direction) { + this._direction = data.direction; + this._currentDirection = data.direction; + } else { + this._direction = "sendrecv"; + this._currentDirection = "sendrecv"; } } @@ -71,9 +66,14 @@ Object.defineProperties(RTCRtpTransceiver.prototype, { return this._direction; }, set: function (direction) { + var self = this; this._direction = direction; - exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_setDirection', [this.peerConnection.pcId, this.tcId, direction]); + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_setDirection', [this.peerConnection.pcId, this._id, direction]); + + function onResultOK(data) { + self.peerConnection.updateTransceiversState(data.transceivers); + } } }, mid: { @@ -95,14 +95,27 @@ Object.defineProperties(RTCRtpTransceiver.prototype, { get: function() { return this._stopped; } + }, + receiverId: { + get: function() { + return this._receiver.track.id; + } } }); RTCRtpTransceiver.prototype.stop = function () { - exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_stop', [this.peerConnection.pcId, this.tcId]); + var self = this; + + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_stop', [this.peerConnection.pcId, this._id]); + + function onResultOK(data) { + self.peerConnection.updateTransceiversState(data.transceivers); + } }; RTCRtpTransceiver.prototype.update = function (data) { + this._id = data.id; + if (data.direction) { this._direction = data.direction; } diff --git a/js/iosrtc.js b/js/iosrtc.js index 83e9d658..426e6b08 100644 --- a/js/iosrtc.js +++ b/js/iosrtc.js @@ -25,7 +25,7 @@ var // Dictionary of MediaStreamRenderers. MediaStream = require('./MediaStream'), { MediaStreamTrack } = require('./MediaStreamTrack'), videoElementsHandler = require('./videoElementsHandler'), - RTCRtpTransceiver = require('./RTCRtpTransceiver'); + { RTCRtpTransceiver } = require('./RTCRtpTransceiver'); /** * Expose the iosrtc object. diff --git a/src/PluginRTCPeerConnection.swift b/src/PluginRTCPeerConnection.swift index dc1b1661..87230197 100644 --- a/src/PluginRTCPeerConnection.swift +++ b/src/PluginRTCPeerConnection.swift @@ -396,7 +396,6 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { } func addTransceiver( - _ tcId: Int, with: PluginMediaStreamTrack?, of: RTCRtpMediaType?, options: NSDictionary?, @@ -431,7 +430,7 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { // NOTE: Creates native track in case it's not already existing. self.getPluginMediaStreamTrack(pluginRTCRtpTransceiver.rtcRtpTransceiver!.receiver.track, trackId: receiverTrackId) - self.pluginRTCRtpTransceivers[tcId] = pluginRTCRtpTransceiver + self.pluginRTCRtpTransceivers[pluginRTCRtpTransceiver.id] = pluginRTCRtpTransceiver let response: NSDictionary = [ "transceivers": self.getTransceiversJSON() @@ -714,31 +713,24 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { let receiverTrack = self.getPluginMediaStreamTrack(transceiver.receiver.track, trackId: nil); let senderTrack = self.getPluginMediaStreamTrack(transceiver.sender.track, trackId: nil); - var tcId: Int = -1 - for pluginTransceiver in pluginRTCRtpTransceivers { - if pluginTransceiver.value.rtcRtpTransceiver == transceiver { - tcId = pluginTransceiver.key - break - } - } - - if (tcId == -1) { - tcId = Int.random(in: 0...Int.max) - - pluginRTCRtpTransceivers[tcId] = PluginRTCRtpTransceiver(transceiver) - } + // TODO: Possible to prevent having to create native instances + // holding the transceivers each time? The native instances + // is created so that JS can set data or call methods associated + // with the transceivers. + let transceiverHolder = PluginRTCRtpTransceiver(transceiver) + self.pluginRTCRtpTransceivers[transceiverHolder.id] = transceiverHolder var currentDirection = RTCRtpTransceiverDirection.inactive transceiver.currentDirection(¤tDirection) return [ - "tcId": tcId, + "id": transceiverHolder.id, "mid": transceiver.mid, "currentDirection": PluginRTCRtpTransceiver.directionToString(currentDirection), "direction": PluginRTCRtpTransceiver.directionToString(transceiver.direction), "stopped": transceiver.isStopped, "receiver": [ "track": receiverTrack!.getJSON() ], - "sender": [ "track": senderTrack!.getJSON() ] + "sender": [ "track": senderTrack?.getJSON() ] ] }) } @@ -943,6 +935,7 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { ]) } + // TODO: Is this event required at all? /* Called when transceier will start receiving data. */ /* func peerConnection(_ peerConnection: RTCPeerConnection, didStartReceivingOn transceiver: RTCRtpTransceiver) { NSLog("PluginRTCPeerConnection | didStartReceivingOn") diff --git a/src/PluginRTCRtpTransceiver.swift b/src/PluginRTCRtpTransceiver.swift index 3134804a..3d5d59ce 100644 --- a/src/PluginRTCRtpTransceiver.swift +++ b/src/PluginRTCRtpTransceiver.swift @@ -1,13 +1,18 @@ import Foundation class PluginRTCRtpTransceiver : NSObject { + // NOTE: ID used to reference this native transceiver from JS. + var id: Int var rtcRtpTransceiver: RTCRtpTransceiver? - init(rtcRtpTransceiver: RTCRtpTransceiver) { + init(_ rtcRtpTransceiver: RTCRtpTransceiver) { NSLog("PluginRTCRtpTransceiver#init(rtcRtpTransceiver)") - super.init() + // TODO: Using random ID could cause conflicts. + self.id = Int.random(in: 0...10000) self.rtcRtpTransceiver = rtcRtpTransceiver + + super.init() } init( @@ -16,15 +21,17 @@ class PluginRTCRtpTransceiver : NSObject { options: NSDictionary? ) { NSLog("PluginRTCRtpTransceiver#init(mediaType)") - super.init() - + let rtcRtpTransceiverInit = PluginRTCRtpTransceiver.initFromOptionsDictionary(options) + self.id = Int.random(in: 0...10000) self.rtcRtpTransceiver = rtcPeerConnection.addTransceiver(of: mediaType, init: rtcRtpTransceiverInit) if self.rtcRtpTransceiver == nil { NSLog("PluginRTCRtpTransceiver#init(mediaType) | rtcPeerConnection.addTransceiver() failed") } + + super.init() } init( @@ -33,26 +40,19 @@ class PluginRTCRtpTransceiver : NSObject { options: NSDictionary? ) { NSLog("PluginRTCRtpTransceiver#init(mediaStreamTrack)") - super.init() - + let rtcRtpTransceiverInit = PluginRTCRtpTransceiver.initFromOptionsDictionary(options) + self.id = Int.random(in: 0...10000) self.rtcRtpTransceiver = rtcPeerConnection.addTransceiver(with: mediaStreamTrack, init: rtcRtpTransceiverInit) if self.rtcRtpTransceiver == nil { NSLog("PluginRTCRtpTransceiver#init(mediaStream) | rtcPeerConnection.addTransceiver() failed") } + + super.init() } - /** - * Constructor for pc.didStartReceivingOn event. - */ - init(_ rtcRtpTransceiver: RTCRtpTransceiver) { - NSLog("RTCRtpTransceiver#init()") - - self.rtcRtpTransceiver = rtcRtpTransceiver - } - deinit { NSLog("PluginRTCRtpTransceiver#deinit()") } @@ -108,8 +108,7 @@ class PluginRTCRtpTransceiver : NSObject { } if options?.object(forKey: "streams") != nil { - let streams = options!.object(forKey: "streams") as! [NSDictionary] - let streamIds = streams.compactMap({$0["_id"] as? String}) + let streamIds = options!.object(forKey: "streams") as! [String] rtcRtpTransceiverInit.streamIds = streamIds } diff --git a/src/iosrtcPlugin.swift b/src/iosrtcPlugin.swift index b5d316ef..9b9bb876 100644 --- a/src/iosrtcPlugin.swift +++ b/src/iosrtcPlugin.swift @@ -405,9 +405,14 @@ class iosrtcPlugin : CDVPlugin { NSLog("iosrtcPlugin#RTCPeerConnection_addTransceiver()") let pcId = command.argument(at: 0) as! Int - let tcId = command.argument(at: 1) as! Int - let trackIdOrMediaType = command.argument(at: 2) as? String - let receiverTrackId = command.argument(at: 4) as? String + let trackIdOrMediaType = command.argument(at: 1) as? String + + var options: NSDictionary? = nil + if command.argument(at: 2) != nil { + options = command.argument(at: 2) as? NSDictionary + } + + let receiverTrackId = command.argument(at: 3) as? String let pluginRTCPeerConnection = self.pluginRTCPeerConnections[pcId] @@ -432,11 +437,6 @@ class iosrtcPlugin : CDVPlugin { } } - var options: NSDictionary? = nil - if command.argument(at: 3) != nil { - options = command.argument(at: 3) as? NSDictionary - } - self.queue.async { [weak pluginRTCPeerConnection, weak pluginMediaStreamTrack] in let callback = { (data: NSDictionary) -> Void in let result = CDVPluginResult( @@ -449,7 +449,6 @@ class iosrtcPlugin : CDVPlugin { } pluginRTCPeerConnection!.addTransceiver( - tcId, with: pluginMediaStreamTrack, of: mediaType, options: options, @@ -479,8 +478,26 @@ class iosrtcPlugin : CDVPlugin { NSLog("iosrtcPlugin#RTCPeerConnection_RTCRtpTransceiver_setDirection() | ERROR: pluginRTCRtpTransceiver with id=\(tcId) does not exist") return; } - - pluginRTCRtpTransceiver!.setDirection(direction: direction) + + self.queue.async { [weak pluginRTCPeerConnection, weak pluginRTCRtpTransceiver] in + let callback = { (data: NSDictionary) -> Void in + let result = CDVPluginResult( + status: CDVCommandStatus_OK, + messageAs: data as? [AnyHashable: Any] + ) + + result!.setKeepCallbackAs(true); + self.emit(command.callbackId, result: result!) + } + + pluginRTCRtpTransceiver!.setDirection(direction: direction) + + let response: NSDictionary = [ + "transceivers": pluginRTCPeerConnection!.getTransceiversJSON() + ] + + callback(response) + } } @objc(RTCPeerConnection_RTCRtpTransceiver_stop:) func RTCPeerConnection_RTCRtpTransceiver_stop(_ command: CDVInvokedUrlCommand) { @@ -502,8 +519,26 @@ class iosrtcPlugin : CDVPlugin { NSLog("iosrtcPlugin#RTCPeerConnection_RTCRtpTransceiver_stop() | ERROR: pluginRTCRtpTransceiver with id=\(tcId) does not exist") return; } - - pluginRTCRtpTransceiver!.stop() + + self.queue.async { [weak pluginRTCPeerConnection, weak pluginRTCRtpTransceiver] in + let callback = { (data: NSDictionary) -> Void in + let result = CDVPluginResult( + status: CDVCommandStatus_OK, + messageAs: data as? [AnyHashable: Any] + ) + + result!.setKeepCallbackAs(true); + self.emit(command.callbackId, result: result!) + } + + pluginRTCRtpTransceiver!.stop() + + let response: NSDictionary = [ + "transceivers": pluginRTCPeerConnection!.getTransceiversJSON() + ] + + callback(response) + } } @objc(RTCPeerConnection_createDataChannel:) func RTCPeerConnection_createDataChannel(_ command: CDVInvokedUrlCommand) { diff --git a/www/cordova-plugin-iosrtc.js b/www/cordova-plugin-iosrtc.js index 98cf8044..4bad8c76 100644 --- a/www/cordova-plugin-iosrtc.js +++ b/www/cordova-plugin-iosrtc.js @@ -1916,7 +1916,7 @@ var debug = _dereq_('debug')('iosrtc:RTCPeerConnection'), RTCDTMFSender = _dereq_('./RTCDTMFSender'), RTCRtpReceiver = _dereq_('./RTCRtpReceiver'), RTCRtpSender = _dereq_('./RTCRtpSender'), - RTCRtpTransceiver = _dereq_('./RTCRtpTransceiver'), + { RTCRtpTransceiver, addTransceiverToPeerConnection } = _dereq_('./RTCRtpTransceiver'), RTCStatsResponse = _dereq_('./RTCStatsResponse'), RTCStatsReport = _dereq_('./RTCStatsReport'), MediaStream = _dereq_('./MediaStream'), @@ -2479,18 +2479,21 @@ RTCPeerConnection.prototype.addTransceiver = function (trackOrKind, init) { throw new Errors.InvalidStateError('peerconnection is closed'); } - var kind, track = null; + var kind, track = null, self = this, trackIdOrKind; if (trackOrKind instanceof MediaStreamTrack) { kind = trackOrKind.kind; track = trackOrKind; + trackIdOrKind = trackOrKind.id; } else { if (!(trackOrKind === "audio" || trackOrKind === "video")) { throw new TypeError("An invalid string was specified as trackOrKind. The string must be either \"audio\" or \"video\"."); } kind = trackOrKind; + trackIdOrKind = trackOrKind; } var receiverTrackID = newMediaStreamTrackId(); + var receiver = new RTCRtpReceiver(this, { track: new MediaStreamTrack({ id: receiverTrackID, @@ -2504,21 +2507,46 @@ RTCPeerConnection.prototype.addTransceiver = function (trackOrKind, init) { var sender = new RTCRtpSender(this, { track }); - - debug('addTransceiver() [trackOrKind:%o, init:%o]', trackOrKind, init); - var transceiver = new RTCRtpTransceiver(this, trackOrKind, init, { receiver, sender }, receiverTrackID); + var data = init; + data.sender = sender; + data.receiver = receiver; + + var transceiver = new RTCRtpTransceiver(this, data); this.transceivers.push(transceiver); + var initJson = {}; + if (init && init.direction) { + initJson.direction = init.direction; + } else { + initJson.direction = 'sendrecv'; + } + + if (init && init.sendEncodings) { + initJson.sendEncodings = init.sendEncodings; + } + + if (init && init.streams) { + initJson.streams = init.streams.map(stream => stream.id); + } else { + initJson.streams = []; + } + + debug('addTransceiver() [trackIdOrKind:%s, init:%o, initJson: %o]', trackIdOrKind, init, initJson); + + addTransceiverToPeerConnection(this, trackIdOrKind, initJson, receiverTrackID) + .then(update => { self.updateTransceiversState(update); }) + .catch(error => { debugerror("addTransceiver() | failure: %s", error); }); + return transceiver; }; RTCPeerConnection.prototype.updateTransceiversState = function(transceivers) { - debug('updateTransceiversState()', transceivers, this.transceivers); + debug('updateTransceiversState()'); this.transceivers = transceivers.map((transceiver) => { const existingTransceiver = this.transceivers.find( - (localTransceiver) => transceiver.tcId === localTransceiver.tcId + (localTransceiver) => localTransceiver.receiverId === transceiver.receiver.track.id ); if (existingTransceiver) { @@ -2526,7 +2554,7 @@ RTCPeerConnection.prototype.updateTransceiversState = function(transceivers) { return existingTransceiver; } - return new RTCRtpTransceiver(this, null, null, transceiver); + return new RTCRtpTransceiver(this, transceiver); }); }; @@ -2909,7 +2937,7 @@ const RTCRtpReceiver = _dereq_('./RTCRtpReceiver'); /** * Expose the RTCRtpTransceiver class. */ -module.exports = RTCRtpTransceiver; +module.exports = { RTCRtpTransceiver, addTransceiverToPeerConnection }; /** * Dependencies. @@ -2917,48 +2945,43 @@ module.exports = RTCRtpTransceiver; var debugerror = _dereq_('debug')('iosrtc:ERROR:RTCRtpTransceiver'), exec = _dereq_('cordova/exec'), - randomNumber = _dereq_('random-number').generator({min: 10000, max: 99999, integer: true}), EventTarget = _dereq_('./EventTarget'); debugerror.log = console.warn.bind(console); -function RTCRtpTransceiver(peerConnection, trackOrKind, init, initialState, receiverTrackId) { - initialState = initialState || {}; - - this.peerConnection = peerConnection; - this._receiver = initialState.receiver instanceof RTCRtpReceiver ? initialState.receiver : new RTCRtpReceiver(peerConnection, initialState.receiver); - this._sender = initialState.sender instanceof RTCRtpSender ? initialState.sender : new RTCRtpSender(peerConnection, initialState.sender); - this._mid = initialState.mid; - this._stopped = false; +function addTransceiverToPeerConnection(peerConnection, trackIdOrKind, init, receiverTrackId) { + return new Promise((resolve, reject) => { + exec(onResultOK, reject, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', + [peerConnection.pcId, trackIdOrKind, init, receiverTrackId]); - if (initialState.tcId) { - this.tcId = initialState.tcId; - this._currentDirection = initialState.currentDirection; - this._direction = initialState.direction; - } else { - var mediaStreamTrackIdOrKind; - if (trackOrKind.id) { - mediaStreamTrackIdOrKind = trackOrKind.id; - } else { - mediaStreamTrackIdOrKind = trackOrKind; + function onResultOK(data) { + resolve(data); } + }); +} - this._currentDirection = "inactive"; +function RTCRtpTransceiver(peerConnection, data) { + data = data || {}; - if (init && init.direction) { - this._direction = init.direction; - } else { - this._direction = "sendrecv"; - } + this.peerConnection = peerConnection; - this.tcId = randomNumber(); + this._id = data.id; + this._receiver = data.receiver instanceof RTCRtpReceiver ? data.receiver : new RTCRtpReceiver(peerConnection, data.receiver); + this._sender = data.sender instanceof RTCRtpSender ? data.sender : new RTCRtpSender(peerConnection, data.sender); + this._stopped = false; - exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_addTransceiver', - [this.peerConnection.pcId, this.tcId, mediaStreamTrackIdOrKind, init, receiverTrackId]); + if (data.mid) { + this._mid = data.mid; + } else { + this._mid = null; } - function onResultOK(data) { - peerConnection.updateTransceiversState(data); + if (data.direction) { + this._direction = data.direction; + this._currentDirection = data.direction; + } else { + this._direction = "sendrecv"; + this._currentDirection = "sendrecv"; } } @@ -2976,9 +2999,14 @@ Object.defineProperties(RTCRtpTransceiver.prototype, { return this._direction; }, set: function (direction) { + var self = this; this._direction = direction; - exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_setDirection', [this.peerConnection.pcId, this.tcId, direction]); + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_setDirection', [this.peerConnection.pcId, this._id, direction]); + + function onResultOK(data) { + self.peerConnection.updateTransceiversState(data.transceivers); + } } }, mid: { @@ -3000,14 +3028,27 @@ Object.defineProperties(RTCRtpTransceiver.prototype, { get: function() { return this._stopped; } + }, + receiverId: { + get: function() { + return this._receiver.track.id; + } } }); RTCRtpTransceiver.prototype.stop = function () { - exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_stop', [this.peerConnection.pcId, this.tcId]); + var self = this; + + exec(onResultOK, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCRtpTransceiver_stop', [this.peerConnection.pcId, this._id]); + + function onResultOK(data) { + self.peerConnection.updateTransceiversState(data.transceivers); + } }; RTCRtpTransceiver.prototype.update = function (data) { + this._id = data.id; + if (data.direction) { this._direction = data.direction; } @@ -3024,7 +3065,7 @@ RTCRtpTransceiver.prototype.update = function (data) { this.sender.update(data.sender); }; -},{"./EventTarget":2,"./RTCRtpReceiver":14,"./RTCRtpSender":15,"cordova/exec":undefined,"debug":24,"random-number":29}],17:[function(_dereq_,module,exports){ +},{"./EventTarget":2,"./RTCRtpReceiver":14,"./RTCRtpSender":15,"cordova/exec":undefined,"debug":24}],17:[function(_dereq_,module,exports){ /** * Expose the RTCSessionDescription class. */ @@ -3624,7 +3665,7 @@ var // Dictionary of MediaStreamRenderers. MediaStream = _dereq_('./MediaStream'), { MediaStreamTrack } = _dereq_('./MediaStreamTrack'), videoElementsHandler = _dereq_('./videoElementsHandler'), - RTCRtpTransceiver = _dereq_('./RTCRtpTransceiver'); + { RTCRtpTransceiver } = _dereq_('./RTCRtpTransceiver'); /** * Expose the iosrtc object. From 4c286a8a9be40119e468db338df31991b3973691 Mon Sep 17 00:00:00 2001 From: Dev1 MacBook Pro Date: Fri, 22 Jan 2021 00:04:47 +0900 Subject: [PATCH 8/9] Fix issue with undefined init object. --- js/RTCPeerConnection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/RTCPeerConnection.js b/js/RTCPeerConnection.js index 65f95562..1e7efa7b 100644 --- a/js/RTCPeerConnection.js +++ b/js/RTCPeerConnection.js @@ -609,7 +609,7 @@ RTCPeerConnection.prototype.addTransceiver = function (trackOrKind, init) { track }); - var data = init; + var data = init || {}; data.sender = sender; data.receiver = receiver; From e6a837d2ed2e67c72bbd08232ac8db8822587904 Mon Sep 17 00:00:00 2001 From: Dev1 MacBook Pro Date: Fri, 22 Jan 2021 18:15:58 +0900 Subject: [PATCH 9/9] Update sender parameters state and parse sendEncodings. * Update sender parameters along with transceivers. * Parse and pass along sendEncodings when doing addTransceiver. --- js/RTCDataChannel.js | 66 ++++++------ js/RTCRtpSender.js | 4 +- src/PluginRTCPeerConnection.swift | 39 +++++++- src/PluginRTCRtpTransceiver.swift | 12 ++- www/cordova-plugin-iosrtc.js | 161 +++++++++++++++++++++++------- 5 files changed, 208 insertions(+), 74 deletions(-) diff --git a/js/RTCDataChannel.js b/js/RTCDataChannel.js index 8488e2ed..d1febc01 100644 --- a/js/RTCDataChannel.js +++ b/js/RTCDataChannel.js @@ -141,38 +141,40 @@ RTCDataChannel.prototype.send = function (data) { return; } - if (typeof data === 'string' || data instanceof String) { - exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCDataChannel_sendString', [ - this.peerConnection.pcId, - this.dcId, - data - ]); - } else if (window.ArrayBuffer && data instanceof window.ArrayBuffer) { - exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCDataChannel_sendBinary', [ - this.peerConnection.pcId, - this.dcId, - data - ]); - } else if ( - (window.Int8Array && data instanceof window.Int8Array) || - (window.Uint8Array && data instanceof window.Uint8Array) || - (window.Uint8ClampedArray && data instanceof window.Uint8ClampedArray) || - (window.Int16Array && data instanceof window.Int16Array) || - (window.Uint16Array && data instanceof window.Uint16Array) || - (window.Int32Array && data instanceof window.Int32Array) || - (window.Uint32Array && data instanceof window.Uint32Array) || - (window.Float32Array && data instanceof window.Float32Array) || - (window.Float64Array && data instanceof window.Float64Array) || - (window.DataView && data instanceof window.DataView) - ) { - exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCDataChannel_sendBinary', [ - this.peerConnection.pcId, - this.dcId, - data.buffer - ]); - } else { - throw new Error('invalid data type'); - } + setImmediate(() => { + if (typeof data === 'string' || data instanceof String) { + exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCDataChannel_sendString', [ + this.peerConnection.pcId, + this.dcId, + data + ]); + } else if (window.ArrayBuffer && data instanceof window.ArrayBuffer) { + exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCDataChannel_sendBinary', [ + this.peerConnection.pcId, + this.dcId, + data + ]); + } else if ( + (window.Int8Array && data instanceof window.Int8Array) || + (window.Uint8Array && data instanceof window.Uint8Array) || + (window.Uint8ClampedArray && data instanceof window.Uint8ClampedArray) || + (window.Int16Array && data instanceof window.Int16Array) || + (window.Uint16Array && data instanceof window.Uint16Array) || + (window.Int32Array && data instanceof window.Int32Array) || + (window.Uint32Array && data instanceof window.Uint32Array) || + (window.Float32Array && data instanceof window.Float32Array) || + (window.Float64Array && data instanceof window.Float64Array) || + (window.DataView && data instanceof window.DataView) + ) { + exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCDataChannel_sendBinary', [ + this.peerConnection.pcId, + this.dcId, + data.buffer + ]); + } else { + throw new Error('invalid data type'); + } + }); }; RTCDataChannel.prototype.close = function () { diff --git a/js/RTCRtpSender.js b/js/RTCRtpSender.js index 131dd227..5069788f 100644 --- a/js/RTCRtpSender.js +++ b/js/RTCRtpSender.js @@ -47,10 +47,12 @@ RTCRtpSender.prototype.replaceTrack = function (withTrack) { }); }; -RTCRtpSender.prototype.update = function ({ track }) { +RTCRtpSender.prototype.update = function ({ track, params }) { if (track) { this.track = this._pc.getOrCreateTrack(track); } else { this.track = null; } + + this.params = params; }; diff --git a/src/PluginRTCPeerConnection.swift b/src/PluginRTCPeerConnection.swift index 87230197..f98d7d79 100644 --- a/src/PluginRTCPeerConnection.swift +++ b/src/PluginRTCPeerConnection.swift @@ -709,6 +709,11 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { } func getTransceiversJSON() -> [NSDictionary] { + if (!IsUnifiedPlan()) { + NSLog("PluginRTCPeerConnection#getTransceiversJSON() | transceiers is not available when using plan-b") + return []; + } + return self.rtcPeerConnection.transceivers.map({ (transceiver: RTCRtpTransceiver) -> NSDictionary in let receiverTrack = self.getPluginMediaStreamTrack(transceiver.receiver.track, trackId: nil); let senderTrack = self.getPluginMediaStreamTrack(transceiver.sender.track, trackId: nil); @@ -722,6 +727,38 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { var currentDirection = RTCRtpTransceiverDirection.inactive transceiver.currentDirection(¤tDirection) + + + let senderParameters = transceiver.sender.parameters; + let senderParamsJSON = [ + "rtcp": [ + "cname": senderParameters.rtcp.cname, + "reducedSize": senderParameters.rtcp.isReducedSize + ], + "headerExtensions": senderParameters.headerExtensions.map({ (headerExtension: RTCRtpHeaderExtension) -> Int32 in + return headerExtension.id + }), + "codecs": senderParameters.codecs.map({ (codec: RTCRtpCodecParameters) -> NSDictionary in + return [ + "payloadType": codec.payloadType, + "mimeType": NSString(format: "%@/%@", codec.name, codec.kind), + "clockRate": (codec.clockRate ?? nil) as Any, + "channels": (codec.numChannels ?? nil) as Any, + "sdpFmtpLine": codec.parameters + ] + }), + "encodings": senderParameters.encodings.map({ (encoding: RTCRtpEncodingParameters) -> NSDictionary in + return [ + "rid": encoding.rid as Any, + "active": encoding.isActive, + "maxBitrate": encoding.maxBitrateBps as Any, + "maxFramerate": encoding.maxFramerate as Any, + "scaleResolutionDownBy": encoding.scaleResolutionDownBy as Any + + ] + }) + + ] as NSDictionary return [ "id": transceiverHolder.id, @@ -730,7 +767,7 @@ class PluginRTCPeerConnection : NSObject, RTCPeerConnectionDelegate { "direction": PluginRTCRtpTransceiver.directionToString(transceiver.direction), "stopped": transceiver.isStopped, "receiver": [ "track": receiverTrack!.getJSON() ], - "sender": [ "track": senderTrack?.getJSON() ] + "sender": [ "track": senderTrack?.getJSON(), "params": senderParamsJSON ] ] }) } diff --git a/src/PluginRTCRtpTransceiver.swift b/src/PluginRTCRtpTransceiver.swift index 3d5d59ce..c6e339d8 100644 --- a/src/PluginRTCRtpTransceiver.swift +++ b/src/PluginRTCRtpTransceiver.swift @@ -113,9 +113,17 @@ class PluginRTCRtpTransceiver : NSObject { rtcRtpTransceiverInit.streamIds = streamIds } - // TODO: Implement sendEncodings configuration if options?.object(forKey: "sendEncodings") != nil { - NSLog("iosrtcPlugin#RTCPeerConnection_addTransceiver() | ERROR: init.sendEncodings not supported yet") + let encodings = options!.object(forKey: "sendEncodings") as! [NSDictionary] + rtcRtpTransceiverInit.sendEncodings = encodings.map({ (encoding: NSDictionary) -> RTCRtpEncodingParameters in + let encodingParameters = RTCRtpEncodingParameters() + encodingParameters.isActive = encoding["active"] as? Bool ?? true + encodingParameters.maxBitrateBps = encoding["maxBitrate"] as? NSNumber + encodingParameters.maxFramerate = encoding["maxFramerate"] as? NSNumber + encodingParameters.rid = encoding["rid"] as? String + encodingParameters.scaleResolutionDownBy = encoding["scaleResolutionDownBy"] as? NSNumber + return encodingParameters + }) } return rtcRtpTransceiverInit diff --git a/www/cordova-plugin-iosrtc.js b/www/cordova-plugin-iosrtc.js index 4bad8c76..ef081f1b 100644 --- a/www/cordova-plugin-iosrtc.js +++ b/www/cordova-plugin-iosrtc.js @@ -88,7 +88,7 @@ EventTarget.prototype.dispatchEvent = function (event) { */ module.exports = EventTarget; -},{"yaeti":30}],3:[function(_dereq_,module,exports){ +},{"yaeti":31}],3:[function(_dereq_,module,exports){ /** * Expose the MediaDeviceInfo class. */ @@ -1445,6 +1445,7 @@ function onEvent(data) { } },{"./EventTarget":2,"cordova/exec":undefined,"debug":24,"random-number":29}],11:[function(_dereq_,module,exports){ +(function (setImmediate){ /** * Expose the RTCDataChannel class. */ @@ -1588,38 +1589,40 @@ RTCDataChannel.prototype.send = function (data) { return; } - if (typeof data === 'string' || data instanceof String) { - exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCDataChannel_sendString', [ - this.peerConnection.pcId, - this.dcId, - data - ]); - } else if (window.ArrayBuffer && data instanceof window.ArrayBuffer) { - exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCDataChannel_sendBinary', [ - this.peerConnection.pcId, - this.dcId, - data - ]); - } else if ( - (window.Int8Array && data instanceof window.Int8Array) || - (window.Uint8Array && data instanceof window.Uint8Array) || - (window.Uint8ClampedArray && data instanceof window.Uint8ClampedArray) || - (window.Int16Array && data instanceof window.Int16Array) || - (window.Uint16Array && data instanceof window.Uint16Array) || - (window.Int32Array && data instanceof window.Int32Array) || - (window.Uint32Array && data instanceof window.Uint32Array) || - (window.Float32Array && data instanceof window.Float32Array) || - (window.Float64Array && data instanceof window.Float64Array) || - (window.DataView && data instanceof window.DataView) - ) { - exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCDataChannel_sendBinary', [ - this.peerConnection.pcId, - this.dcId, - data.buffer - ]); - } else { - throw new Error('invalid data type'); - } + setImmediate(() => { + if (typeof data === 'string' || data instanceof String) { + exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCDataChannel_sendString', [ + this.peerConnection.pcId, + this.dcId, + data + ]); + } else if (window.ArrayBuffer && data instanceof window.ArrayBuffer) { + exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCDataChannel_sendBinary', [ + this.peerConnection.pcId, + this.dcId, + data + ]); + } else if ( + (window.Int8Array && data instanceof window.Int8Array) || + (window.Uint8Array && data instanceof window.Uint8Array) || + (window.Uint8ClampedArray && data instanceof window.Uint8ClampedArray) || + (window.Int16Array && data instanceof window.Int16Array) || + (window.Uint16Array && data instanceof window.Uint16Array) || + (window.Int32Array && data instanceof window.Int32Array) || + (window.Uint32Array && data instanceof window.Uint32Array) || + (window.Float32Array && data instanceof window.Float32Array) || + (window.Float64Array && data instanceof window.Float64Array) || + (window.DataView && data instanceof window.DataView) + ) { + exec(null, null, 'iosrtcPlugin', 'RTCPeerConnection_RTCDataChannel_sendBinary', [ + this.peerConnection.pcId, + this.dcId, + data.buffer + ]); + } else { + throw new Error('invalid data type'); + } + }); }; RTCDataChannel.prototype.close = function () { @@ -1707,7 +1710,8 @@ function onEvent(data) { } } -},{"./EventTarget":2,"cordova/exec":undefined,"debug":24,"random-number":29}],12:[function(_dereq_,module,exports){ +}).call(this,_dereq_("timers").setImmediate) +},{"./EventTarget":2,"cordova/exec":undefined,"debug":24,"random-number":29,"timers":30}],12:[function(_dereq_,module,exports){ /** * Expose the RTCIceCandidate class. */ @@ -2508,7 +2512,7 @@ RTCPeerConnection.prototype.addTransceiver = function (trackOrKind, init) { track }); - var data = init; + var data = init || {}; data.sender = sender; data.receiver = receiver; @@ -2922,12 +2926,14 @@ RTCRtpSender.prototype.replaceTrack = function (withTrack) { }); }; -RTCRtpSender.prototype.update = function ({ track }) { +RTCRtpSender.prototype.update = function ({ track, params }) { if (track) { this.track = this._pc.getOrCreateTrack(track); } else { this.track = null; } + + this.params = params; }; },{}],16:[function(_dereq_,module,exports){ @@ -5226,13 +5232,92 @@ void function(root){ }(this) },{}],30:[function(_dereq_,module,exports){ +(function (setImmediate,clearImmediate){ +var nextTick = _dereq_('process/browser.js').nextTick; +var apply = Function.prototype.apply; +var slice = Array.prototype.slice; +var immediateIds = {}; +var nextImmediateId = 0; + +// DOM APIs, for completeness + +exports.setTimeout = function() { + return new Timeout(apply.call(setTimeout, window, arguments), clearTimeout); +}; +exports.setInterval = function() { + return new Timeout(apply.call(setInterval, window, arguments), clearInterval); +}; +exports.clearTimeout = +exports.clearInterval = function(timeout) { timeout.close(); }; + +function Timeout(id, clearFn) { + this._id = id; + this._clearFn = clearFn; +} +Timeout.prototype.unref = Timeout.prototype.ref = function() {}; +Timeout.prototype.close = function() { + this._clearFn.call(window, this._id); +}; + +// Does not start the time, just sets up the members needed. +exports.enroll = function(item, msecs) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = msecs; +}; + +exports.unenroll = function(item) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = -1; +}; + +exports._unrefActive = exports.active = function(item) { + clearTimeout(item._idleTimeoutId); + + var msecs = item._idleTimeout; + if (msecs >= 0) { + item._idleTimeoutId = setTimeout(function onTimeout() { + if (item._onTimeout) + item._onTimeout(); + }, msecs); + } +}; + +// That's not how node.js implements it but the exposed api is the same. +exports.setImmediate = typeof setImmediate === "function" ? setImmediate : function(fn) { + var id = nextImmediateId++; + var args = arguments.length < 2 ? false : slice.call(arguments, 1); + + immediateIds[id] = true; + + nextTick(function onNextTick() { + if (immediateIds[id]) { + // fn.call() is faster so we optimize for the common use-case + // @see http://jsperf.com/call-apply-segu + if (args) { + fn.apply(null, args); + } else { + fn.call(null); + } + // Prevent ids from leaking + exports.clearImmediate(id); + } + }); + + return id; +}; + +exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate : function(id) { + delete immediateIds[id]; +}; +}).call(this,_dereq_("timers").setImmediate,_dereq_("timers").clearImmediate) +},{"process/browser.js":28,"timers":30}],31:[function(_dereq_,module,exports){ module.exports = { EventTarget : _dereq_('./lib/EventTarget'), Event : _dereq_('./lib/Event') }; -},{"./lib/Event":31,"./lib/EventTarget":32}],31:[function(_dereq_,module,exports){ +},{"./lib/Event":32,"./lib/EventTarget":33}],32:[function(_dereq_,module,exports){ (function (global){ /** * In browsers export the native Event interface. @@ -5241,7 +5326,7 @@ module.exports = module.exports = global.Event; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],32:[function(_dereq_,module,exports){ +},{}],33:[function(_dereq_,module,exports){ function yaetiEventTarget() { this._listeners = {};