Merge branch 'charlesmchen/callLifecycleEdgeCases'
This commit is contained in:
commit
d3d4157c2f
@ -98,6 +98,108 @@ protocol CallServiceObserver: class {
|
||||
remoteVideoTrack: RTCVideoTrack?)
|
||||
}
|
||||
|
||||
// Gather all per-call state in one place.
|
||||
private class SignalCallData: NSObject {
|
||||
public let call: SignalCall
|
||||
|
||||
// Used to coordinate promises across delegate methods
|
||||
let fulfillCallConnectedPromise: (() -> Void)
|
||||
let rejectCallConnectedPromise: ((Error) -> Void)
|
||||
let callConnectedPromise: Promise<Void>
|
||||
|
||||
// Used to ensure any received ICE messages wait until the peer connection client is set up.
|
||||
let fulfillPeerConnectionClientPromise: (() -> Void)
|
||||
let rejectPeerConnectionClientPromise: ((Error) -> Void)
|
||||
let peerConnectionClientPromise: Promise<Void>
|
||||
|
||||
// Used to ensure CallOffer was sent before sending any ICE updates.
|
||||
let fulfillReadyToSendIceUpdatesPromise: (() -> Void)
|
||||
let rejectReadyToSendIceUpdatesPromise: ((Error) -> Void)
|
||||
let readyToSendIceUpdatesPromise: Promise<Void>
|
||||
|
||||
weak var localVideoTrack: RTCVideoTrack? {
|
||||
didSet {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
Logger.info("\(self.logTag) \(#function)")
|
||||
}
|
||||
}
|
||||
|
||||
weak var remoteVideoTrack: RTCVideoTrack? {
|
||||
didSet {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
Logger.info("\(self.logTag) \(#function)")
|
||||
}
|
||||
}
|
||||
|
||||
var isRemoteVideoEnabled = false {
|
||||
didSet {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
Logger.info("\(self.logTag) \(#function): \(isRemoteVideoEnabled)")
|
||||
}
|
||||
}
|
||||
|
||||
var peerConnectionClient: PeerConnectionClient? {
|
||||
didSet {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
Logger.debug("\(self.logTag) .peerConnectionClient setter: \(oldValue != nil) -> \(peerConnectionClient != nil) \(String(describing: peerConnectionClient))")
|
||||
}
|
||||
}
|
||||
|
||||
required init(call: SignalCall) {
|
||||
self.call = call
|
||||
|
||||
let (callConnectedPromise, fulfillCallConnectedPromise, rejectCallConnectedPromise) = Promise<Void>.pending()
|
||||
self.callConnectedPromise = callConnectedPromise
|
||||
self.fulfillCallConnectedPromise = fulfillCallConnectedPromise
|
||||
self.rejectCallConnectedPromise = rejectCallConnectedPromise
|
||||
|
||||
let (peerConnectionClientPromise, fulfillPeerConnectionClientPromise, rejectPeerConnectionClientPromise) = Promise<Void>.pending()
|
||||
self.peerConnectionClientPromise = peerConnectionClientPromise
|
||||
self.fulfillPeerConnectionClientPromise = fulfillPeerConnectionClientPromise
|
||||
self.rejectPeerConnectionClientPromise = rejectPeerConnectionClientPromise
|
||||
|
||||
let (readyToSendIceUpdatesPromise, fulfillReadyToSendIceUpdatesPromise, rejectReadyToSendIceUpdatesPromise) = Promise<Void>.pending()
|
||||
self.readyToSendIceUpdatesPromise = readyToSendIceUpdatesPromise
|
||||
self.fulfillReadyToSendIceUpdatesPromise = fulfillReadyToSendIceUpdatesPromise
|
||||
self.rejectReadyToSendIceUpdatesPromise = rejectReadyToSendIceUpdatesPromise
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
deinit {
|
||||
Logger.debug("[SignalCallData] deinit")
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
public func terminate() {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
Logger.debug("\(self.logTag) in \(#function)")
|
||||
|
||||
self.call.removeAllObservers()
|
||||
|
||||
// In case we're still waiting on this promise somewhere, we need to reject it to avoid a memory leak.
|
||||
// There is no harm in rejecting a previously fulfilled promise.
|
||||
rejectCallConnectedPromise(CallError.obsoleteCall(description: "Terminating call"))
|
||||
|
||||
// In case we're still waiting on the peer connection setup somewhere, we need to reject it to avoid a memory leak.
|
||||
// There is no harm in rejecting a previously fulfilled promise.
|
||||
rejectPeerConnectionClientPromise(CallError.obsoleteCall(description: "Terminating call"))
|
||||
|
||||
// In case we're still waiting on this promise somewhere, we need to reject it to avoid a memory leak.
|
||||
// There is no harm in rejecting a previously fulfilled promise.
|
||||
rejectReadyToSendIceUpdatesPromise(CallError.obsoleteCall(description: "Terminating call"))
|
||||
|
||||
peerConnectionClient?.terminate()
|
||||
Logger.debug("\(self.logTag) setting peerConnectionClient in \(#function)")
|
||||
}
|
||||
}
|
||||
|
||||
// This class' state should only be accessed on the main queue.
|
||||
@objc class CallService: NSObject, CallObserver, PeerConnectionClientDelegate {
|
||||
|
||||
@ -122,85 +224,71 @@ protocol CallServiceObserver: class {
|
||||
|
||||
// MARK: Ivars
|
||||
|
||||
var peerConnectionClient: PeerConnectionClient? {
|
||||
fileprivate var callData: SignalCallData? {
|
||||
didSet {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
Logger.debug("\(self.logTag) .peerConnectionClient setter: \(oldValue != nil) -> \(peerConnectionClient != nil) \(String(describing: peerConnectionClient))")
|
||||
}
|
||||
}
|
||||
|
||||
var call: SignalCall? {
|
||||
didSet {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
oldValue?.removeObserver(self)
|
||||
call?.addObserverAndSyncState(observer: self)
|
||||
oldValue?.call.removeObserver(self)
|
||||
callData?.call.addObserverAndSyncState(observer: self)
|
||||
|
||||
updateIsVideoEnabled()
|
||||
|
||||
// Prevent device from sleeping while we have an active call.
|
||||
if oldValue != call {
|
||||
if oldValue != callData {
|
||||
if let oldValue = oldValue {
|
||||
DeviceSleepManager.sharedInstance.removeBlock(blockObject: oldValue)
|
||||
}
|
||||
stopAnyCallTimer()
|
||||
if let call = call {
|
||||
DeviceSleepManager.sharedInstance.addBlock(blockObject: call)
|
||||
if let callData = callData {
|
||||
DeviceSleepManager.sharedInstance.addBlock(blockObject: callData)
|
||||
self.startCallTimer()
|
||||
}
|
||||
}
|
||||
|
||||
Logger.debug("\(self.logTag) .call setter: \(oldValue?.identifiersForLogs as Optional) -> \(call?.identifiersForLogs as Optional)")
|
||||
Logger.debug("\(self.logTag) .callData setter: \(oldValue?.call.identifiersForLogs as Optional) -> \(callData?.call.identifiersForLogs as Optional)")
|
||||
|
||||
for observer in observers {
|
||||
observer.value?.didUpdateCall(call: call)
|
||||
observer.value?.didUpdateCall(call: callData?.call)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used to coordinate promises across delegate methods
|
||||
private var fulfillCallConnectedPromise: (() -> Void)?
|
||||
private var rejectCallConnectedPromise: ((Error) -> Void)?
|
||||
|
||||
// Used by waitForPeerConnectionClient to make sure any received
|
||||
// ICE messages wait until the peer connection client is set up.
|
||||
private var fulfillPeerConnectionClientPromise: (() -> Void)?
|
||||
private var rejectPeerConnectionClientPromise: ((Error) -> Void)?
|
||||
private var peerConnectionClientPromise: Promise<Void>?
|
||||
|
||||
// Used by waituntilReadyToSendIceUpdates to make sure CallOffer was
|
||||
// sent before sending any ICE updates.
|
||||
private var fulfillReadyToSendIceUpdatesPromise: (() -> Void)?
|
||||
private var rejectReadyToSendIceUpdatesPromise: ((Error) -> Void)?
|
||||
private var readyToSendIceUpdatesPromise: Promise<Void>?
|
||||
|
||||
weak var localVideoTrack: RTCVideoTrack? {
|
||||
didSet {
|
||||
var call: SignalCall? {
|
||||
get {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
Logger.info("\(self.logTag) \(#function)")
|
||||
|
||||
fireDidUpdateVideoTracks()
|
||||
return callData?.call
|
||||
}
|
||||
}
|
||||
|
||||
weak var remoteVideoTrack: RTCVideoTrack? {
|
||||
didSet {
|
||||
var peerConnectionClient: PeerConnectionClient? {
|
||||
get {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
Logger.info("\(self.logTag) \(#function)")
|
||||
|
||||
fireDidUpdateVideoTracks()
|
||||
return callData?.peerConnectionClient
|
||||
}
|
||||
}
|
||||
var isRemoteVideoEnabled = false {
|
||||
didSet {
|
||||
var localVideoTrack: RTCVideoTrack? {
|
||||
get {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
Logger.info("\(self.logTag) \(#function): \(isRemoteVideoEnabled)")
|
||||
return callData?.localVideoTrack
|
||||
}
|
||||
}
|
||||
var remoteVideoTrack: RTCVideoTrack? {
|
||||
get {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
fireDidUpdateVideoTracks()
|
||||
return callData?.remoteVideoTrack
|
||||
}
|
||||
}
|
||||
var isRemoteVideoEnabled: Bool {
|
||||
get {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
guard let callData = callData else {
|
||||
return false
|
||||
}
|
||||
return callData.isRemoteVideoEnabled
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,7 +358,8 @@ protocol CallServiceObserver: class {
|
||||
return Promise(error: CallError.assertionError(description: errorDescription))
|
||||
}
|
||||
|
||||
self.call = call
|
||||
let callData = SignalCallData(call: call)
|
||||
self.callData = callData
|
||||
|
||||
let callRecord = TSCall(timestamp: NSDate.ows_millisecondTimeStamp(), withCallNumber: call.remotePhoneNumber, callType: RPRecentCallTypeOutgoingIncomplete, in: call.thread)
|
||||
callRecord.save()
|
||||
@ -283,7 +372,7 @@ protocol CallServiceObserver: class {
|
||||
throw CallError.obsoleteCall(description: "obsolete call in \(#function)")
|
||||
}
|
||||
|
||||
guard self.peerConnectionClient == nil else {
|
||||
guard callData.peerConnectionClient == nil else {
|
||||
let errorDescription = "\(self.logTag) peerconnection was unexpectedly already set."
|
||||
Logger.error(errorDescription)
|
||||
OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionAlreadySet(), file: #file, function: #function, line: #line)
|
||||
@ -294,8 +383,8 @@ protocol CallServiceObserver: class {
|
||||
|
||||
let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .outgoing, useTurnOnly: useTurnOnly)
|
||||
Logger.debug("\(self.logTag) setting peerConnectionClient in \(#function) for call: \(call.identifiersForLogs)")
|
||||
self.peerConnectionClient = peerConnectionClient
|
||||
self.fulfillPeerConnectionClientPromise?()
|
||||
callData.peerConnectionClient = peerConnectionClient
|
||||
callData.fulfillPeerConnectionClientPromise()
|
||||
|
||||
return peerConnectionClient.createOffer()
|
||||
}.then { (sessionDescription: HardenedRTCSessionDescription) -> Promise<Void> in
|
||||
@ -321,10 +410,6 @@ protocol CallServiceObserver: class {
|
||||
// clients that don't support receiving ICE updates before receiving the call offer.
|
||||
self.readyToSendIceUpdates(call: call)
|
||||
|
||||
let (callConnectedPromise, fulfill, reject) = Promise<Void>.pending()
|
||||
self.fulfillCallConnectedPromise = fulfill
|
||||
self.rejectCallConnectedPromise = reject
|
||||
|
||||
// Don't let the outgoing call ring forever. We don't support inbound ringing forever anyway.
|
||||
let timeout: Promise<Void> = after(interval: connectingTimeoutSeconds).then { () -> Void in
|
||||
// This code will always be called, whether or not the call has timed out.
|
||||
@ -333,7 +418,7 @@ protocol CallServiceObserver: class {
|
||||
throw CallError.timeout(description: "timed out waiting to receive call answer")
|
||||
}
|
||||
|
||||
return race(timeout, callConnectedPromise)
|
||||
return race(timeout, callData.callConnectedPromise)
|
||||
}.then {
|
||||
Logger.info(self.call == call
|
||||
? "\(self.logTag) outgoing call connected: \(call.identifiersForLogs)."
|
||||
@ -360,22 +445,17 @@ protocol CallServiceObserver: class {
|
||||
func readyToSendIceUpdates(call: SignalCall) {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
guard self.call == call else {
|
||||
guard let callData = self.callData else {
|
||||
self.handleFailedCall(failedCall: call, error: .obsoleteCall(description:"obsolete call in \(#function)"))
|
||||
return
|
||||
}
|
||||
|
||||
if self.fulfillReadyToSendIceUpdatesPromise == nil {
|
||||
createReadyToSendIceUpdatesPromise()
|
||||
}
|
||||
|
||||
guard let fulfillReadyToSendIceUpdatesPromise = self.fulfillReadyToSendIceUpdatesPromise else {
|
||||
OWSProdError(OWSAnalyticsEvents.callServiceMissingFulfillReadyToSendIceUpdatesPromise(), file: #file, function: #function, line: #line)
|
||||
self.handleFailedCall(failedCall: call, error: CallError.assertionError(description: "failed to create fulfillReadyToSendIceUpdatesPromise"))
|
||||
guard callData.call == call else {
|
||||
self.handleFailedCall(failedCall: call, error: .obsoleteCall(description:"obsolete call in \(#function)"))
|
||||
return
|
||||
}
|
||||
|
||||
fulfillReadyToSendIceUpdatesPromise()
|
||||
callData.fulfillReadyToSendIceUpdatesPromise()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -559,7 +639,8 @@ protocol CallServiceObserver: class {
|
||||
|
||||
Logger.info("\(self.logTag) starting new call: \(newCall.identifiersForLogs)")
|
||||
|
||||
self.call = newCall
|
||||
let callData = SignalCallData(call: newCall)
|
||||
self.callData = callData
|
||||
|
||||
var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: "\(#function)", completionBlock: { [weak self] status in
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
@ -598,8 +679,8 @@ protocol CallServiceObserver: class {
|
||||
|
||||
Logger.debug("\(self.logTag) setting peerConnectionClient in \(#function) for: \(newCall.identifiersForLogs)")
|
||||
let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .incoming, useTurnOnly: useTurnOnly)
|
||||
self.peerConnectionClient = peerConnectionClient
|
||||
self.fulfillPeerConnectionClientPromise?()
|
||||
callData.peerConnectionClient = peerConnectionClient
|
||||
callData.fulfillPeerConnectionClientPromise()
|
||||
|
||||
let offerSessionDescription = RTCSessionDescription(type: .offer, sdp: callerSessionDescription)
|
||||
let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
|
||||
@ -627,8 +708,6 @@ protocol CallServiceObserver: class {
|
||||
// a more intuitive ordering.
|
||||
self.readyToSendIceUpdates(call: newCall)
|
||||
|
||||
let (promise, fulfill, reject) = Promise<Void>.pending()
|
||||
|
||||
let timeout: Promise<Void> = after(interval: connectingTimeoutSeconds).then { () -> Void in
|
||||
// rejecting a promise by throwing is safely a no-op if the promise has already been fulfilled
|
||||
OWSProdInfo(OWSAnalyticsEvents.callServiceErrorTimeoutWhileConnectingIncoming(), file: #file, function: #function, line: #line)
|
||||
@ -636,10 +715,7 @@ protocol CallServiceObserver: class {
|
||||
}
|
||||
|
||||
// This will be fulfilled (potentially) by the RTCDataChannel delegate method
|
||||
self.fulfillCallConnectedPromise = fulfill
|
||||
self.rejectCallConnectedPromise = reject
|
||||
|
||||
return race(promise, timeout)
|
||||
return race(callData.callConnectedPromise, timeout)
|
||||
}.then {
|
||||
Logger.info(self.call == newCall
|
||||
? "\(self.logTag) incoming call connected: \(newCall.identifiersForLogs)."
|
||||
@ -660,6 +736,7 @@ protocol CallServiceObserver: class {
|
||||
}.always {
|
||||
Logger.debug("\(self.logTag) ending background task awaiting inbound call connection")
|
||||
|
||||
assert(backgroundTask != nil)
|
||||
backgroundTask = nil
|
||||
}
|
||||
incomingCallPromise.retainUntilComplete()
|
||||
@ -669,7 +746,15 @@ protocol CallServiceObserver: class {
|
||||
* Remote client (could be caller or callee) sent us a connectivity update
|
||||
*/
|
||||
public func handleRemoteAddedIceCandidate(thread: TSContactThread, callId: UInt64, sdp: String, lineIndex: Int32, mid: String) {
|
||||
waitForPeerConnectionClient().then { () -> Void in
|
||||
Logger.verbose("\(logTag) \(#function) callId: \(callId)")
|
||||
|
||||
guard let callData = self.callData else {
|
||||
OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line)
|
||||
self.handleFailedCurrentCall(error: .obsoleteCall(description: "ignoring remote ice update, since there is no current call."))
|
||||
return
|
||||
}
|
||||
|
||||
callData.peerConnectionClientPromise.then { () -> Void in
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
guard let call = self.call else {
|
||||
@ -692,10 +777,11 @@ protocol CallServiceObserver: class {
|
||||
return
|
||||
}
|
||||
|
||||
Logger.verbose("\(self.logTag) \(#function) addRemoteIceCandidate")
|
||||
peerConnectionClient.addRemoteIceCandidate(RTCIceCandidate(sdp: sdp, sdpMLineIndex: lineIndex, sdpMid: mid))
|
||||
}.catch { error in
|
||||
OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleRemoteAddedIceCandidate(), file: #file, function: #function, line: #line)
|
||||
Logger.error("\(self.logTag) in \(#function) waitForPeerConnectionClient failed with error: \(error)")
|
||||
Logger.error("\(self.logTag) in \(#function) peerConnectionClientPromise failed with error: \(error)")
|
||||
}.retainUntilComplete()
|
||||
}
|
||||
|
||||
@ -706,15 +792,16 @@ protocol CallServiceObserver: class {
|
||||
private func handleLocalAddedIceCandidate(_ iceCandidate: RTCIceCandidate) {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
guard let call = self.call else {
|
||||
guard let callData = self.callData else {
|
||||
OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line)
|
||||
self.handleFailedCurrentCall(error: CallError.assertionError(description: "ignoring local ice candidate, since there is no current call."))
|
||||
return
|
||||
}
|
||||
let call = callData.call
|
||||
|
||||
// Wait until we've sent the CallOffer before sending any ice updates for the call to ensure
|
||||
// intuitive message ordering for other clients.
|
||||
waitUntilReadyToSendIceUpdates().then { () -> Void in
|
||||
callData.readyToSendIceUpdatesPromise.then { () -> Void in
|
||||
guard call == self.call else {
|
||||
self.handleFailedCurrentCall(error: .obsoleteCall(description: "current call changed since we became ready to send ice updates"))
|
||||
return
|
||||
@ -730,7 +817,7 @@ protocol CallServiceObserver: class {
|
||||
|
||||
let iceUpdateMessage = OWSCallIceUpdateMessage(callId: call.signalingId, sdp: iceCandidate.sdp, sdpMLineIndex: iceCandidate.sdpMLineIndex, sdpMid: iceCandidate.sdpMid)
|
||||
|
||||
Logger.info("\(self.logTag) in \(#function) sending ICE Candidate.")
|
||||
Logger.info("\(self.logTag) in \(#function) sending ICE Candidate \(call.identifiersForLogs).")
|
||||
let callMessage = OWSOutgoingCallMessage(thread: call.thread, iceUpdateMessage: iceUpdateMessage)
|
||||
let sendPromise = self.messageSender.sendPromise(message: callMessage)
|
||||
sendPromise.retainUntilComplete()
|
||||
@ -876,13 +963,13 @@ protocol CallServiceObserver: class {
|
||||
|
||||
Logger.debug("\(self.logTag) in \(#function)")
|
||||
|
||||
guard let currentCall = self.call else {
|
||||
OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line)
|
||||
handleFailedCall(failedCall: call, error: CallError.assertionError(description: "\(self.logTag) ignoring \(#function) since there is no current call"))
|
||||
guard let currentCallData = self.callData else {
|
||||
OWSProdError(OWSAnalyticsEvents.callServiceCallDataMissing(), file: #file, function: #function, line: #line)
|
||||
handleFailedCall(failedCall: call, error: CallError.assertionError(description: "\(self.logTag) callData unexpectedly nil in \(#function)"))
|
||||
return
|
||||
}
|
||||
|
||||
guard call == currentCall else {
|
||||
guard call == currentCallData.call else {
|
||||
// This could conceivably happen if the other party of an old call was slow to send us their answer
|
||||
// and we've subsequently engaged in another call. Don't kill the current call, but just ignore it.
|
||||
Logger.warn("\(self.logTag) ignoring \(#function) for call other than current call")
|
||||
@ -904,14 +991,14 @@ protocol CallServiceObserver: class {
|
||||
let message = DataChannelMessage.forConnected(callId: call.signalingId)
|
||||
peerConnectionClient.sendDataChannelMessage(data: message.asData(), description: "connected", isCritical: true)
|
||||
|
||||
handleConnectedCall(call)
|
||||
handleConnectedCall(currentCallData)
|
||||
}
|
||||
|
||||
/**
|
||||
* For outgoing call, when the callee has chosen to accept the call.
|
||||
* For incoming call, when the local user has chosen to accept the call.
|
||||
*/
|
||||
func handleConnectedCall(_ call: SignalCall) {
|
||||
private func handleConnectedCall(_ callData: SignalCallData) {
|
||||
Logger.info("\(self.logTag) in \(#function)")
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
@ -921,16 +1008,15 @@ protocol CallServiceObserver: class {
|
||||
return
|
||||
}
|
||||
|
||||
Logger.info("\(self.logTag) handleConnectedCall: \(call.identifiersForLogs).")
|
||||
Logger.info("\(self.logTag) handleConnectedCall: \(callData.call.identifiersForLogs).")
|
||||
|
||||
assert(self.fulfillCallConnectedPromise != nil)
|
||||
// cancel connection timeout
|
||||
self.fulfillCallConnectedPromise?()
|
||||
callData.fulfillCallConnectedPromise()
|
||||
|
||||
call.state = .connected
|
||||
callData.call.state = .connected
|
||||
|
||||
// We don't risk transmitting any media until the remote client has admitted to being connected.
|
||||
ensureAudioState(call: call, peerConnectionClient: peerConnectionClient)
|
||||
ensureAudioState(call: callData.call, peerConnectionClient: peerConnectionClient)
|
||||
peerConnectionClient.setLocalVideoEnabled(enabled: shouldHaveLocalVideoTrack())
|
||||
}
|
||||
|
||||
@ -1195,13 +1281,14 @@ protocol CallServiceObserver: class {
|
||||
private func handleDataChannelMessage(_ message: OWSWebRTCProtosData) {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
guard let call = self.call else {
|
||||
guard let callData = self.callData else {
|
||||
// This should never happen; return to a known good state.
|
||||
owsFail("\(self.logTag) received data message, but there is no current call. Ignoring.")
|
||||
OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line)
|
||||
handleFailedCurrentCall(error: CallError.assertionError(description: "\(self.logTag) received data message, but there is no current call. Ignoring."))
|
||||
return
|
||||
}
|
||||
let call = callData.call
|
||||
|
||||
if message.hasConnected() {
|
||||
Logger.debug("\(self.logTag) remote participant sent Connected via data channel: \(call.identifiersForLogs).")
|
||||
@ -1217,7 +1304,7 @@ protocol CallServiceObserver: class {
|
||||
}
|
||||
|
||||
self.callUIAdapter.recipientAcceptedCall(call)
|
||||
handleConnectedCall(call)
|
||||
handleConnectedCall(callData)
|
||||
|
||||
} else if message.hasHangup() {
|
||||
Logger.debug("\(self.logTag) remote participant sent Hangup via data channel: \(call.identifiersForLogs).")
|
||||
@ -1236,7 +1323,8 @@ protocol CallServiceObserver: class {
|
||||
} else if message.hasVideoStreamingStatus() {
|
||||
Logger.debug("\(self.logTag) remote participant sent VideoStreamingStatus via data channel: \(call.identifiersForLogs).")
|
||||
|
||||
self.isRemoteVideoEnabled = message.videoStreamingStatus.enabled()
|
||||
callData.isRemoteVideoEnabled = message.videoStreamingStatus.enabled()
|
||||
self.fireDidUpdateVideoTracks()
|
||||
} else {
|
||||
Logger.info("\(self.logTag) received unknown or empty DataChannelMessage: \(call.identifiersForLogs).")
|
||||
}
|
||||
@ -1321,8 +1409,13 @@ protocol CallServiceObserver: class {
|
||||
Logger.debug("\(self.logTag) \(#function) Ignoring event from obsolete peerConnectionClient")
|
||||
return
|
||||
}
|
||||
guard let callData = callData else {
|
||||
Logger.debug("\(self.logTag) \(#function) Ignoring event from obsolete peerConnectionClient")
|
||||
return
|
||||
}
|
||||
|
||||
self.localVideoTrack = videoTrack
|
||||
callData.localVideoTrack = videoTrack
|
||||
fireDidUpdateVideoTracks()
|
||||
}
|
||||
|
||||
internal func peerConnectionClient(_ peerConnectionClient: PeerConnectionClient, didUpdateRemote videoTrack: RTCVideoTrack?) {
|
||||
@ -1332,94 +1425,16 @@ protocol CallServiceObserver: class {
|
||||
Logger.debug("\(self.logTag) \(#function) Ignoring event from obsolete peerConnectionClient")
|
||||
return
|
||||
}
|
||||
guard let callData = callData else {
|
||||
Logger.debug("\(self.logTag) \(#function) Ignoring event from obsolete peerConnectionClient")
|
||||
return
|
||||
}
|
||||
|
||||
self.remoteVideoTrack = videoTrack
|
||||
callData.remoteVideoTrack = videoTrack
|
||||
fireDidUpdateVideoTracks()
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
private func waitUntilReadyToSendIceUpdates() -> Promise<Void> {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
if self.readyToSendIceUpdatesPromise == nil {
|
||||
createReadyToSendIceUpdatesPromise()
|
||||
}
|
||||
|
||||
guard let readyToSendIceUpdatesPromise = self.readyToSendIceUpdatesPromise else {
|
||||
OWSProdError(OWSAnalyticsEvents.callServiceCouldNotCreateReadyToSendIceUpdatesPromise(), file: #file, function: #function, line: #line)
|
||||
return Promise(error: CallError.assertionError(description: "failed to create readyToSendIceUpdatesPromise"))
|
||||
}
|
||||
|
||||
return readyToSendIceUpdatesPromise
|
||||
}
|
||||
|
||||
private func createReadyToSendIceUpdatesPromise() {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
guard self.readyToSendIceUpdatesPromise == nil else {
|
||||
owsFail("expected readyToSendIceUpdatesPromise to be nil")
|
||||
return
|
||||
}
|
||||
|
||||
guard self.fulfillReadyToSendIceUpdatesPromise == nil else {
|
||||
owsFail("expected fulfillReadyToSendIceUpdatesPromise to be nil")
|
||||
return
|
||||
}
|
||||
|
||||
guard self.rejectReadyToSendIceUpdatesPromise == nil else {
|
||||
owsFail("expected rejectReadyToSendIceUpdatesPromise to be nil")
|
||||
return
|
||||
}
|
||||
|
||||
let (promise, fulfill, reject) = Promise<Void>.pending()
|
||||
self.fulfillReadyToSendIceUpdatesPromise = fulfill
|
||||
self.rejectReadyToSendIceUpdatesPromise = reject
|
||||
self.readyToSendIceUpdatesPromise = promise
|
||||
}
|
||||
|
||||
private func waitForPeerConnectionClient() -> Promise<Void> {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
guard self.peerConnectionClient == nil else {
|
||||
// peerConnectionClient already set
|
||||
return Promise(value: ())
|
||||
}
|
||||
|
||||
if self.peerConnectionClientPromise == nil {
|
||||
createPeerConnectionClientPromise()
|
||||
}
|
||||
|
||||
guard let peerConnectionClientPromise = self.peerConnectionClientPromise else {
|
||||
OWSProdError(OWSAnalyticsEvents.callServiceCouldNotCreatePeerConnectionClientPromise(), file: #file, function: #function, line: #line)
|
||||
return Promise(error: CallError.assertionError(description: "failed to create peerConnectionClientPromise"))
|
||||
}
|
||||
|
||||
return peerConnectionClientPromise
|
||||
}
|
||||
|
||||
private func createPeerConnectionClientPromise() {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
guard self.peerConnectionClientPromise == nil else {
|
||||
owsFail("expected peerConnectionClientPromise to be nil")
|
||||
return
|
||||
}
|
||||
|
||||
guard self.fulfillPeerConnectionClientPromise == nil else {
|
||||
owsFail("expected fulfillPeerConnectionClientPromise to be nil")
|
||||
return
|
||||
}
|
||||
|
||||
guard self.rejectPeerConnectionClientPromise == nil else {
|
||||
owsFail("expected rejectPeerConnectionClientPromise to be nil")
|
||||
return
|
||||
}
|
||||
|
||||
let (promise, fulfill, reject) = Promise<Void>.pending()
|
||||
self.fulfillPeerConnectionClientPromise = fulfill
|
||||
self.rejectPeerConnectionClientPromise = reject
|
||||
self.peerConnectionClientPromise = promise
|
||||
}
|
||||
// MARK: -
|
||||
|
||||
/**
|
||||
* RTCIceServers are used when attempting to establish an optimal connection to the other party. SignalService supplies
|
||||
@ -1511,44 +1526,14 @@ protocol CallServiceObserver: class {
|
||||
|
||||
Logger.debug("\(self.logTag) in \(#function)")
|
||||
|
||||
self.localVideoTrack = nil
|
||||
self.remoteVideoTrack = nil
|
||||
self.isRemoteVideoEnabled = false
|
||||
let currentCallData = self.callData
|
||||
self.callData = nil
|
||||
|
||||
self.peerConnectionClient?.terminate()
|
||||
Logger.debug("\(self.logTag) setting peerConnectionClient in \(#function)")
|
||||
self.peerConnectionClient = nil
|
||||
currentCallData?.terminate()
|
||||
|
||||
self.call?.removeAllObservers()
|
||||
self.callUIAdapter.didTerminateCall(self.call)
|
||||
self.call = nil
|
||||
|
||||
self.fulfillCallConnectedPromise = nil
|
||||
|
||||
// In case we're still waiting on this promise somewhere, we need to reject it to avoid a memory leak.
|
||||
// There is no harm in rejecting a previously fulfilled promise.
|
||||
if let rejectCallConnectedPromise = self.rejectCallConnectedPromise {
|
||||
rejectCallConnectedPromise(CallError.obsoleteCall(description: "Terminating call"))
|
||||
}
|
||||
self.rejectCallConnectedPromise = nil
|
||||
|
||||
// In case we're still waiting on the peer connection setup somewhere, we need to reject it to avoid a memory leak.
|
||||
// There is no harm in rejecting a previously fulfilled promise.
|
||||
if let rejectPeerConnectionClientPromise = self.rejectPeerConnectionClientPromise {
|
||||
rejectPeerConnectionClientPromise(CallError.obsoleteCall(description: "Terminating call"))
|
||||
}
|
||||
self.rejectPeerConnectionClientPromise = nil
|
||||
self.fulfillPeerConnectionClientPromise = nil
|
||||
self.peerConnectionClientPromise = nil
|
||||
|
||||
// In case we're still waiting on this promise somewhere, we need to reject it to avoid a memory leak.
|
||||
// There is no harm in rejecting a previously fulfilled promise.
|
||||
if let rejectReadyToSendIceUpdatesPromise = self.rejectReadyToSendIceUpdatesPromise {
|
||||
rejectReadyToSendIceUpdatesPromise(CallError.obsoleteCall(description: "Terminating call"))
|
||||
}
|
||||
self.fulfillReadyToSendIceUpdatesPromise = nil
|
||||
self.rejectReadyToSendIceUpdatesPromise = nil
|
||||
self.readyToSendIceUpdatesPromise = nil
|
||||
fireDidUpdateVideoTracks()
|
||||
}
|
||||
|
||||
// MARK: - CallObserver
|
||||
|
||||
@ -166,6 +166,10 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
Logger.debug("[PeerConnectionClient] deinit")
|
||||
}
|
||||
|
||||
// MARK: - Media Streams
|
||||
|
||||
private func createSignalingDataChannel() {
|
||||
|
||||
@ -76,6 +76,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
+ (NSString *)callServicePeerConnectionMissing;
|
||||
|
||||
+ (NSString *)callServiceCallDataMissing;
|
||||
|
||||
+ (NSString *)contactsErrorContactsIntersectionFailed;
|
||||
|
||||
+ (NSString *)errorAttachmentRequestFailed;
|
||||
|
||||
@ -157,6 +157,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return @"call_service_peer_connection_missing";
|
||||
}
|
||||
|
||||
+ (NSString *)callServiceCallDataMissing
|
||||
{
|
||||
return @"call_service_call_data_missing";
|
||||
}
|
||||
|
||||
+ (NSString *)contactsErrorContactsIntersectionFailed
|
||||
{
|
||||
return @"contacts_error_contacts_intersection_failed";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user