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)
}
// FIXME TODO do we need to timeout?
fileprivate let timeoutSeconds = 60
// Should be roughly synced with Android client for consistency
fileprivate let connectingTimeoutSeconds = 120
// All Observer methods will be invoked from the main thread.
protocol CallServiceObserver: class {
@ -298,7 +298,7 @@ protocol CallServiceObserver: class {
return getIceServers().then { iceServers -> Promise<HardenedRTCSessionDescription> in
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")
Logger.debug("\(self.TAG) setting peerConnectionClient in \(#function)")
@ -311,6 +311,17 @@ protocol CallServiceObserver: class {
let callMessage = OWSOutgoingCallMessage(thread: thread, offerMessage: offerMessage)
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
Logger.error("\(self.TAG) placing call failed with error: \(error)")
@ -456,7 +467,7 @@ protocol CallServiceObserver: class {
}
assert(self.peerConnectionClient == nil, "Unexpected PeerConnectionClient instance")
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 constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
@ -481,7 +492,7 @@ protocol CallServiceObserver: class {
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
throw CallError.timeout(description: "timed out waiting for call to connect")
}

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

@ -17,6 +17,10 @@ enum CallState: String {
case remoteBusy // terminal
}
enum CallDirection {
case outgoing, incoming
}
// All Observer methods will be invoked from the main thread.
protocol CallObserver: class {
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.
let signalingId: UInt64
let direction: CallDirection
// Distinguishes between calls locally, e.g. in CallKit
let localId: UUID
@ -105,7 +111,8 @@ protocol CallObserver: class {
// 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.signalingId = signalingId
self.state = state
@ -113,11 +120,11 @@ protocol CallObserver: class {
}
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 {
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)
class CallViewController: UIViewController, CallObserver, CallServiceObserver, RTCEAGLVideoViewDelegate {
enum CallDirection {
case unspecified, outgoing, incoming
}
let TAG = "[CallViewController]"
// Dependencies
@ -24,7 +20,6 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
// MARK: Properties
var callDirection: CallDirection = .unspecified
var thread: TSContactThread!
var call: SignalCall!
@ -139,17 +134,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
contactNameLabel.text = contactsManager.displayName(forPhoneIdentifier: thread.contactIdentifier())
contactAvatarView.image = OWSAvatarBuilder.buildImage(for: thread, contactsManager: contactsManager)
switch callDirection {
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.
}
assert(call != nil)
// Subscribe for future call updates
call.addObserverAndSyncState(observer: self)
@ -496,16 +481,6 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
// 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) {
// TODO Show something in UI.
Logger.error("\(TAG) call failed with error: \(error)")
@ -553,6 +528,17 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
case .remoteBusy:
return NSLocalizedString("END_CALL_RESPONDER_IS_BUSY", comment: "Call setup status label")
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")
}
}

@ -72,7 +72,6 @@
static NSTimeInterval const kTSMessageSentDateShowTimeInterval = 5 * 60;
static NSString *const OWSMessagesViewControllerSegueInitiateCall = @"initiateCallSegue";
static NSString *const OWSMessagesViewControllerSegueShowFingerprint = @"fingerprintSegue";
static NSString *const OWSMessagesViewControllerSeguePushConversationSettings =
@"OWSMessagesViewControllerSeguePushConversationSettings";
@ -1600,20 +1599,8 @@ typedef enum : NSUInteger {
OWSConversationSettingsTableViewController *controller
= (OWSConversationSettingsTableViewController *)segue.destinationViewController;
[controller configureWithThread:self.thread];
} else if ([segue.identifier isEqualToString:OWSMessagesViewControllerSegueInitiateCall]) {
if (![segue.destinationViewController isKindOfClass:[OWSCallViewController class]]) {
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];
} else {
DDLogDebug(@"%@ Received segue: %@", self.tag, segue.identifier);
}
}

@ -519,7 +519,6 @@ NSString *const SignalsViewControllerSegueShowIncomingCall = @"ShowIncomingCallS
return;
}
OWSCallViewController *callViewController = (OWSCallViewController *)segue.destinationViewController;
[callViewController setIncomingCallDirection];
if (![sender isKindOfClass:[SignalCall class]]) {
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 */
"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' */
"CALL_STATUS_FORMAT" = "Signal %@";

Loading…
Cancel
Save