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:
Michael Kirk 2017-01-12 00:06:39 +00:00
parent bd65dc6ba7
commit 8998853aff
6 changed files with 282 additions and 177 deletions

View File

@ -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 */,

View File

@ -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 {

View File

@ -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"
}
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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)
}
}