Move RTCPeerConnectionDelegate to PeerConnectionClient
This makes sense as PeerConnectionClient is our interface to WebRTC - Makes it easier to test PeerConnectionClient and CallService - Allows us to shrink CallService class a bit (it's huge) // FREEBIE
This commit is contained in:
parent
bd65dc6ba7
commit
8998853aff
|
@ -52,6 +52,7 @@
|
|||
456F6E231E24133500FD2210 /* Platform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450DF2041E0D74AC003D14BE /* Platform.swift */; };
|
||||
456F6E241E24133E00FD2210 /* CallKitCallUIAdaptee.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F659721E1BD99C00444429 /* CallKitCallUIAdaptee.swift */; };
|
||||
456F6E251E24216100FD2210 /* DataChannelMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45464DBB1DFA041F001D3FD6 /* DataChannelMessage.swift */; };
|
||||
456F6E2F1E261D1000FD2210 /* PeerConnectionClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 456F6E2E1E261D1000FD2210 /* PeerConnectionClientTest.swift */; };
|
||||
4574A5D61DD6704700C6B692 /* CallService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4574A5D51DD6704700C6B692 /* CallService.swift */; };
|
||||
45794E861E00620000066731 /* CallUIAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45794E851E00620000066731 /* CallUIAdapter.swift */; };
|
||||
45843D1F1D2236B30013E85A /* OWSContactsSearcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */; };
|
||||
|
@ -634,6 +635,7 @@
|
|||
45666F7A1D9C0533008FE134 /* OWSDatabaseMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSDatabaseMigration.m; path = Migrations/OWSDatabaseMigration.m; sourceTree = "<group>"; };
|
||||
45666F7C1D9C0814008FE134 /* OWSDatabaseMigrationRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSDatabaseMigrationRunner.h; path = Migrations/OWSDatabaseMigrationRunner.h; sourceTree = "<group>"; };
|
||||
45666F7D1D9C0814008FE134 /* OWSDatabaseMigrationRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSDatabaseMigrationRunner.m; path = Migrations/OWSDatabaseMigrationRunner.m; sourceTree = "<group>"; };
|
||||
456F6E2E1E261D1000FD2210 /* PeerConnectionClientTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerConnectionClientTest.swift; sourceTree = "<group>"; };
|
||||
4574A5D51DD6704700C6B692 /* CallService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallService.swift; sourceTree = "<group>"; };
|
||||
45794E851E00620000066731 /* CallUIAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CallUIAdapter.swift; path = UserInterface/CallUIAdapter.swift; sourceTree = "<group>"; };
|
||||
45843D1D1D2236B30013E85A /* OWSContactsSearcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSearcher.h; sourceTree = "<group>"; };
|
||||
|
@ -2146,6 +2148,7 @@
|
|||
B660F6731C29867F00687D6E /* call */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
456F6E2E1E261D1000FD2210 /* PeerConnectionClientTest.swift */,
|
||||
B660F6741C29867F00687D6E /* RecentCallTest.m */,
|
||||
);
|
||||
path = call;
|
||||
|
@ -3240,6 +3243,7 @@
|
|||
B660F70D1C29988E00687D6E /* AnonymousAudioCallbackHandler.m in Sources */,
|
||||
B660F70E1C29988E00687D6E /* RemoteIOAudio.m in Sources */,
|
||||
B660F70F1C29988E00687D6E /* RemoteIOBufferListWrapper.m in Sources */,
|
||||
456F6E2F1E261D1000FD2210 /* PeerConnectionClientTest.swift in Sources */,
|
||||
B660F7101C29988E00687D6E /* SpeexCodec.m in Sources */,
|
||||
B660F7111C29988E00687D6E /* SoundBoard.m in Sources */,
|
||||
B660F7121C29988E00687D6E /* SoundInstance.m in Sources */,
|
||||
|
|
|
@ -75,7 +75,7 @@ enum CallError: Error {
|
|||
// FIXME TODO do we need to timeout?
|
||||
fileprivate let timeoutSeconds = 60
|
||||
|
||||
@objc class CallService: NSObject, RTCDataChannelDelegate, RTCPeerConnectionDelegate {
|
||||
@objc class CallService: NSObject, PeerConnectionClientDelegate, RTCDataChannelDelegate {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
|
@ -167,7 +167,7 @@ fileprivate let timeoutSeconds = 60
|
|||
|
||||
return getIceServers().then(on: CallService.signalingQueue) { iceServers -> Promise<HardenedRTCSessionDescription> in
|
||||
Logger.debug("\(self.TAG) got ice servers:\(iceServers)")
|
||||
let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, peerConnectionDelegate: self)
|
||||
let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self)
|
||||
self.peerConnectionClient = peerConnectionClient
|
||||
|
||||
// When calling, it's our responsibility to create the DataChannel. Receivers will not have to do this explicitly.
|
||||
|
@ -315,7 +315,7 @@ fileprivate let timeoutSeconds = 60
|
|||
}.then(on: CallService.signalingQueue) { (iceServers: [RTCIceServer]) -> Promise<HardenedRTCSessionDescription> in
|
||||
// FIXME for first time call recipients I think we'll see mic/camera permission requests here,
|
||||
// even though, from the users perspective, no incoming call is yet visible.
|
||||
self.peerConnectionClient = PeerConnectionClient(iceServers: iceServers, peerConnectionDelegate: self)
|
||||
self.peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self)
|
||||
|
||||
let offerSessionDescription = RTCSessionDescription(type: .offer, sdp: callerSessionDescription)
|
||||
let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
|
||||
|
@ -323,7 +323,6 @@ fileprivate let timeoutSeconds = 60
|
|||
// Find a sessionDescription compatible with my constraints and the remote sessionDescription
|
||||
return self.peerConnectionClient!.negotiateSessionDescription(remoteDescription: offerSessionDescription, constraints: constraints)
|
||||
}.then(on: CallService.signalingQueue) { (negotiatedSessionDescription: HardenedRTCSessionDescription) in
|
||||
// TODO? WebRtcCallService.this.lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING);
|
||||
Logger.debug("\(self.TAG) set the remote description")
|
||||
|
||||
let answerMessage = OWSCallAnswerMessage(callId: newCall.signalingId, sessionDescription: negotiatedSessionDescription.sdp)
|
||||
|
@ -784,6 +783,37 @@ fileprivate let timeoutSeconds = 60
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - PeerConnectionClientDelegate
|
||||
|
||||
/**
|
||||
* The connection has been established. The clients can now communicate.
|
||||
*/
|
||||
internal func peerConnectionClientIceConnected(_ peerconnectionClient: PeerConnectionClient) {
|
||||
CallService.signalingQueue.async {
|
||||
self.handleIceConnected()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The connection failed to establish. The clients will not be able to communicate.
|
||||
*/
|
||||
internal func peerConnectionClientIceFailed(_ peerconnectionClient: PeerConnectionClient) {
|
||||
CallService.signalingQueue.async {
|
||||
self.handleFailedCall(error: CallError.disconnected)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* During the Signaling process each client generates IceCandidates locally, which contain information about how to
|
||||
* reach the local client via the internet. The delegate must shuttle these IceCandates to the other (remote) client
|
||||
* out of band, as part of establishing a connection over WebRTC.
|
||||
*/
|
||||
internal func peerConnectionClient(_ peerconnectionClient: PeerConnectionClient, addedLocalIceCandidate iceCandidate: RTCIceCandidate) {
|
||||
CallService.signalingQueue.async {
|
||||
self.handleLocalAddedIceCandidate(iceCandidate)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
/**
|
||||
|
@ -799,7 +829,8 @@ fileprivate let timeoutSeconds = 60
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* RTCIceServers are used when attempting to establish an optimal connection to the other party. SignalService supplies
|
||||
* a list of servers, plus we have fallback servers hardcoded in the app.
|
||||
*/
|
||||
private func getIceServers() -> Promise<[RTCIceServer]> {
|
||||
return firstly {
|
||||
|
@ -836,38 +867,15 @@ fileprivate let timeoutSeconds = 60
|
|||
terminateCall()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up any existing call state and get ready to receive a new call.
|
||||
*/
|
||||
private func terminateCall() {
|
||||
assertOnSignalingQueue()
|
||||
|
||||
// lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING);
|
||||
// NotificationBarManager.setCallEnded(this);
|
||||
//
|
||||
// incomingRinger.stop();
|
||||
// outgoingRinger.stop();
|
||||
// outgoingRinger.playDisconnected();
|
||||
//
|
||||
// if (peerConnection != null) {
|
||||
// peerConnection.dispose();
|
||||
// peerConnection = null;
|
||||
// }
|
||||
//
|
||||
// if (eglBase != null && localRenderer != null && remoteRenderer != null) {
|
||||
// localRenderer.release();
|
||||
// remoteRenderer.release();
|
||||
// eglBase.release();
|
||||
// }
|
||||
//
|
||||
// shutdownAudio();
|
||||
//
|
||||
// this.callState = CallState.STATE_IDLE;
|
||||
// this.recipient = null;
|
||||
// this.callId = null;
|
||||
// this.audioEnabled = false;
|
||||
// this.videoEnabled = false;
|
||||
// this.pendingIceUpdates = null;
|
||||
// lockManager.updatePhoneState(LockManager.PhoneState.IDLE);
|
||||
Logger.debug("\(TAG) in \(#function)")
|
||||
|
||||
peerConnectionClient?.terminate()
|
||||
|
||||
peerConnectionClient = nil
|
||||
call = nil
|
||||
thread = nil
|
||||
|
@ -877,11 +885,11 @@ fileprivate let timeoutSeconds = 60
|
|||
}
|
||||
|
||||
// MARK: - RTCDataChannelDelegate
|
||||
// TODO move `RTCDataChannelDelegate` stuff into peerConnectionClient and add a method to peerConnectionClientDelegate `receiveDataChannelMssage(_ message:OWSWebRTCProtos)
|
||||
|
||||
/** The data channel state changed. */
|
||||
public func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) {
|
||||
Logger.debug("\(TAG) dataChannelDidChangeState: \(dataChannel)")
|
||||
// SignalingQueue.dispatch.async {}
|
||||
}
|
||||
|
||||
/** The data channel successfully received a data buffer. */
|
||||
|
@ -903,138 +911,6 @@ fileprivate let timeoutSeconds = 60
|
|||
public func dataChannel(_ dataChannel: RTCDataChannel, didChangeBufferedAmount amount: UInt64) {
|
||||
Logger.debug("\(TAG) didChangeBufferedAmount: \(amount)")
|
||||
}
|
||||
|
||||
// MARK: - RTCPeerConnectionDelegate
|
||||
|
||||
/** Called when the SignalingState changed. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) {
|
||||
Logger.debug("\(TAG) didChange signalingState:\(stateChanged.debugDescription)")
|
||||
}
|
||||
|
||||
/** Called when media is received on a new stream from remote peer. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) {
|
||||
Logger.debug("\(TAG) didAdd stream:\(stream)")
|
||||
}
|
||||
|
||||
/** Called when a remote peer closes a stream. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) {
|
||||
Logger.debug("\(TAG) didRemove Stream:\(stream)")
|
||||
}
|
||||
|
||||
/** Called when negotiation is needed, for example ICE has restarted. */
|
||||
public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) {
|
||||
Logger.debug("\(TAG) shouldNegotiate")
|
||||
}
|
||||
|
||||
/** Called any time the IceConnectionState changes. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) {
|
||||
Logger.debug("\(TAG) didChange IceConnectionState:\(newState.debugDescription)")
|
||||
|
||||
CallService.signalingQueue.async {
|
||||
switch newState {
|
||||
case .connected, .completed:
|
||||
self.handleIceConnected()
|
||||
case .failed:
|
||||
Logger.warn("\(self.TAG) RTCIceConnection failed.")
|
||||
guard self.thread != nil else {
|
||||
Logger.error("\(self.TAG) refusing to hangup for failed IceConnection because there is no current thread")
|
||||
return
|
||||
}
|
||||
self.handleFailedCall(error: CallError.disconnected)
|
||||
default:
|
||||
Logger.debug("\(self.TAG) ignoring change IceConnectionState:\(newState.debugDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Called any time the IceGatheringState changes. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) {
|
||||
Logger.debug("\(TAG) didChange IceGatheringState:\(newState.debugDescription)")
|
||||
}
|
||||
|
||||
/** New ice candidate has been found. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {
|
||||
Logger.debug("\(TAG) didGenerate IceCandidate:\(candidate.sdp)")
|
||||
CallService.signalingQueue.async {
|
||||
self.handleLocalAddedIceCandidate(candidate)
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when a group of local Ice candidates have been removed. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {
|
||||
Logger.debug("\(TAG) didRemove IceCandidates:\(candidates)")
|
||||
}
|
||||
|
||||
/** New data channel has been opened. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) {
|
||||
Logger.debug("\(TAG) didOpen dataChannel:\(dataChannel)")
|
||||
CallService.signalingQueue.async {
|
||||
guard let peerConnectionClient = self.peerConnectionClient else {
|
||||
Logger.error("\(self.TAG) surprised to find nil peerConnectionClient in \(#function)")
|
||||
return
|
||||
}
|
||||
|
||||
Logger.debug("\(self.TAG) set dataChannel")
|
||||
peerConnectionClient.dataChannel = dataChannel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark: Pretty Print Objc enums.
|
||||
|
||||
fileprivate extension RTCSignalingState {
|
||||
var debugDescription: String {
|
||||
switch self {
|
||||
case .stable:
|
||||
return "stable"
|
||||
case .haveLocalOffer:
|
||||
return "haveLocalOffer"
|
||||
case .haveLocalPrAnswer:
|
||||
return "haveLocalPrAnswer"
|
||||
case .haveRemoteOffer:
|
||||
return "haveRemoteOffer"
|
||||
case .haveRemotePrAnswer:
|
||||
return "haveRemotePrAnswer"
|
||||
case .closed:
|
||||
return "closed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension RTCIceGatheringState {
|
||||
var debugDescription: String {
|
||||
switch self {
|
||||
case .new:
|
||||
return "new"
|
||||
case .gathering:
|
||||
return "gathering"
|
||||
case .complete:
|
||||
return "complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension RTCIceConnectionState {
|
||||
var debugDescription: String {
|
||||
switch self {
|
||||
case .new:
|
||||
return "new"
|
||||
case .checking:
|
||||
return "checking"
|
||||
case .connected:
|
||||
return "connected"
|
||||
case .completed:
|
||||
return "completed"
|
||||
case .failed:
|
||||
return "failed"
|
||||
case .disconnected:
|
||||
return "disconnected"
|
||||
case .closed:
|
||||
return "closed"
|
||||
case .count:
|
||||
return "count"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension MessageSender {
|
||||
|
|
|
@ -8,14 +8,36 @@ import WebRTC
|
|||
let kAudioTrackType = kRTCMediaStreamTrackKindAudio
|
||||
let kVideoTrackType = kRTCMediaStreamTrackKindVideo
|
||||
|
||||
/**
|
||||
* The PeerConnectionClient notifies it's delegate (the CallService) of key events in the call signaling life cycle
|
||||
*/
|
||||
protocol PeerConnectionClientDelegate: class {
|
||||
|
||||
/**
|
||||
* The connection has been established. The clients can now communicate.
|
||||
*/
|
||||
func peerConnectionClientIceConnected(_ peerconnectionClient: PeerConnectionClient)
|
||||
|
||||
/**
|
||||
* The connection failed to establish. The clients will not be able to communicate.
|
||||
*/
|
||||
func peerConnectionClientIceFailed(_ peerconnectionClient: PeerConnectionClient)
|
||||
|
||||
/**
|
||||
* During the Signaling process each client generates IceCandidates locally, which contain information about how to
|
||||
* reach the local client via the internet. The delegate must shuttle these IceCandates to the other (remote) client
|
||||
* out of band, as part of establishing a connection over WebRTC.
|
||||
*/
|
||||
func peerConnectionClient(_ peerconnectionClient: PeerConnectionClient, addedLocalIceCandidate iceCandidate: RTCIceCandidate)
|
||||
}
|
||||
|
||||
/**
|
||||
* `PeerConnectionClient` is our interface to WebRTC.
|
||||
*
|
||||
* It is primarily a wrapper around `RTCPeerConnection`, which is responsible for sending and receiving our call data
|
||||
* including audio, video, and some signaling - though the bulk of the signaling is *establishing* the connection,
|
||||
* meaning we can't use the connection to transmit yet.
|
||||
* including audio, video, and some post-connected signaling (hangup, add video)
|
||||
*/
|
||||
class PeerConnectionClient: NSObject {
|
||||
class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate {
|
||||
|
||||
let TAG = "[PeerConnectionClient]"
|
||||
enum Identifiers: String {
|
||||
|
@ -25,9 +47,12 @@ class PeerConnectionClient: NSObject {
|
|||
dataChannelSignaling = "signaling"
|
||||
}
|
||||
|
||||
// Delegate is notified of key events in the call lifecycle.
|
||||
private weak var delegate: PeerConnectionClientDelegate!
|
||||
|
||||
// Connection
|
||||
|
||||
private let peerConnection: RTCPeerConnection
|
||||
internal var peerConnection: RTCPeerConnection!
|
||||
private let iceServers: [RTCIceServer]
|
||||
private let connectionConstraints: RTCMediaConstraints
|
||||
private let configuration: RTCConfiguration
|
||||
|
@ -51,8 +76,9 @@ class PeerConnectionClient: NSObject {
|
|||
private var videoTrack: RTCVideoTrack?
|
||||
private var cameraConstraints: RTCMediaConstraints
|
||||
|
||||
init(iceServers: [RTCIceServer], peerConnectionDelegate: RTCPeerConnectionDelegate) {
|
||||
init(iceServers: [RTCIceServer], delegate: PeerConnectionClientDelegate) {
|
||||
self.iceServers = iceServers
|
||||
self.delegate = delegate
|
||||
|
||||
configuration = RTCConfiguration()
|
||||
configuration.iceServers = iceServers
|
||||
|
@ -61,14 +87,15 @@ class PeerConnectionClient: NSObject {
|
|||
|
||||
let connectionConstraintsDict = ["DtlsSrtpKeyAgreement": "true"]
|
||||
connectionConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: connectionConstraintsDict)
|
||||
peerConnection = factory.peerConnection(with: configuration,
|
||||
constraints: connectionConstraints,
|
||||
delegate: peerConnectionDelegate)
|
||||
|
||||
audioConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints:nil)
|
||||
cameraConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
|
||||
|
||||
super.init()
|
||||
|
||||
peerConnection = factory.peerConnection(with: configuration,
|
||||
constraints: connectionConstraints,
|
||||
delegate: self)
|
||||
createAudioSender()
|
||||
createVideoSender()
|
||||
}
|
||||
|
@ -255,12 +282,14 @@ class PeerConnectionClient: NSObject {
|
|||
// 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
|
||||
// we are likely to crash if we retain any peer connection properties when the peerconnection is released
|
||||
Logger.debug("\(TAG) in \(#function)")
|
||||
audioTrack = nil
|
||||
videoTrack = nil
|
||||
dataChannel = nil
|
||||
audioSender = nil
|
||||
videoSender = nil
|
||||
|
||||
peerConnection.delegate = nil
|
||||
peerConnection.close()
|
||||
}
|
||||
|
||||
|
@ -275,6 +304,67 @@ class PeerConnectionClient: NSObject {
|
|||
let buffer = RTCDataBuffer(data: data, isBinary: false)
|
||||
return dataChannel.sendData(buffer)
|
||||
}
|
||||
|
||||
// MARK: - RTCPeerConnectionDelegate
|
||||
|
||||
/** Called when the SignalingState changed. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) {
|
||||
Logger.debug("\(TAG) didChange signalingState:\(stateChanged.debugDescription)")
|
||||
}
|
||||
|
||||
/** Called when media is received on a new stream from remote peer. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) {
|
||||
Logger.debug("\(TAG) didAdd stream:\(stream)")
|
||||
}
|
||||
|
||||
/** Called when a remote peer closes a stream. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) {
|
||||
Logger.debug("\(TAG) didRemove Stream:\(stream)")
|
||||
}
|
||||
|
||||
/** Called when negotiation is needed, for example ICE has restarted. */
|
||||
public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) {
|
||||
Logger.debug("\(TAG) shouldNegotiate")
|
||||
}
|
||||
|
||||
/** Called any time the IceConnectionState changes. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) {
|
||||
Logger.debug("\(TAG) didChange IceConnectionState:\(newState.debugDescription)")
|
||||
switch newState {
|
||||
case .connected, .completed:
|
||||
self.delegate.peerConnectionClientIceConnected(self)
|
||||
case .failed:
|
||||
Logger.warn("\(self.TAG) RTCIceConnection failed.")
|
||||
self.delegate.peerConnectionClientIceFailed(self)
|
||||
default:
|
||||
Logger.debug("\(self.TAG) ignoring change IceConnectionState:\(newState.debugDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
/** Called any time the IceGatheringState changes. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) {
|
||||
Logger.debug("\(TAG) didChange IceGatheringState:\(newState.debugDescription)")
|
||||
}
|
||||
|
||||
/** New ice candidate has been found. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {
|
||||
Logger.debug("\(TAG) didGenerate IceCandidate:\(candidate.sdp)")
|
||||
self.delegate.peerConnectionClient(self, addedLocalIceCandidate: candidate)
|
||||
}
|
||||
|
||||
/** Called when a group of local Ice candidates have been removed. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {
|
||||
Logger.debug("\(TAG) didRemove IceCandidates:\(candidates)")
|
||||
}
|
||||
|
||||
/** New data channel has been opened. */
|
||||
public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) {
|
||||
Logger.debug("\(TAG) didOpen dataChannel:\(dataChannel)")
|
||||
CallService.signalingQueue.async {
|
||||
Logger.debug("\(self.TAG) set dataChannel")
|
||||
self.dataChannel = dataChannel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -304,3 +394,60 @@ class HardenedRTCSessionDescription {
|
|||
return RTCSessionDescription.init(type: rtcSessionDescription.type, sdp: description)
|
||||
}
|
||||
}
|
||||
|
||||
// Mark: Pretty Print Objc enums.
|
||||
|
||||
fileprivate extension RTCSignalingState {
|
||||
var debugDescription: String {
|
||||
switch self {
|
||||
case .stable:
|
||||
return "stable"
|
||||
case .haveLocalOffer:
|
||||
return "haveLocalOffer"
|
||||
case .haveLocalPrAnswer:
|
||||
return "haveLocalPrAnswer"
|
||||
case .haveRemoteOffer:
|
||||
return "haveRemoteOffer"
|
||||
case .haveRemotePrAnswer:
|
||||
return "haveRemotePrAnswer"
|
||||
case .closed:
|
||||
return "closed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension RTCIceGatheringState {
|
||||
var debugDescription: String {
|
||||
switch self {
|
||||
case .new:
|
||||
return "new"
|
||||
case .gathering:
|
||||
return "gathering"
|
||||
case .complete:
|
||||
return "complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension RTCIceConnectionState {
|
||||
var debugDescription: String {
|
||||
switch self {
|
||||
case .new:
|
||||
return "new"
|
||||
case .checking:
|
||||
return "checking"
|
||||
case .connected:
|
||||
return "connected"
|
||||
case .completed:
|
||||
return "completed"
|
||||
case .failed:
|
||||
return "failed"
|
||||
case .disconnected:
|
||||
return "disconnected"
|
||||
case .closed:
|
||||
return "closed"
|
||||
case .count:
|
||||
return "count"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,7 +98,6 @@ final class CallKitCallManager: NSObject {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
fileprivate extension Array {
|
||||
|
||||
mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows {
|
||||
|
@ -108,5 +107,4 @@ fileprivate extension Array {
|
|||
|
||||
remove(at: index)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
|||
return providerConfiguration
|
||||
}
|
||||
|
||||
init(callService: CallService, notificationsAdapter: CallNotificationsAdapter) {
|
||||
init(callService: CallService, notificationsAdapter: CallNotificationsAdapter) {
|
||||
self.callManager = CallKitCallManager()
|
||||
self.callService = callService
|
||||
self.notificationsAdapter = notificationsAdapter
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
// Created by Michael Kirk on 1/11/17.
|
||||
// Copyright © 2017 Open Whisper Systems. All rights reserved.
|
||||
|
||||
import XCTest
|
||||
import WebRTC
|
||||
|
||||
/**
|
||||
* Playing the role of the call service.
|
||||
*/
|
||||
class FakePeerConnectionClientDelegate: PeerConnectionClientDelegate {
|
||||
|
||||
enum ConnectionState {
|
||||
case connected, failed
|
||||
}
|
||||
|
||||
var connectionState: ConnectionState?
|
||||
var localIceCandidates = [RTCIceCandidate]()
|
||||
|
||||
internal func peerConnectionClientIceConnected(_ peerconnectionClient: PeerConnectionClient) {
|
||||
connectionState = .connected
|
||||
}
|
||||
|
||||
internal func peerConnectionClientIceFailed(_ peerconnectionClient: PeerConnectionClient) {
|
||||
connectionState = .failed
|
||||
}
|
||||
|
||||
internal func peerConnectionClient(_ peerconnectionClient: PeerConnectionClient, addedLocalIceCandidate iceCandidate: RTCIceCandidate) {
|
||||
localIceCandidates.append(iceCandidate)
|
||||
}
|
||||
}
|
||||
|
||||
class PeerConnectionClientTest: XCTestCase {
|
||||
|
||||
var client: PeerConnectionClient!
|
||||
var clientDelegate: FakePeerConnectionClientDelegate!
|
||||
var peerConnection: RTCPeerConnection!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
let iceServers = [RTCIceServer]()
|
||||
clientDelegate = FakePeerConnectionClientDelegate()
|
||||
client = PeerConnectionClient(iceServers: iceServers, delegate: clientDelegate)
|
||||
peerConnection = client.peerConnection
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
client.terminate()
|
||||
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testIceConnectionStateChange() {
|
||||
XCTAssertNil(clientDelegate.connectionState)
|
||||
|
||||
client.peerConnection(peerConnection, didChange: RTCIceConnectionState.connected)
|
||||
XCTAssertEqual(FakePeerConnectionClientDelegate.ConnectionState.connected, clientDelegate.connectionState)
|
||||
|
||||
client.peerConnection(peerConnection, didChange: RTCIceConnectionState.completed)
|
||||
XCTAssertEqual(FakePeerConnectionClientDelegate.ConnectionState.connected, clientDelegate.connectionState)
|
||||
|
||||
client.peerConnection(peerConnection, didChange: RTCIceConnectionState.failed)
|
||||
XCTAssertEqual(FakePeerConnectionClientDelegate.ConnectionState.failed, clientDelegate.connectionState)
|
||||
}
|
||||
|
||||
func testIceCandidateAdded() {
|
||||
XCTAssertEqual(0, clientDelegate.localIceCandidates.count)
|
||||
|
||||
let candidate1 = RTCIceCandidate(sdp: "sdp-1", sdpMLineIndex: 0, sdpMid: "sdpMid-1")
|
||||
let candidate2 = RTCIceCandidate(sdp: "sdp-2", sdpMLineIndex: 0, sdpMid: "sdpMid-2")
|
||||
let candidate3 = RTCIceCandidate(sdp: "sdp-3", sdpMLineIndex: 0, sdpMid: "sdpMid-3")
|
||||
|
||||
client.peerConnection(peerConnection, didGenerate: candidate1)
|
||||
client.peerConnection(peerConnection, didGenerate: candidate2)
|
||||
client.peerConnection(peerConnection, didGenerate: candidate3)
|
||||
|
||||
XCTAssertEqual(3, clientDelegate.localIceCandidates.count)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue