diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index fef6abe60..ce24cc842 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -168,24 +168,21 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { // MARK: Actions func startSessionCall() { guard case .offer = mode else { return } - var promise: Promise! + var promise: Promise! Storage.write(with: { transaction in promise = self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction) }, completion: { [weak self] in - let _ = promise.done { + let _ = promise.done { timestamp in + self?.callMessageTimestamp = timestamp Storage.shared.write { transaction in - self?.webRTCSession.sendOffer(to: self!.sessionID, using: transaction as! YapDatabaseReadWriteTransaction).done { timestamp in - self?.hasStartedConnecting = true - self?.callMessageTimestamp = timestamp - }.retainUntilComplete() + self?.webRTCSession.sendOffer(to: self!.sessionID, using: transaction as! YapDatabaseReadWriteTransaction).retainUntilComplete() } } }) } - func answerSessionCall(action: CXAnswerCallAction) { + func answerSessionCall() { guard case .answer = mode else { return } - answerCallAction = action hasStartedConnecting = true if let sdp = remoteSDP { webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally @@ -194,8 +191,14 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { } } + func answerSessionCallInBackground(action: CXAnswerCallAction) { + answerCallAction = action + self.answerSessionCall() + } + func endSessionCall() { guard !hasEnded else { return } + webRTCSession.hangUp() Storage.write { transaction in self.webRTCSession.endCall(with: self.sessionID, using: transaction) } @@ -259,6 +262,15 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { isRemoteVideoEnabled = isEnabled } + public func didReceiveHangUpSignal() { + DispatchQueue.main.async { + if let currentBanner = IncomingCallBanner.current { currentBanner.dismiss() } + if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleEndCallMessage() } + if let miniCallView = MiniCallView.current { miniCallView.dismiss() } + AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: .remoteEnded) + } + } + public func dataChannelDidOpen() { // Send initial video status if (isVideoEnabled) { diff --git a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift index 3a9344ed0..ab8954e08 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift @@ -19,7 +19,7 @@ extension SessionCallManager: CXProviderDelegate { guard let call = self.currentCall else { return action.fail() } if CurrentAppContext().isMainAppAndActive { if let _ = CurrentAppContext().frontmostViewController() as? CallVC { - call.answerSessionCall(action: action) + call.answerSessionCall() } else { let userDefaults = UserDefaults.standard if userDefaults[.hasSeenCallIPExposureWarning] { @@ -28,12 +28,14 @@ extension SessionCallManager: CXProviderDelegate { showCallModal() } } + action.fulfill() } else { - call.answerSessionCall(action: action) + call.answerSessionCallInBackground(action: action) } } public func provider(_ provider: CXProvider, perform action: CXEndCallAction) { + print("[CallKit] Perform CXEndCallAction") AssertIsOnMainThread() guard let call = self.currentCall else { return action.fail() } call.endSessionCall() diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 826e37afa..27f665cec 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -60,7 +60,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { private lazy var answerButton: UIButton = { let result = UIButton(type: .custom) - result.isHidden = call.hasConnected + result.isHidden = call.hasStartedConnecting let image = UIImage(named: "AnswerCall")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) @@ -145,9 +145,11 @@ final class CallVC : UIViewController, VideoPreviewDelegate { private lazy var callInfoLabel: UILabel = { let result = UILabel() + result.isHidden = call.hasConnected result.textColor = .white result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) result.textAlignment = .center + if call.hasStartedConnecting { result.text = "Connecting..." } return result }() @@ -299,7 +301,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { callInfoLabel.text = "Connecting..." } - func handleEndCallMessage(_ message: CallMessage) { + func handleEndCallMessage() { print("[Calls] Ending call.") callInfoLabel.isHidden = false callInfoLabel.text = "Call Ended" diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 2669cd533..6a09ed0aa 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -68,7 +68,7 @@ extension AppDelegate { MessageReceiver.handleEndCallMessage = { message in DispatchQueue.main.async { if let currentBanner = IncomingCallBanner.current { currentBanner.dismiss() } - if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleEndCallMessage(message) } + if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleEndCallMessage() } if let miniCallView = MiniCallView.current { miniCallView.dismiss() } AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: .remoteEnded) } diff --git a/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift b/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift index fa1dcd735..1587e081a 100644 --- a/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift +++ b/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift @@ -30,6 +30,9 @@ extension WebRTCSession: RTCDataChannelDelegate { if let isRemoteVideoEnabled = json["video"] as? Bool { delegate?.isRemoteVideoDidChange(isEnabled: isRemoteVideoEnabled) } + if let _ = json["hangup"] { + delegate?.didReceiveHangUpSignal() + } } } } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 79da8db65..a3dbeab6d 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -7,6 +7,7 @@ public protocol WebRTCSessionDelegate : AnyObject { func webRTCIsConnected() func isRemoteVideoDidChange(isEnabled: Bool) func dataChannelDidOpen() + func didReceiveHangUpSignal() } /// See https://webrtc.org/getting-started/overview for more information. @@ -110,17 +111,19 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } // MARK: Signaling - public func sendPreOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { + public func sendPreOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { print("[Calls] Sending pre-offer message.") guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } - let (promise, seal) = Promise.pending() + let (promise, seal) = Promise.pending() DispatchQueue.main.async { let message = CallMessage() message.uuid = self.uuid message.kind = .preOffer + let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) + tsMessage.save(with: transaction) MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 { print("[Calls] Pre-offer message has been sent.") - seal.fulfill(()) + seal.fulfill((tsMessage.timestamp)) }.catch2 { error in seal.reject(error) } @@ -128,10 +131,10 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { return promise } - public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { + public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { print("[Calls] Sending offer message.") guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } - let (promise, seal) = Promise.pending() + let (promise, seal) = Promise.pending() peerConnection.offer(for: mediaConstraints) { [weak self] sdp, error in if let error = error { seal.reject(error) @@ -149,10 +152,8 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { message.uuid = self.uuid message.kind = .offer message.sdps = [ sdp.sdp ] - let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) - tsMessage.save(with: transaction) MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 { - seal.fulfill(tsMessage.timestamp) + seal.fulfill(()) }.catch2 { error in seal.reject(error) } @@ -240,7 +241,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { print("[Calls] Peer connection did add stream.") - configureAudioSession() +// configureAudioSession() } public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) { @@ -258,6 +259,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { print("[Calls] ICE connection state changed to: \(state).") if state == .connected { delegate?.webRTCIsConnected() +// configureAudioSession() } } @@ -312,4 +314,8 @@ extension WebRTCSession { localVideoTrack.isEnabled = true sendJSON(["video": true]) } + + public func hangUp() { + sendJSON(["hangup": true]) + } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index b33962655..f3a7850a7 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -291,20 +291,20 @@ extension MessageReceiver { handlePreOfferCallMessage?(message) case .offer: print("[Calls] Received offer message.") - if getWebRTCSession().uuid != message.uuid! { + if WebRTCSession.current?.uuid != message.uuid! { // TODO: Call in progress, put the new call on hold/reject return } handleOfferCallMessage?(message) case .answer: print("[Calls] Received answer message.") - guard getWebRTCSession().uuid == message.uuid! else { return } + guard WebRTCSession.current?.uuid == message.uuid! else { return } let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) getWebRTCSession().handleRemoteSDP(sdp, from: message.sender!) handleAnswerCallMessage?(message) case .provisionalAnswer: break // TODO: Implement case let .iceCandidates(sdpMLineIndexes, sdpMids): - guard getWebRTCSession().uuid == message.uuid! else { return } + guard WebRTCSession.current?.uuid == message.uuid! else { return } var candidates: [RTCIceCandidate] = [] let sdps = message.sdps! for i in 0..