Make signaling happen using Session messages

This commit is contained in:
Niels Andriesse 2021-08-17 15:41:13 +10:00
parent 0749510f4e
commit 525eb40d8d
22 changed files with 294 additions and 422 deletions

View File

@ -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 = "<group>"; };
B806ECA026C4A7E4008BDA44 /* WebRTCWrapper+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCWrapper+UI.swift"; sourceTree = "<group>"; };
B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = "<group>"; };
B80F469926C63DD000DCE243 /* RoomInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomInfo.swift; sourceTree = "<group>"; };
B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedConversationCell.swift; sourceTree = "<group>"; };
B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerVC.swift; sourceTree = "<group>"; };
B81D25B726157F20004D1FE1 /* storage-seed-1.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "storage-seed-1.crt"; sourceTree = "<group>"; };
@ -1203,9 +1195,7 @@
B87588582644CA9D000E60D0 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
B8758859264503A6000E60D0 /* JoinOpenGroupModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinOpenGroupModal.swift; sourceTree = "<group>"; };
B877E24126CA12910007970A /* CallVCV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVCV2.swift; sourceTree = "<group>"; };
B877E24326CA12F00007970A /* CallVCV2+MessageSending.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVCV2+MessageSending.swift"; sourceTree = "<group>"; };
B877E24526CA13BA0007970A /* CallVCV2+Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVCV2+Camera.swift"; sourceTree = "<group>"; };
B877E24726CA15170007970A /* CallVCV2+WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVCV2+WebSocket.swift"; sourceTree = "<group>"; };
B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Interaction.swift"; sourceTree = "<group>"; };
B879D448247E1BE300DB3608 /* PathVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathVC.swift; sourceTree = "<group>"; };
B879D44A247E1D9200DB3608 /* PathStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathStatusView.swift; sourceTree = "<group>"; };
@ -1230,11 +1220,7 @@
B8B32044258C117C0020074B /* ContactsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsMigration.swift; sourceTree = "<group>"; };
B8B320B6258C30D70020074B /* HTMLMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadata.swift; sourceTree = "<group>"; };
B8B558F026C4BB0600693325 /* CameraManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = "<group>"; };
B8B558F226C4CA4600693325 /* TestCallConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCallConfig.swift; sourceTree = "<group>"; };
B8B558FA26C4D25C00693325 /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = "<group>"; };
B8B558FC26C4D35400693325 /* TestCallServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCallServer.swift; sourceTree = "<group>"; };
B8B558FE26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCWrapper+MessageHandling.swift"; sourceTree = "<group>"; };
B8B5590026C4E2A400693325 /* SignalingMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalingMessage.swift; sourceTree = "<group>"; };
B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
B8BAC75B2695645400EA1759 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = "<group>"; };
B8BAC75C2695648500EA1759 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -2194,18 +2180,6 @@
path = "Message Cells";
sourceTree = "<group>";
};
B877E24026CA11170007970A /* Temp */ = {
isa = PBXGroup;
children = (
B8B5590026C4E2A400693325 /* SignalingMessage.swift */,
B8B558FA26C4D25C00693325 /* WebSocket.swift */,
B8B558F226C4CA4600693325 /* TestCallConfig.swift */,
B8B558FC26C4D35400693325 /* TestCallServer.swift */,
B80F469926C63DD000DCE243 /* RoomInfo.swift */,
);
path = Temp;
sourceTree = "<group>";
};
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 */,

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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 ])
}
}

View File

@ -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
}
}

View File

@ -1,5 +1,6 @@
import Foundation
import AVFoundation
import SessionUtilitiesKit
@objc
protocol CameraManagerDelegate : AnyObject {

View File

@ -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)
}

View File

@ -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;
}

View File

@ -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

View File

@ -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]?
}

View File

@ -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)
}
}

View File

@ -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"
}

View File

@ -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<RoomInfo> {
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<Void> {
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<Void> {
let url = "\(TestCallConfig.defaultServerURL)/message/\(roomID)/\(userID)"
return HTTP.execute(.post, url, body: message).map2 { _ in }
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}
})
}

View File

@ -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<Void> {
public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise<Void> {
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<Void>.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<Void> {
public func sendAnswer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise<Void> {
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<Void>.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.")
}
}

View File

@ -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()
}
}
}

View File

@ -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) {

View File

@ -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"),
]
}

View File

@ -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 {

View File

@ -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

View File

@ -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 {