Merge branch 'mkirk/timeout-outgoing-call'

pull/1/head
Michael Kirk 8 years ago
commit 34e4650c46

@ -77,8 +77,8 @@ enum CallError: Error {
case timeout(description: String) case timeout(description: String)
} }
// FIXME TODO do we need to timeout? // Should be roughly synced with Android client for consistency
fileprivate let timeoutSeconds = 60 fileprivate let connectingTimeoutSeconds = 120
// All Observer methods will be invoked from the main thread. // All Observer methods will be invoked from the main thread.
protocol CallServiceObserver: class { protocol CallServiceObserver: class {
@ -298,7 +298,7 @@ protocol CallServiceObserver: class {
return getIceServers().then { iceServers -> Promise<HardenedRTCSessionDescription> in return getIceServers().then { iceServers -> Promise<HardenedRTCSessionDescription> in
Logger.debug("\(self.TAG) got ice servers:\(iceServers)") Logger.debug("\(self.TAG) got ice servers:\(iceServers)")
let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callType: .outgoing) let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .outgoing)
assert(self.peerConnectionClient == nil, "Unexpected PeerConnectionClient instance") assert(self.peerConnectionClient == nil, "Unexpected PeerConnectionClient instance")
Logger.debug("\(self.TAG) setting peerConnectionClient in \(#function)") Logger.debug("\(self.TAG) setting peerConnectionClient in \(#function)")
@ -311,6 +311,17 @@ protocol CallServiceObserver: class {
let callMessage = OWSOutgoingCallMessage(thread: thread, offerMessage: offerMessage) let callMessage = OWSOutgoingCallMessage(thread: thread, offerMessage: offerMessage)
return self.messageSender.sendCallMessage(callMessage) return self.messageSender.sendCallMessage(callMessage)
} }
}.then {
let (callConnectedPromise, fulfill, _) = Promise<Void>.pending()
self.fulfillCallConnectedPromise = fulfill
// Don't let the outgoing call ring forever. We don't support inbound ringing forever anyway.
let timeout: Promise<Void> = after(interval: TimeInterval(connectingTimeoutSeconds)).then { () -> Void in
// rejecting a promise by throwing is safely a no-op if the promise has already been fulfilled
throw CallError.timeout(description: "timed out waiting to receive call answer")
}
return race(timeout, callConnectedPromise)
}.catch { error in }.catch { error in
Logger.error("\(self.TAG) placing call failed with error: \(error)") Logger.error("\(self.TAG) placing call failed with error: \(error)")
@ -456,7 +467,7 @@ protocol CallServiceObserver: class {
} }
assert(self.peerConnectionClient == nil, "Unexpected PeerConnectionClient instance") assert(self.peerConnectionClient == nil, "Unexpected PeerConnectionClient instance")
Logger.debug("\(self.self.TAG) setting peerConnectionClient in \(#function)") Logger.debug("\(self.self.TAG) setting peerConnectionClient in \(#function)")
self.peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callType: .incoming) self.peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .incoming)
let offerSessionDescription = RTCSessionDescription(type: .offer, sdp: callerSessionDescription) let offerSessionDescription = RTCSessionDescription(type: .offer, sdp: callerSessionDescription)
let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
@ -481,7 +492,7 @@ protocol CallServiceObserver: class {
let (promise, fulfill, _) = Promise<Void>.pending() let (promise, fulfill, _) = Promise<Void>.pending()
let timeout: Promise<Void> = after(interval: TimeInterval(timeoutSeconds)).then { () -> Void in let timeout: Promise<Void> = after(interval: TimeInterval(connectingTimeoutSeconds)).then { () -> Void in
// rejecting a promise by throwing is safely a no-op if the promise has already been fulfilled // rejecting a promise by throwing is safely a no-op if the promise has already been fulfilled
throw CallError.timeout(description: "timed out waiting for call to connect") throw CallError.timeout(description: "timed out waiting for call to connect")
} }

@ -64,11 +64,6 @@ protocol PeerConnectionClientDelegate: class {
*/ */
class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelDelegate { class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelDelegate {
enum CallType {
case incoming
case outgoing
}
let TAG = "[PeerConnectionClient]" let TAG = "[PeerConnectionClient]"
enum Identifiers: String { enum Identifiers: String {
case mediaStream = "ARDAMS", case mediaStream = "ARDAMS",
@ -125,7 +120,7 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
private var remoteVideoTrack: RTCVideoTrack? private var remoteVideoTrack: RTCVideoTrack?
private var cameraConstraints: RTCMediaConstraints private var cameraConstraints: RTCMediaConstraints
init(iceServers: [RTCIceServer], delegate: PeerConnectionClientDelegate, callType: CallType) { init(iceServers: [RTCIceServer], delegate: PeerConnectionClientDelegate, callDirection: CallDirection) {
AssertIsOnMainThread() AssertIsOnMainThread()
self.iceServers = iceServers self.iceServers = iceServers
@ -152,7 +147,7 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD
createAudioSender() createAudioSender()
createVideoSender() createVideoSender()
if callType == .outgoing { if callDirection == .outgoing {
// When placing an outgoing call, it's our responsibility to create the DataChannel. // When placing an outgoing call, it's our responsibility to create the DataChannel.
// Recipient will not have to do this explicitly. // Recipient will not have to do this explicitly.
createSignalingDataChannel() createSignalingDataChannel()

@ -17,6 +17,10 @@ enum CallState: String {
case remoteBusy // terminal case remoteBusy // terminal
} }
enum CallDirection {
case outgoing, incoming
}
// All Observer methods will be invoked from the main thread. // All Observer methods will be invoked from the main thread.
protocol CallObserver: class { protocol CallObserver: class {
func stateDidChange(call: SignalCall, state: CallState) func stateDidChange(call: SignalCall, state: CallState)
@ -40,6 +44,8 @@ protocol CallObserver: class {
// Signal Service identifier for this Call. Used to coordinate the call across remote clients. // Signal Service identifier for this Call. Used to coordinate the call across remote clients.
let signalingId: UInt64 let signalingId: UInt64
let direction: CallDirection
// Distinguishes between calls locally, e.g. in CallKit // Distinguishes between calls locally, e.g. in CallKit
let localId: UUID let localId: UUID
@ -105,7 +111,8 @@ protocol CallObserver: class {
// MARK: Initializers and Factory Methods // MARK: Initializers and Factory Methods
init(localId: UUID, signalingId: UInt64, state: CallState, remotePhoneNumber: String) { init(direction: CallDirection, localId: UUID, signalingId: UInt64, state: CallState, remotePhoneNumber: String) {
self.direction = direction
self.localId = localId self.localId = localId
self.signalingId = signalingId self.signalingId = signalingId
self.state = state self.state = state
@ -113,11 +120,11 @@ protocol CallObserver: class {
} }
class func outgoingCall(localId: UUID, remotePhoneNumber: String) -> SignalCall { class func outgoingCall(localId: UUID, remotePhoneNumber: String) -> SignalCall {
return SignalCall(localId: localId, signalingId: newCallSignalingId(), state: .dialing, remotePhoneNumber: remotePhoneNumber) return SignalCall(direction: .outgoing, localId: localId, signalingId: newCallSignalingId(), state: .dialing, remotePhoneNumber: remotePhoneNumber)
} }
class func incomingCall(localId: UUID, remotePhoneNumber: String, signalingId: UInt64) -> SignalCall { class func incomingCall(localId: UUID, remotePhoneNumber: String, signalingId: UInt64) -> SignalCall {
return SignalCall(localId: localId, signalingId: signalingId, state: .answering, remotePhoneNumber: remotePhoneNumber) return SignalCall(direction: .incoming, localId: localId, signalingId: signalingId, state: .answering, remotePhoneNumber: remotePhoneNumber)
} }
// - // -

@ -11,10 +11,6 @@ import PromiseKit
@objc(OWSCallViewController) @objc(OWSCallViewController)
class CallViewController: UIViewController, CallObserver, CallServiceObserver, RTCEAGLVideoViewDelegate { class CallViewController: UIViewController, CallObserver, CallServiceObserver, RTCEAGLVideoViewDelegate {
enum CallDirection {
case unspecified, outgoing, incoming
}
let TAG = "[CallViewController]" let TAG = "[CallViewController]"
// Dependencies // Dependencies
@ -24,7 +20,6 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
// MARK: Properties // MARK: Properties
var callDirection: CallDirection = .unspecified
var thread: TSContactThread! var thread: TSContactThread!
var call: SignalCall! var call: SignalCall!
@ -139,17 +134,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
contactNameLabel.text = contactsManager.displayName(forPhoneIdentifier: thread.contactIdentifier()) contactNameLabel.text = contactsManager.displayName(forPhoneIdentifier: thread.contactIdentifier())
contactAvatarView.image = OWSAvatarBuilder.buildImage(for: thread, contactsManager: contactsManager) contactAvatarView.image = OWSAvatarBuilder.buildImage(for: thread, contactsManager: contactsManager)
switch callDirection { assert(call != nil)
case .unspecified:
Logger.error("\(TAG) must set call direction before call starts.")
showCallFailed(error: OWSErrorMakeAssertionError())
case .outgoing:
self.call = self.callUIAdapter.startOutgoingCall(handle: thread.contactIdentifier())
case .incoming:
Logger.error("\(TAG) handling Incoming call")
// No-op, since call service is already set up at this point, the result of which was presenting this viewController.
}
// Subscribe for future call updates // Subscribe for future call updates
call.addObserverAndSyncState(observer: self) call.addObserverAndSyncState(observer: self)
@ -496,16 +481,6 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
// MARK: - Methods // MARK: - Methods
// objc accessible way to set our swift enum.
func setOutgoingCallDirection() {
callDirection = .outgoing
}
// objc accessible way to set our swift enum.
func setIncomingCallDirection() {
callDirection = .incoming
}
func showCallFailed(error: Error) { func showCallFailed(error: Error) {
// TODO Show something in UI. // TODO Show something in UI.
Logger.error("\(TAG) call failed with error: \(error)") Logger.error("\(TAG) call failed with error: \(error)")
@ -553,6 +528,17 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
case .remoteBusy: case .remoteBusy:
return NSLocalizedString("END_CALL_RESPONDER_IS_BUSY", comment: "Call setup status label") return NSLocalizedString("END_CALL_RESPONDER_IS_BUSY", comment: "Call setup status label")
case .localFailure: case .localFailure:
if let error = call.error {
switch (error) {
case .timeout(description: _):
if self.call.direction == .outgoing {
return NSLocalizedString("CALL_SCREEN_STATUS_NO_ANSWER", comment: "Call setup status label after outgoing call times out")
}
default:
break
}
}
return NSLocalizedString("END_CALL_UNCATEGORIZED_FAILURE", comment: "Call setup status label") return NSLocalizedString("END_CALL_UNCATEGORIZED_FAILURE", comment: "Call setup status label")
} }
} }

@ -72,7 +72,6 @@
static NSTimeInterval const kTSMessageSentDateShowTimeInterval = 5 * 60; static NSTimeInterval const kTSMessageSentDateShowTimeInterval = 5 * 60;
static NSString *const OWSMessagesViewControllerSegueInitiateCall = @"initiateCallSegue";
static NSString *const OWSMessagesViewControllerSegueShowFingerprint = @"fingerprintSegue"; static NSString *const OWSMessagesViewControllerSegueShowFingerprint = @"fingerprintSegue";
static NSString *const OWSMessagesViewControllerSeguePushConversationSettings = static NSString *const OWSMessagesViewControllerSeguePushConversationSettings =
@"OWSMessagesViewControllerSeguePushConversationSettings"; @"OWSMessagesViewControllerSeguePushConversationSettings";
@ -1600,20 +1599,8 @@ typedef enum : NSUInteger {
OWSConversationSettingsTableViewController *controller OWSConversationSettingsTableViewController *controller
= (OWSConversationSettingsTableViewController *)segue.destinationViewController; = (OWSConversationSettingsTableViewController *)segue.destinationViewController;
[controller configureWithThread:self.thread]; [controller configureWithThread:self.thread];
} else if ([segue.identifier isEqualToString:OWSMessagesViewControllerSegueInitiateCall]) { } else {
if (![segue.destinationViewController isKindOfClass:[OWSCallViewController class]]) { DDLogDebug(@"%@ Received segue: %@", self.tag, segue.identifier);
DDLogError(@"%@ Expected CallViewController but got: %@", self.tag, segue.destinationViewController);
return;
}
OWSCallViewController *callViewController = (OWSCallViewController *)segue.destinationViewController;
if (![self.thread isKindOfClass:[TSContactThread class]]) {
DDLogError(@"%@ Unexpectedly trying to call in group thread:%@. This isn't supported.", self.thread, self.tag);
return;
}
callViewController.thread = (TSContactThread *)self.thread;
[callViewController setOutgoingCallDirection];
} }
} }

@ -519,7 +519,6 @@ NSString *const SignalsViewControllerSegueShowIncomingCall = @"ShowIncomingCallS
return; return;
} }
OWSCallViewController *callViewController = (OWSCallViewController *)segue.destinationViewController; OWSCallViewController *callViewController = (OWSCallViewController *)segue.destinationViewController;
[callViewController setIncomingCallDirection];
if (![sender isKindOfClass:[SignalCall class]]) { if (![sender isKindOfClass:[SignalCall class]]) {
DDLogError(@"%@ expecting call segueu to be sent by a SignalCall, but found: %@", self.tag, sender); DDLogError(@"%@ expecting call segueu to be sent by a SignalCall, but found: %@", self.tag, sender);

@ -76,6 +76,9 @@
/* Accessibilty label for placing call button */ /* Accessibilty label for placing call button */
"CALL_LABEL" = "Call"; "CALL_LABEL" = "Call";
/* Call setup status label after outgoing call times out */
"CALL_SCREEN_STATUS_NO_ANSWER" = "No Answer.";
/* embeds {{Call Status}} in call screen label. For ongoing calls, {{Call Status}} is a seconds timer like 01:23, otherwise {{Call Status}} is a short text like 'Ringing', 'Busy', or 'Failed Call' */ /* embeds {{Call Status}} in call screen label. For ongoing calls, {{Call Status}} is a seconds timer like 01:23, otherwise {{Call Status}} is a short text like 'Ringing', 'Busy', or 'Failed Call' */
"CALL_STATUS_FORMAT" = "Signal %@"; "CALL_STATUS_FORMAT" = "Signal %@";

Loading…
Cancel
Save