From 4fc848ebdce7d708a3e692337d7d76cad8dfb227 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO <> Date: Mon, 20 Jan 2025 14:35:51 +1100 Subject: [PATCH] add detailed call connection info --- .../Calls/Call Management/SessionCall.swift | 24 +++++++++++-- Session/Calls/CallVC.swift | 36 +++++++++++++------ .../WebRTCSession+MessageHandling.swift | 1 + Session/Calls/WebRTC/WebRTCSession.swift | 18 ++++++++-- 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 7c71c98da..30d60b92b 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -87,6 +87,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { didSet { stateDidChange?() hasConnectedDidChange?() + updateCallDetailedStatus?("Call Connected") } } @@ -94,6 +95,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { didSet { stateDidChange?() hasEndedDidChange?() + updateCallDetailedStatus?("") } } @@ -113,6 +115,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { var remoteVideoStateDidChange: ((Bool) -> Void)? var hasStartedReconnecting: (() -> Void)? var hasReconnected: (() -> Void)? + var updateCallDetailedStatus: ((String) -> Void)? // MARK: - Derived Properties @@ -249,6 +252,8 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { self.callInteractionId = interaction?.id + self.updateCallDetailedStatus?("Creating Call") + try? webRTCSession .sendPreOffer( db, @@ -259,8 +264,9 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { .retry(5) // Start the timeout timer for the call .handleEvents(receiveOutput: { [weak self] _ in self?.setupTimeoutTimer() }) - .flatMap { _ in - webRTCSession + .flatMap { [weak self] _ in + self?.updateCallDetailedStatus?("Sending Call Offer") + return webRTCSession .sendOffer(to: thread) .retry(5) } @@ -269,6 +275,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { switch result { case .finished: SNLog("[Calls] Offer message sent") + self?.updateCallDetailedStatus?("Sending Connection Candidates") case .failure(let error): SNLog("[Calls] Error initializing call after 5 retries: \(error), ending call...") self?.handleCallInitializationFailed() @@ -284,6 +291,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { if let sdp = remoteSDP { SNLog("[Calls] Got remote sdp already") + self.updateCallDetailedStatus?("Answering Call") webRTCSession.handleRemoteSDP(sdp, from: sessionId) // This sends an answer message internally } } @@ -422,6 +430,18 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { isRemoteVideoEnabled = isEnabled } + public func iceCandidateDidSend() { + DispatchQueue.main.async { + self.updateCallDetailedStatus?("Awaiting Recipient Answer...") + } + } + + public func iceCandidateDidReceive() { + DispatchQueue.main.async { + self.updateCallDetailedStatus?("Handling Connection Candidates") + } + } + public func didReceiveHangUpSignal() { self.hasEnded = true DispatchQueue.main.async { diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 4271ce2eb..4d207b471 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -315,7 +315,6 @@ final class CallVC: UIViewController, VideoPreviewDelegate { result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) result.themeTextColor = .textPrimary result.textAlignment = .center - result.isHidden = call.hasConnected if call.hasStartedConnecting { result.text = "callsConnecting".localized() } @@ -331,6 +330,15 @@ final class CallVC: UIViewController, VideoPreviewDelegate { return result }() + private lazy var callInfoLabelStackView: UIStackView = { + let result: UIStackView = UIStackView(arrangedSubviews: [callInfoLabel, callDetailedInfoLabel]) + result.axis = .vertical + result.spacing = Values.mediumSpacing + result.isHidden = call.hasConnected + + return result + }() + private lazy var callDurationLabel: UILabel = { let result = UILabel() result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) @@ -359,11 +367,11 @@ final class CallVC: UIViewController, VideoPreviewDelegate { remoteVideoView.alpha = isEnabled ? 1 : 0 } - if self.callInfoLabel.alpha < 0.5 { + if self.callInfoLabelStackView.alpha < 0.5 { UIView.animate(withDuration: 0.25) { self.operationPanel.alpha = 1 self.responsePanel.alpha = 1 - self.callInfoLabel.alpha = 1 + self.callInfoLabelStackView.alpha = 1 } } } @@ -396,7 +404,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate { self?.durationTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in self?.updateDuration() } - self?.callInfoLabel.isHidden = true + self?.callInfoLabelStackView.isHidden = true self?.callDurationLabel.isHidden = false } } @@ -411,7 +419,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate { self.call.hasStartedReconnecting = { [weak self] in DispatchQueue.main.async { - self?.callInfoLabel.isHidden = false + self?.callInfoLabelStackView.isHidden = false self?.callDurationLabel.isHidden = true self?.callInfoLabel.text = "callsReconnecting".localized() } @@ -419,10 +427,16 @@ final class CallVC: UIViewController, VideoPreviewDelegate { self.call.hasReconnected = { [weak self] in DispatchQueue.main.async { - self?.callInfoLabel.isHidden = true + self?.callInfoLabelStackView.isHidden = true self?.callDurationLabel.isHidden = false } } + + self.call.updateCallDetailedStatus = { [weak self] status in + DispatchQueue.main.async { + self?.callDetailedInfoLabel.text = status + } + } } required init(coder: NSCoder) { preconditionFailure("Use init(for:) instead.") } @@ -519,10 +533,10 @@ final class CallVC: UIViewController, VideoPreviewDelegate { callInfoLabelContainer.pin(.top, to: .bottom, of: profilePictureView) callInfoLabelContainer.pin(.bottom, to: .bottom, of: profilePictureContainer) callInfoLabelContainer.pin([ UIView.HorizontalEdge.left, UIView.HorizontalEdge.right ], to: view) - callInfoLabelContainer.addSubview(callInfoLabel) + callInfoLabelContainer.addSubview(callInfoLabelStackView) callInfoLabelContainer.addSubview(callDurationLabel) - callInfoLabel.translatesAutoresizingMaskIntoConstraints = false - callInfoLabel.center(in: callInfoLabelContainer) + callInfoLabelStackView.translatesAutoresizingMaskIntoConstraints = false + callInfoLabelStackView.center(in: callInfoLabelContainer) callDurationLabel.translatesAutoresizingMaskIntoConstraints = false callDurationLabel.center(in: callInfoLabelContainer) } @@ -596,7 +610,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate { func handleEndCallMessage() { SNLog("[Calls] Ending call.") - self.callInfoLabel.isHidden = false + self.callInfoLabelStackView.isHidden = false self.callDurationLabel.isHidden = true self.callInfoLabel.text = "callsEnded".localized() @@ -605,7 +619,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate { remoteVideoView.alpha = 0 self.operationPanel.alpha = 1 self.responsePanel.alpha = 1 - self.callInfoLabel.alpha = 1 + self.callInfoLabelStackView.alpha = 1 } Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { [weak self] _ in diff --git a/Session/Calls/WebRTC/WebRTCSession+MessageHandling.swift b/Session/Calls/WebRTC/WebRTCSession+MessageHandling.swift index 5bac29a03..551c787e2 100644 --- a/Session/Calls/WebRTC/WebRTCSession+MessageHandling.swift +++ b/Session/Calls/WebRTC/WebRTCSession+MessageHandling.swift @@ -9,6 +9,7 @@ extension WebRTCSession { public func handleICECandidates(_ candidate: [RTCIceCandidate]) { SNLog("[Calls] Received ICE candidate message.") + self.delegate?.iceCandidateDidReceive() candidate.forEach { peerConnection?.add($0, completionHandler: { _ in }) } } diff --git a/Session/Calls/WebRTC/WebRTCSession.swift b/Session/Calls/WebRTC/WebRTCSession.swift index b766b862c..57d9ed29b 100644 --- a/Session/Calls/WebRTC/WebRTCSession.swift +++ b/Session/Calls/WebRTC/WebRTCSession.swift @@ -12,6 +12,8 @@ public protocol WebRTCSessionDelegate: AnyObject { func webRTCIsConnected() func isRemoteVideoDidChange(isEnabled: Bool) + func iceCandidateDidSend() + func iceCandidateDidReceive() func dataChannelDidOpen() func didReceiveHangUpSignal() func reconnectIfNeeded() @@ -339,9 +341,21 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .flatMap { [dependencies = self.dependencies] sendData in - MessageSender.sendImmediate(data: sendData, using: dependencies) + MessageSender + .sendImmediate(data: sendData, using: dependencies) + .retry(5) } - .sinkUntilComplete() + .sinkUntilComplete( + receiveCompletion: { [weak self] result in + switch result { + case .finished: + SNLog("[Calls] ICE candidates sent") + self?.delegate?.iceCandidateDidSend() + case .failure(let error): + SNLog("[Calls] Error sending ICE candidates due to error: \(error)") + } + } + ) } public func endCall(