2017-01-18 23:29:47 +01:00
//
// C o p y r i g h t ( c ) 2 0 1 7 O p e n W h i s p e r S y s t e m s . A l l r i g h t s r e s e r v e d .
//
2016-11-12 18:22:29 +01:00
import Foundation
import PromiseKit
import WebRTC
/* *
2017-01-18 23:29:47 +01:00
* ` CallService ` is a global singleton that manages the state of WebRTC - backed Signal Calls
* ( as opposed to legacy " RedPhone Calls " ) .
2016-11-12 18:22:29 +01:00
*
2017-01-18 23:29:47 +01:00
* It serves as a connection between the ` CallUIAdapter ` and the ` PeerConnectionClient ` .
2017-01-06 14:40:13 +01:00
*
* # # Signaling
*
* Signaling refers to the setup and tear down of the connection . Before the connection is established , this must happen
2017-01-31 23:21:48 +01:00
* out of band ( using Signal Service ) , but once the connection is established it ' s possible to publish updates
2017-01-06 14:40:13 +01:00
* ( like hangup ) via the established channel .
*
2017-01-31 21:28:01 +01:00
* Signaling state is synchronized on the main thread and only mutated in the handleXXX family of methods .
2017-01-10 18:36:54 +01:00
*
* Following is a high level process of the exchange of messages that takes place during call signaling .
2017-01-06 14:40:13 +01:00
*
* # # # Key
*
* -- [ SOMETHING ] --> represents a message of type " Something " sent from the caller to the callee
* <-- [ SOMETHING ] -- represents a message of type " Something " sent from the callee to the caller
* SS : Message sent via Signal Service
* DC : Message sent via WebRTC Data Channel
*
* # # # Message Exchange / State Flow Overview
2016-11-12 18:22:29 +01:00
*
* | Caller | Callee |
* +----------------------------+-------------------------+
2017-01-10 18:36:54 +01:00
* Start outgoing call : ` handleOutgoingCall ` . . .
2017-01-31 23:21:48 +01:00
-- [ SS . CallOffer ] -->
2017-01-10 18:36:54 +01:00
* . . . and start generating ICE updates .
* As ICE candidates are generated , ` handleLocalAddedIceCandidate ` is called .
* and we * store * the ICE updates for later .
2016-11-12 18:22:29 +01:00
*
2017-01-06 14:40:13 +01:00
* Received call offer : ` handleReceivedOffer `
2016-11-12 18:22:29 +01:00
* Send call answer
* <-- [ SS . CallAnswer ] --
2017-01-10 18:36:54 +01:00
* Start generating ICE updates .
* As they are generated ` handleLocalAddedIceCandidate ` is called
2017-01-31 23:21:48 +01:00
which immediately sends the ICE updates to the Caller .
2017-01-06 14:40:13 +01:00
* <-- [ SS . ICEUpdate ] -- ( sent multiple times )
2016-11-12 18:22:29 +01:00
*
2017-01-06 14:40:13 +01:00
* Received CallAnswer : ` handleReceivedAnswer `
2017-01-10 18:36:54 +01:00
* So send any stored ice updates ( and send future ones immediately )
2016-11-12 18:22:29 +01:00
* -- [ SS . ICEUpdates ] -->
*
* Once compatible ICE updates have been exchanged . . .
2017-01-06 14:40:13 +01:00
* both parties : ` handleIceConnected `
2016-11-12 18:22:29 +01:00
*
* Show remote ringing UI
* Connect to offered Data Channel
* Show incoming call UI .
*
2017-01-06 14:40:13 +01:00
* If callee answers Call
2016-11-12 18:22:29 +01:00
* send connected message
* <-- [ DC . ConnectedMesage ] --
* Received connected message
* Show Call is connected .
2017-01-06 14:40:13 +01:00
*
* Hang up ( this could equally be sent by the Callee )
* -- [ DC . Hangup ] -->
* -- [ SS . Hangup ] -->
2016-11-12 18:22:29 +01:00
*/
enum CallError : Error {
case providerReset
case assertionError ( description : String )
case disconnected
case externalError ( underlyingError : Error )
2017-04-18 17:51:14 +02:00
case timeout ( description : String )
case obsoleteCall ( description : String )
2016-11-12 18:22:29 +01:00
}
2017-02-08 01:37:05 +01:00
// S h o u l d b e r o u g h l y s y n c e d w i t h A n d r o i d c l i e n t f o r c o n s i s t e n c y
2017-02-08 16:27:11 +01:00
fileprivate let connectingTimeoutSeconds = 120
2016-11-12 18:22:29 +01:00
2017-01-26 16:05:41 +01:00
// A l l O b s e r v e r m e t h o d s w i l l b e i n v o k e d f r o m t h e m a i n t h r e a d .
protocol CallServiceObserver : class {
2017-02-03 21:37:16 +01:00
/* *
* Fired whenever the call changes .
*/
func didUpdateCall ( call : SignalCall ? )
2017-01-26 16:05:41 +01:00
/* *
* Fired whenever the local or remote video track become active or inactive .
*/
func didUpdateVideoTracks ( localVideoTrack : RTCVideoTrack ? ,
remoteVideoTrack : RTCVideoTrack ? )
}
2017-01-31 21:28:01 +01:00
// T h i s c l a s s ' s t a t e s h o u l d o n l y b e a c c e s s e d o n t h e m a i n q u e u e .
2017-01-26 16:05:41 +01:00
@objc class CallService : NSObject , CallObserver , PeerConnectionClientDelegate {
2016-11-12 18:22:29 +01:00
// MARK: - P r o p e r t i e s
let TAG = " [CallService] "
2017-01-26 16:05:41 +01:00
var observers = [ Weak < CallServiceObserver > ] ( )
2016-11-12 18:22:29 +01:00
// MARK: D e p e n d e n c i e s
2017-02-02 18:21:48 +01:00
private let accountManager : AccountManager
private let messageSender : MessageSender
private let contactsManager : OWSContactsManager
private let notificationsAdapter : CallNotificationsAdapter
// E x p o s e d b y e n v i r o n m e n t . m
internal var callUIAdapter : CallUIAdapter !
2016-11-12 18:22:29 +01:00
// MARK: C l a s s
static let fallbackIceServer = RTCIceServer ( urlStrings : [ " stun:stun1.l.google.com:19302 " ] )
// MARK: I v a r s
2017-02-02 03:33:41 +01:00
var peerConnectionClient : PeerConnectionClient ? {
didSet {
AssertIsOnMainThread ( )
2017-02-04 02:17:42 +01:00
Logger . debug ( " \( self . TAG ) .peerConnectionClient setter: \( oldValue != nil ) -> \( peerConnectionClient != nil ) \( peerConnectionClient ) " )
2017-02-02 03:33:41 +01:00
}
}
2017-01-06 14:40:13 +01:00
// T O D O c o d e c l e a n u p : m o v e t h r e a d i n t o S i g n a l C a l l ? O r r e f a c t o r m e s s a g e S e n d e r t o t a k e S i g n a l R e c i p i e n t i d e n t i f i e r .
2016-11-12 18:22:29 +01:00
var thread : TSContactThread ?
2017-01-26 16:05:41 +01:00
var call : SignalCall ? {
didSet {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
oldValue ? . removeObserver ( self )
call ? . addObserverAndSyncState ( observer : self )
2017-01-31 21:28:01 +01:00
updateIsVideoEnabled ( )
2017-02-07 15:35:37 +01:00
updateLockTimerEnabling ( )
2017-02-03 21:37:16 +01:00
2017-02-04 02:17:42 +01:00
Logger . debug ( " \( self . TAG ) .call setter: \( oldValue != nil ) -> \( call != nil ) \( call ) " )
2017-02-03 21:37:16 +01:00
for observer in observers {
observer . value ? . didUpdateCall ( call : call )
}
2017-01-26 16:05:41 +01:00
}
}
2017-01-06 14:40:13 +01:00
/* *
* In the process of establishing a connection between the clients ( ICE process ) we must exchange ICE updates .
2017-01-31 23:21:48 +01:00
* Because this happens via Signal Service it ' s possible the callee user has not accepted any change in the caller ' s
2017-01-06 14:40:13 +01:00
* identity . In which case * each * ICE update would cause an " identity change " warning on the callee ' s device . Since
2017-01-31 23:21:48 +01:00
* this could be several messages , the caller stores all ICE updates until receiving positive confirmation that the
* callee has received a message from us . This positive confirmation comes in the form of the callees ` CallAnswer `
2017-01-06 14:40:13 +01:00
* message .
*/
2016-11-12 18:22:29 +01:00
var sendIceUpdatesImmediately = true
var pendingIceUpdateMessages = [ OWSCallIceUpdateMessage ] ( )
2017-01-06 14:40:13 +01:00
// e n s u r e t h e i n c o m i n g c a l l p r o m i s e i s n ' t d e a l l o c ' d p r e m a t u r e l y
2016-11-12 18:22:29 +01:00
var incomingCallPromise : Promise < Void > ?
// U s e d t o c o o r d i n a t e p r o m i s e s a c r o s s d e l e g a t e m e t h o d s
2017-01-18 23:29:47 +01:00
var fulfillCallConnectedPromise : ( ( ) -> Void ) ?
2016-11-12 18:22:29 +01:00
2017-01-26 16:05:41 +01:00
weak var localVideoTrack : RTCVideoTrack ? {
didSet {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
Logger . info ( " \( self . TAG ) \( #function ) " )
fireDidUpdateVideoTracks ( )
}
}
weak var remoteVideoTrack : RTCVideoTrack ? {
didSet {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
Logger . info ( " \( self . TAG ) \( #function ) " )
fireDidUpdateVideoTracks ( )
}
}
2017-01-26 17:33:42 +01:00
var isRemoteVideoEnabled = false {
didSet {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-01-26 17:33:42 +01:00
Logger . info ( " \( self . TAG ) \( #function ) " )
fireDidUpdateVideoTracks ( )
}
}
2017-01-26 16:05:41 +01:00
2016-11-12 18:22:29 +01:00
required init ( accountManager : AccountManager , contactsManager : OWSContactsManager , messageSender : MessageSender , notificationsAdapter : CallNotificationsAdapter ) {
self . accountManager = accountManager
2017-02-02 18:21:48 +01:00
self . contactsManager = contactsManager
2016-11-12 18:22:29 +01:00
self . messageSender = messageSender
2017-02-02 18:21:48 +01:00
self . notificationsAdapter = notificationsAdapter
2016-11-12 18:22:29 +01:00
super . init ( )
2017-02-02 18:21:48 +01:00
self . createCallUIAdapter ( )
2017-01-30 23:50:28 +01:00
NotificationCenter . default . addObserver ( self ,
selector : #selector ( didEnterBackground ) ,
name : NSNotification . Name . UIApplicationDidEnterBackground ,
object : nil )
NotificationCenter . default . addObserver ( self ,
selector : #selector ( didBecomeActive ) ,
name : NSNotification . Name . UIApplicationDidBecomeActive ,
object : nil )
2017-01-31 21:28:01 +01:00
}
2017-01-30 23:50:28 +01:00
deinit {
NotificationCenter . default . removeObserver ( self )
}
func didEnterBackground ( ) {
AssertIsOnMainThread ( )
Logger . info ( " \( self . TAG ) \( #function ) " )
self . updateIsVideoEnabled ( )
}
func didBecomeActive ( ) {
AssertIsOnMainThread ( )
Logger . info ( " \( self . TAG ) \( #function ) " )
self . updateIsVideoEnabled ( )
2016-11-12 18:22:29 +01:00
}
2017-02-22 16:06:01 +01:00
/* *
* Choose whether to use CallKit or a Notification backed interface for calling .
*/
2017-02-02 18:21:48 +01:00
public func createCallUIAdapter ( ) {
AssertIsOnMainThread ( )
if self . call != nil {
Logger . warn ( " \( TAG ) ending current call in \( #function ) . Did user toggle callkit preference while in a call? " )
self . terminateCall ( )
}
self . callUIAdapter = CallUIAdapter ( callService : self , contactsManager : self . contactsManager , notificationsAdapter : self . notificationsAdapter )
}
2016-11-12 18:22:29 +01:00
// MARK: - C l a s s M e t h o d s
// MARK: N o t i f i c a t i o n s
// W r a p p i n g t h e s e c l a s s c o n s t a n t s i n a m e t h o d t o m a k e i t a c c e s s i b l e t o o b j c
class func callServiceActiveCallNotificationName ( ) -> String {
return " CallServiceActiveCallNotification "
}
// MARK: - S e r v i c e A c t i o n s
/* *
* Initiate an outgoing call .
*/
public func handleOutgoingCall ( _ call : SignalCall ) -> Promise < Void > {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
2017-02-02 21:10:50 +01:00
guard self . call = = nil else {
let errorDescription = " \( TAG ) call was unexpectedly already set. "
Logger . error ( errorDescription )
call . state = . localFailure
return Promise ( error : CallError . assertionError ( description : errorDescription ) )
}
2016-11-12 18:22:29 +01:00
self . call = call
let thread = TSContactThread . getOrCreateThread ( contactId : call . remotePhoneNumber )
self . thread = thread
sendIceUpdatesImmediately = false
pendingIceUpdateMessages = [ ]
2017-02-08 21:52:48 +01:00
let callRecord = TSCall ( timestamp : NSDate . ows_millisecondTimeStamp ( ) , withCallNumber : call . remotePhoneNumber , callType : RPRecentCallTypeOutgoingIncomplete , in : thread )
2016-11-12 18:22:29 +01:00
callRecord . save ( )
2017-02-08 21:52:48 +01:00
call . callRecord = callRecord
2016-11-12 18:22:29 +01:00
guard self . peerConnectionClient = = nil else {
let errorDescription = " \( TAG ) peerconnection was unexpectedly already set. "
Logger . error ( errorDescription )
call . state = . localFailure
return Promise ( error : CallError . assertionError ( description : errorDescription ) )
}
2017-02-03 16:51:40 +01:00
return getIceServers ( ) . then { iceServers -> Promise < HardenedRTCSessionDescription > in
2016-11-12 18:22:29 +01:00
Logger . debug ( " \( self . TAG ) got ice servers: \( iceServers ) " )
2017-01-31 21:28:01 +01:00
2017-04-18 17:50:55 +02:00
guard self . call = = call else {
2017-04-19 16:18:03 +02:00
throw CallError . obsoleteCall ( description : " obsolete call in \( #function ) " )
2017-04-18 17:50:55 +02:00
}
2017-02-22 16:06:01 +01:00
let useTurnOnly = Environment . getCurrent ( ) . preferences . doCallsHideIPAddress ( )
let peerConnectionClient = PeerConnectionClient ( iceServers : iceServers , delegate : self , callDirection : . outgoing , useTurnOnly : useTurnOnly )
2017-01-12 01:28:07 +01:00
2017-02-01 23:38:17 +01:00
assert ( self . peerConnectionClient = = nil , " Unexpected PeerConnectionClient instance " )
2017-02-03 16:25:03 +01:00
Logger . debug ( " \( self . TAG ) setting peerConnectionClient in \( #function ) " )
2017-01-12 01:28:07 +01:00
self . peerConnectionClient = peerConnectionClient
2016-11-12 18:22:29 +01:00
2017-04-24 21:16:17 +02:00
return peerConnectionClient . createOffer ( )
2017-02-03 16:51:40 +01:00
} . then { ( sessionDescription : HardenedRTCSessionDescription ) -> Promise < Void > in
2017-04-18 17:50:55 +02:00
guard self . call = = call else {
2017-04-19 16:18:03 +02:00
throw CallError . obsoleteCall ( description : " obsolete call in \( #function ) " )
2017-04-18 17:50:55 +02:00
}
2017-04-24 21:16:17 +02:00
guard let peerConnectionClient = self . peerConnectionClient else {
throw CallError . assertionError ( description : " peerConnectionClient was unexpectedly nil in \( #function ) " )
}
return peerConnectionClient . setLocalSessionDescription ( sessionDescription ) . then {
2017-01-31 23:21:48 +01:00
let offerMessage = OWSCallOfferMessage ( callId : call . signalingId , sessionDescription : sessionDescription . sdp )
let callMessage = OWSOutgoingCallMessage ( thread : thread , offerMessage : offerMessage )
return self . messageSender . sendCallMessage ( callMessage )
}
2017-02-08 01:37:05 +01:00
} . then {
2017-04-18 17:50:55 +02:00
guard self . call = = call else {
2017-04-19 16:18:03 +02:00
throw CallError . obsoleteCall ( description : " obsolete call in \( #function ) " )
2017-04-18 17:50:55 +02:00
}
2017-02-08 01:37:05 +01:00
let ( callConnectedPromise , fulfill , _ ) = Promise < Void > . pending ( )
self . fulfillCallConnectedPromise = fulfill
// D o n ' t l e t t h e o u t g o i n g c a l l r i n g f o r e v e r . W e d o n ' t s u p p o r t i n b o u n d r i n g i n g f o r e v e r a n y w a y .
2017-02-08 16:27:11 +01:00
let timeout : Promise < Void > = after ( interval : TimeInterval ( connectingTimeoutSeconds ) ) . then { ( ) -> Void in
2017-02-08 01:37:05 +01:00
// r e j e c t i n g a p r o m i s e b y t h r o w i n g i s s a f e l y a n o - o p i f t h e p r o m i s e h a s a l r e a d y b e e n f u l f i l l e d
2017-04-18 17:51:14 +02:00
throw CallError . timeout ( description : " timed out waiting to receive call answer " )
2017-02-08 01:37:05 +01:00
}
return race ( timeout , callConnectedPromise )
2017-02-08 19:39:23 +01:00
} . then {
2017-04-18 18:26:18 +02:00
Logger . info ( self . call = = call
? " \( self . TAG ) outgoing call connected. "
: " \( self . TAG ) obsolete outgoing call connected. " )
2017-02-03 16:51:40 +01:00
} . catch { error in
2017-01-31 23:21:48 +01:00
Logger . error ( " \( self . TAG ) placing call failed with error: \( error ) " )
if let callError = error as ? CallError {
2017-04-18 18:12:17 +02:00
self . handleFailedCall ( failedCall : call , error : callError )
2017-01-31 23:21:48 +01:00
} else {
let externalError = CallError . externalError ( underlyingError : error )
2017-04-18 18:12:17 +02:00
self . handleFailedCall ( failedCall : call , error : externalError )
2017-01-31 23:21:48 +01:00
}
2016-11-12 18:22:29 +01:00
}
}
/* *
* Called by the call initiator after receiving a CallAnswer from the callee .
*/
public func handleReceivedAnswer ( thread : TSContactThread , callId : UInt64 , sessionDescription : String ) {
Logger . debug ( " \( TAG ) received call answer for call: \( callId ) thread: \( thread ) " )
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
guard let call = self . call else {
2017-04-18 17:50:55 +02:00
Logger . warn ( " \( self . TAG ) ignoring obsolete call in \( #function ) " )
2016-11-12 18:22:29 +01:00
return
}
guard call . signalingId = = callId else {
2017-04-18 17:50:55 +02:00
Logger . warn ( " \( self . TAG ) ignoring obsolete call in \( #function ) " )
2016-11-12 18:22:29 +01:00
return
}
// N o w t h a t w e k n o w t h e r e c i p i e n t t r u s t s o u r i d e n t i t y , w e n o l o n g e r n e e d t o e n q u e u e I C E u p d a t e s .
self . sendIceUpdatesImmediately = true
if pendingIceUpdateMessages . count > 0 {
let callMessage = OWSOutgoingCallMessage ( thread : thread , iceUpdateMessages : pendingIceUpdateMessages )
2017-01-06 14:40:44 +01:00
_ = messageSender . sendCallMessage ( callMessage ) . catch { error in
2016-11-12 18:22:29 +01:00
Logger . error ( " \( self . TAG ) failed to send ice updates in \( #function ) with error: \( error ) " )
}
}
guard let peerConnectionClient = self . peerConnectionClient else {
2017-04-18 18:12:17 +02:00
handleFailedCall ( failedCall : call , error : CallError . assertionError ( description : " peerConnectionClient was unexpectedly nil in \( #function ) " ) )
2016-11-12 18:22:29 +01:00
return
}
let sessionDescription = RTCSessionDescription ( type : . answer , sdp : sessionDescription )
_ = peerConnectionClient . setRemoteSessionDescription ( sessionDescription ) . then {
Logger . debug ( " \( self . TAG ) successfully set remote description " )
2017-02-03 16:51:40 +01:00
} . catch { error in
2017-01-31 23:21:48 +01:00
if let callError = error as ? CallError {
2017-04-18 18:12:17 +02:00
self . handleFailedCall ( failedCall : call , error : callError )
2017-01-31 23:21:48 +01:00
} else {
let externalError = CallError . externalError ( underlyingError : error )
2017-04-18 18:12:17 +02:00
self . handleFailedCall ( failedCall : call , error : externalError )
2017-01-31 23:21:48 +01:00
}
2016-11-12 18:22:29 +01:00
}
}
2017-01-06 14:40:13 +01:00
/* *
* User didn ' t answer incoming call
*/
2016-11-12 18:22:29 +01:00
public func handleMissedCall ( _ call : SignalCall , thread : TSContactThread ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-02-08 22:39:32 +01:00
2016-11-12 18:22:29 +01:00
// I n s e r t m i s s e d c a l l r e c o r d
2017-02-08 22:39:32 +01:00
if let callRecord = call . callRecord {
2017-03-22 17:49:26 +01:00
if callRecord . callType = = RPRecentCallTypeIncoming {
2017-02-08 22:39:32 +01:00
callRecord . updateCallType ( RPRecentCallTypeMissed )
}
} else {
2017-02-08 21:52:48 +01:00
call . callRecord = TSCall ( timestamp : NSDate . ows_millisecondTimeStamp ( ) ,
withCallNumber : thread . contactIdentifier ( ) ,
callType : RPRecentCallTypeMissed ,
in : thread )
}
assert ( call . callRecord != nil )
call . callRecord ? . save ( )
2016-11-12 18:22:29 +01:00
2017-01-31 21:28:01 +01:00
self . callUIAdapter . reportMissedCall ( call )
2016-11-12 18:22:29 +01:00
}
2017-01-06 14:40:13 +01:00
/* *
* Received a call while already in another call .
*/
private func handleLocalBusyCall ( _ call : SignalCall , thread : TSContactThread ) {
Logger . debug ( " \( TAG ) \( #function ) for call: \( call ) thread: \( thread ) " )
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-01-06 14:40:13 +01:00
let busyMessage = OWSCallBusyMessage ( callId : call . signalingId )
let callMessage = OWSOutgoingCallMessage ( thread : thread , busyMessage : busyMessage )
_ = messageSender . sendCallMessage ( callMessage )
handleMissedCall ( call , thread : thread )
}
/* *
* The callee was already in another call .
*/
2016-11-12 18:22:29 +01:00
public func handleRemoteBusy ( thread : TSContactThread ) {
Logger . debug ( " \( TAG ) \( #function ) for thread: \( thread ) " )
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
guard let call = self . call else {
2017-04-18 18:26:18 +02:00
Logger . warn ( " \( self . TAG ) ignoring obsolete call in \( #function ) " )
2017-04-18 17:50:55 +02:00
return
}
guard thread . contactIdentifier ( ) = = call . remotePhoneNumber else {
Logger . warn ( " \( self . TAG ) ignoring obsolete call in \( #function ) " )
2016-11-12 18:22:29 +01:00
return
}
call . state = . remoteBusy
2017-02-02 03:33:30 +01:00
callUIAdapter . remoteBusy ( call )
2016-11-12 18:22:29 +01:00
terminateCall ( )
}
/* *
* Received an incoming call offer . We still have to complete setting up the Signaling channel before we notify
* the user of an incoming call .
*/
public func handleReceivedOffer ( thread : TSContactThread , callId : UInt64 , sessionDescription callerSessionDescription : String ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
Logger . verbose ( " \( TAG ) receivedCallOffer for thread: \( thread ) " )
let newCall = SignalCall . incomingCall ( localId : UUID ( ) , remotePhoneNumber : thread . contactIdentifier ( ) , signalingId : callId )
2017-03-23 14:55:39 +01:00
guard call = = nil else {
2016-11-12 18:22:29 +01:00
// T O D O o n i O S 1 0 + w e c a n u s e C a l l K i t t o s w a p c a l l s r a t h e r t h a n j u s t r e t u r n i n g b u s y i m m e d i a t e l y .
2017-04-18 17:50:55 +02:00
Logger . verbose ( " \( TAG ) receivedCallOffer for thread: \( thread ) but we're already in call: \( call ! ) " )
2016-11-12 18:22:29 +01:00
handleLocalBusyCall ( newCall , thread : thread )
return
}
self . thread = thread
call = newCall
let backgroundTask = UIApplication . shared . beginBackgroundTask {
2017-04-18 17:51:14 +02:00
let timeout = CallError . timeout ( description : " background task time ran out before call connected. " )
2017-01-31 21:28:01 +01:00
DispatchQueue . main . async {
2017-02-04 02:17:42 +01:00
guard self . call = = newCall else {
2017-04-18 17:50:55 +02:00
Logger . warn ( " \( self . TAG ) ignoring obsolete call in \( #function ) " )
2017-02-04 02:17:42 +01:00
return
}
2017-04-18 18:12:17 +02:00
self . handleFailedCall ( failedCall : newCall , error : timeout )
2016-11-12 18:22:29 +01:00
}
}
incomingCallPromise = firstly {
return getIceServers ( )
2017-02-03 16:51:40 +01:00
} . then { ( iceServers : [ RTCIceServer ] ) -> Promise < HardenedRTCSessionDescription > in
2017-01-31 23:21:48 +01:00
// F I X M E f o r f i r s t t i m e c a l l r e c i p i e n t s I t h i n k w e ' l l s e e m i c / c a m e r a p e r m i s s i o n r e q u e s t s h e r e ,
// e v e n t h o u g h , f r o m t h e u s e r s p e r s p e c t i v e , n o i n c o m i n g c a l l i s y e t v i s i b l e .
2017-02-04 02:17:42 +01:00
guard self . call = = newCall else {
2017-04-18 17:51:14 +02:00
throw CallError . obsoleteCall ( description : " getIceServers() response for obsolete call " )
2017-02-04 02:17:42 +01:00
}
2017-02-01 23:38:17 +01:00
assert ( self . peerConnectionClient = = nil , " Unexpected PeerConnectionClient instance " )
2017-02-22 16:06:01 +01:00
// F o r c o n t a c t s n o t s t o r e d i n o u r s y s t e m c o n t a c t s , w e a s s u m e t h e y a r e a n u n k n o w n c a l l e r , a n d w e f o r c e
// a T U R N c o n n e c t i o n , s o a s n o t t o r e v e a l a n y c o n n e c t i v i t y i n f o r m a t i o n ( I P / p o r t ) t o t h e c a l l e r .
let unknownCaller = self . contactsManager . contact ( forPhoneIdentifier : thread . contactIdentifier ( ) ) = = nil
let useTurnOnly = unknownCaller || Environment . getCurrent ( ) . preferences . doCallsHideIPAddress ( )
2017-02-03 16:25:03 +01:00
Logger . debug ( " \( self . self . TAG ) setting peerConnectionClient in \( #function ) " )
2017-04-24 21:16:17 +02:00
let peerConnectionClient = PeerConnectionClient ( iceServers : iceServers , delegate : self , callDirection : . incoming , useTurnOnly : useTurnOnly )
self . peerConnectionClient = peerConnectionClient
2016-11-12 18:22:29 +01:00
2017-01-31 23:21:48 +01:00
let offerSessionDescription = RTCSessionDescription ( type : . offer , sdp : callerSessionDescription )
let constraints = RTCMediaConstraints ( mandatoryConstraints : nil , optionalConstraints : nil )
2016-11-12 18:22:29 +01:00
2017-01-31 23:21:48 +01:00
// F i n d a s e s s i o n D e s c r i p t i o n c o m p a t i b l e w i t h m y c o n s t r a i n t s a n d t h e r e m o t e s e s s i o n D e s c r i p t i o n
2017-04-24 21:16:17 +02:00
return peerConnectionClient . negotiateSessionDescription ( remoteDescription : offerSessionDescription , constraints : constraints )
2017-02-03 16:51:40 +01:00
} . then { ( negotiatedSessionDescription : HardenedRTCSessionDescription ) in
2017-02-04 02:17:42 +01:00
guard self . call = = newCall else {
2017-04-18 17:51:14 +02:00
throw CallError . obsoleteCall ( description : " negotiateSessionDescription() response for obsolete call " )
2017-02-04 02:17:42 +01:00
}
2017-01-31 23:21:48 +01:00
Logger . debug ( " \( self . TAG ) set the remote description " )
2016-11-12 18:22:29 +01:00
2017-01-31 23:21:48 +01:00
let answerMessage = OWSCallAnswerMessage ( callId : newCall . signalingId , sessionDescription : negotiatedSessionDescription . sdp )
let callAnswerMessage = OWSOutgoingCallMessage ( thread : thread , answerMessage : answerMessage )
2016-11-12 18:22:29 +01:00
2017-01-31 23:21:48 +01:00
return self . messageSender . sendCallMessage ( callAnswerMessage )
2017-02-03 16:51:40 +01:00
} . then {
2017-02-04 02:17:42 +01:00
guard self . call = = newCall else {
2017-04-18 17:51:14 +02:00
throw CallError . obsoleteCall ( description : " sendCallMessage() response for obsolete call " )
2017-02-04 02:17:42 +01:00
}
2017-01-31 23:21:48 +01:00
Logger . debug ( " \( self . TAG ) successfully sent callAnswerMessage " )
2016-11-12 18:22:29 +01:00
2017-01-31 23:21:48 +01:00
let ( promise , fulfill , _ ) = Promise < Void > . pending ( )
2016-11-12 18:22:29 +01:00
2017-02-08 16:27:11 +01:00
let timeout : Promise < Void > = after ( interval : TimeInterval ( connectingTimeoutSeconds ) ) . then { ( ) -> Void in
2017-01-31 23:21:48 +01:00
// r e j e c t i n g a p r o m i s e b y t h r o w i n g i s s a f e l y a n o - o p i f t h e p r o m i s e h a s a l r e a d y b e e n f u l f i l l e d
2017-04-18 17:51:14 +02:00
throw CallError . timeout ( description : " timed out waiting for call to connect " )
2017-01-31 23:21:48 +01:00
}
2016-11-12 18:22:29 +01:00
2017-01-31 23:21:48 +01:00
// T h i s w i l l b e f u l f i l l e d ( p o t e n t i a l l y ) b y t h e R T C D a t a C h a n n e l d e l e g a t e m e t h o d
self . fulfillCallConnectedPromise = fulfill
2016-11-12 18:22:29 +01:00
2017-01-31 23:21:48 +01:00
return race ( promise , timeout )
2017-02-08 19:39:23 +01:00
} . then {
2017-04-18 18:26:18 +02:00
Logger . info ( self . call = = newCall
? " \( self . TAG ) incoming call connected. "
: " \( self . TAG ) obsolete incoming call connected. " )
2017-02-03 16:51:40 +01:00
} . catch { error in
2017-02-04 02:17:42 +01:00
guard self . call = = newCall else {
Logger . debug ( " \( self . TAG ) error for obsolete call: \( error ) " )
return
}
2017-01-31 23:21:48 +01:00
if let callError = error as ? CallError {
2017-04-18 18:12:17 +02:00
self . handleFailedCall ( failedCall : newCall , error : callError )
2017-01-31 23:21:48 +01:00
} else {
let externalError = CallError . externalError ( underlyingError : error )
2017-04-18 18:12:17 +02:00
self . handleFailedCall ( failedCall : newCall , error : externalError )
2017-01-31 23:21:48 +01:00
}
} . always {
Logger . debug ( " \( self . TAG ) ending background task awaiting inbound call connection " )
UIApplication . shared . endBackgroundTask ( backgroundTask )
2016-11-12 18:22:29 +01:00
}
}
2017-01-06 14:40:13 +01:00
/* *
* Remote client ( could be caller or callee ) sent us a connectivity update
*/
2016-11-12 18:22:29 +01:00
public func handleRemoteAddedIceCandidate ( thread : TSContactThread , callId : UInt64 , sdp : String , lineIndex : Int32 , mid : String ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
Logger . debug ( " \( TAG ) called \( #function ) " )
guard self . thread != nil else {
2017-04-18 18:12:17 +02:00
Logger . warn ( " ignoring remote ice update for thread: \( thread . uniqueId ) since there is no current thread. Call already ended? " )
2016-11-12 18:22:29 +01:00
return
}
guard thread . contactIdentifier ( ) = = self . thread ! . contactIdentifier ( ) else {
2017-04-18 18:12:17 +02:00
Logger . warn ( " ignoring remote ice update for thread: \( thread . uniqueId ) since there is no current thread. Call already ended? " )
2016-11-12 18:22:29 +01:00
return
}
guard let call = self . call else {
2017-04-18 18:12:17 +02:00
Logger . warn ( " ignoring remote ice update for thread: \( thread . uniqueId ) since there is no current thread. Call already ended? " )
2016-11-12 18:22:29 +01:00
return
}
guard call . signalingId = = callId else {
2017-04-18 18:12:17 +02:00
Logger . warn ( " ignoring remote ice update for thread: \( thread . uniqueId ) since there is no current thread. Call already ended? " )
2016-11-12 18:22:29 +01:00
return
}
guard let peerConnectionClient = self . peerConnectionClient else {
2017-04-18 18:12:17 +02:00
Logger . warn ( " ignoring remote ice update for thread: \( thread . uniqueId ) since there is no current thread. Call already ended? " )
2016-11-12 18:22:29 +01:00
return
}
peerConnectionClient . addIceCandidate ( RTCIceCandidate ( sdp : sdp , sdpMLineIndex : lineIndex , sdpMid : mid ) )
}
2017-01-06 14:40:13 +01:00
/* *
2017-01-31 23:21:48 +01:00
* Local client ( could be caller or callee ) generated some connectivity information that we should send to the
2017-01-06 14:40:13 +01:00
* remote client .
*/
2016-11-12 18:22:29 +01:00
private func handleLocalAddedIceCandidate ( _ iceCandidate : RTCIceCandidate ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
guard let call = self . call else {
2017-04-18 18:12:17 +02:00
// T h i s w i l l o n l y b e c a l l e d f o r t h e c u r r e n t p e e r C o n n e c t i o n C l i e n t , s o
// f a i l t h e c u r r e n t c a l l .
handleFailedCurrentCall ( error : . assertionError ( description : " ignoring local ice candidate, since there is no current call. " ) )
2016-11-12 18:22:29 +01:00
return
}
guard call . state != . idle else {
2017-04-18 18:12:17 +02:00
// T h i s w i l l o n l y b e c a l l e d f o r t h e c u r r e n t p e e r C o n n e c t i o n C l i e n t , s o
// f a i l t h e c u r r e n t c a l l .
handleFailedCurrentCall ( error : . assertionError ( description : " ignoring local ice candidate, since call is now idle. " ) )
2016-11-12 18:22:29 +01:00
return
}
guard let thread = self . thread else {
2017-04-18 18:12:17 +02:00
// T h i s w i l l o n l y b e c a l l e d f o r t h e c u r r e n t p e e r C o n n e c t i o n C l i e n t , s o
// f a i l t h e c u r r e n t c a l l .
handleFailedCurrentCall ( error : . assertionError ( description : " ignoring local ice candidate, because there was no current TSContactThread. " ) )
2016-11-12 18:22:29 +01:00
return
}
let iceUpdateMessage = OWSCallIceUpdateMessage ( callId : call . signalingId , sdp : iceCandidate . sdp , sdpMLineIndex : iceCandidate . sdpMLineIndex , sdpMid : iceCandidate . sdpMid )
if self . sendIceUpdatesImmediately {
let callMessage = OWSOutgoingCallMessage ( thread : thread , iceUpdateMessage : iceUpdateMessage )
2017-01-06 14:40:44 +01:00
_ = self . messageSender . sendCallMessage ( callMessage )
2016-11-12 18:22:29 +01:00
} else {
// F o r o u t g o i n g m e s s a g e s , w e w a i t t o s e n d i c e u p d a t e s u n t i l w e ' r e s u r e c l i e n t r e c e i v e d o u r c a l l m e s s a g e .
// e . g . i f t h e c l i e n t h a s b l o c k e d o u r m e s s a g e d u e t o a n i d e n t i t y c h a n g e , w e ' d o t h e r w i s e
// b o m b a r d t h e m w i t h a b u n c h * m o r e * u n d e c i p h e r a b l e m e s s a g e s .
Logger . debug ( " \( TAG ) enqueuing iceUpdate until we receive call answer " )
self . pendingIceUpdateMessages . append ( iceUpdateMessage )
return
}
}
2017-01-06 14:40:13 +01:00
/* *
* The clients can now communicate via WebRTC .
*
2017-01-31 23:21:48 +01:00
* Called by both caller and callee . Compatible ICE messages have been exchanged between the local and remote
2017-01-06 14:40:13 +01:00
* client .
*/
2016-11-12 18:22:29 +01:00
private func handleIceConnected ( ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
Logger . debug ( " \( TAG ) in \( #function ) " )
guard let call = self . call else {
2017-04-18 18:12:17 +02:00
// T h i s w i l l o n l y b e c a l l e d f o r t h e c u r r e n t p e e r C o n n e c t i o n C l i e n t , s o
// f a i l t h e c u r r e n t c a l l .
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) ignoring \( #function ) since there is no current call. " ) )
2016-11-12 18:22:29 +01:00
return
}
guard let thread = self . thread else {
2017-04-18 18:12:17 +02:00
// T h i s w i l l o n l y b e c a l l e d f o r t h e c u r r e n t p e e r C o n n e c t i o n C l i e n t , s o
// f a i l t h e c u r r e n t c a l l .
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) ignoring \( #function ) since there is no current thread. " ) )
2016-11-12 18:22:29 +01:00
return
}
switch call . state {
case . dialing :
call . state = . remoteRinging
case . answering :
call . state = . localRinging
2017-01-31 21:28:01 +01:00
self . callUIAdapter . reportIncomingCall ( call , thread : thread )
2016-11-12 18:22:29 +01:00
case . remoteRinging :
Logger . info ( " \( TAG ) call alreading ringing. Ignoring \( #function ) " )
2017-01-31 19:42:15 +01:00
case . connected :
Logger . info ( " \( TAG ) Call reconnected \( #function ) " )
2016-11-12 18:22:29 +01:00
default :
Logger . debug ( " \( TAG ) unexpected call state for \( #function ) : \( call . state ) " )
}
}
2017-01-06 14:40:13 +01:00
/* *
* The remote client ( caller or callee ) ended the call .
*/
2016-11-12 18:22:29 +01:00
public func handleRemoteHangup ( thread : TSContactThread ) {
Logger . debug ( " \( TAG ) in \( #function ) " )
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
guard thread . contactIdentifier ( ) = = self . thread ? . contactIdentifier ( ) else {
2017-01-31 21:28:01 +01:00
// T h i s c a n s a f e l y b e i g n o r e d .
2016-11-12 18:22:29 +01:00
// W e d o n ' t w a n t t o f a i l t h e c u r r e n t c a l l b e c a u s e a n o l d c a l l w a s s l o w t o s e n d u s t h e h a n g u p m e s s a g e .
Logger . warn ( " \( TAG ) ignoring hangup for thread: \( thread ) which is not the current thread: \( self . thread ) " )
return
}
guard let call = self . call else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-19 16:18:03 +02:00
assertionFailure ( " \( TAG ) call was unexpectedly nil in \( #function ) " )
2017-04-18 18:12:17 +02:00
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) call was unexpectedly nil in \( #function ) " ) )
2016-11-12 18:22:29 +01:00
return
}
switch call . state {
case . idle , . dialing , . answering , . localRinging , . localFailure , . remoteBusy , . remoteRinging :
handleMissedCall ( call , thread : thread )
case . connected , . localHangup , . remoteHangup :
Logger . info ( " \( TAG ) call is finished. " )
}
call . state = . remoteHangup
// N o t i f y U I
2017-01-31 21:28:01 +01:00
callUIAdapter . remoteDidHangupCall ( call )
2016-11-12 18:22:29 +01:00
// s e l f . c a l l i s n i l ' d i n ` t e r m i n a t e C a l l ` , s o i t ' s i m p o r t a n t w e u p d a t e i t ' s s t a t e * b e f o r e * c a l l i n g ` t e r m i n a t e C a l l `
terminateCall ( )
}
2017-01-04 23:32:18 +01:00
/* *
2017-01-06 14:40:13 +01:00
* User chose to answer call referrred to by call ` localId ` . Used by the Callee only .
*
* Used by notification actions which can ' t serialize a call object .
2017-01-04 23:32:18 +01:00
*/
2016-11-12 18:22:29 +01:00
public func handleAnswerCall ( localId : UUID ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
guard let call = self . call else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-19 16:18:03 +02:00
assertionFailure ( " \( TAG ) call was unexpectedly nil in \( #function ) " )
2017-04-18 18:12:17 +02:00
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) call was unexpectedly nil in \( #function ) " ) )
2016-11-12 18:22:29 +01:00
return
}
guard call . localId = = localId else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-19 16:18:03 +02:00
assertionFailure ( " \( TAG ) callLocalId: \( localId ) doesn't match current calls: \( call . localId ) " )
2017-04-18 18:12:17 +02:00
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) callLocalId: \( localId ) doesn't match current calls: \( call . localId ) " ) )
2016-11-12 18:22:29 +01:00
return
}
2017-01-31 21:28:01 +01:00
self . handleAnswerCall ( call )
2016-11-12 18:22:29 +01:00
}
2017-01-06 14:40:13 +01:00
/* *
* User chose to answer call referrred to by call ` localId ` . Used by the Callee only .
*/
2016-11-12 18:22:29 +01:00
public func handleAnswerCall ( _ call : SignalCall ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
Logger . debug ( " \( TAG ) in \( #function ) " )
guard self . call != nil else {
2017-04-18 18:12:17 +02:00
handleFailedCall ( failedCall : call , error : . assertionError ( description : " \( TAG ) ignoring \( #function ) since there is no current call " ) )
2016-11-12 18:22:29 +01:00
return
}
guard call = = self . call ! else {
// T h i s c o u l d c o n c e i v a b l y h a p p e n i f t h e o t h e r p a r t y o f a n o l d c a l l w a s s l o w t o s e n d u s t h e i r a n s w e r
// a n d w e ' v e s u b s e q u e n t l y e n g a g e d i n a n o t h e r c a l l . D o n ' t k i l l t h e c u r r e n t c a l l , b u t j u s t i g n o r e i t .
Logger . warn ( " \( TAG ) ignoring \( #function ) for call other than current call " )
return
}
guard let thread = self . thread else {
2017-04-18 18:12:17 +02:00
handleFailedCall ( failedCall : call , error : . assertionError ( description : " \( TAG ) ignoring \( #function ) for call other than current call " ) )
2016-11-12 18:22:29 +01:00
return
}
guard let peerConnectionClient = self . peerConnectionClient else {
2017-04-18 18:12:17 +02:00
handleFailedCall ( failedCall : call , error : . assertionError ( description : " \( TAG ) missing peerconnection client in \( #function ) " ) )
2016-11-12 18:22:29 +01:00
return
}
2017-02-08 21:52:48 +01:00
let callRecord = TSCall ( timestamp : NSDate . ows_millisecondTimeStamp ( ) , withCallNumber : call . remotePhoneNumber , callType : RPRecentCallTypeIncomingIncomplete , in : thread )
2016-11-12 18:22:29 +01:00
callRecord . save ( )
2017-02-08 21:52:48 +01:00
call . callRecord = callRecord
2016-11-12 18:22:29 +01:00
let message = DataChannelMessage . forConnected ( callId : call . signalingId )
2017-02-01 16:44:29 +01:00
peerConnectionClient . sendDataChannelMessage ( data : message . asData ( ) )
2016-11-12 18:22:29 +01:00
handleConnectedCall ( call )
}
2017-01-04 23:32:18 +01:00
/* *
2017-01-06 14:40:13 +01:00
* 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 .
2017-01-04 23:32:18 +01:00
*/
2016-11-12 18:22:29 +01:00
func handleConnectedCall ( _ call : SignalCall ) {
Logger . debug ( " \( TAG ) in \( #function ) " )
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
guard let peerConnectionClient = self . peerConnectionClient else {
2017-04-18 18:12:17 +02:00
handleFailedCall ( failedCall : call , error : . assertionError ( description : " \( TAG ) peerConnectionClient unexpectedly nil in \( #function ) " ) )
2016-11-12 18:22:29 +01:00
return
}
2017-02-08 19:39:23 +01:00
assert ( self . fulfillCallConnectedPromise != nil )
// c a n c e l c o n n e c t i o n t i m e o u t
self . fulfillCallConnectedPromise ? ( )
2016-11-12 18:22:29 +01:00
call . state = . connected
// W e d o n ' t r i s k t r a n s m i t t i n g a n y m e d i a u n t i l t h e r e m o t e c l i e n t h a s a d m i t t e d t o b e i n g c o n n e c t e d .
2017-01-18 23:29:47 +01:00
peerConnectionClient . setAudioEnabled ( enabled : ! call . isMuted )
2017-01-26 16:05:41 +01:00
peerConnectionClient . setLocalVideoEnabled ( enabled : shouldHaveLocalVideoTrack ( ) )
2016-11-12 18:22:29 +01:00
}
2017-01-06 14:40:13 +01:00
/* *
* Local user chose to decline the call vs . answering it .
*
* The call is referred to by call ` localId ` , which is included in Notification actions .
*
* Incoming call only .
*/
2016-11-12 18:22:29 +01:00
public func handleDeclineCall ( localId : UUID ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
guard let call = self . call else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-19 16:18:03 +02:00
assertionFailure ( " \( TAG ) call was unexpectedly nil in \( #function ) " )
2017-04-18 18:12:17 +02:00
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) call was unexpectedly nil in \( #function ) " ) )
2016-11-12 18:22:29 +01:00
return
}
guard call . localId = = localId else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-19 16:18:03 +02:00
assertionFailure ( " \( TAG ) callLocalId: \( localId ) doesn't match current calls: \( call . localId ) " )
2017-04-18 18:12:17 +02:00
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) callLocalId: \( localId ) doesn't match current calls: \( call . localId ) " ) )
2016-11-12 18:22:29 +01:00
return
}
2017-01-31 21:28:01 +01:00
self . handleDeclineCall ( call )
2016-11-12 18:22:29 +01:00
}
2017-01-06 14:40:13 +01:00
/* *
* Local user chose to decline the call vs . answering it .
*
* Incoming call only .
*/
2016-11-12 18:22:29 +01:00
public func handleDeclineCall ( _ call : SignalCall ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
Logger . info ( " \( TAG ) in \( #function ) " )
// C u r r e n t l y w e j u s t h a n d l e t h i s a s a h a n g u p . B u t w e c o u l d o f f e r m o r e d e s c r i p t i v e a c t i o n . e . g . D a t a C h a n n e l m e s s a g e
handleLocalHungupCall ( call )
}
2017-01-06 14:40:13 +01:00
/* *
* Local user chose to end the call .
*
* Can be used for Incoming and Outgoing calls .
*/
2016-11-12 18:22:29 +01:00
func handleLocalHungupCall ( _ call : SignalCall ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
guard self . call != nil else {
2017-04-18 18:12:17 +02:00
handleFailedCall ( failedCall : call , error : . assertionError ( description : " \( TAG ) ignoring \( #function ) since there is no current call " ) )
2016-11-12 18:22:29 +01:00
return
}
guard call = = self . call ! else {
2017-04-18 18:12:17 +02:00
handleFailedCall ( failedCall : call , error : . assertionError ( description : " \( TAG ) ignoring \( #function ) for call other than current call " ) )
2016-11-12 18:22:29 +01:00
return
}
guard let peerConnectionClient = self . peerConnectionClient else {
2017-04-18 18:12:17 +02:00
handleFailedCall ( failedCall : call , error : . assertionError ( description : " \( TAG ) missing peerconnection client in \( #function ) " ) )
2016-11-12 18:22:29 +01:00
return
}
guard let thread = self . thread else {
2017-04-18 18:12:17 +02:00
handleFailedCall ( failedCall : call , error : . assertionError ( description : " \( TAG ) missing thread in \( #function ) " ) )
2016-11-12 18:22:29 +01:00
return
}
call . state = . localHangup
// T O D O s o m e t h i n g l i k e t h i s l i f t e d f r o m S i g n a l - A n d r o i d .
// t h i s . a c c o u n t M a n a g e r . c a n c e l I n F l i g h t R e q u e s t s ( ) ;
// t h i s . m e s s a g e S e n d e r . c a n c e l I n F l i g h t R e q u e s t s ( ) ;
// I f t h e c a l l i s c o n n e c t e d , w e c a n s e n d t h e h a n g u p v i a t h e d a t a c h a n n e l .
let message = DataChannelMessage . forHangup ( callId : call . signalingId )
2017-02-01 16:44:29 +01:00
peerConnectionClient . sendDataChannelMessage ( data : message . asData ( ) )
2016-11-12 18:22:29 +01:00
// I f t h e c a l l h a s n ' t s t a r t e d y e t , w e d o n ' t h a v e a d a t a c h a n n e l t o c o m m u n i c a t e t h e h a n g u p . U s e S i g n a l S e r v i c e M e s s a g e .
let hangupMessage = OWSCallHangupMessage ( callId : call . signalingId )
let callMessage = OWSOutgoingCallMessage ( thread : thread , hangupMessage : hangupMessage )
2017-02-03 16:51:40 +01:00
_ = self . messageSender . sendCallMessage ( callMessage ) . then {
2016-11-12 18:22:29 +01:00
Logger . debug ( " \( self . TAG ) successfully sent hangup call message to \( thread ) " )
2017-02-03 16:51:40 +01:00
} . catch { error in
2017-01-31 23:21:48 +01:00
Logger . error ( " \( self . TAG ) failed to send hangup call message to \( thread ) with error: \( error ) " )
2016-11-12 18:22:29 +01:00
}
terminateCall ( )
}
2017-01-06 14:40:13 +01:00
/* *
* Local user toggled to mute audio .
*
* Can be used for Incoming and Outgoing calls .
*/
2017-01-18 23:29:47 +01:00
func setIsMuted ( isMuted : Bool ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
guard let peerConnectionClient = self . peerConnectionClient else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-19 16:18:03 +02:00
assertionFailure ( " \( TAG ) peerConnectionClient was unexpectedly nil in \( #function ) " )
2017-04-18 18:12:17 +02:00
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) peerConnectionClient unexpectedly nil in \( #function ) " ) )
2016-11-12 18:22:29 +01:00
return
}
2017-01-09 15:28:04 +01:00
guard let call = self . call else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-19 16:18:03 +02:00
assertionFailure ( " \( TAG ) call was unexpectedly nil in \( #function ) " )
2017-04-18 18:12:17 +02:00
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) call unexpectedly nil in \( #function ) " ) )
2017-01-09 15:28:04 +01:00
return
}
call . isMuted = isMuted
2016-11-12 18:22:29 +01:00
peerConnectionClient . setAudioEnabled ( enabled : ! isMuted )
}
2017-01-18 23:29:47 +01:00
/* *
* Local user toggled video .
*
* Can be used for Incoming and Outgoing calls .
*/
2017-01-27 17:11:33 +01:00
func setHasLocalVideo ( hasLocalVideo : Bool ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-01-18 23:29:47 +01:00
2017-01-30 19:46:58 +01:00
let authStatus = AVCaptureDevice . authorizationStatus ( forMediaType : AVMediaTypeVideo )
switch authStatus {
case . notDetermined :
Logger . debug ( " \( TAG ) authStatus: AVAuthorizationStatusNotDetermined " )
break
case . restricted :
Logger . debug ( " \( TAG ) authStatus: AVAuthorizationStatusRestricted " )
break
case . denied :
Logger . debug ( " \( TAG ) authStatus: AVAuthorizationStatusDenied " )
break
case . authorized :
Logger . debug ( " \( TAG ) authStatus: AVAuthorizationStatusAuthorized " )
break
}
2017-01-30 21:54:31 +01:00
// W e d o n ' t n e e d t o w o r r y a b o u t t h e u s e r g r a n t i n g o r r e m o t i n g t h i s p e r m i s s i o n
2017-01-30 19:54:44 +01:00
// d u r i n g a c a l l w h i l e t h e a p p i s i n t h e b a c k g r o u n d , b e c a u s e c h a n g i n g t h i s
// p e r m i s s i o n k i l l s t h e a p p .
2017-01-30 19:46:58 +01:00
if authStatus != . authorized {
2017-01-31 21:28:01 +01:00
let title = NSLocalizedString ( " MISSING_CAMERA_PERMISSION_TITLE " , comment : " Alert title when camera is not authorized " )
let message = NSLocalizedString ( " MISSING_CAMERA_PERMISSION_MESSAGE " , comment : " Alert body when camera is not authorized " )
let okButton = NSLocalizedString ( " OK " , comment : " " )
2017-01-30 19:46:58 +01:00
2017-01-31 21:28:01 +01:00
let alert = UIAlertView ( title : title , message : message , delegate : nil , cancelButtonTitle : okButton )
alert . show ( )
2017-01-30 19:46:58 +01:00
return
}
2017-01-18 23:29:47 +01:00
guard let peerConnectionClient = self . peerConnectionClient else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-19 16:18:03 +02:00
assertionFailure ( " \( TAG ) peerConnectionClient was unexpectedly nil in \( #function ) " )
2017-04-18 18:12:17 +02:00
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) peerConnectionClient unexpectedly nil in \( #function ) " ) )
2017-01-18 23:29:47 +01:00
return
}
guard let call = self . call else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-19 16:18:03 +02:00
assertionFailure ( " \( TAG ) call was unexpectedly nil in \( #function ) " )
2017-04-18 18:12:17 +02:00
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) call unexpectedly nil in \( #function ) " ) )
2017-01-18 23:29:47 +01:00
return
}
2017-01-27 17:11:33 +01:00
call . hasLocalVideo = hasLocalVideo
2017-01-26 16:05:41 +01:00
peerConnectionClient . setLocalVideoEnabled ( enabled : shouldHaveLocalVideoTrack ( ) )
2017-01-18 23:29:47 +01:00
}
func handleCallKitStartVideo ( ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
self . setHasLocalVideo ( hasLocalVideo : true )
2017-01-18 23:29:47 +01:00
}
2017-01-06 14:40:13 +01:00
/* *
2017-01-31 23:21:48 +01:00
* Local client received a message on the WebRTC data channel .
2017-01-06 14:40:13 +01:00
*
2017-01-31 23:21:48 +01:00
* The WebRTC data channel is a faster signaling channel than out of band Signal Service messages . Once it ' s
* established we use it to communicate further signaling information . The one sort - of exception is that with
* hangup messages we redundantly send a Signal Service hangup message , which is more reliable , and since the hangup
2017-01-06 14:40:13 +01:00
* action is idemptotent , there ' s no harm done .
*
* Used by both Incoming and Outgoing calls .
*/
2016-11-12 18:22:29 +01:00
private func handleDataChannelMessage ( _ message : OWSWebRTCProtosData ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
guard let call = self . call else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-19 16:18:03 +02:00
assertionFailure ( " \( TAG ) received data message, but there is no current call. Ignoring. " )
2017-04-18 18:12:17 +02:00
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) received data message, but there is no current call. Ignoring. " ) )
2016-11-12 18:22:29 +01:00
return
}
if message . hasConnected ( ) {
Logger . debug ( " \( TAG ) remote participant sent Connected via data channel " )
let connected = message . connected !
guard connected . id = = call . signalingId else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-19 16:18:03 +02:00
assertionFailure ( " \( TAG ) received connected message for call with id: \( connected . id ) but current call has id: \( call . signalingId ) " )
2017-04-18 18:12:17 +02:00
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) received connected message for call with id: \( connected . id ) but current call has id: \( call . signalingId ) " ) )
2016-11-12 18:22:29 +01:00
return
}
2017-01-31 21:28:01 +01:00
self . callUIAdapter . recipientAcceptedCall ( call )
2016-11-12 18:22:29 +01:00
handleConnectedCall ( call )
} else if message . hasHangup ( ) {
Logger . debug ( " \( TAG ) remote participant sent Hangup via data channel " )
let hangup = message . hangup !
guard hangup . id = = call . signalingId else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-19 16:18:03 +02:00
assertionFailure ( " \( TAG ) received hangup message for call with id: \( hangup . id ) but current call has id: \( call . signalingId ) " )
2017-04-18 18:12:17 +02:00
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) received hangup message for call with id: \( hangup . id ) but current call has id: \( call . signalingId ) " ) )
2016-11-12 18:22:29 +01:00
return
}
guard let thread = self . thread else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-19 16:18:03 +02:00
assertionFailure ( " \( TAG ) current contact thread is unexpectedly nil when receiving hangup DataChannelMessage " )
2017-04-18 18:12:17 +02:00
handleFailedCurrentCall ( error : . assertionError ( description : " \( TAG ) current contact thread is unexpectedly nil when receiving hangup DataChannelMessage " ) )
2016-11-12 18:22:29 +01:00
return
}
handleRemoteHangup ( thread : thread )
} else if message . hasVideoStreamingStatus ( ) {
Logger . debug ( " \( TAG ) remote participant sent VideoStreamingStatus via data channel " )
2017-01-26 17:33:42 +01:00
self . isRemoteVideoEnabled = message . videoStreamingStatus . enabled ( )
2017-02-02 21:51:07 +01:00
} else {
Logger . info ( " \( TAG ) received unknown or empty DataChannelMessage " )
2016-11-12 18:22:29 +01:00
}
}
2017-01-12 01:06:39 +01:00
// MARK: - P e e r C o n n e c t i o n C l i e n t D e l e g a t e
/* *
* The connection has been established . The clients can now communicate .
*/
2017-02-03 16:25:03 +01:00
internal func peerConnectionClientIceConnected ( _ peerConnectionClient : PeerConnectionClient ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-02-03 16:51:40 +01:00
guard peerConnectionClient = = self . peerConnectionClient else {
2017-02-03 16:25:03 +01:00
Logger . debug ( " \( self . TAG ) \( #function ) Ignoring event from obsolete peerConnectionClient " )
return
}
2017-01-31 21:28:01 +01:00
self . handleIceConnected ( )
2017-01-12 01:06:39 +01:00
}
/* *
* The connection failed to establish . The clients will not be able to communicate .
*/
2017-02-03 16:25:03 +01:00
internal func peerConnectionClientIceFailed ( _ peerConnectionClient : PeerConnectionClient ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-02-03 16:51:40 +01:00
guard peerConnectionClient = = self . peerConnectionClient else {
2017-02-03 16:25:03 +01:00
Logger . debug ( " \( self . TAG ) \( #function ) Ignoring event from obsolete peerConnectionClient " )
return
}
2017-04-18 18:12:17 +02:00
// R e t u r n t o a k n o w n g o o d s t a t e .
self . handleFailedCurrentCall ( error : CallError . disconnected )
2017-01-12 01:06:39 +01:00
}
/* *
* 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 .
*/
2017-02-03 16:25:03 +01:00
internal func peerConnectionClient ( _ peerConnectionClient : PeerConnectionClient , addedLocalIceCandidate iceCandidate : RTCIceCandidate ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-02-03 16:51:40 +01:00
guard peerConnectionClient = = self . peerConnectionClient else {
2017-02-03 16:25:03 +01:00
Logger . debug ( " \( self . TAG ) \( #function ) Ignoring event from obsolete peerConnectionClient " )
return
}
2017-01-31 21:28:01 +01:00
self . handleLocalAddedIceCandidate ( iceCandidate )
2017-01-12 01:06:39 +01:00
}
2017-01-12 01:28:07 +01:00
/* *
* Once the peerconnection is established , we can receive messages via the data channel , and notify the delegate .
*/
2017-02-03 16:25:03 +01:00
internal func peerConnectionClient ( _ peerConnectionClient : PeerConnectionClient , received dataChannelMessage : OWSWebRTCProtosData ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-02-03 16:51:40 +01:00
guard peerConnectionClient = = self . peerConnectionClient else {
2017-02-03 16:25:03 +01:00
Logger . debug ( " \( self . TAG ) \( #function ) Ignoring event from obsolete peerConnectionClient " )
return
}
2017-01-31 21:28:01 +01:00
self . handleDataChannelMessage ( dataChannelMessage )
2017-01-12 01:28:07 +01:00
}
2017-02-03 16:25:03 +01:00
internal func peerConnectionClient ( _ peerConnectionClient : PeerConnectionClient , didUpdateLocal videoTrack : RTCVideoTrack ? ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-02-03 16:51:40 +01:00
guard peerConnectionClient = = self . peerConnectionClient else {
2017-02-03 16:25:03 +01:00
Logger . debug ( " \( self . TAG ) \( #function ) Ignoring event from obsolete peerConnectionClient " )
return
}
2017-01-31 21:28:01 +01:00
self . localVideoTrack = videoTrack
2017-01-26 16:05:41 +01:00
}
2017-02-03 16:25:03 +01:00
internal func peerConnectionClient ( _ peerConnectionClient : PeerConnectionClient , didUpdateRemote videoTrack : RTCVideoTrack ? ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-02-03 16:51:40 +01:00
guard peerConnectionClient = = self . peerConnectionClient else {
2017-02-03 16:25:03 +01:00
Logger . debug ( " \( self . TAG ) \( #function ) Ignoring event from obsolete peerConnectionClient " )
return
}
2017-01-31 21:28:01 +01:00
self . remoteVideoTrack = videoTrack
2017-01-26 16:05:41 +01:00
}
2016-11-12 18:22:29 +01:00
// MARK: H e l p e r s
2017-01-06 14:40:13 +01:00
/* *
2017-01-12 01:06:39 +01:00
* 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 .
2017-01-06 14:40:13 +01:00
*/
2016-11-12 18:22:29 +01:00
private func getIceServers ( ) -> Promise < [ RTCIceServer ] > {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
return firstly {
return accountManager . getTurnServerInfo ( )
2017-02-03 16:51:40 +01:00
} . then { turnServerInfo -> [ RTCIceServer ] in
2017-01-31 23:21:48 +01:00
Logger . debug ( " \( self . TAG ) got turn server urls: \( turnServerInfo . urls ) " )
return turnServerInfo . urls . map { url in
if url . hasPrefix ( " turn " ) {
// O n l y " t u r n : " s e r v e r s r e q u i r e a u t h e n t i c a t i o n . D o n ' t i n c l u d e t h e c r e d e n t i a l s t o o t h e r I C E s e r v e r s
// a s 1 . ) t h e y a r e n ' t u s e d , a n d 2 . ) t h e n o n - t u r n s e r v e r s m i g h t n o t b e u n d e r o u r c o n t r o l .
// e . g . w e u s e a p u b l i c f a l l b a c k S T U N s e r v e r .
return RTCIceServer ( urlStrings : [ url ] , username : turnServerInfo . username , credential : turnServerInfo . password )
} else {
return RTCIceServer ( urlStrings : [ url ] )
}
} + [ CallService . fallbackIceServer ]
} . recover { error -> [ RTCIceServer ] in
Logger . error ( " \( self . TAG ) fetching ICE servers failed with error: \( error ) " )
Logger . warn ( " \( self . TAG ) using fallback ICE Servers " )
return [ CallService . fallbackIceServer ]
2016-11-12 18:22:29 +01:00
}
}
2017-04-18 18:12:17 +02:00
// T h i s m e t h o d s h o u l d b e c a l l e d w h e n e i t h e r : a ) w e k n o w o r a s s u m e t h a t
// t h e e r r o r i s r e l a t e d t o t h e c u r r e n t c a l l . b ) t h e e r r o r i s s o s e r i o u s
// t h a t w e w a n t t o t e r m i n a t e t h e c u r r e n t c a l l ( i f a n y ) i n o r d e r t o
// r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-18 17:50:55 +02:00
public func handleFailedCurrentCall ( error : CallError ) {
2017-04-18 18:12:17 +02:00
handleFailedCall ( failedCall : self . call , error : error , forceTerminate : true )
2017-04-18 17:50:55 +02:00
}
2017-04-18 18:12:17 +02:00
// T h i s m e t h o d s h o u l d b e c a l l e d w h e n a f a t a l e r r o r o c c u r r e d f o r a c a l l .
//
// * I f w e k n o w w h i c h c a l l i t w a s , w e s h o u l d u p d a t e t h a t c a l l ' s s t a t e
// t o r e f l e c t t h e e r r o r .
// * I F F t h a t c a l l i s t h e c u r r e n t c a l l , w e w a n t t o t e r m i n a t e i t .
public func handleFailedCall ( failedCall : SignalCall ? , error : CallError , forceTerminate : Bool = false ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
Logger . error ( " \( TAG ) call failed with error: \( error ) " )
2017-04-18 17:50:55 +02:00
guard let failedCall = failedCall else {
Logger . debug ( " \( TAG ) in \( #function ) ignoring obsolete call. " )
return
}
2017-02-08 20:27:29 +01:00
2017-04-18 17:50:55 +02:00
// I t ' s e s s e n t i a l t o s e t c a l l . s t a t e b e f o r e t e r m i n a t e C a l l , b e c a u s e t e r m i n a t e C a l l n i l s s e l f . c a l l
failedCall . error = error
failedCall . state = . localFailure
self . callUIAdapter . failCall ( failedCall , error : error )
2017-02-08 20:27:29 +01:00
2017-04-18 18:12:17 +02:00
// O n l y t e r m i n a t e t h e c u r r e n t c a l l i f t h e e r r o r p e r t a i n s t o t h e c u r r e n t c a l l ,
// o r i f w e ' r e t r y i n g t o r e t u r n t o a k n o w n g o o d s t a t e .
let shouldTerminate = forceTerminate || failedCall = = self . call
guard shouldTerminate else {
2017-04-18 17:50:55 +02:00
Logger . debug ( " \( TAG ) in \( #function ) ignoring obsolete call. " )
return
2017-01-26 19:18:06 +01:00
}
2016-11-12 18:22:29 +01:00
2017-04-18 17:50:55 +02:00
// O n l y t e r m i n a t e t h e c a l l i f i t i s t h e c u r r e n t c a l l .
2016-11-12 18:22:29 +01:00
terminateCall ( )
}
2017-01-12 01:06:39 +01:00
/* *
* Clean up any existing call state and get ready to receive a new call .
*/
2016-11-12 18:22:29 +01:00
private func terminateCall ( ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
2017-01-12 01:06:39 +01:00
Logger . debug ( " \( TAG ) in \( #function ) " )
2016-11-12 18:22:29 +01:00
2017-01-26 16:05:41 +01:00
localVideoTrack = nil
remoteVideoTrack = nil
2017-01-26 17:33:42 +01:00
isRemoteVideoEnabled = false
2017-02-03 16:25:03 +01:00
2017-02-03 17:29:40 +01:00
PeerConnectionClient . stopAudioSession ( )
peerConnectionClient ? . terminate ( )
2017-02-03 16:25:03 +01:00
Logger . debug ( " \( TAG ) setting peerConnectionClient in \( #function ) " )
peerConnectionClient = nil
2017-01-19 15:38:50 +01:00
call ? . removeAllObservers ( )
2016-11-12 18:22:29 +01:00
call = nil
thread = nil
incomingCallPromise = nil
sendIceUpdatesImmediately = true
pendingIceUpdateMessages = [ ]
2017-01-26 16:05:41 +01:00
}
// MARK: - C a l l O b s e r v e r
internal func stateDidChange ( call : SignalCall , state : CallState ) {
AssertIsOnMainThread ( )
Logger . info ( " \( self . TAG ) \( #function ) : \( state ) " )
2017-02-07 15:35:37 +01:00
updateIsVideoEnabled ( )
2017-01-26 16:05:41 +01:00
}
2017-01-27 17:11:33 +01:00
internal func hasLocalVideoDidChange ( call : SignalCall , hasLocalVideo : Bool ) {
2017-01-26 16:05:41 +01:00
AssertIsOnMainThread ( )
2017-01-27 17:11:33 +01:00
Logger . info ( " \( self . TAG ) \( #function ) : \( hasLocalVideo ) " )
2017-01-26 16:05:41 +01:00
self . updateIsVideoEnabled ( )
}
internal func muteDidChange ( call : SignalCall , isMuted : Bool ) {
AssertIsOnMainThread ( )
// D o n o t h i n g
}
internal func speakerphoneDidChange ( call : SignalCall , isEnabled : Bool ) {
AssertIsOnMainThread ( )
// D o n o t h i n g
}
// MARK: - V i d e o
private func shouldHaveLocalVideoTrack ( ) -> Bool {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
// T h e i O S s i m u l a t o r d o e s n ' t p r o v i d e a n y s o r t o f c a m e r a c a p t u r e
// s u p p o r t o r e m u l a t i o n ( h t t p : / / g o o . g l / r H A n C 1 ) s o d o n ' t b o t h e r
// t r y i n g t o o p e n a l o c a l s t r e a m .
return ( ! Platform . isSimulator &&
2017-01-30 23:50:28 +01:00
UIApplication . shared . applicationState != . background &&
2017-01-26 16:05:41 +01:00
call != nil &&
call ! . state = = . connected &&
2017-01-27 17:11:33 +01:00
call ! . hasLocalVideo )
2017-01-26 16:05:41 +01:00
}
2017-02-25 02:33:43 +01:00
// T O D O o n l y f i r e t h i s w h e n i t ' s c h a n g e d ? a s o f r i g h t n o w i t g e t s c a l l e d w h e n e v e r y o u e . g . l o c k t h e p h o n e w h i l e i t ' s i n c o m i n g r i n g i n g .
2017-01-26 16:05:41 +01:00
private func updateIsVideoEnabled ( ) {
AssertIsOnMainThread ( )
2017-01-31 21:28:01 +01:00
guard let call = self . call else {
return
}
guard let peerConnectionClient = self . peerConnectionClient else {
return
}
2017-01-26 17:33:42 +01:00
2017-01-31 21:28:01 +01:00
let shouldHaveLocalVideoTrack = self . shouldHaveLocalVideoTrack ( )
2017-01-26 17:33:42 +01:00
2017-01-31 21:28:01 +01:00
Logger . info ( " \( self . TAG ) \( #function ) : \( shouldHaveLocalVideoTrack ) " )
2017-01-26 17:33:42 +01:00
2017-01-31 21:28:01 +01:00
self . peerConnectionClient ? . setLocalVideoEnabled ( enabled : shouldHaveLocalVideoTrack )
2017-01-26 16:05:41 +01:00
2017-01-31 21:28:01 +01:00
let message = DataChannelMessage . forVideoStreamingStatus ( callId : call . signalingId , enabled : shouldHaveLocalVideoTrack )
2017-02-01 16:44:29 +01:00
peerConnectionClient . sendDataChannelMessage ( data : message . asData ( ) )
2017-01-26 16:05:41 +01:00
}
// MARK: - O b s e r v e r s
// T h e o b s e r v e r - r e l a t e d m e t h o d s s h o u l d b e i n v o k e d o n t h e m a i n t h r e a d .
func addObserverAndSyncState ( observer : CallServiceObserver ) {
AssertIsOnMainThread ( )
observers . append ( Weak ( value : observer ) )
// S y n c h r o n i z e o b s e r v e r w i t h c u r r e n t c a l l s t a t e
2017-01-31 21:28:01 +01:00
let localVideoTrack = self . localVideoTrack
let remoteVideoTrack = self . isRemoteVideoEnabled ? self . remoteVideoTrack : nil
observer . didUpdateVideoTracks ( localVideoTrack : localVideoTrack ,
remoteVideoTrack : remoteVideoTrack )
2017-01-26 16:05:41 +01:00
}
// T h e o b s e r v e r - r e l a t e d m e t h o d s s h o u l d b e i n v o k e d o n t h e m a i n t h r e a d .
func removeObserver ( _ observer : CallServiceObserver ) {
AssertIsOnMainThread ( )
while let index = observers . index ( where : { $0 . value = = = observer } ) {
observers . remove ( at : index )
}
}
// T h e o b s e r v e r - r e l a t e d m e t h o d s s h o u l d b e i n v o k e d o n t h e m a i n t h r e a d .
func removeAllObservers ( ) {
AssertIsOnMainThread ( )
observers = [ ]
}
2017-02-07 15:35:37 +01:00
private func fireDidUpdateVideoTracks ( ) {
2017-01-31 21:28:01 +01:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
let localVideoTrack = self . localVideoTrack
2017-01-26 17:33:42 +01:00
let remoteVideoTrack = self . isRemoteVideoEnabled ? self . remoteVideoTrack : nil
2017-01-26 16:05:41 +01:00
2017-01-31 21:28:01 +01:00
for observer in observers {
observer . value ? . didUpdateVideoTracks ( localVideoTrack : localVideoTrack ,
remoteVideoTrack : remoteVideoTrack )
2017-01-30 21:54:31 +01:00
}
2017-02-07 15:35:37 +01:00
}
private func updateLockTimerEnabling ( ) {
AssertIsOnMainThread ( )
2017-01-31 21:28:01 +01:00
2017-02-07 15:35:37 +01:00
// P r e v e n t s c r e e n f r o m d i m m i n g d u r i n g c a l l .
2017-01-31 21:28:01 +01:00
//
2017-02-07 15:35:37 +01:00
// N o t e t h a t t h i s s t a t e h a s n o e f f e c t i f a p p i s i n t h e b a c k g r o u n d .
let hasCall = call != nil
UIApplication . shared . isIdleTimerDisabled = hasCall
2016-11-12 18:22:29 +01:00
}
}
2017-01-06 14:40:44 +01:00
fileprivate extension MessageSender {
/* *
* Wrap message sending in a Promise for easier callback chaining .
*/
fileprivate func sendCallMessage ( _ message : OWSOutgoingCallMessage ) -> Promise < Void > {
return Promise { fulfill , reject in
self . send ( message , success : fulfill , failure : reject )
}
}
}