diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index c866a785e..03b59d124 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -159,7 +159,6 @@ B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */; }; B806ECA126C4A7E4008BDA44 /* WebRTCWrapper+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA026C4A7E4008BDA44 /* WebRTCWrapper+UI.swift */; }; B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */; }; - B80F469A26C63DD000DCE243 /* RoomInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80F469926C63DD000DCE243 /* RoomInfo.swift */; }; B817AD9A26436593009DF825 /* SimplifiedConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */; }; B817AD9C26436F73009DF825 /* ThreadPickerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */; }; B81D25C426157F40004D1FE1 /* storage-seed-3.crt in Resources */ = {isa = PBXBuildFile; fileRef = B81D25B926157F20004D1FE1 /* storage-seed-3.crt */; }; @@ -202,9 +201,7 @@ B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; }; B875885A264503A6000E60D0 /* JoinOpenGroupModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8758859264503A6000E60D0 /* JoinOpenGroupModal.swift */; }; B877E24226CA12910007970A /* CallVCV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B877E24126CA12910007970A /* CallVCV2.swift */; }; - B877E24426CA12F00007970A /* CallVCV2+MessageSending.swift in Sources */ = {isa = PBXBuildFile; fileRef = B877E24326CA12F00007970A /* CallVCV2+MessageSending.swift */; }; B877E24626CA13BA0007970A /* CallVCV2+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = B877E24526CA13BA0007970A /* CallVCV2+Camera.swift */; }; - B877E24826CA15170007970A /* CallVCV2+WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = B877E24726CA15170007970A /* CallVCV2+WebSocket.swift */; }; B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */; }; B879D449247E1BE300DB3608 /* PathVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B879D448247E1BE300DB3608 /* PathVC.swift */; }; B87EF17126367CF800124B3C /* FileServerAPIV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B87EF17026367CF800124B3C /* FileServerAPIV2.swift */; }; @@ -252,11 +249,7 @@ B8B3204E258C15C80020074B /* ContactsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B32044258C117C0020074B /* ContactsMigration.swift */; }; B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B320B6258C30D70020074B /* HTMLMetadata.swift */; }; B8B558F126C4BB0600693325 /* CameraManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F026C4BB0600693325 /* CameraManager.swift */; }; - B8B558F326C4CA4600693325 /* TestCallConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F226C4CA4600693325 /* TestCallConfig.swift */; }; - B8B558FB26C4D25C00693325 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FA26C4D25C00693325 /* WebSocket.swift */; }; - B8B558FD26C4D35400693325 /* TestCallServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FC26C4D35400693325 /* TestCallServer.swift */; }; B8B558FF26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FE26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift */; }; - B8B5590126C4E2A400693325 /* SignalingMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B5590026C4E2A400693325 /* SignalingMessage.swift */; }; B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A4238F627000BA5194 /* HomeVC.swift */; }; B8BC00C0257D90E30032E807 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BC00BF257D90E30032E807 /* General.swift */; }; B8C2B2C82563685C00551B4D /* CircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C2B2C72563685C00551B4D /* CircleView.swift */; }; @@ -1156,7 +1149,6 @@ B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCell.swift; sourceTree = ""; }; B806ECA026C4A7E4008BDA44 /* WebRTCWrapper+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCWrapper+UI.swift"; sourceTree = ""; }; B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = ""; }; - B80F469926C63DD000DCE243 /* RoomInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomInfo.swift; sourceTree = ""; }; B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedConversationCell.swift; sourceTree = ""; }; B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerVC.swift; sourceTree = ""; }; B81D25B726157F20004D1FE1 /* storage-seed-1.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "storage-seed-1.crt"; sourceTree = ""; }; @@ -1203,9 +1195,7 @@ B87588582644CA9D000E60D0 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; B8758859264503A6000E60D0 /* JoinOpenGroupModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinOpenGroupModal.swift; sourceTree = ""; }; B877E24126CA12910007970A /* CallVCV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVCV2.swift; sourceTree = ""; }; - B877E24326CA12F00007970A /* CallVCV2+MessageSending.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVCV2+MessageSending.swift"; sourceTree = ""; }; B877E24526CA13BA0007970A /* CallVCV2+Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVCV2+Camera.swift"; sourceTree = ""; }; - B877E24726CA15170007970A /* CallVCV2+WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVCV2+WebSocket.swift"; sourceTree = ""; }; B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Interaction.swift"; sourceTree = ""; }; B879D448247E1BE300DB3608 /* PathVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathVC.swift; sourceTree = ""; }; B879D44A247E1D9200DB3608 /* PathStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathStatusView.swift; sourceTree = ""; }; @@ -1230,11 +1220,7 @@ B8B32044258C117C0020074B /* ContactsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsMigration.swift; sourceTree = ""; }; B8B320B6258C30D70020074B /* HTMLMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadata.swift; sourceTree = ""; }; B8B558F026C4BB0600693325 /* CameraManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = ""; }; - B8B558F226C4CA4600693325 /* TestCallConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCallConfig.swift; sourceTree = ""; }; - B8B558FA26C4D25C00693325 /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = ""; }; - B8B558FC26C4D35400693325 /* TestCallServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCallServer.swift; sourceTree = ""; }; B8B558FE26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCWrapper+MessageHandling.swift"; sourceTree = ""; }; - B8B5590026C4E2A400693325 /* SignalingMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalingMessage.swift; sourceTree = ""; }; B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B8BAC75B2695645400EA1759 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; B8BAC75C2695648500EA1759 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; @@ -2194,18 +2180,6 @@ path = "Message Cells"; sourceTree = ""; }; - B877E24026CA11170007970A /* Temp */ = { - isa = PBXGroup; - children = ( - B8B5590026C4E2A400693325 /* SignalingMessage.swift */, - B8B558FA26C4D25C00693325 /* WebSocket.swift */, - B8B558F226C4CA4600693325 /* TestCallConfig.swift */, - B8B558FC26C4D35400693325 /* TestCallServer.swift */, - B80F469926C63DD000DCE243 /* RoomInfo.swift */, - ); - path = Temp; - sourceTree = ""; - }; B887C38125C7C79700E11DAE /* Input View */ = { isa = PBXGroup; children = ( @@ -2348,9 +2322,7 @@ isa = PBXGroup; children = ( B877E24126CA12910007970A /* CallVCV2.swift */, - B877E24326CA12F00007970A /* CallVCV2+MessageSending.swift */, B877E24526CA13BA0007970A /* CallVCV2+Camera.swift */, - B877E24726CA15170007970A /* CallVCV2+WebSocket.swift */, B8B558F026C4BB0600693325 /* CameraManager.swift */, ); path = Calls; @@ -2385,7 +2357,6 @@ B8DE1FB226C22F1F0079C9CE /* Calls */ = { isa = PBXGroup; children = ( - B877E24026CA11170007970A /* Temp */, B8DE1FB326C22F2F0079C9CE /* WebRTCWrapper.swift */, B806ECA026C4A7E4008BDA44 /* WebRTCWrapper+UI.swift */, B8B558FE26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift */, @@ -4753,7 +4724,6 @@ C32C5B3F256DC1DF003C73A2 /* TSQuotedMessage+Conversion.swift in Sources */, B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, C3C2A7712553A41E00C340D1 /* ControlMessage.swift in Sources */, - B80F469A26C63DD000DCE243 /* RoomInfo.swift in Sources */, C32C5D19256DD493003C73A2 /* OWSLinkPreview.swift in Sources */, C32C5CF0256DD3E4003C73A2 /* Storage+Shared.swift in Sources */, C300A5BD2554B00D00555489 /* ReadReceipt.swift in Sources */, @@ -4766,7 +4736,6 @@ C3DA9C0725AE7396008F7C7E /* ConfigurationMessage.swift in Sources */, B8856CEE256F1054001CE70E /* OWSAudioPlayer.m in Sources */, C32C5EDC256DF501003C73A2 /* YapDatabaseConnection+OWS.m in Sources */, - B8B558FD26C4D35400693325 /* TestCallServer.swift in Sources */, C3BBE0762554CDA60050F1E3 /* Configuration.swift in Sources */, C35D76DB26606304009AA5FB /* ThreadUpdateBatcher.swift in Sources */, B8B32033258B235D0020074B /* Storage+Contacts.swift in Sources */, @@ -4776,7 +4745,6 @@ C32C5AAB256DBE8F003C73A2 /* TSIncomingMessage+Conversion.swift in Sources */, B866CE112581C1A900535CC4 /* Sodium+Conversion.swift in Sources */, C32C5A88256DBCF9003C73A2 /* MessageReceiver+Handling.swift in Sources */, - B8B558F326C4CA4600693325 /* TestCallConfig.swift in Sources */, C32C5C1B256DC9E0003C73A2 /* General.swift in Sources */, C32C5A02256DB658003C73A2 /* MessageSender+Convenience.swift in Sources */, B8566C6C256F60F50045A0B9 /* OWSUserProfile.m in Sources */, @@ -4786,7 +4754,6 @@ C32C5BEF256DC8EE003C73A2 /* OWSDisappearingMessagesJob.m in Sources */, C34A977425A3E34A00852C71 /* ClosedGroupControlMessage.swift in Sources */, B88FA7B826045D100049422F /* OpenGroupAPIV2.swift in Sources */, - B8B558FB26C4D25C00693325 /* WebSocket.swift in Sources */, C32C5E97256DE0CB003C73A2 /* OWSPrimaryStorage.m in Sources */, C32C5EB9256DE130003C73A2 /* OWSQuotedReplyModel+Conversion.swift in Sources */, C3A71D1F25589AC30043A11F /* WebSocketResources.pb.swift in Sources */, @@ -4824,7 +4791,6 @@ C32C5D23256DD4C0003C73A2 /* Mention.swift in Sources */, C32C5FD6256E0346003C73A2 /* Notification+Thread.swift in Sources */, C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */, - B8B5590126C4E2A400693325 /* SignalingMessage.swift in Sources */, B806ECA126C4A7E4008BDA44 /* WebRTCWrapper+UI.swift in Sources */, C32C5C88256DD0D2003C73A2 /* Storage+Messaging.swift in Sources */, C32C59C7256DB41F003C73A2 /* TSThread.m in Sources */, @@ -4958,7 +4924,6 @@ B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */, 45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */, 34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */, - B877E24826CA15170007970A /* CallVCV2+WebSocket.swift in Sources */, 4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */, C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */, 340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */, @@ -4968,7 +4933,6 @@ C328250F25CA06020062D0A7 /* VoiceMessageView.swift in Sources */, B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */, 3488F9362191CC4000E524CC /* MediaView.swift in Sources */, - B877E24426CA12F00007970A /* CallVCV2+MessageSending.swift in Sources */, B8569AC325CB5D2900DBA3DB /* ConversationVC+Interaction.swift in Sources */, 3496955C219B605E00DCFE74 /* ImagePickerController.swift in Sources */, C31D1DE32521718E005D4DA8 /* UserSelectionVC.swift in Sources */, diff --git a/Session/Calls/CallVCV2+Camera.swift b/Session/Calls/CallVCV2+Camera.swift index 030b8bc47..89bf426fe 100644 --- a/Session/Calls/CallVCV2+Camera.swift +++ b/Session/Calls/CallVCV2+Camera.swift @@ -9,6 +9,6 @@ extension CallVCV2 : CameraManagerDelegate { let timestampNs = Int64(timestamp * 1000000000) let frame = RTCVideoFrame(buffer: rtcPixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: timestampNs) frame.timeStamp = Int32(timestamp) - callManager.handleLocalFrameCaptured(frame) + webRTCWrapper.handleLocalFrameCaptured(frame) } } diff --git a/Session/Calls/CallVCV2+MessageSending.swift b/Session/Calls/CallVCV2+MessageSending.swift deleted file mode 100644 index c472cce3d..000000000 --- a/Session/Calls/CallVCV2+MessageSending.swift +++ /dev/null @@ -1,31 +0,0 @@ -import WebRTC - -extension CallVCV2 : WebRTCWrapperDelegate { - - /// Invoked by `CallManager` upon initiating or accepting a call. This method sends the SDP to the other - /// party before streaming starts. - func sendSDP(_ sdp: RTCSessionDescription) { - guard let room = room else { return } - let json = [ - "type" : RTCSessionDescription.string(for: sdp.type), - "sdp" : sdp.sdp - ] - guard let data = try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) else { return } - print("[Calls] Sending SDP to test call server: \(json).") - TestCallServer.send(data, roomID: room.roomID, userID: room.clientID).retainUntilComplete() - } - - /// Invoked when the peer connection has generated an ICE candidate. - func sendICECandidate(_ candidate: RTCIceCandidate) { - guard let room = room else { return } - let json = [ - "type" : "candidate", - "label" : "\(candidate.sdpMLineIndex)", - "id" : candidate.sdpMid, - "candidate" : candidate.sdp - ] - guard let data = try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) else { return } - print("[Calls] Sending ICE candidate to test call server: \(json).") - TestCallServer.send(data, roomID: room.roomID, userID: room.clientID).retainUntilComplete() - } -} diff --git a/Session/Calls/CallVCV2+WebSocket.swift b/Session/Calls/CallVCV2+WebSocket.swift deleted file mode 100644 index 40763be5e..000000000 --- a/Session/Calls/CallVCV2+WebSocket.swift +++ /dev/null @@ -1,28 +0,0 @@ - -extension CallVCV2 : WebSocketDelegate { - - func handleWebSocketConnected() { - guard let room = room else { return } - let json = [ - "cmd" : "register", - "roomid" : room.roomID, - "clientid" : room.clientID - ] - guard let data = try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) else { return } - print("[Calls] Web socket connected. Sending: \(json).") - socket?.send(data) - print("[Calls] Is initiator: \(room.isInitiator).") - if room.isInitiator { - callManager.offer().retainUntilComplete() - } - } - - func handleWebSocketDisconnected() { - socket?.delegate = nil - } - - func handleWebSocketMessage(_ message: String) { - print("[Calls] Message received through web socket: \(message).") - handle([ message ]) - } -} diff --git a/Session/Calls/CallVCV2.swift b/Session/Calls/CallVCV2.swift index 177e5e56b..b40d71d99 100644 --- a/Session/Calls/CallVCV2.swift +++ b/Session/Calls/CallVCV2.swift @@ -1,15 +1,12 @@ import WebRTC +import SessionUIKit +import SessionMessagingKit +import SessionUtilitiesKit -final class CallVCV2 : UIViewController { - let roomID = "37923672516" // NOTE: You need to change this every time to ensure the room isn't full - var room: RoomInfo? - var socket: WebSocket? - - lazy var callManager: WebRTCWrapper = { - let result = WebRTCWrapper() - result.delegate = self - return result - }() +final class CallVCV2 : UIViewController, WebRTCWrapperDelegate { + let sessionID: String + let mode: Mode + let webRTCWrapper: WebRTCWrapper lazy var cameraManager: CameraManager = { let result = CameraManager() @@ -18,30 +15,53 @@ final class CallVCV2 : UIViewController { }() lazy var videoCapturer: RTCVideoCapturer = { - return RTCCameraVideoCapturer(delegate: callManager.localVideoSource) + return RTCCameraVideoCapturer(delegate: webRTCWrapper.localVideoSource) }() + // MARK: Mode + enum Mode { + case offer + case answer(sdp: RTCSessionDescription) + } + // MARK: Lifecycle + init(for sessionID: String, mode: Mode) { + self.sessionID = sessionID + self.mode = mode + self.webRTCWrapper = WebRTCWrapper.current ?? WebRTCWrapper(for: sessionID) + super.init(nibName: nil, bundle: nil) + self.webRTCWrapper.delegate = self + } + + required init(coder: NSCoder) { preconditionFailure("Use init(for:) instead.") } + override func viewDidLoad() { super.viewDidLoad() + WebRTCWrapper.current = webRTCWrapper setUpViewHierarchy() cameraManager.prepare() touch(videoCapturer) - autoConnectToTestRoom() + if case .offer = mode { + Storage.write { transaction in + self.webRTCWrapper.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() + } + } else if case let .answer(sdp) = mode { + webRTCWrapper.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally + } } func setUpViewHierarchy() { // Remote video view let remoteVideoView = RTCMTLVideoView() remoteVideoView.contentMode = .scaleAspectFill - callManager.attachRemoteRenderer(remoteVideoView) + webRTCWrapper.attachRemoteRenderer(remoteVideoView) view.addSubview(remoteVideoView) remoteVideoView.translatesAutoresizingMaskIntoConstraints = false remoteVideoView.pin(to: view) // Local video view let localVideoView = RTCMTLVideoView() localVideoView.contentMode = .scaleAspectFill - callManager.attachLocalRenderer(localVideoView) + webRTCWrapper.attachLocalRenderer(localVideoView) localVideoView.set(.width, to: 80) localVideoView.set(.height, to: 173) view.addSubview(localVideoView) @@ -60,37 +80,7 @@ final class CallVCV2 : UIViewController { cameraManager.stop() } - // MARK: General - func autoConnectToTestRoom() { - // Connect to a random test room - TestCallServer.join(roomID: roomID).done2 { [weak self] room in - print("[Calls] Connected to test room.") - guard let self = self else { return } - self.room = room - if let messages = room.messages { - self.handle(messages) - } - let socket = WebSocket(url: URL(string: room.wssURL)!) - socket.delegate = self - socket.connect() - self.socket = socket - }.catch2 { error in - SNLog("Couldn't join room due to error: \(error).") - } - } - - func handle(_ messages: [String]) { - print("[Calls] Handling messages:") - messages.forEach { print("[Calls] \($0)") } - messages.forEach { message in - let signalingMessage = SignalingMessage.from(message: message) - switch signalingMessage { - case .candidate(let candidate): callManager.handleICECandidate(candidate) - case .answer(let answer): callManager.handleRemoteSDP(answer) - case .offer(let offer): callManager.handleRemoteSDP(offer) - default: break - } - } - callManager.drainICECandidateQueue() + deinit { + WebRTCWrapper.current = nil } } diff --git a/Session/Calls/CameraManager.swift b/Session/Calls/CameraManager.swift index e9af7ddc1..f1e9b0616 100644 --- a/Session/Calls/CameraManager.swift +++ b/Session/Calls/CameraManager.swift @@ -1,5 +1,6 @@ import Foundation import AVFoundation +import SessionUtilitiesKit @objc protocol CameraManagerDelegate : AnyObject { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 1fba6eab4..0de4bcdfb 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -27,7 +27,8 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc } @objc func startCall() { - let callVC = CallVCV2() + guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } + let callVC = CallVCV2(for: contactSessionID, mode: .offer) navigationController!.pushViewController(callVC, animated: true, completion: nil) } diff --git a/Session/Meta/AppDelegate.m b/Session/Meta/AppDelegate.m index 29e9e0cda..8fb513275 100644 --- a/Session/Meta/AppDelegate.m +++ b/Session/Meta/AppDelegate.m @@ -182,7 +182,7 @@ static NSTimeInterval launchStartedAt; [SNConfiguration performMainSetup]; [SNAppearance switchToSessionAppearance]; - + if (CurrentAppContext().isRunningTests) { return YES; } @@ -219,10 +219,11 @@ static NSTimeInterval launchStartedAt; name:RegistrationStateDidChangeNotification object:nil]; - // Loki - Observe data nuke request notifications [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleDataNukeRequested:) name:NSNotification.dataNukeRequested object:nil]; OWSLogInfo(@"application: didFinishLaunchingWithOptions completed."); + + [self setUpCallHandling]; return YES; } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index bb28b0b81..c9ea18bdc 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -1,7 +1,29 @@ import PromiseKit +import WebRTC extension AppDelegate { + @objc + func setUpCallHandling() { + MessageReceiver.handleOfferCallMessage = { message in + DispatchQueue.main.async { + let sdp = RTCSessionDescription(type: .offer, sdp: message.sdp!) + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + let alert = UIAlertController(title: "Incoming Call", message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Accept", style: .default, handler: { _ in + let callVC = CallVCV2(for: message.sender!, mode: .answer(sdp: sdp)) + presentingVC.dismiss(animated: true) { + presentingVC.present(callVC, animated: true, completion: nil) + } + })) + alert.addAction(UIAlertAction(title: "Decline", style: .default, handler: { _ in + // Do nothing + })) + presentingVC.present(alert, animated: true, completion: nil) + } + } + } + @objc(syncConfigurationIfNeeded) func syncConfigurationIfNeeded() { guard Storage.shared.getUser()?.name != nil else { return } @@ -43,7 +65,7 @@ extension AppDelegate { @objc func getAppModeOrSystemDefault() -> AppMode { let userDefaults = UserDefaults.standard - + guard userDefaults.dictionaryRepresentation().keys.contains("appMode") else { if #available(iOS 13.0, *) { return UITraitCollection.current.userInterfaceStyle == .dark ? .dark : .light diff --git a/SessionMessagingKit/Calls/Temp/RoomInfo.swift b/SessionMessagingKit/Calls/Temp/RoomInfo.swift deleted file mode 100644 index affe92543..000000000 --- a/SessionMessagingKit/Calls/Temp/RoomInfo.swift +++ /dev/null @@ -1,9 +0,0 @@ - -public struct RoomInfo { - public let roomID: String - public let wssURL: String - public let wssPostURL: String - public let clientID: String - public let isInitiator: Bool - public let messages: [String]? -} diff --git a/SessionMessagingKit/Calls/Temp/SignalingMessage.swift b/SessionMessagingKit/Calls/Temp/SignalingMessage.swift deleted file mode 100644 index 3ca82215c..000000000 --- a/SessionMessagingKit/Calls/Temp/SignalingMessage.swift +++ /dev/null @@ -1,58 +0,0 @@ -import Foundation -import WebRTC - -public enum SignalingMessage { - case candidate(_ message: RTCIceCandidate) - case answer(_ message: RTCSessionDescription) - case offer(_ message: RTCSessionDescription) - case bye - - public static func from(message: String) -> SignalingMessage? { - guard let data = message.data(using: String.Encoding.utf8), - let json = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON else { return nil } - let messageAsJSON: JSON - if let string = json["msg"] as? String { - guard let data = string.data(using: String.Encoding.utf8), - let json = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON else { return nil } - messageAsJSON = json - } else { - messageAsJSON = json - } - guard let type = messageAsJSON["type"] as? String else { return nil } - switch type { - case "candidate": - guard let candidate = RTCIceCandidate.candidate(from: messageAsJSON) else { return nil } - return .candidate(candidate) - case "answer": - guard let sdp = messageAsJSON["sdp"] as? String else { return nil } - return .answer(RTCSessionDescription(type: .answer, sdp: sdp)) - case "offer": - guard let sdp = messageAsJSON["sdp"] as? String else { return nil } - return .offer(RTCSessionDescription(type: .offer, sdp: sdp)) - case "bye": - return .bye - default: return nil - } - } -} - -extension RTCIceCandidate { - - public func serialize() -> Data? { - let json = [ - "type": "candidate", - "label": "\(sdpMLineIndex)", - "id": sdpMid, - "candidate": sdp - ] - return try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) - } - - static func candidate(from json: JSON) -> RTCIceCandidate? { - let sdp = json["candidate"] as? String - let sdpMid = json["id"] as? String - let labelStr = json["label"] as? String - let label = (json["label"] as? Int32) ?? 0 - return RTCIceCandidate(sdp: sdp ?? "", sdpMLineIndex: Int32(labelStr ?? "") ?? label, sdpMid: sdpMid) - } -} diff --git a/SessionMessagingKit/Calls/Temp/TestCallConfig.swift b/SessionMessagingKit/Calls/Temp/TestCallConfig.swift deleted file mode 100644 index 9bdec5f31..000000000 --- a/SessionMessagingKit/Calls/Temp/TestCallConfig.swift +++ /dev/null @@ -1,12 +0,0 @@ - -public enum TestCallConfig { - - public static let defaultICEServers = [ - "stun:stun.l.google.com:19302", - "stun:stun1.l.google.com:19302", - "stun:stun2.l.google.com:19302", - "stun:stun3.l.google.com:19302", - "stun:stun4.l.google.com:19302" - ] - public static let defaultServerURL = "https://appr.tc" -} diff --git a/SessionMessagingKit/Calls/Temp/TestCallServer.swift b/SessionMessagingKit/Calls/Temp/TestCallServer.swift deleted file mode 100644 index 98c3fc684..000000000 --- a/SessionMessagingKit/Calls/Temp/TestCallServer.swift +++ /dev/null @@ -1,49 +0,0 @@ -import Foundation -import PromiseKit - -public enum TestCallServer { - - public enum Error : LocalizedError { - case roomFull - - public var errorDescription: String? { - switch self { - case .roomFull: return "The room is full." - } - } - } - - public static func join(roomID: String) -> Promise { - let url = "\(TestCallConfig.defaultServerURL)/join/\(roomID)" - return HTTP.execute(.post, url).map2 { json in - guard let status = json["result"] as? String else { throw HTTP.Error.invalidJSON } - guard status != "FULL" else { throw Error.roomFull } - guard let info = json["params"] as? JSON, - let roomID = info["room_id"] as? String, - let wssURL = info["wss_url"] as? String, - let wssPostURL = info["wss_post_url"] as? String, - let clientID = info["client_id"] as? String else { throw HTTP.Error.invalidJSON } - let isInitiator: Bool - if let bool = info["is_initiator"] as? Bool { - isInitiator = bool - } else if let string = info["is_initiator"] as? String { - isInitiator = (string == "true") - } else { - throw HTTP.Error.invalidJSON - } - let messages = info["messages"] as? [String] - return RoomInfo(roomID: roomID, wssURL: wssURL, wssPostURL: wssPostURL, - clientID: clientID, isInitiator: isInitiator, messages: messages) - } - } - - public static func leave(roomID: String, userID: String) -> Promise { - let url = "\(TestCallConfig.defaultServerURL)/leave/\(roomID)/\(userID)" - return HTTP.execute(.post, url).map2 { _ in } - } - - public static func send(_ message: Data, roomID: String, userID: String) -> Promise { - let url = "\(TestCallConfig.defaultServerURL)/message/\(roomID)/\(userID)" - return HTTP.execute(.post, url, body: message).map2 { _ in } - } -} diff --git a/SessionMessagingKit/Calls/Temp/WebSocket.swift b/SessionMessagingKit/Calls/Temp/WebSocket.swift deleted file mode 100644 index 29685bb70..000000000 --- a/SessionMessagingKit/Calls/Temp/WebSocket.swift +++ /dev/null @@ -1,52 +0,0 @@ -import Foundation -import SocketRocket - -public protocol WebSocketDelegate : AnyObject { - - func handleWebSocketConnected() - func handleWebSocketDisconnected() - func handleWebSocketMessage(_ message: String) -} - -public final class WebSocket : NSObject, SRWebSocketDelegate { - private let socket: SRWebSocket - public weak var delegate: WebSocketDelegate? - - public init(url: URL) { - socket = SRWebSocket(url: url) - super.init() - socket.delegate = self - } - - public func connect() { - socket.open() - } - - public func send(_ data: Data) { - socket.send(data) - } - - public func webSocketDidOpen(_ webSocket: SRWebSocket!) { - delegate?.handleWebSocketConnected() - } - - public func webSocket(_ webSocket: SRWebSocket!, didReceiveMessage message: Any!) { - guard let message = message as? String else { return } - delegate?.handleWebSocketMessage(message) - } - - public func disconnect() { - socket.close() - delegate?.handleWebSocketDisconnected() - } - - public func webSocket(_ webSocket: SRWebSocket!, didFailWithError error: Error!) { - SNLog("Web socket failed with error: \(error?.localizedDescription ?? "nil").") - disconnect() - } - - public func webSocket(_ webSocket: SRWebSocket!, didCloseWithCode code: Int, reason: String!, wasClean: Bool) { - SNLog("Web socket closed.") - disconnect() - } -} diff --git a/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift b/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift index 3eced68bb..88f60a24d 100644 --- a/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift +++ b/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift @@ -4,10 +4,10 @@ extension WebRTCWrapper { public func handleICECandidate(_ candidate: RTCIceCandidate) { print("[Calls] Received ICE candidate message.") - candidateQueue.append(candidate) + peerConnection.add(candidate) } - public func handleRemoteSDP(_ sdp: RTCSessionDescription) { + public func handleRemoteSDP(_ sdp: RTCSessionDescription, from sessionID: String) { print("[Calls] Received remote SDP: \(sdp.sdp).") peerConnection.setRemoteDescription(sdp, completionHandler: { [weak self] error in if let error = error { @@ -15,8 +15,10 @@ extension WebRTCWrapper { } else { guard let self = self, sdp.type == .offer, self.peerConnection.localDescription == nil else { return } - // Answer the call - self.answer().retainUntilComplete() + // Automatically answer the call + Storage.write { transaction in + self.sendAnswer(to: sessionID, using: transaction).retainUntilComplete() + } } }) } diff --git a/SessionMessagingKit/Calls/WebRTCWrapper.swift b/SessionMessagingKit/Calls/WebRTCWrapper.swift index c2a153732..49cd939bb 100644 --- a/SessionMessagingKit/Calls/WebRTCWrapper.swift +++ b/SessionMessagingKit/Calls/WebRTCWrapper.swift @@ -3,15 +3,20 @@ import WebRTC public protocol WebRTCWrapperDelegate : AnyObject { var videoCapturer: RTCVideoCapturer { get } - - func sendSDP(_ sdp: RTCSessionDescription) - func sendICECandidate(_ candidate: RTCIceCandidate) } /// See https://webrtc.org/getting-started/overview for more information. public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { public weak var delegate: WebRTCWrapperDelegate? - internal var candidateQueue: [RTCIceCandidate] = [] + private let contactSessionID: String + + private let defaultICEServers = [ + "stun:stun.l.google.com:19302", + "stun:stun1.l.google.com:19302", + "stun:stun2.l.google.com:19302", + "stun:stun3.l.google.com:19302", + "stun:stun4.l.google.com:19302" + ] internal lazy var factory: RTCPeerConnectionFactory = { RTCInitializeSSL() @@ -24,7 +29,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { /// remote peer, maintain and monitor the connection, and close the connection once it's no longer needed. internal lazy var peerConnection: RTCPeerConnection = { let configuration = RTCConfiguration() - configuration.iceServers = [ RTCIceServer(urlStrings: TestCallConfig.defaultICEServers) ] + configuration.iceServers = [ RTCIceServer(urlStrings: defaultICEServers) ] configuration.sdpSemantics = .unifiedPlan let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [:]) return factory.peerConnection(with: configuration, constraints: constraints, delegate: self) @@ -74,7 +79,10 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { } // MARK: Initialization - public override init() { + public static var current: WebRTCWrapper? + + public init(for contactSessionID: String) { + self.contactSessionID = contactSessionID super.init() let mediaStreamTrackIDS = ["ARDAMS"] peerConnection.add(audioTrack, streamIds: mediaStreamTrackIDS) @@ -93,19 +101,10 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { audioSession.unlockForConfiguration() } - // MARK: General - public func drainICECandidateQueue() { - print("[Calls] Draining ICE candidate queue.") - candidateQueue.forEach { peerConnection.add($0) } - candidateQueue.removeAll() - } - // MARK: Call Management - public func offer() -> Promise { + public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { print("[Calls] Initiating call.") - /* - guard let thread = TSContactThread.fetch(for: publicKey, using: transaction) else { return Promise(error: Error.noThread) } - */ + guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } let (promise, seal) = Promise.pending() peerConnection.offer(for: mediaConstraints) { [weak self] sdp, error in if let error = error { @@ -118,26 +117,21 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { return seal.reject(error) } } - - self.delegate?.sendSDP(sdp) - - /* - let message = CallMessage() - message.type = .offer - message.sdp = sdp.sdp - MessageSender.send(message, in: thread, using: transaction) - */ - seal.fulfill(()) + DispatchQueue.main.async { + let message = CallMessage() + message.kind = .offer + message.sdp = sdp.sdp + MessageSender.send(message, in: thread, using: transaction) + seal.fulfill(()) + } } } return promise } - public func answer() -> Promise { + public func sendAnswer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { print("[Calls] Accepting call.") - /* - guard let thread = TSContactThread.fetch(for: publicKey, using: transaction) else { return Promise(error: Error.noThread) } - */ + guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } let (promise, seal) = Promise.pending() peerConnection.answer(for: mediaConstraints) { [weak self] sdp, error in if let error = error { @@ -150,16 +144,13 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { return seal.reject(error) } } - - self.delegate?.sendSDP(sdp) - - /* - let message = CallMessage() - message.type = .answer - message.sdp = sdp.sdp - MessageSender.send(message, in: thread, using: transaction) - */ - seal.fulfill(()) + DispatchQueue.main.async { + let message = CallMessage() + message.kind = .answer + message.sdp = sdp.sdp + MessageSender.send(message, in: thread, using: transaction) + seal.fulfill(()) + } } } return promise @@ -171,7 +162,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { // MARK: Delegate public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCSignalingState) { - SNLog("Signaling state changed to: \(state).") + print("[Calls] Signaling state changed to: \(state).") } public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { @@ -187,23 +178,29 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCIceConnectionState) { - SNLog("ICE connection state changed to: \(state).") + print("[Calls] ICE connection state changed to: \(state).") } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCIceGatheringState) { - SNLog("ICE gathering state changed to: \(state).") + print("[Calls] ICE gathering state changed to: \(state).") } public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { - SNLog("ICE candidate generated.") - delegate?.sendICECandidate(candidate) + print("[Calls] ICE candidate generated.") + Storage.write { transaction in + guard let thread = TSContactThread.fetch(for: self.contactSessionID, using: transaction) else { return } + let message = CallMessage() + message.kind = .iceCandidate(sdpMLineIndex: UInt32(candidate.sdpMLineIndex), sdpMid: candidate.sdpMid!) + message.sdp = candidate.sdp + MessageSender.send(message, in: thread, using: transaction) + } } public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) { - SNLog("\(candidates.count) ICE candidate(s) removed.") + print("[Calls] \(candidates.count) ICE candidate(s) removed.") } public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { - SNLog("Data channel opened.") + print("[Calls] Data channel opened.") } } diff --git a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift index 4b35f08e9..b4fed52a8 100644 --- a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift @@ -3,52 +3,111 @@ import WebRTC /// See https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription for more information. @objc(SNCallMessage) public final class CallMessage : ControlMessage { - public var type: RTCSdpType? + public var kind: Kind? /// See https://developer.mozilla.org/en-US/docs/Glossary/SDP for more information. public var sdp: String? + // MARK: Kind + public enum Kind : Codable, CustomStringConvertible { + case offer + case answer + case provisionalAnswer + case iceCandidate(sdpMLineIndex: UInt32, sdpMid: String) + + public var description: String { + switch self { + case .offer: return "offer" + case .answer: return "answer" + case .provisionalAnswer: return "provisionalAnswer" + case .iceCandidate(_, _): return "iceCandidate" + } + } + } + // MARK: Initialization public override init() { super.init() } - internal init(type: RTCSdpType, sdp: String) { + internal init(kind: Kind, sdp: String) { super.init() - self.type = type + self.kind = kind self.sdp = sdp } // MARK: Validation public override var isValid: Bool { guard super.isValid else { return false } - return type != nil && sdp != nil + return kind != nil && sdp != nil } // MARK: Coding public required init?(coder: NSCoder) { super.init(coder: coder) - if let type = coder.decodeObject(forKey: "type") as! RTCSdpType? { self.type = type } + guard let rawKind = coder.decodeObject(forKey: "kind") as! String? else { return nil } + switch rawKind { + case "offer": kind = .offer + case "answer": kind = .answer + case "provisionalAnswer": kind = .provisionalAnswer + case "iceCandidate": + guard let sdpMLineIndex = coder.decodeObject(forKey: "sdpMLineIndex") as? UInt32, + let sdpMid = coder.decodeObject(forKey: "sdpMid") as? String else { return nil } + kind = .iceCandidate(sdpMLineIndex: sdpMLineIndex, sdpMid: sdpMid) + default: preconditionFailure() + } if let sdp = coder.decodeObject(forKey: "sdp") as! String? { self.sdp = sdp } } public override func encode(with coder: NSCoder) { super.encode(with: coder) - coder.encode(type, forKey: "type") + switch kind { + case .offer: coder.encode("offer", forKey: "kind") + case .answer: coder.encode("answer", forKey: "kind") + case .provisionalAnswer: coder.encode("provisionalAnswer", forKey: "kind") + case let .iceCandidate(sdpMLineIndex, sdpMid): + coder.encode("iceCandidate", forKey: "kind") + coder.encode(sdpMLineIndex, forKey: "sdpMLineIndex") + coder.encode(sdpMid, forKey: "sdpMid") + default: preconditionFailure() + } coder.encode(sdp, forKey: "sdp") } // MARK: Proto Conversion public override class func fromProto(_ proto: SNProtoContent) -> CallMessage? { guard let callMessageProto = proto.callMessage else { return nil } - let type = callMessageProto.type + let kind: Kind + switch callMessageProto.type { + case .offer: kind = .offer + case .answer: kind = .answer + case .provisionalAnswer: kind = .provisionalAnswer + case .iceCandidate: + let sdpMLineIndex = callMessageProto.sdpMlineIndex + guard let sdpMid = callMessageProto.sdpMid else { return nil } + kind = .iceCandidate(sdpMLineIndex: sdpMLineIndex, sdpMid: sdpMid) + } let sdp = callMessageProto.sdp - return CallMessage(type: RTCSdpType.from(type), sdp: sdp) + return CallMessage(kind: kind, sdp: sdp) } public override func toProto(using transaction: YapDatabaseReadWriteTransaction) -> SNProtoContent? { - guard let type = type, let sdp = sdp else { + guard let kind = kind, let sdp = sdp else { SNLog("Couldn't construct call message proto from: \(self).") return nil } - let callMessageProto = SNProtoCallMessage.builder(type: type.toProto(), sdp: sdp) + if case .offer = kind { + print("[Calls] Converting offer message to proto.") + } + let type: SNProtoCallMessage.SNProtoCallMessageType + switch kind { + case .offer: type = .offer + case .answer: type = .answer + case .provisionalAnswer: type = .provisionalAnswer + case .iceCandidate(_, _): type = .iceCandidate + } + let callMessageProto = SNProtoCallMessage.builder(type: type, sdp: sdp) + if case let .iceCandidate(sdpMLineIndex, sdpMid) = kind { + callMessageProto.setSdpMlineIndex(sdpMLineIndex) + callMessageProto.setSdpMid(sdpMid) + } let contentProto = SNProtoContent.builder() do { contentProto.setCallMessage(try callMessageProto.build()) @@ -63,39 +122,9 @@ public final class CallMessage : ControlMessage { public override var description: String { """ CallMessage( - type: \(type?.description ?? "null"), + kind: \(kind?.description ?? "null"), sdp: \(sdp ?? "null") ) """ } } - -// MARK: RTCSdpType + Utilities -extension RTCSdpType : CustomStringConvertible { - - public var description: String { - switch self { - case .answer: return "answer" - case .offer: return "offer" - case .prAnswer: return "prAnswer" - default: preconditionFailure() - } - } - - fileprivate static func from(_ type: SNProtoCallMessage.SNProtoCallMessageType) -> RTCSdpType { - switch type { - case .answer: return .answer - case .offer: return .offer - case .provisionalAnswer: return .prAnswer - } - } - - fileprivate func toProto() -> SNProtoCallMessage.SNProtoCallMessageType { - switch self { - case .answer: return .answer - case .offer: return .offer - case .prAnswer: return .provisionalAnswer - default: preconditionFailure() - } - } -} diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index 8da553d2b..9348b2524 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -656,6 +656,7 @@ extension SNProtoContent.SNProtoContentBuilder { case offer = 1 case answer = 2 case provisionalAnswer = 3 + case iceCandidate = 4 } private class func SNProtoCallMessageTypeWrap(_ value: SessionProtos_CallMessage.TypeEnum) -> SNProtoCallMessageType { @@ -663,6 +664,7 @@ extension SNProtoContent.SNProtoContentBuilder { case .offer: return .offer case .answer: return .answer case .provisionalAnswer: return .provisionalAnswer + case .iceCandidate: return .iceCandidate } } @@ -671,6 +673,7 @@ extension SNProtoContent.SNProtoContentBuilder { case .offer: return .offer case .answer: return .answer case .provisionalAnswer: return .provisionalAnswer + case .iceCandidate: return .iceCandidate } } @@ -683,6 +686,12 @@ extension SNProtoContent.SNProtoContentBuilder { // asBuilder() constructs a builder that reflects the proto's contents. @objc public func asBuilder() -> SNProtoCallMessageBuilder { let builder = SNProtoCallMessageBuilder(type: type, sdp: sdp) + if hasSdpMlineIndex { + builder.setSdpMlineIndex(sdpMlineIndex) + } + if let _value = sdpMid { + builder.setSdpMid(_value) + } return builder } @@ -707,6 +716,14 @@ extension SNProtoContent.SNProtoContentBuilder { proto.sdp = valueParam } + @objc public func setSdpMlineIndex(_ valueParam: UInt32) { + proto.sdpMlineIndex = valueParam + } + + @objc public func setSdpMid(_ valueParam: String) { + proto.sdpMid = valueParam + } + @objc public func build() throws -> SNProtoCallMessage { return try SNProtoCallMessage.parseProto(proto) } @@ -722,6 +739,23 @@ extension SNProtoContent.SNProtoContentBuilder { @objc public let sdp: String + @objc public var sdpMlineIndex: UInt32 { + return proto.sdpMlineIndex + } + @objc public var hasSdpMlineIndex: Bool { + return proto.hasSdpMlineIndex + } + + @objc public var sdpMid: String? { + guard proto.hasSdpMid else { + return nil + } + return proto.sdpMid + } + @objc public var hasSdpMid: Bool { + return proto.hasSdpMid + } + private init(proto: SessionProtos_CallMessage, type: SNProtoCallMessageType, sdp: String) { diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index 1114aba7e..350844f08 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -329,6 +329,24 @@ struct SessionProtos_CallMessage { /// Clears the value of `sdp`. Subsequent reads from it will return its default value. mutating func clearSdp() {self._sdp = nil} + var sdpMlineIndex: UInt32 { + get {return _sdpMlineIndex ?? 0} + set {_sdpMlineIndex = newValue} + } + /// Returns true if `sdpMlineIndex` has been explicitly set. + var hasSdpMlineIndex: Bool {return self._sdpMlineIndex != nil} + /// Clears the value of `sdpMlineIndex`. Subsequent reads from it will return its default value. + mutating func clearSdpMlineIndex() {self._sdpMlineIndex = nil} + + var sdpMid: String { + get {return _sdpMid ?? String()} + set {_sdpMid = newValue} + } + /// Returns true if `sdpMid` has been explicitly set. + var hasSdpMid: Bool {return self._sdpMid != nil} + /// Clears the value of `sdpMid`. Subsequent reads from it will return its default value. + mutating func clearSdpMid() {self._sdpMid = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() enum TypeEnum: SwiftProtobuf.Enum { @@ -336,6 +354,7 @@ struct SessionProtos_CallMessage { case offer // = 1 case answer // = 2 case provisionalAnswer // = 3 + case iceCandidate // = 4 init() { self = .offer @@ -346,6 +365,7 @@ struct SessionProtos_CallMessage { case 1: self = .offer case 2: self = .answer case 3: self = .provisionalAnswer + case 4: self = .iceCandidate default: return nil } } @@ -355,6 +375,7 @@ struct SessionProtos_CallMessage { case .offer: return 1 case .answer: return 2 case .provisionalAnswer: return 3 + case .iceCandidate: return 4 } } @@ -364,6 +385,8 @@ struct SessionProtos_CallMessage { fileprivate var _type: SessionProtos_CallMessage.TypeEnum? = nil fileprivate var _sdp: String? = nil + fileprivate var _sdpMlineIndex: UInt32? = nil + fileprivate var _sdpMid: String? = nil } #if swift(>=4.2) @@ -1795,6 +1818,8 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "type"), 2: .same(proto: "sdp"), + 3: .same(proto: "sdpMLineIndex"), + 4: .same(proto: "sdpMid"), ] public var isInitialized: Bool { @@ -1811,6 +1836,8 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self._type) }() case 2: try { try decoder.decodeSingularStringField(value: &self._sdp) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self._sdpMlineIndex) }() + case 4: try { try decoder.decodeSingularStringField(value: &self._sdpMid) }() default: break } } @@ -1823,12 +1850,20 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa if let v = self._sdp { try visitor.visitSingularStringField(value: v, fieldNumber: 2) } + if let v = self._sdpMlineIndex { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) + } + if let v = self._sdpMid { + try visitor.visitSingularStringField(value: v, fieldNumber: 4) + } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: SessionProtos_CallMessage, rhs: SessionProtos_CallMessage) -> Bool { if lhs._type != rhs._type {return false} if lhs._sdp != rhs._sdp {return false} + if lhs._sdpMlineIndex != rhs._sdpMlineIndex {return false} + if lhs._sdpMid != rhs._sdpMid {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1839,6 +1874,7 @@ extension SessionProtos_CallMessage.TypeEnum: SwiftProtobuf._ProtoNameProviding 1: .same(proto: "OFFER"), 2: .same(proto: "ANSWER"), 3: .same(proto: "PROVISIONAL_ANSWER"), + 4: .same(proto: "ICE_CANDIDATE"), ] } diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index 76995d94a..7cd083135 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -57,12 +57,15 @@ message CallMessage { OFFER = 1; ANSWER = 2; PROVISIONAL_ANSWER = 3; + ICE_CANDIDATE = 4; } // @required - required Type type = 1; + required Type type = 1; // @required - required string sdp = 2; + required string sdp = 2; + optional uint32 sdpMLineIndex = 3; + optional string sdpMid = 4; } message KeyPair { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 7bf42c26e..9ae682d0c 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -1,5 +1,6 @@ import SignalCoreKit import SessionSnodeKit +import WebRTC extension MessageReceiver { @@ -16,6 +17,7 @@ extension MessageReceiver { case let message as ExpirationTimerUpdate: handleExpirationTimerUpdate(message, using: transaction) case let message as ConfigurationMessage: handleConfigurationMessage(message, using: transaction) case let message as UnsendRequest: handleUnsendRequest(message, using: transaction) + case let message as CallMessage: handleCallMessage(message, using: transaction) case let message as VisibleMessage: try handleVisibleMessage(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction) default: fatalError() } @@ -251,6 +253,33 @@ extension MessageReceiver { + // MARK: - Call Messages + + public static func handleCallMessage(_ message: CallMessage, using transaction: Any) { + let webRTCWrapper: WebRTCWrapper + if let current = WebRTCWrapper.current { + webRTCWrapper = current + } else { + WebRTCWrapper.current = WebRTCWrapper(for: message.sender!) + webRTCWrapper = WebRTCWrapper.current! + } + switch message.kind! { + case .offer: + print("[Calls] Received offer message.") + handleOfferCallMessage?(message) + case .answer: + print("[Calls] Received answer message.") + let sdp = RTCSessionDescription(type: .answer, sdp: message.sdp!) + webRTCWrapper.handleRemoteSDP(sdp, from: message.sender!) + case .provisionalAnswer: break // TODO: Implement + case let .iceCandidate(sdpMLineIndex, sdpMid): + let candidate = RTCIceCandidate(sdp: message.sdp!, sdpMLineIndex: Int32(sdpMLineIndex), sdpMid: sdpMid) + webRTCWrapper.handleICECandidate(candidate) + } + } + + + // MARK: - Visible Messages @discardableResult diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 13ca16e8e..78ba53208 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -1,7 +1,8 @@ import SessionUtilitiesKit public enum MessageReceiver { - private static var lastEncryptionKeyPairRequest: [String:Date] = [:] + private static var lastEncryptionKeyPairRequest: [String:Date] = [:] + public static var handleOfferCallMessage: ((CallMessage) -> Void)? public enum Error : LocalizedError { case duplicateMessage @@ -126,6 +127,7 @@ public enum MessageReceiver { if let configurationMessage = ConfigurationMessage.fromProto(proto) { return configurationMessage } if let unsendRequest = UnsendRequest.fromProto(proto) { return unsendRequest } if let visibleMessage = VisibleMessage.fromProto(proto) { return visibleMessage } + if let callMessage = CallMessage.fromProto(proto) { return callMessage } return nil }() if let message = message {