Make signaling happen using Session messages
This commit is contained in:
parent
0749510f4e
commit
525eb40d8d
|
@ -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 */,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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 ])
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Foundation
|
||||
import AVFoundation
|
||||
import SessionUtilitiesKit
|
||||
|
||||
@objc
|
||||
protocol CameraManagerDelegate : AnyObject {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]?
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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 }
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue