|
|
@ -155,11 +155,11 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
fileprivate func createVideoSender() {
|
|
|
|
fileprivate func createVideoSender() {
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
|
|
|
|
Logger.debug("\(self.TAG) in \(#function)")
|
|
|
|
Logger.debug("\(TAG) in \(#function)")
|
|
|
|
assert(self.videoSender == nil, "\(#function) should only be called once.")
|
|
|
|
assert(self.videoSender == nil, "\(#function) should only be called once.")
|
|
|
|
|
|
|
|
|
|
|
|
guard !Platform.isSimulator else {
|
|
|
|
guard !Platform.isSimulator else {
|
|
|
|
Logger.warn("\(self.TAG) Refusing to create local video track on simulator which has no capture device.")
|
|
|
|
Logger.warn("\(TAG) Refusing to create local video track on simulator which has no capture device.")
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -209,7 +209,7 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
fileprivate func createAudioSender() {
|
|
|
|
fileprivate func createAudioSender() {
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
|
|
|
|
Logger.debug("\(self.TAG) in \(#function)")
|
|
|
|
Logger.debug("\(TAG) in \(#function)")
|
|
|
|
assert(self.audioSender == nil, "\(#function) should only be called once.")
|
|
|
|
assert(self.audioSender == nil, "\(#function) should only be called once.")
|
|
|
|
|
|
|
|
|
|
|
|
let audioSource = factory.audioSource(with: self.audioConstraints)
|
|
|
|
let audioSource = factory.audioSource(with: self.audioConstraints)
|
|
|
@ -253,10 +253,15 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func createOffer() -> Promise<HardenedRTCSessionDescription> {
|
|
|
|
public func createOffer() -> Promise<HardenedRTCSessionDescription> {
|
|
|
|
var result: Promise<HardenedRTCSessionDescription>? = nil
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
PeerConnectionClient.signalingQueue.sync {
|
|
|
|
|
|
|
|
result = Promise { fulfill, reject in
|
|
|
|
return Promise { fulfill, reject in
|
|
|
|
peerConnection.offer(for: self.defaultOfferConstraints, completionHandler: { (sdp: RTCSessionDescription?, error: Error?) in
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PeerConnectionClient.signalingQueue.async {
|
|
|
|
|
|
|
|
self.assertOnSignalingQueue()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.peerConnection.offer(for: self.defaultOfferConstraints, completionHandler: { (sdp: RTCSessionDescription?, error: Error?) in
|
|
|
|
PeerConnectionClient.signalingQueue.async {
|
|
|
|
PeerConnectionClient.signalingQueue.async {
|
|
|
|
guard error == nil else {
|
|
|
|
guard error == nil else {
|
|
|
|
reject(error!)
|
|
|
|
reject(error!)
|
|
|
@ -275,63 +280,55 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// TODO: Propagate exception
|
|
|
|
|
|
|
|
return result!
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func setLocalSessionDescriptionInternal(_ sessionDescription: HardenedRTCSessionDescription) -> Promise<Void> {
|
|
|
|
public func setLocalSessionDescriptionInternal(_ sessionDescription: HardenedRTCSessionDescription) -> Promise<Void> {
|
|
|
|
assertOnSignalingQueue()
|
|
|
|
return PromiseKit.wrap { resolve in
|
|
|
|
|
|
|
|
self.assertOnSignalingQueue()
|
|
|
|
return PromiseKit.wrap {
|
|
|
|
|
|
|
|
Logger.verbose("\(self.TAG) setting local session description: \(sessionDescription)")
|
|
|
|
Logger.verbose("\(self.TAG) setting local session description: \(sessionDescription)")
|
|
|
|
peerConnection.setLocalDescription(sessionDescription.rtcSessionDescription, completionHandler: $0)
|
|
|
|
self.peerConnection.setLocalDescription(sessionDescription.rtcSessionDescription, completionHandler:resolve)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func setLocalSessionDescription(_ sessionDescription: HardenedRTCSessionDescription) -> Promise<Void> {
|
|
|
|
public func setLocalSessionDescription(_ sessionDescription: HardenedRTCSessionDescription) -> Promise<Void> {
|
|
|
|
var result: Promise<Void>? = nil
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
PeerConnectionClient.signalingQueue.sync {
|
|
|
|
|
|
|
|
result = setLocalSessionDescriptionInternal(sessionDescription)
|
|
|
|
return PromiseKit.wrap { resolve in
|
|
|
|
|
|
|
|
PeerConnectionClient.signalingQueue.async {
|
|
|
|
|
|
|
|
self.assertOnSignalingQueue()
|
|
|
|
|
|
|
|
Logger.verbose("\(self.TAG) setting local session description: \(sessionDescription)")
|
|
|
|
|
|
|
|
self.peerConnection.setLocalDescription(sessionDescription.rtcSessionDescription, completionHandler:resolve)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// TODO: Propagate exception
|
|
|
|
|
|
|
|
return result!
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func negotiateSessionDescription(remoteDescription: RTCSessionDescription, constraints: RTCMediaConstraints) -> Promise<HardenedRTCSessionDescription> {
|
|
|
|
public func negotiateSessionDescription(remoteDescription: RTCSessionDescription, constraints: RTCMediaConstraints) -> Promise<HardenedRTCSessionDescription> {
|
|
|
|
var result: Promise<HardenedRTCSessionDescription>? = nil
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
PeerConnectionClient.signalingQueue.sync {
|
|
|
|
|
|
|
|
result = firstly {
|
|
|
|
return setRemoteSessionDescription(remoteDescription)
|
|
|
|
return self.setRemoteSessionDescriptionInternal(remoteDescription)
|
|
|
|
.then(on: PeerConnectionClient.signalingQueue) {
|
|
|
|
}.then(on: PeerConnectionClient.signalingQueue) {
|
|
|
|
|
|
|
|
return self.negotiateAnswerSessionDescription(constraints: constraints)
|
|
|
|
return self.negotiateAnswerSessionDescription(constraints: constraints)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// TODO: Propagate exception
|
|
|
|
|
|
|
|
return result!
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private func setRemoteSessionDescriptionInternal(_ sessionDescription: RTCSessionDescription) -> Promise<Void> {
|
|
|
|
public func setRemoteSessionDescription(_ sessionDescription: RTCSessionDescription) -> Promise<Void> {
|
|
|
|
assertOnSignalingQueue()
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
|
|
|
|
return PromiseKit.wrap {
|
|
|
|
return PromiseKit.wrap { resolve in
|
|
|
|
|
|
|
|
PeerConnectionClient.signalingQueue.async {
|
|
|
|
|
|
|
|
self.assertOnSignalingQueue()
|
|
|
|
Logger.verbose("\(self.TAG) setting remote description: \(sessionDescription)")
|
|
|
|
Logger.verbose("\(self.TAG) setting remote description: \(sessionDescription)")
|
|
|
|
peerConnection.setRemoteDescription(sessionDescription, completionHandler: $0)
|
|
|
|
self.peerConnection.setRemoteDescription(sessionDescription, completionHandler: resolve)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func setRemoteSessionDescription(_ sessionDescription: RTCSessionDescription) -> Promise<Void> {
|
|
|
|
|
|
|
|
var result: Promise<Void>? = nil
|
|
|
|
|
|
|
|
PeerConnectionClient.signalingQueue.sync {
|
|
|
|
|
|
|
|
result = setRemoteSessionDescriptionInternal(sessionDescription)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// TODO: Propagate exception
|
|
|
|
|
|
|
|
return result!
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private func negotiateAnswerSessionDescription(constraints: RTCMediaConstraints) -> Promise<HardenedRTCSessionDescription> {
|
|
|
|
private func negotiateAnswerSessionDescription(constraints: RTCMediaConstraints) -> Promise<HardenedRTCSessionDescription> {
|
|
|
|
assertOnSignalingQueue()
|
|
|
|
assertOnSignalingQueue()
|
|
|
|
|
|
|
|
|
|
|
|
return Promise { fulfill, reject in
|
|
|
|
return Promise { fulfill, reject in
|
|
|
|
|
|
|
|
assertOnSignalingQueue()
|
|
|
|
|
|
|
|
|
|
|
|
Logger.debug("\(self.TAG) negotiating answer session.")
|
|
|
|
Logger.debug("\(self.TAG) negotiating answer session.")
|
|
|
|
|
|
|
|
|
|
|
|
peerConnection.answer(for: constraints, completionHandler: { (sdp: RTCSessionDescription?, error: Error?) in
|
|
|
|
peerConnection.answer(for: constraints, completionHandler: { (sdp: RTCSessionDescription?, error: Error?) in
|
|
|
@ -370,6 +367,13 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
|
|
|
|
|
|
|
|
public func terminate() {
|
|
|
|
public func terminate() {
|
|
|
|
PeerConnectionClient.signalingQueue.async {
|
|
|
|
PeerConnectionClient.signalingQueue.async {
|
|
|
|
|
|
|
|
self.terminateInternal()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private func terminateInternal() {
|
|
|
|
|
|
|
|
assertOnSignalingQueue()
|
|
|
|
|
|
|
|
|
|
|
|
// Some notes on preventing crashes while disposing of peerConnection for video calls
|
|
|
|
// Some notes on preventing crashes while disposing of peerConnection for video calls
|
|
|
|
// from: https://groups.google.com/forum/#!searchin/discuss-webrtc/objc$20crash$20dealloc%7Csort:relevance/discuss-webrtc/7D-vk5yLjn8/rBW2D6EW4GYJ
|
|
|
|
// from: https://groups.google.com/forum/#!searchin/discuss-webrtc/objc$20crash$20dealloc%7Csort:relevance/discuss-webrtc/7D-vk5yLjn8/rBW2D6EW4GYJ
|
|
|
|
// The sequence to make it work appears to be
|
|
|
|
// The sequence to make it work appears to be
|
|
|
@ -382,24 +386,27 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
// audioTrack is a strong property because we need access to it to mute/unmute, but I was seeing it
|
|
|
|
// audioTrack is a strong property because we need access to it to mute/unmute, but I was seeing it
|
|
|
|
// become nil when it was only a weak property. So we retain it and manually nil the reference here, because
|
|
|
|
// become nil when it was only a weak property. So we retain it and manually nil the reference here, because
|
|
|
|
// we are likely to crash if we retain any peer connection properties when the peerconnection is released
|
|
|
|
// we are likely to crash if we retain any peer connection properties when the peerconnection is released
|
|
|
|
Logger.debug("\(self.TAG) in \(#function)")
|
|
|
|
Logger.debug("\(TAG) in \(#function)")
|
|
|
|
self.audioTrack = nil
|
|
|
|
audioTrack = nil
|
|
|
|
self.localVideoTrack = nil
|
|
|
|
localVideoTrack = nil
|
|
|
|
self.remoteVideoTrack = nil
|
|
|
|
remoteVideoTrack = nil
|
|
|
|
self.dataChannel = nil
|
|
|
|
dataChannel = nil
|
|
|
|
self.audioSender = nil
|
|
|
|
audioSender = nil
|
|
|
|
self.videoSender = nil
|
|
|
|
videoSender = nil
|
|
|
|
|
|
|
|
|
|
|
|
self.peerConnection.delegate = nil
|
|
|
|
peerConnection.delegate = nil
|
|
|
|
self.peerConnection.close()
|
|
|
|
peerConnection.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - Data Channel
|
|
|
|
// MARK: - Data Channel
|
|
|
|
|
|
|
|
|
|
|
|
public func sendDataChannelMessage(data: Data) -> Bool {
|
|
|
|
public func sendDataChannelMessage(data: Data) -> Bool {
|
|
|
|
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
|
|
|
|
var result = false
|
|
|
|
var result = false
|
|
|
|
PeerConnectionClient.signalingQueue.sync {
|
|
|
|
PeerConnectionClient.signalingQueue.sync {
|
|
|
|
|
|
|
|
assertOnSignalingQueue()
|
|
|
|
|
|
|
|
|
|
|
|
guard let dataChannel = self.dataChannel else {
|
|
|
|
guard let dataChannel = self.dataChannel else {
|
|
|
|
Logger.error("\(self.TAG) in \(#function) ignoring sending \(data) for nil dataChannel")
|
|
|
|
Logger.error("\(self.TAG) in \(#function) ignoring sending \(data) for nil dataChannel")
|
|
|
|
result = false
|
|
|
|
result = false
|
|
|
@ -416,7 +423,7 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
|
|
|
|
|
|
|
|
/** The data channel state changed. */
|
|
|
|
/** The data channel state changed. */
|
|
|
|
internal func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) {
|
|
|
|
internal func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) {
|
|
|
|
Logger.debug("\(self.TAG) dataChannelDidChangeState: \(dataChannel)")
|
|
|
|
Logger.debug("\(TAG) dataChannelDidChangeState: \(dataChannel)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** The data channel successfully received a data buffer. */
|
|
|
|
/** The data channel successfully received a data buffer. */
|
|
|
@ -440,14 +447,14 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
|
|
|
|
|
|
|
|
/** The data channel's |bufferedAmount| changed. */
|
|
|
|
/** The data channel's |bufferedAmount| changed. */
|
|
|
|
internal func dataChannel(_ dataChannel: RTCDataChannel, didChangeBufferedAmount amount: UInt64) {
|
|
|
|
internal func dataChannel(_ dataChannel: RTCDataChannel, didChangeBufferedAmount amount: UInt64) {
|
|
|
|
Logger.debug("\(self.TAG) didChangeBufferedAmount: \(amount)")
|
|
|
|
Logger.debug("\(TAG) didChangeBufferedAmount: \(amount)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - RTCPeerConnectionDelegate
|
|
|
|
// MARK: - RTCPeerConnectionDelegate
|
|
|
|
|
|
|
|
|
|
|
|
/** Called when the SignalingState changed. */
|
|
|
|
/** Called when the SignalingState changed. */
|
|
|
|
internal func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) {
|
|
|
|
internal func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) {
|
|
|
|
Logger.debug("\(self.TAG) didChange signalingState:\(stateChanged.debugDescription)")
|
|
|
|
Logger.debug("\(TAG) didChange signalingState:\(stateChanged.debugDescription)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Called when media is received on a new stream from remote peer. */
|
|
|
|
/** Called when media is received on a new stream from remote peer. */
|
|
|
@ -469,12 +476,12 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
|
|
|
|
|
|
|
|
/** Called when a remote peer closes a stream. */
|
|
|
|
/** Called when a remote peer closes a stream. */
|
|
|
|
internal func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) {
|
|
|
|
internal func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) {
|
|
|
|
Logger.debug("\(self.TAG) didRemove Stream:\(stream)")
|
|
|
|
Logger.debug("\(TAG) didRemove Stream:\(stream)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Called when negotiation is needed, for example ICE has restarted. */
|
|
|
|
/** Called when negotiation is needed, for example ICE has restarted. */
|
|
|
|
internal func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) {
|
|
|
|
internal func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) {
|
|
|
|
Logger.debug("\(self.TAG) shouldNegotiate")
|
|
|
|
Logger.debug("\(TAG) shouldNegotiate")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Called any time the IceConnectionState changes. */
|
|
|
|
/** Called any time the IceConnectionState changes. */
|
|
|
@ -505,7 +512,7 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
|
|
|
|
|
|
|
|
/** Called any time the IceGatheringState changes. */
|
|
|
|
/** Called any time the IceGatheringState changes. */
|
|
|
|
internal func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) {
|
|
|
|
internal func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) {
|
|
|
|
Logger.debug("\(self.TAG) didChange IceGatheringState:\(newState.debugDescription)")
|
|
|
|
Logger.debug("\(TAG) didChange IceGatheringState:\(newState.debugDescription)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** New ice candidate has been found. */
|
|
|
|
/** New ice candidate has been found. */
|
|
|
@ -522,7 +529,7 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
|
|
|
|
|
|
|
|
|
|
|
/** Called when a group of local Ice candidates have been removed. */
|
|
|
|
/** Called when a group of local Ice candidates have been removed. */
|
|
|
|
internal func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {
|
|
|
|
internal func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {
|
|
|
|
Logger.debug("\(self.TAG) didRemove IceCandidates:\(candidates)")
|
|
|
|
Logger.debug("\(TAG) didRemove IceCandidates:\(candidates)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** New data channel has been opened. */
|
|
|
|
/** New data channel has been opened. */
|
|
|
|