From 030c869d06d9e773875a17d0b1c658283af1adf3 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 10 Aug 2021 14:09:45 +1000 Subject: [PATCH 001/368] Add WebRTC dependency & implement CallMessage --- Podfile | 2 + Podfile.lock | 6 +- Session.xcodeproj/project.pbxproj | 30 ++ SessionMessagingKit/Calls/File.swift | 16 + .../Control Messages/CallMessage.swift | 101 ++++++ .../Protos/Generated/SNProto.swift | 153 +++++++++ .../Protos/Generated/SessionProtos.pb.swift | 309 ++++++++++++++---- .../Protos/SessionProtos.proto | 19 +- 8 files changed, 568 insertions(+), 68 deletions(-) create mode 100644 SessionMessagingKit/Calls/File.swift create mode 100644 SessionMessagingKit/Messages/Control Messages/CallMessage.swift diff --git a/Podfile b/Podfile index 5452d4e81..1369c0913 100644 --- a/Podfile +++ b/Podfile @@ -12,6 +12,7 @@ target 'Session' do pod 'PureLayout', '~> 3.1.8', :inhibit_warnings => true pod 'Reachability', :inhibit_warnings => true pod 'Sodium', '~> 0.8.0', :inhibit_warnings => true + pod 'WebRTC', '~> 63.11', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true pod 'YYImage', git: 'https://github.com/signalapp/YYImage', :inhibit_warnings => true pod 'ZXingObjC', :inhibit_warnings => true @@ -69,6 +70,7 @@ target 'SessionMessagingKit' do pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true pod 'Sodium', '~> 0.8.0', :inhibit_warnings => true pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true + pod 'WebRTC', '~> 63.11', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true end diff --git a/Podfile.lock b/Podfile.lock index 804a148c7..86d31c1b4 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -51,6 +51,7 @@ PODS: - SQLCipher/standard (4.4.0): - SQLCipher/common - SwiftProtobuf (1.5.0) + - WebRTC (63.11.20455) - YapDatabase/SQLCipher (3.1.1): - YapDatabase/SQLCipher/Core (= 3.1.1) - YapDatabase/SQLCipher/Extensions (= 3.1.1) @@ -135,6 +136,7 @@ DEPENDENCIES: - SignalCoreKit (from `https://github.com/signalapp/SignalCoreKit.git`) - Sodium (~> 0.8.0) - SwiftProtobuf (~> 1.5.0) + - WebRTC (~> 63.11) - YapDatabase/SQLCipher (from `https://github.com/loki-project/session-ios-yap-database.git`, branch `signal-release`) - YYImage (from `https://github.com/signalapp/YYImage`) - ZXingObjC @@ -154,6 +156,7 @@ SPEC REPOS: - Sodium - SQLCipher - SwiftProtobuf + - WebRTC - ZXingObjC EXTERNAL SOURCES: @@ -204,10 +207,11 @@ SPEC CHECKSUMS: Sodium: 63c0ca312a932e6da481689537d4b35568841bdc SQLCipher: e434ed542b24f38ea7b36468a13f9765e1b5c072 SwiftProtobuf: 241400280f912735c1e1b9fe675fdd2c6c4d42e2 + WebRTC: f2a6203584745fe53532633397557876b5d71640 YapDatabase: b418a4baa6906e8028748938f9159807fd039af4 YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 50e6a35c838ba28d2ee02bc6018fdd297c04e55f +PODFILE CHECKSUM: 15bcb2aeee31dc86a3a9febc85208ba890b0dddf COCOAPODS: 1.10.1 diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6facfea31..8823383d9 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -266,6 +266,8 @@ B8D64FCB25BA78A90029CFC0 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; B8D84EA325DF745A005A043E /* LinkPreviewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D84EA225DF745A005A043E /* LinkPreviewState.swift */; }; B8D84ECF25E3108A005A043E /* ExpandingAttachmentsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D84ECE25E3108A005A043E /* ExpandingAttachmentsButton.swift */; }; + B8DE1FB426C22F2F0079C9CE /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8DE1FB326C22F2F0079C9CE /* File.swift */; }; + B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */; }; B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */; }; B8EB20F02640F7F000773E52 /* OpenGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */; }; B8F5F52925EC4F8A003BF8D4 /* BlockListUIUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = B8F5F52825EC4F8A003BF8D4 /* BlockListUIUtils.m */; }; @@ -1245,6 +1247,9 @@ B8D8F19225661BF80092EF10 /* Storage+Messaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Messaging.swift"; sourceTree = ""; }; B8D8F1BC25661C6F0092EF10 /* Storage+OnionRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+OnionRequests.swift"; sourceTree = ""; }; B8D8F1EF256621180092EF10 /* MessageSender+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "MessageSender+Convenience.swift"; path = "../../SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift"; sourceTree = ""; }; + B8DE1FAF26C228780079C9CE /* SignalRingRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SignalRingRTC.framework; path = Dependencies/SignalRingRTC.framework; sourceTree = ""; }; + B8DE1FB326C22F2F0079C9CE /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; }; + B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessage.swift; sourceTree = ""; }; B8EB20E6263F7E4B00773E52 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+OpenGroupInvitation.swift"; sourceTree = ""; }; B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupInvitationView.swift; sourceTree = ""; }; @@ -2329,6 +2334,21 @@ path = Shared; sourceTree = ""; }; + B8DE1FB126C22AF20079C9CE /* Calls */ = { + isa = PBXGroup; + children = ( + ); + path = Calls; + sourceTree = ""; + }; + B8DE1FB226C22F1F0079C9CE /* Calls */ = { + isa = PBXGroup; + children = ( + B8DE1FB326C22F2F0079C9CE /* File.swift */, + ); + path = Calls; + sourceTree = ""; + }; B8F5F61925EDE4B0003BF8D4 /* Data Extraction */ = { isa = PBXGroup; children = ( @@ -2376,6 +2396,7 @@ isa = PBXGroup; children = ( C3C2A7702553A41E00C340D1 /* ControlMessage.swift */, + B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */, C300A5BC2554B00D00555489 /* ReadReceipt.swift */, C300A5D22554B05A00555489 /* TypingIndicator.swift */, C34A977325A3E34A00852C71 /* ClosedGroupControlMessage.swift */, @@ -3239,6 +3260,7 @@ C32C5BB9256DC7C4003C73A2 /* To Do */, C3BBE0752554CDA60050F1E3 /* Configuration.swift */, C3BBE07F2554CDD70050F1E3 /* Storage.swift */, + B8DE1FB226C22F1F0079C9CE /* Calls */, B8B3201F258B1A540020074B /* Contacts */, C32C5BCB256DC818003C73A2 /* Database */, C300A5BB2554AFFB00555489 /* Messages */, @@ -3457,6 +3479,7 @@ D221A08C169C9E5E00537ABF /* Frameworks */ = { isa = PBXGroup; children = ( + B8DE1FAF26C228780079C9CE /* SignalRingRTC.framework */, C35E8AA22485C72300ACB629 /* SwiftCSV.framework */, B847570023D568EB00759540 /* SignalServiceKit.framework */, 3496955F21A2FC8100DCFE74 /* CloudKit.framework */, @@ -3511,6 +3534,7 @@ children = ( C3F0A58F255C8E3D007BE2A3 /* Meta */, C36096BC25AD1C3E008B62B2 /* Backups */, + B8DE1FB126C22AF20079C9CE /* Calls */, C360969C25AD18BA008B62B2 /* Closed Groups */, B835246C25C38AA20089A44F /* Conversations */, C32B405424A961E1001117B5 /* Dependencies */, @@ -4204,6 +4228,7 @@ "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework", "${BUILT_PRODUCTS_DIR}/SQLCipher/SQLCipher.framework", "${BUILT_PRODUCTS_DIR}/Sodium/Sodium.framework", + "${PODS_ROOT}/WebRTC/WebRTC.framework", "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework", "${BUILT_PRODUCTS_DIR}/YapDatabase/YapDatabase.framework", "${BUILT_PRODUCTS_DIR}/ZXingObjC/ZXingObjC.framework", @@ -4226,6 +4251,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SQLCipher.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sodium.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YapDatabase.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ZXingObjC.framework", @@ -4667,8 +4693,10 @@ B8856ECE256F1E58001CE70E /* OWSPreferences.m in Sources */, C3C2A7842553AAF300C340D1 /* SNProto.swift in Sources */, C32C5DC0256DD743003C73A2 /* Poller.swift in Sources */, + B8DE1FB426C22F2F0079C9CE /* File.swift in Sources */, C3C2A7682553A3D900C340D1 /* VisibleMessage+Contact.swift in Sources */, C3A3A171256E1D25004D228D /* SSKReachabilityManager.swift in Sources */, + B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */, C3A71D0B2558989C0043A11F /* MessageWrapper.swift in Sources */, B8F5F60325EDE16F003BF8D4 /* DataExtractionNotification.swift in Sources */, C32C5D24256DD4C0003C73A2 /* MentionsManager.swift in Sources */, @@ -6165,6 +6193,7 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", + "$(PROJECT_DIR)/Dependencies", ); GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; @@ -6233,6 +6262,7 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", + "$(PROJECT_DIR)/Dependencies", ); GCC_OPTIMIZATION_LEVEL = 3; GCC_PRECOMPILE_PREFIX_HEADER = YES; diff --git a/SessionMessagingKit/Calls/File.swift b/SessionMessagingKit/Calls/File.swift new file mode 100644 index 000000000..16c7be30c --- /dev/null +++ b/SessionMessagingKit/Calls/File.swift @@ -0,0 +1,16 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import WebRTC + +//The RTCSessionDescription interface describes one end of a connection—or potential connection—and how it's configured. Each RTCSessionDescription consists of a description type indicating which part of the offer/answer negotiation process it describes and of the SDP descriptor of the session. + +enum Foo { + + func bar() { + + + RTCSessionDescription(type: .answer, sdp: "") + } +} diff --git a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift new file mode 100644 index 000000000..4b35f08e9 --- /dev/null +++ b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift @@ -0,0 +1,101 @@ +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? + /// See https://developer.mozilla.org/en-US/docs/Glossary/SDP for more information. + public var sdp: String? + + // MARK: Initialization + public override init() { super.init() } + + internal init(type: RTCSdpType, sdp: String) { + super.init() + self.type = type + self.sdp = sdp + } + + // MARK: Validation + public override var isValid: Bool { + guard super.isValid else { return false } + return type != 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 } + 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") + 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 sdp = callMessageProto.sdp + return CallMessage(type: RTCSdpType.from(type), sdp: sdp) + } + + public override func toProto(using transaction: YapDatabaseReadWriteTransaction) -> SNProtoContent? { + guard let type = type, let sdp = sdp else { + SNLog("Couldn't construct call message proto from: \(self).") + return nil + } + let callMessageProto = SNProtoCallMessage.builder(type: type.toProto(), sdp: sdp) + let contentProto = SNProtoContent.builder() + do { + contentProto.setCallMessage(try callMessageProto.build()) + return try contentProto.build() + } catch { + SNLog("Couldn't construct call message proto from: \(self).") + return nil + } + } + + // MARK: Description + public override var description: String { + """ + CallMessage( + type: \(type?.description ?? "null"), + sdp: \(sdp ?? "null") + ) + """ + } +} + +// MARK: RTCSdpType + Utilities +extension RTCSdpType : CustomStringConvertible { + + public var description: String { + switch self { + case .answer: return "answer" + case .offer: return "offer" + case .prAnswer: return "prAnswer" + default: preconditionFailure() + } + } + + fileprivate static func from(_ type: SNProtoCallMessage.SNProtoCallMessageType) -> RTCSdpType { + switch type { + case .answer: return .answer + case .offer: return .offer + case .provisionalAnswer: return .prAnswer + } + } + + fileprivate func toProto() -> SNProtoCallMessage.SNProtoCallMessageType { + switch self { + case .answer: return .answer + case .offer: return .offer + case .prAnswer: return .provisionalAnswer + default: preconditionFailure() + } + } +} diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index 8d4f98a4e..8da553d2b 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -466,6 +466,9 @@ extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder { if let _value = dataMessage { builder.setDataMessage(_value) } + if let _value = callMessage { + builder.setCallMessage(_value) + } if let _value = receiptMessage { builder.setReceiptMessage(_value) } @@ -494,6 +497,10 @@ extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder { proto.dataMessage = valueParam.proto } + @objc public func setCallMessage(_ valueParam: SNProtoCallMessage) { + proto.callMessage = valueParam.proto + } + @objc public func setReceiptMessage(_ valueParam: SNProtoReceiptMessage) { proto.receiptMessage = valueParam.proto } @@ -527,6 +534,8 @@ extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder { @objc public let dataMessage: SNProtoDataMessage? + @objc public let callMessage: SNProtoCallMessage? + @objc public let receiptMessage: SNProtoReceiptMessage? @objc public let typingMessage: SNProtoTypingMessage? @@ -539,6 +548,7 @@ extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder { private init(proto: SessionProtos_Content, dataMessage: SNProtoDataMessage?, + callMessage: SNProtoCallMessage?, receiptMessage: SNProtoReceiptMessage?, typingMessage: SNProtoTypingMessage?, configurationMessage: SNProtoConfigurationMessage?, @@ -546,6 +556,7 @@ extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder { unsendRequest: SNProtoUnsendRequest?) { self.proto = proto self.dataMessage = dataMessage + self.callMessage = callMessage self.receiptMessage = receiptMessage self.typingMessage = typingMessage self.configurationMessage = configurationMessage @@ -569,6 +580,11 @@ extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder { dataMessage = try SNProtoDataMessage.parseProto(proto.dataMessage) } + var callMessage: SNProtoCallMessage? = nil + if proto.hasCallMessage { + callMessage = try SNProtoCallMessage.parseProto(proto.callMessage) + } + var receiptMessage: SNProtoReceiptMessage? = nil if proto.hasReceiptMessage { receiptMessage = try SNProtoReceiptMessage.parseProto(proto.receiptMessage) @@ -600,6 +616,7 @@ extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder { let result = SNProtoContent(proto: proto, dataMessage: dataMessage, + callMessage: callMessage, receiptMessage: receiptMessage, typingMessage: typingMessage, configurationMessage: configurationMessage, @@ -629,6 +646,142 @@ extension SNProtoContent.SNProtoContentBuilder { #endif +// MARK: - SNProtoCallMessage + +@objc public class SNProtoCallMessage: NSObject { + + // MARK: - SNProtoCallMessageType + + @objc public enum SNProtoCallMessageType: Int32 { + case offer = 1 + case answer = 2 + case provisionalAnswer = 3 + } + + private class func SNProtoCallMessageTypeWrap(_ value: SessionProtos_CallMessage.TypeEnum) -> SNProtoCallMessageType { + switch value { + case .offer: return .offer + case .answer: return .answer + case .provisionalAnswer: return .provisionalAnswer + } + } + + private class func SNProtoCallMessageTypeUnwrap(_ value: SNProtoCallMessageType) -> SessionProtos_CallMessage.TypeEnum { + switch value { + case .offer: return .offer + case .answer: return .answer + case .provisionalAnswer: return .provisionalAnswer + } + } + + // MARK: - SNProtoCallMessageBuilder + + @objc public class func builder(type: SNProtoCallMessageType, sdp: String) -> SNProtoCallMessageBuilder { + return SNProtoCallMessageBuilder(type: type, sdp: sdp) + } + + // asBuilder() constructs a builder that reflects the proto's contents. + @objc public func asBuilder() -> SNProtoCallMessageBuilder { + let builder = SNProtoCallMessageBuilder(type: type, sdp: sdp) + return builder + } + + @objc public class SNProtoCallMessageBuilder: NSObject { + + private var proto = SessionProtos_CallMessage() + + @objc fileprivate override init() {} + + @objc fileprivate init(type: SNProtoCallMessageType, sdp: String) { + super.init() + + setType(type) + setSdp(sdp) + } + + @objc public func setType(_ valueParam: SNProtoCallMessageType) { + proto.type = SNProtoCallMessageTypeUnwrap(valueParam) + } + + @objc public func setSdp(_ valueParam: String) { + proto.sdp = valueParam + } + + @objc public func build() throws -> SNProtoCallMessage { + return try SNProtoCallMessage.parseProto(proto) + } + + @objc public func buildSerializedData() throws -> Data { + return try SNProtoCallMessage.parseProto(proto).serializedData() + } + } + + fileprivate let proto: SessionProtos_CallMessage + + @objc public let type: SNProtoCallMessageType + + @objc public let sdp: String + + private init(proto: SessionProtos_CallMessage, + type: SNProtoCallMessageType, + sdp: String) { + self.proto = proto + self.type = type + self.sdp = sdp + } + + @objc + public func serializedData() throws -> Data { + return try self.proto.serializedData() + } + + @objc public class func parseData(_ serializedData: Data) throws -> SNProtoCallMessage { + let proto = try SessionProtos_CallMessage(serializedData: serializedData) + return try parseProto(proto) + } + + fileprivate class func parseProto(_ proto: SessionProtos_CallMessage) throws -> SNProtoCallMessage { + guard proto.hasType else { + throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") + } + let type = SNProtoCallMessageTypeWrap(proto.type) + + guard proto.hasSdp else { + throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: sdp") + } + let sdp = proto.sdp + + // MARK: - Begin Validation Logic for SNProtoCallMessage - + + // MARK: - End Validation Logic for SNProtoCallMessage - + + let result = SNProtoCallMessage(proto: proto, + type: type, + sdp: sdp) + return result + } + + @objc public override var debugDescription: String { + return "\(proto)" + } +} + +#if DEBUG + +extension SNProtoCallMessage { + @objc public func serializedDataIgnoringErrors() -> Data? { + return try! self.serializedData() + } +} + +extension SNProtoCallMessage.SNProtoCallMessageBuilder { + @objc public func buildIgnoringErrors() -> SNProtoCallMessage? { + return try! self.build() + } +} + +#endif + // MARK: - SNProtoKeyPair @objc public class SNProtoKeyPair: NSObject { diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index ea16218a6..1114aba7e 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -235,71 +235,145 @@ struct SessionProtos_Content { // methods supported on all messages. var dataMessage: SessionProtos_DataMessage { - get {return _dataMessage ?? SessionProtos_DataMessage()} - set {_dataMessage = newValue} + get {return _storage._dataMessage ?? SessionProtos_DataMessage()} + set {_uniqueStorage()._dataMessage = newValue} } /// Returns true if `dataMessage` has been explicitly set. - var hasDataMessage: Bool {return self._dataMessage != nil} + var hasDataMessage: Bool {return _storage._dataMessage != nil} /// Clears the value of `dataMessage`. Subsequent reads from it will return its default value. - mutating func clearDataMessage() {self._dataMessage = nil} + mutating func clearDataMessage() {_uniqueStorage()._dataMessage = nil} + + var callMessage: SessionProtos_CallMessage { + get {return _storage._callMessage ?? SessionProtos_CallMessage()} + set {_uniqueStorage()._callMessage = newValue} + } + /// Returns true if `callMessage` has been explicitly set. + var hasCallMessage: Bool {return _storage._callMessage != nil} + /// Clears the value of `callMessage`. Subsequent reads from it will return its default value. + mutating func clearCallMessage() {_uniqueStorage()._callMessage = nil} var receiptMessage: SessionProtos_ReceiptMessage { - get {return _receiptMessage ?? SessionProtos_ReceiptMessage()} - set {_receiptMessage = newValue} + get {return _storage._receiptMessage ?? SessionProtos_ReceiptMessage()} + set {_uniqueStorage()._receiptMessage = newValue} } /// Returns true if `receiptMessage` has been explicitly set. - var hasReceiptMessage: Bool {return self._receiptMessage != nil} + var hasReceiptMessage: Bool {return _storage._receiptMessage != nil} /// Clears the value of `receiptMessage`. Subsequent reads from it will return its default value. - mutating func clearReceiptMessage() {self._receiptMessage = nil} + mutating func clearReceiptMessage() {_uniqueStorage()._receiptMessage = nil} var typingMessage: SessionProtos_TypingMessage { - get {return _typingMessage ?? SessionProtos_TypingMessage()} - set {_typingMessage = newValue} + get {return _storage._typingMessage ?? SessionProtos_TypingMessage()} + set {_uniqueStorage()._typingMessage = newValue} } /// Returns true if `typingMessage` has been explicitly set. - var hasTypingMessage: Bool {return self._typingMessage != nil} + var hasTypingMessage: Bool {return _storage._typingMessage != nil} /// Clears the value of `typingMessage`. Subsequent reads from it will return its default value. - mutating func clearTypingMessage() {self._typingMessage = nil} + mutating func clearTypingMessage() {_uniqueStorage()._typingMessage = nil} var configurationMessage: SessionProtos_ConfigurationMessage { - get {return _configurationMessage ?? SessionProtos_ConfigurationMessage()} - set {_configurationMessage = newValue} + get {return _storage._configurationMessage ?? SessionProtos_ConfigurationMessage()} + set {_uniqueStorage()._configurationMessage = newValue} } /// Returns true if `configurationMessage` has been explicitly set. - var hasConfigurationMessage: Bool {return self._configurationMessage != nil} + var hasConfigurationMessage: Bool {return _storage._configurationMessage != nil} /// Clears the value of `configurationMessage`. Subsequent reads from it will return its default value. - mutating func clearConfigurationMessage() {self._configurationMessage = nil} + mutating func clearConfigurationMessage() {_uniqueStorage()._configurationMessage = nil} var dataExtractionNotification: SessionProtos_DataExtractionNotification { - get {return _dataExtractionNotification ?? SessionProtos_DataExtractionNotification()} - set {_dataExtractionNotification = newValue} + get {return _storage._dataExtractionNotification ?? SessionProtos_DataExtractionNotification()} + set {_uniqueStorage()._dataExtractionNotification = newValue} } /// Returns true if `dataExtractionNotification` has been explicitly set. - var hasDataExtractionNotification: Bool {return self._dataExtractionNotification != nil} + var hasDataExtractionNotification: Bool {return _storage._dataExtractionNotification != nil} /// Clears the value of `dataExtractionNotification`. Subsequent reads from it will return its default value. - mutating func clearDataExtractionNotification() {self._dataExtractionNotification = nil} + mutating func clearDataExtractionNotification() {_uniqueStorage()._dataExtractionNotification = nil} var unsendRequest: SessionProtos_UnsendRequest { - get {return _unsendRequest ?? SessionProtos_UnsendRequest()} - set {_unsendRequest = newValue} + get {return _storage._unsendRequest ?? SessionProtos_UnsendRequest()} + set {_uniqueStorage()._unsendRequest = newValue} } /// Returns true if `unsendRequest` has been explicitly set. - var hasUnsendRequest: Bool {return self._unsendRequest != nil} + var hasUnsendRequest: Bool {return _storage._unsendRequest != nil} /// Clears the value of `unsendRequest`. Subsequent reads from it will return its default value. - mutating func clearUnsendRequest() {self._unsendRequest = nil} + mutating func clearUnsendRequest() {_uniqueStorage()._unsendRequest = nil} var unknownFields = SwiftProtobuf.UnknownStorage() init() {} - fileprivate var _dataMessage: SessionProtos_DataMessage? = nil - fileprivate var _receiptMessage: SessionProtos_ReceiptMessage? = nil - fileprivate var _typingMessage: SessionProtos_TypingMessage? = nil - fileprivate var _configurationMessage: SessionProtos_ConfigurationMessage? = nil - fileprivate var _dataExtractionNotification: SessionProtos_DataExtractionNotification? = nil - fileprivate var _unsendRequest: SessionProtos_UnsendRequest? = nil + fileprivate var _storage = _StorageClass.defaultInstance } +struct SessionProtos_CallMessage { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// @required + var type: SessionProtos_CallMessage.TypeEnum { + get {return _type ?? .offer} + set {_type = newValue} + } + /// Returns true if `type` has been explicitly set. + var hasType: Bool {return self._type != nil} + /// Clears the value of `type`. Subsequent reads from it will return its default value. + mutating func clearType() {self._type = nil} + + /// @required + var sdp: String { + get {return _sdp ?? String()} + set {_sdp = newValue} + } + /// Returns true if `sdp` has been explicitly set. + var hasSdp: Bool {return self._sdp != nil} + /// Clears the value of `sdp`. Subsequent reads from it will return its default value. + mutating func clearSdp() {self._sdp = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + enum TypeEnum: SwiftProtobuf.Enum { + typealias RawValue = Int + case offer // = 1 + case answer // = 2 + case provisionalAnswer // = 3 + + init() { + self = .offer + } + + init?(rawValue: Int) { + switch rawValue { + case 1: self = .offer + case 2: self = .answer + case 3: self = .provisionalAnswer + default: return nil + } + } + + var rawValue: Int { + switch self { + case .offer: return 1 + case .answer: return 2 + case .provisionalAnswer: return 3 + } + } + + } + + init() {} + + fileprivate var _type: SessionProtos_CallMessage.TypeEnum? = nil + fileprivate var _sdp: String? = nil +} + +#if swift(>=4.2) + +extension SessionProtos_CallMessage.TypeEnum: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + struct SessionProtos_KeyPair { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -1595,6 +1669,7 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm static let protoMessageName: String = _protobuf_package + ".Content" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "dataMessage"), + 3: .same(proto: "callMessage"), 5: .same(proto: "receiptMessage"), 6: .same(proto: "typingMessage"), 7: .same(proto: "configurationMessage"), @@ -1602,13 +1677,129 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm 9: .same(proto: "unsendRequest"), ] + fileprivate class _StorageClass { + var _dataMessage: SessionProtos_DataMessage? = nil + var _callMessage: SessionProtos_CallMessage? = nil + var _receiptMessage: SessionProtos_ReceiptMessage? = nil + var _typingMessage: SessionProtos_TypingMessage? = nil + var _configurationMessage: SessionProtos_ConfigurationMessage? = nil + var _dataExtractionNotification: SessionProtos_DataExtractionNotification? = nil + var _unsendRequest: SessionProtos_UnsendRequest? = nil + + static let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _dataMessage = source._dataMessage + _callMessage = source._callMessage + _receiptMessage = source._receiptMessage + _typingMessage = source._typingMessage + _configurationMessage = source._configurationMessage + _dataExtractionNotification = source._dataExtractionNotification + _unsendRequest = source._unsendRequest + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + public var isInitialized: Bool { - if let v = self._dataMessage, !v.isInitialized {return false} - if let v = self._receiptMessage, !v.isInitialized {return false} - if let v = self._typingMessage, !v.isInitialized {return false} - if let v = self._configurationMessage, !v.isInitialized {return false} - if let v = self._dataExtractionNotification, !v.isInitialized {return false} - if let v = self._unsendRequest, !v.isInitialized {return false} + return withExtendedLifetime(_storage) { (_storage: _StorageClass) in + if let v = _storage._dataMessage, !v.isInitialized {return false} + if let v = _storage._callMessage, !v.isInitialized {return false} + if let v = _storage._receiptMessage, !v.isInitialized {return false} + if let v = _storage._typingMessage, !v.isInitialized {return false} + if let v = _storage._configurationMessage, !v.isInitialized {return false} + if let v = _storage._dataExtractionNotification, !v.isInitialized {return false} + if let v = _storage._unsendRequest, !v.isInitialized {return false} + return true + } + } + + mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &_storage._dataMessage) }() + case 3: try { try decoder.decodeSingularMessageField(value: &_storage._callMessage) }() + case 5: try { try decoder.decodeSingularMessageField(value: &_storage._receiptMessage) }() + case 6: try { try decoder.decodeSingularMessageField(value: &_storage._typingMessage) }() + case 7: try { try decoder.decodeSingularMessageField(value: &_storage._configurationMessage) }() + case 8: try { try decoder.decodeSingularMessageField(value: &_storage._dataExtractionNotification) }() + case 9: try { try decoder.decodeSingularMessageField(value: &_storage._unsendRequest) }() + default: break + } + } + } + } + + func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + if let v = _storage._dataMessage { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } + if let v = _storage._callMessage { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } + if let v = _storage._receiptMessage { + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + } + if let v = _storage._typingMessage { + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + } + if let v = _storage._configurationMessage { + try visitor.visitSingularMessageField(value: v, fieldNumber: 7) + } + if let v = _storage._dataExtractionNotification { + try visitor.visitSingularMessageField(value: v, fieldNumber: 8) + } + if let v = _storage._unsendRequest { + try visitor.visitSingularMessageField(value: v, fieldNumber: 9) + } + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SessionProtos_Content, rhs: SessionProtos_Content) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._dataMessage != rhs_storage._dataMessage {return false} + if _storage._callMessage != rhs_storage._callMessage {return false} + if _storage._receiptMessage != rhs_storage._receiptMessage {return false} + if _storage._typingMessage != rhs_storage._typingMessage {return false} + if _storage._configurationMessage != rhs_storage._configurationMessage {return false} + if _storage._dataExtractionNotification != rhs_storage._dataExtractionNotification {return false} + if _storage._unsendRequest != rhs_storage._unsendRequest {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".CallMessage" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "type"), + 2: .same(proto: "sdp"), + ] + + public var isInitialized: Bool { + if self._type == nil {return false} + if self._sdp == nil {return false} return true } @@ -1618,51 +1809,39 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm // allocates stack space for every case branch when no optimizations are // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._dataMessage) }() - case 5: try { try decoder.decodeSingularMessageField(value: &self._receiptMessage) }() - case 6: try { try decoder.decodeSingularMessageField(value: &self._typingMessage) }() - case 7: try { try decoder.decodeSingularMessageField(value: &self._configurationMessage) }() - case 8: try { try decoder.decodeSingularMessageField(value: &self._dataExtractionNotification) }() - case 9: try { try decoder.decodeSingularMessageField(value: &self._unsendRequest) }() + case 1: try { try decoder.decodeSingularEnumField(value: &self._type) }() + case 2: try { try decoder.decodeSingularStringField(value: &self._sdp) }() default: break } } } func traverse(visitor: inout V) throws { - if let v = self._dataMessage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + if let v = self._type { + try visitor.visitSingularEnumField(value: v, fieldNumber: 1) } - if let v = self._receiptMessage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } - if let v = self._typingMessage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } - if let v = self._configurationMessage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - } - if let v = self._dataExtractionNotification { - try visitor.visitSingularMessageField(value: v, fieldNumber: 8) - } - if let v = self._unsendRequest { - try visitor.visitSingularMessageField(value: v, fieldNumber: 9) + if let v = self._sdp { + try visitor.visitSingularStringField(value: v, fieldNumber: 2) } try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: SessionProtos_Content, rhs: SessionProtos_Content) -> Bool { - if lhs._dataMessage != rhs._dataMessage {return false} - if lhs._receiptMessage != rhs._receiptMessage {return false} - if lhs._typingMessage != rhs._typingMessage {return false} - if lhs._configurationMessage != rhs._configurationMessage {return false} - if lhs._dataExtractionNotification != rhs._dataExtractionNotification {return false} - if lhs._unsendRequest != rhs._unsendRequest {return false} + 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.unknownFields != rhs.unknownFields {return false} return true } } +extension SessionProtos_CallMessage.TypeEnum: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "OFFER"), + 2: .same(proto: "ANSWER"), + 3: .same(proto: "PROVISIONAL_ANSWER"), + ] +} + extension SessionProtos_KeyPair: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = _protobuf_package + ".KeyPair" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index 5b1999255..76995d94a 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -39,15 +39,30 @@ message UnsendRequest { required uint64 timestamp = 1; // @required required string author = 2; -} +} message Content { optional DataMessage dataMessage = 1; + optional CallMessage callMessage = 3; optional ReceiptMessage receiptMessage = 5; optional TypingMessage typingMessage = 6; optional ConfigurationMessage configurationMessage = 7; optional DataExtractionNotification dataExtractionNotification = 8; - optional UnsendRequest unsendRequest = 9; + optional UnsendRequest unsendRequest = 9; +} + +message CallMessage { + + enum Type { + OFFER = 1; + ANSWER = 2; + PROVISIONAL_ANSWER = 3; + } + + // @required + required Type type = 1; + // @required + required string sdp = 2; } message KeyPair { From 1a12199d0b6c7813e3620763d74fd6b3f8455e42 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 10 Aug 2021 14:19:01 +1000 Subject: [PATCH 002/368] Implement CallManager --- Session.xcodeproj/project.pbxproj | 16 +- SessionMessagingKit/Calls/CallManager.swift | 190 ++++++++++++++++++ SessionMessagingKit/Calls/File.swift | 16 -- .../MessageReceiver+Handling.swift | 12 +- SessionMessagingKit/Threads/TSContactThread.h | 2 +- 5 files changed, 201 insertions(+), 35 deletions(-) create mode 100644 SessionMessagingKit/Calls/CallManager.swift delete mode 100644 SessionMessagingKit/Calls/File.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 8823383d9..da45f9d5e 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -266,7 +266,7 @@ B8D64FCB25BA78A90029CFC0 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; B8D84EA325DF745A005A043E /* LinkPreviewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D84EA225DF745A005A043E /* LinkPreviewState.swift */; }; B8D84ECF25E3108A005A043E /* ExpandingAttachmentsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D84ECE25E3108A005A043E /* ExpandingAttachmentsButton.swift */; }; - B8DE1FB426C22F2F0079C9CE /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8DE1FB326C22F2F0079C9CE /* File.swift */; }; + B8DE1FB426C22F2F0079C9CE /* CallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8DE1FB326C22F2F0079C9CE /* CallManager.swift */; }; B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */; }; B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */; }; B8EB20F02640F7F000773E52 /* OpenGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */; }; @@ -1248,7 +1248,7 @@ B8D8F1BC25661C6F0092EF10 /* Storage+OnionRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+OnionRequests.swift"; sourceTree = ""; }; B8D8F1EF256621180092EF10 /* MessageSender+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "MessageSender+Convenience.swift"; path = "../../SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift"; sourceTree = ""; }; B8DE1FAF26C228780079C9CE /* SignalRingRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SignalRingRTC.framework; path = Dependencies/SignalRingRTC.framework; sourceTree = ""; }; - B8DE1FB326C22F2F0079C9CE /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; }; + B8DE1FB326C22F2F0079C9CE /* CallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = ""; }; B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessage.swift; sourceTree = ""; }; B8EB20E6263F7E4B00773E52 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+OpenGroupInvitation.swift"; sourceTree = ""; }; @@ -2334,17 +2334,10 @@ path = Shared; sourceTree = ""; }; - B8DE1FB126C22AF20079C9CE /* Calls */ = { - isa = PBXGroup; - children = ( - ); - path = Calls; - sourceTree = ""; - }; B8DE1FB226C22F1F0079C9CE /* Calls */ = { isa = PBXGroup; children = ( - B8DE1FB326C22F2F0079C9CE /* File.swift */, + B8DE1FB326C22F2F0079C9CE /* CallManager.swift */, ); path = Calls; sourceTree = ""; @@ -3534,7 +3527,6 @@ children = ( C3F0A58F255C8E3D007BE2A3 /* Meta */, C36096BC25AD1C3E008B62B2 /* Backups */, - B8DE1FB126C22AF20079C9CE /* Calls */, C360969C25AD18BA008B62B2 /* Closed Groups */, B835246C25C38AA20089A44F /* Conversations */, C32B405424A961E1001117B5 /* Dependencies */, @@ -4693,7 +4685,7 @@ B8856ECE256F1E58001CE70E /* OWSPreferences.m in Sources */, C3C2A7842553AAF300C340D1 /* SNProto.swift in Sources */, C32C5DC0256DD743003C73A2 /* Poller.swift in Sources */, - B8DE1FB426C22F2F0079C9CE /* File.swift in Sources */, + B8DE1FB426C22F2F0079C9CE /* CallManager.swift in Sources */, C3C2A7682553A3D900C340D1 /* VisibleMessage+Contact.swift in Sources */, C3A3A171256E1D25004D228D /* SSKReachabilityManager.swift in Sources */, B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */, diff --git a/SessionMessagingKit/Calls/CallManager.swift b/SessionMessagingKit/Calls/CallManager.swift new file mode 100644 index 000000000..1660e9a67 --- /dev/null +++ b/SessionMessagingKit/Calls/CallManager.swift @@ -0,0 +1,190 @@ +import PromiseKit +import WebRTC + +/// See https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription for more information. +public final class CallManager : NSObject, RTCPeerConnectionDelegate { + + private lazy var factory: RTCPeerConnectionFactory = { + RTCInitializeSSL() + let videoEncoderFactory = RTCVideoEncoderFactoryH264() + let videoDecoderFactory = RTCVideoDecoderFactoryH264() + return RTCPeerConnectionFactory(encoderFactory: videoEncoderFactory, decoderFactory: videoDecoderFactory) + }() + + /// Represents a WebRTC connection between the user and a remote peer. Provides methods to connect to a + /// remote peer, maintain and monitor the connection, and close the connection once it's no longer needed. + private lazy var peerConnection: RTCPeerConnection = { + let configuration = RTCConfiguration() + // TODO: Configure + // TODO: Do these constraints make sense? + let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [ "DtlsSrtpKeyAgreement" : "true" ]) + return factory.peerConnection(with: configuration, constraints: constraints, delegate: self) + }() + + private lazy var constraints: RTCMediaConstraints = { + let mandatory: [String:String] = [ + kRTCMediaConstraintsOfferToReceiveAudio : kRTCMediaConstraintsValueTrue, + kRTCMediaConstraintsOfferToReceiveVideo : kRTCMediaConstraintsValueTrue + ] + let optional: [String:String] = [:] + // TODO: Do these constraints make sense? + return RTCMediaConstraints(mandatoryConstraints: mandatory, optionalConstraints: optional) + }() + + // Audio + private lazy var audioSource: RTCAudioSource = { + // TODO: Do these constraints make sense? + let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [:]) + return factory.audioSource(with: constraints) + }() + + private lazy var audioTrack: RTCAudioTrack = { + return factory.audioTrack(with: audioSource, trackId: "ARDAMSa0") + }() + + // Video + private lazy var localVideoSource: RTCVideoSource = { + return factory.videoSource() + }() + + private lazy var localVideoTrack: RTCVideoTrack = { + return factory.videoTrack(with: localVideoSource, trackId: "ARDAMSv0") + }() + + private lazy var videoCapturer: RTCVideoCapturer = { + return RTCCameraVideoCapturer(delegate: localVideoSource) + }() + + private lazy var remoteVideoTrack: RTCVideoTrack? = { + return peerConnection.receivers.first { $0.track.kind == "video" }?.track as? RTCVideoTrack + }() + + // Stream + private lazy var stream: RTCMediaStream = { + let result = factory.mediaStream(withStreamId: "ARDAMS") + result.addAudioTrack(audioTrack) + result.addVideoTrack(localVideoTrack) + return result + }() + + // MARK: Error + public enum Error : LocalizedError { + case noThread + + public var errorDescription: String? { + switch self { + case .noThread: return "Couldn't find thread for contact." + } + } + } + + // MARK: Initialization + private override init() { + super.init() + peerConnection.add(stream) + // Configure audio session + let audioSession = RTCAudioSession.sharedInstance() + audioSession.lockForConfiguration() + do { + try audioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue) + try audioSession.setMode(AVAudioSession.Mode.voiceChat.rawValue) + try audioSession.overrideOutputAudioPort(.speaker) + try audioSession.setActive(true) + } catch let error { + SNLog("Couldn't set up WebRTC audio session due to error: \(error)") + } + audioSession.unlockForConfiguration() + } + + public static let shared = CallManager() + + // MARK: Call Management + public func initiateCall(with publicKey: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { + guard let thread = TSContactThread.fetch(for: publicKey, using: transaction) else { return Promise(error: Error.noThread) } + let (promise, seal) = Promise.pending() + peerConnection.offer(for: constraints) { [weak self] sdp, error in + if let error = error { + seal.reject(error) + } else { + guard let self = self, let sdp = sdp else { preconditionFailure() } + self.peerConnection.setLocalDescription(sdp) { error in + if let error = error { + print("Couldn't initiate call due to error: \(error).") + return seal.reject(error) + } + } + let message = CallMessage() + message.type = .offer + message.sdp = sdp.sdp + MessageSender.send(message, in: thread, using: transaction) + seal.fulfill(()) + } + } + return promise + } + + public func acceptCall(with publicKey: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { + guard let thread = TSContactThread.fetch(for: publicKey, using: transaction) else { return Promise(error: Error.noThread) } + let (promise, seal) = Promise.pending() + peerConnection.answer(for: constraints) { [weak self] sdp, error in + if let error = error { + seal.reject(error) + } else { + guard let self = self, let sdp = sdp else { preconditionFailure() } + self.peerConnection.setLocalDescription(sdp) { error in + if let error = error { + print("Couldn't accept call due to error: \(error).") + return seal.reject(error) + } + } + let message = CallMessage() + message.type = .answer + message.sdp = sdp.sdp + MessageSender.send(message, in: thread, using: transaction) + seal.fulfill(()) + } + } + return promise + } + + public func endCall() { + peerConnection.close() + } + + // MARK: Delegate + public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCSignalingState) { + SNLog("Signaling state changed to: \(state).") + } + + public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { + // Do nothing + } + + public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) { + // Do nothing + } + + public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) { + // Do nothing + } + + public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCIceConnectionState) { + SNLog("ICE connection state changed to: \(state).") + } + + public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCIceGatheringState) { + SNLog("ICE gathering state changed to: \(state).") + } + + public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { + SNLog("ICE candidate generated.") + } + + public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) { + SNLog("\(candidates.count) ICE candidate(s) removed.") + } + + public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { + SNLog("Data channel opened.") + } +} diff --git a/SessionMessagingKit/Calls/File.swift b/SessionMessagingKit/Calls/File.swift deleted file mode 100644 index 16c7be30c..000000000 --- a/SessionMessagingKit/Calls/File.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import WebRTC - -//The RTCSessionDescription interface describes one end of a connection—or potential connection—and how it's configured. Each RTCSessionDescription consists of a description type indicating which part of the offer/answer negotiation process it describes and of the SDP descriptor of the session. - -enum Foo { - - func bar() { - - - RTCSessionDescription(type: .answer, sdp: "") - } -} diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index e06c502b9..7bf42c26e 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -52,7 +52,7 @@ extension MessageReceiver { public static func showTypingIndicatorIfNeeded(for senderPublicKey: String) { var threadOrNil: TSContactThread? Storage.read { transaction in - threadOrNil = TSContactThread.getWithContactSessionID(senderPublicKey, transaction: transaction) + threadOrNil = TSContactThread.fetch(for: senderPublicKey, using: transaction) } guard let thread = threadOrNil else { return } func showTypingIndicatorsIfNeeded() { @@ -70,7 +70,7 @@ extension MessageReceiver { public static func hideTypingIndicatorIfNeeded(for senderPublicKey: String) { var threadOrNil: TSContactThread? Storage.read { transaction in - threadOrNil = TSContactThread.getWithContactSessionID(senderPublicKey, transaction: transaction) + threadOrNil = TSContactThread.fetch(for: senderPublicKey, using: transaction) } guard let thread = threadOrNil else { return } func hideTypingIndicatorsIfNeeded() { @@ -88,7 +88,7 @@ extension MessageReceiver { public static func cancelTypingIndicatorsIfNeeded(for senderPublicKey: String) { var threadOrNil: TSContactThread? Storage.read { transaction in - threadOrNil = TSContactThread.getWithContactSessionID(senderPublicKey, transaction: transaction) + threadOrNil = TSContactThread.fetch(for: senderPublicKey, using: transaction) } guard let thread = threadOrNil else { return } func cancelTypingIndicatorsIfNeeded() { @@ -110,7 +110,7 @@ extension MessageReceiver { private static func handleDataExtractionNotification(_ message: DataExtractionNotification, using transaction: Any) { let transaction = transaction as! YapDatabaseReadWriteTransaction guard message.groupPublicKey == nil, - let thread = TSContactThread.getWithContactSessionID(message.sender!, transaction: transaction) else { return } + let thread = TSContactThread.fetch(for: message.sender!, using: transaction) else { return } let type: TSInfoMessageType switch message.kind! { case .screenshot: type = .screenshotNotification @@ -140,7 +140,7 @@ extension MessageReceiver { let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) threadOrNil = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) } else { - threadOrNil = TSContactThread.getWithContactSessionID(syncTarget ?? senderPublicKey, transaction: transaction) + threadOrNil = TSContactThread.fetch(for: syncTarget ?? senderPublicKey, using: transaction) } guard let thread = threadOrNil else { return } let configuration = OWSDisappearingMessagesConfiguration(threadId: thread.uniqueId!, enabled: true, durationSeconds: duration) @@ -160,7 +160,7 @@ extension MessageReceiver { let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) threadOrNil = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) } else { - threadOrNil = TSContactThread.getWithContactSessionID(syncTarget ?? senderPublicKey, transaction: transaction) + threadOrNil = TSContactThread.fetch(for: syncTarget ?? senderPublicKey, using: transaction) } guard let thread = threadOrNil else { return } let configuration = OWSDisappearingMessagesConfiguration(threadId: thread.uniqueId!, enabled: false, durationSeconds: 24 * 60 * 60) diff --git a/SessionMessagingKit/Threads/TSContactThread.h b/SessionMessagingKit/Threads/TSContactThread.h index f40a7b98c..f3e6aa085 100644 --- a/SessionMessagingKit/Threads/TSContactThread.h +++ b/SessionMessagingKit/Threads/TSContactThread.h @@ -18,7 +18,7 @@ extern NSString *const TSContactThreadPrefix; transaction:(YapDatabaseReadWriteTransaction *)transaction; // Unlike getOrCreateThreadWithContactSessionID, this will _NOT_ create a thread if one does not already exist. -+ (nullable instancetype)getThreadWithContactSessionID:(NSString *)contactSessionID transaction:(YapDatabaseReadTransaction *)transaction; ++ (nullable instancetype)getThreadWithContactSessionID:(NSString *)contactSessionID transaction:(YapDatabaseReadTransaction *)transaction NS_SWIFT_NAME(fetch(for:using:)); - (NSString *)contactSessionID; From 67792ad15f476c4907df1c26b795f686f4be074c Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 12 Aug 2021 10:52:41 +1000 Subject: [PATCH 003/368] Add UI utilities --- Session.xcodeproj/project.pbxproj | 4 ++++ .../Calls/CallManager+UI.swift | 16 ++++++++++++++ SessionMessagingKit/Calls/CallManager.swift | 22 +++++++++---------- 3 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 SessionMessagingKit/Calls/CallManager+UI.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index da45f9d5e..60c08d13e 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -157,6 +157,7 @@ B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */; }; B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */; }; B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */; }; + B806ECA126C4A7E4008BDA44 /* CallManager+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */; }; B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */; }; B817AD9A26436593009DF825 /* SimplifiedConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */; }; B817AD9C26436F73009DF825 /* ThreadPickerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */; }; @@ -1142,6 +1143,7 @@ B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = System/Library/Frameworks/PushKit.framework; sourceTree = SDKROOT; }; B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderView.swift; sourceTree = ""; }; B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCell.swift; sourceTree = ""; }; + B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallManager+UI.swift"; sourceTree = ""; }; B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = ""; }; B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedConversationCell.swift; sourceTree = ""; }; B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerVC.swift; sourceTree = ""; }; @@ -2338,6 +2340,7 @@ isa = PBXGroup; children = ( B8DE1FB326C22F2F0079C9CE /* CallManager.swift */, + B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */, ); path = Calls; sourceTree = ""; @@ -4765,6 +4768,7 @@ C32C5D23256DD4C0003C73A2 /* Mention.swift in Sources */, C32C5FD6256E0346003C73A2 /* Notification+Thread.swift in Sources */, C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */, + B806ECA126C4A7E4008BDA44 /* CallManager+UI.swift in Sources */, C32C5C88256DD0D2003C73A2 /* Storage+Messaging.swift in Sources */, C32C59C7256DB41F003C73A2 /* TSThread.m in Sources */, C300A5B22554AF9800555489 /* VisibleMessage+Profile.swift in Sources */, diff --git a/SessionMessagingKit/Calls/CallManager+UI.swift b/SessionMessagingKit/Calls/CallManager+UI.swift new file mode 100644 index 000000000..7ef7af1ea --- /dev/null +++ b/SessionMessagingKit/Calls/CallManager+UI.swift @@ -0,0 +1,16 @@ +import WebRTC + +extension CallManager { + + func attachLocalRenderer(_ renderer: RTCVideoRenderer) { + localVideoTrack.add(renderer) + } + + func attachRemoteRenderer(_ renderer: RTCVideoRenderer) { + remoteVideoTrack?.add(renderer) + } + + func handleLocalFrameCaptured(_ videoFrame: RTCVideoFrame) { + localVideoSource.capturer(videoCapturer, didCapture: videoFrame) + } +} diff --git a/SessionMessagingKit/Calls/CallManager.swift b/SessionMessagingKit/Calls/CallManager.swift index 1660e9a67..50db4c794 100644 --- a/SessionMessagingKit/Calls/CallManager.swift +++ b/SessionMessagingKit/Calls/CallManager.swift @@ -4,7 +4,7 @@ import WebRTC /// See https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription for more information. public final class CallManager : NSObject, RTCPeerConnectionDelegate { - private lazy var factory: RTCPeerConnectionFactory = { + internal lazy var factory: RTCPeerConnectionFactory = { RTCInitializeSSL() let videoEncoderFactory = RTCVideoEncoderFactoryH264() let videoDecoderFactory = RTCVideoDecoderFactoryH264() @@ -13,7 +13,7 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { /// Represents a WebRTC connection between the user and a remote peer. Provides methods to connect to a /// remote peer, maintain and monitor the connection, and close the connection once it's no longer needed. - private lazy var peerConnection: RTCPeerConnection = { + internal lazy var peerConnection: RTCPeerConnection = { let configuration = RTCConfiguration() // TODO: Configure // TODO: Do these constraints make sense? @@ -21,7 +21,7 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { return factory.peerConnection(with: configuration, constraints: constraints, delegate: self) }() - private lazy var constraints: RTCMediaConstraints = { + internal lazy var constraints: RTCMediaConstraints = { let mandatory: [String:String] = [ kRTCMediaConstraintsOfferToReceiveAudio : kRTCMediaConstraintsValueTrue, kRTCMediaConstraintsOfferToReceiveVideo : kRTCMediaConstraintsValueTrue @@ -32,35 +32,35 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { }() // Audio - private lazy var audioSource: RTCAudioSource = { + internal lazy var audioSource: RTCAudioSource = { // TODO: Do these constraints make sense? let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [:]) return factory.audioSource(with: constraints) }() - private lazy var audioTrack: RTCAudioTrack = { + internal lazy var audioTrack: RTCAudioTrack = { return factory.audioTrack(with: audioSource, trackId: "ARDAMSa0") }() // Video - private lazy var localVideoSource: RTCVideoSource = { + internal lazy var localVideoSource: RTCVideoSource = { return factory.videoSource() }() - private lazy var localVideoTrack: RTCVideoTrack = { + internal lazy var localVideoTrack: RTCVideoTrack = { return factory.videoTrack(with: localVideoSource, trackId: "ARDAMSv0") }() - private lazy var videoCapturer: RTCVideoCapturer = { + internal lazy var videoCapturer: RTCVideoCapturer = { return RTCCameraVideoCapturer(delegate: localVideoSource) }() - private lazy var remoteVideoTrack: RTCVideoTrack? = { + internal lazy var remoteVideoTrack: RTCVideoTrack? = { return peerConnection.receivers.first { $0.track.kind == "video" }?.track as? RTCVideoTrack }() // Stream - private lazy var stream: RTCMediaStream = { + internal lazy var stream: RTCMediaStream = { let result = factory.mediaStream(withStreamId: "ARDAMS") result.addAudioTrack(audioTrack) result.addVideoTrack(localVideoTrack) @@ -79,7 +79,7 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { } // MARK: Initialization - private override init() { + internal override init() { super.init() peerConnection.add(stream) // Configure audio session From b513eeb898d12eb80c9ce1dd9ba3c9e9be20e555 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 12 Aug 2021 11:46:04 +1000 Subject: [PATCH 004/368] Add mock TURN server implementation --- Session.xcodeproj/project.pbxproj | 4 ++++ SessionMessagingKit/Calls/TURNServer.swift | 20 ++++++++++++++++++++ SessionUtilitiesKit/Networking/HTTP.swift | 5 ++++- 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 SessionMessagingKit/Calls/TURNServer.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 60c08d13e..1b415eb5a 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -158,6 +158,7 @@ B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */; }; B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */; }; B806ECA126C4A7E4008BDA44 /* CallManager+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */; }; + B806ECA326C4A8C6008BDA44 /* TURNServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA226C4A8C6008BDA44 /* TURNServer.swift */; }; B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */; }; B817AD9A26436593009DF825 /* SimplifiedConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */; }; B817AD9C26436F73009DF825 /* ThreadPickerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */; }; @@ -1144,6 +1145,7 @@ B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderView.swift; sourceTree = ""; }; B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCell.swift; sourceTree = ""; }; B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallManager+UI.swift"; sourceTree = ""; }; + B806ECA226C4A8C6008BDA44 /* TURNServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TURNServer.swift; sourceTree = ""; }; B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = ""; }; B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedConversationCell.swift; sourceTree = ""; }; B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerVC.swift; sourceTree = ""; }; @@ -2341,6 +2343,7 @@ children = ( B8DE1FB326C22F2F0079C9CE /* CallManager.swift */, B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */, + B806ECA226C4A8C6008BDA44 /* TURNServer.swift */, ); path = Calls; sourceTree = ""; @@ -4753,6 +4756,7 @@ B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */, C3DB66C3260ACCE6001EFC55 /* OpenGroupPollerV2.swift in Sources */, C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */, + B806ECA326C4A8C6008BDA44 /* TURNServer.swift in Sources */, C32C5FBB256E0206003C73A2 /* OWSBackgroundTask.m in Sources */, B8856CA8256F0F42001CE70E /* OWSBackupFragment.m in Sources */, C32C5C3D256DCBAF003C73A2 /* AppReadiness.m in Sources */, diff --git a/SessionMessagingKit/Calls/TURNServer.swift b/SessionMessagingKit/Calls/TURNServer.swift new file mode 100644 index 000000000..f00af77ec --- /dev/null +++ b/SessionMessagingKit/Calls/TURNServer.swift @@ -0,0 +1,20 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import PromiseKit + +enum MockTURNSserver { + + static func getICEServerURL() -> Promise { + HTTP.execute(.get, "https://appr.tc/params").map2 { json in + guard let url = json["ice_server_url"] as? String else { throw HTTP.Error.invalidJSON } + return url + } + } + + static func makeTurnServerRequest(iceServerURL: String) -> Promise { + let headers = [ "referer" : "https://appr.tc" ] + return HTTP.execute(.post, iceServerURL, body: nil, headers: headers) + } +} diff --git a/SessionUtilitiesKit/Networking/HTTP.swift b/SessionUtilitiesKit/Networking/HTTP.swift index 5ce5e12ac..e1b2d5f93 100644 --- a/SessionUtilitiesKit/Networking/HTTP.swift +++ b/SessionUtilitiesKit/Networking/HTTP.swift @@ -109,7 +109,7 @@ public enum HTTP { } } - public static func execute(_ verb: Verb, _ url: String, body: Data?, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise { + public static func execute(_ verb: Verb, _ url: String, body: Data?, headers: [String:String] = [:], timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise { var request = URLRequest(url: URL(string: url)!) request.httpMethod = verb.rawValue request.httpBody = body @@ -117,6 +117,9 @@ public enum HTTP { request.allHTTPHeaderFields?.removeValue(forKey: "User-Agent") request.setValue("WhatsApp", forHTTPHeaderField: "User-Agent") // Set a fake value request.setValue("en-us", forHTTPHeaderField: "Accept-Language") // Set a fake value + headers.forEach { (key, value) in + request.setValue(value, forHTTPHeaderField: key) + } let (promise, seal) = Promise.pending() let urlSession = useSeedNodeURLSession ? seedNodeURLSession : snodeURLSession let task = urlSession.dataTask(with: request) { data, response, error in From 170da7a276e17b46af30b2bfcb242796d32ad6bd Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 12 Aug 2021 13:12:44 +1000 Subject: [PATCH 005/368] Implement VideoCallVC & CameraManager --- Podfile | 3 + Podfile.lock | 2 +- Session.xcodeproj/project.pbxproj | 16 +++++ Session/Calls/CameraManager.swift | 69 +++++++++++++++++++ Session/Calls/VideoCallVC.swift | 58 ++++++++++++++++ .../Calls/CallManager+UI.swift | 6 +- SessionMessagingKit/Calls/TURNServer.swift | 4 -- 7 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 Session/Calls/CameraManager.swift create mode 100644 Session/Calls/VideoCallVC.swift diff --git a/Podfile b/Podfile index 1369c0913..d6631360a 100644 --- a/Podfile +++ b/Podfile @@ -26,12 +26,14 @@ target 'SessionShareExtension' do pod 'PromiseKit', :inhibit_warnings => true pod 'PureLayout', '~> 3.1.8', :inhibit_warnings => true pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true + pod 'WebRTC', '~> 63.11', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true end target 'SessionNotificationServiceExtension' do pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true + pod 'WebRTC', '~> 63.11', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true end @@ -49,6 +51,7 @@ target 'SignalUtilitiesKit' do pod 'SAMKeychain', :inhibit_warnings => true pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true + pod 'WebRTC', '~> 63.11', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true pod 'YYImage', git: 'https://github.com/signalapp/YYImage', :inhibit_warnings => true end diff --git a/Podfile.lock b/Podfile.lock index 86d31c1b4..b917479f8 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -212,6 +212,6 @@ SPEC CHECKSUMS: YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 15bcb2aeee31dc86a3a9febc85208ba890b0dddf +PODFILE CHECKSUM: e94e0a63e3b5609dad5b74fbb8e1266ccce2f011 COCOAPODS: 1.10.1 diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 1b415eb5a..6faf55ff1 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -247,6 +247,8 @@ B8B32033258B235D0020074B /* Storage+Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B32032258B235D0020074B /* Storage+Contacts.swift */; }; B8B3204E258C15C80020074B /* ContactsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B32044258C117C0020074B /* ContactsMigration.swift */; }; B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B320B6258C30D70020074B /* HTMLMetadata.swift */; }; + B8B558EF26C4B56C00693325 /* VideoCallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558EE26C4B56C00693325 /* VideoCallVC.swift */; }; + B8B558F126C4BB0600693325 /* CameraManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F026C4BB0600693325 /* CameraManager.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 */; }; @@ -1215,6 +1217,8 @@ B8B32032258B235D0020074B /* Storage+Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Contacts.swift"; sourceTree = ""; }; B8B32044258C117C0020074B /* ContactsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsMigration.swift; sourceTree = ""; }; B8B320B6258C30D70020074B /* HTMLMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadata.swift; sourceTree = ""; }; + B8B558EE26C4B56C00693325 /* VideoCallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCallVC.swift; sourceTree = ""; }; + B8B558F026C4BB0600693325 /* CameraManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = ""; }; B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B8BAC75B2695645400EA1759 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; B8BAC75C2695648500EA1759 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; @@ -2312,6 +2316,15 @@ path = Contacts; sourceTree = ""; }; + B8B558ED26C4B55F00693325 /* Calls */ = { + isa = PBXGroup; + children = ( + B8B558EE26C4B56C00693325 /* VideoCallVC.swift */, + B8B558F026C4BB0600693325 /* CameraManager.swift */, + ); + path = Calls; + sourceTree = ""; + }; B8CCF63B239757C10091D419 /* Shared */ = { isa = PBXGroup; children = ( @@ -3533,6 +3546,7 @@ children = ( C3F0A58F255C8E3D007BE2A3 /* Meta */, C36096BC25AD1C3E008B62B2 /* Backups */, + B8B558ED26C4B55F00693325 /* Calls */, C360969C25AD18BA008B62B2 /* Closed Groups */, B835246C25C38AA20089A44F /* Conversations */, C32B405424A961E1001117B5 /* Dependencies */, @@ -4872,6 +4886,7 @@ C328254925CA60E60062D0A7 /* ContextMenuVC+Action.swift in Sources */, 4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */, 34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */, + B8B558F126C4BB0600693325 /* CameraManager.swift in Sources */, B8F5F71A25F1B35C003BF8D4 /* MediaPlaceholderView.swift in Sources */, 341341EF2187467A00192D59 /* ConversationViewModel.m in Sources */, 4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */, @@ -4922,6 +4937,7 @@ B8269D3D25C7B34D00488AB4 /* InputTextView.swift in Sources */, 76EB054018170B33006006FC /* AppDelegate.m in Sources */, 340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */, + B8B558EF26C4B56C00693325 /* VideoCallVC.swift in Sources */, C33100082558FF6D00070591 /* NewConversationButtonSet.swift in Sources */, C3AAFFF225AE99710089E6DD /* AppDelegate.swift in Sources */, B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */, diff --git a/Session/Calls/CameraManager.swift b/Session/Calls/CameraManager.swift new file mode 100644 index 000000000..622fb13b0 --- /dev/null +++ b/Session/Calls/CameraManager.swift @@ -0,0 +1,69 @@ +import Foundation +import AVFoundation + +@objc +protocol CameraCaptureDelegate : AnyObject { + + func captureVideoOutput(sampleBuffer: CMSampleBuffer) +} + +final class CameraManager : NSObject { + private let captureSession = AVCaptureSession() + private let videoDataOutput = AVCaptureVideoDataOutput() + private let audioDataOutput = AVCaptureAudioDataOutput() + private let dataOutputQueue = DispatchQueue(label: "CameraManager.dataOutputQueue", qos: .userInitiated, attributes: [], autoreleaseFrequency: .workItem) + private var isCapturing = false + weak var delegate: CameraCaptureDelegate? + + private lazy var videoCaptureDevice: AVCaptureDevice? = { + return AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) + }() + + static let shared = CameraManager() + + private override init() { } + + func prepare() { + if let videoCaptureDevice = videoCaptureDevice, + let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice), captureSession.canAddInput(videoInput) { + captureSession.addInput(videoInput) + } + if captureSession.canAddOutput(videoDataOutput) { + captureSession.addOutput(videoDataOutput) + videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)] + videoDataOutput.setSampleBufferDelegate(self, queue: dataOutputQueue) + videoDataOutput.connection(with: .video)?.videoOrientation = .portrait + videoDataOutput.connection(with: .video)?.automaticallyAdjustsVideoMirroring = false + videoDataOutput.connection(with: .video)?.isVideoMirrored = true + } else { + SNLog("Couldn't add video data output to capture session.") + captureSession.commitConfiguration() + } + } + + func start() { + guard !isCapturing else { return } + isCapturing = true + #if arch(arm64) + captureSession.startRunning() + #endif + } + + func stop() { + guard isCapturing else { return } + isCapturing = false + #if arch(arm64) + captureSession.stopRunning() + #endif + } +} + +extension CameraManager : AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate { + + func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { + guard connection == videoDataOutput.connection(with: .video) else { return } + delegate?.captureVideoOutput(sampleBuffer: sampleBuffer) + } + + func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { } +} diff --git a/Session/Calls/VideoCallVC.swift b/Session/Calls/VideoCallVC.swift new file mode 100644 index 000000000..5bb8501a5 --- /dev/null +++ b/Session/Calls/VideoCallVC.swift @@ -0,0 +1,58 @@ +import UIKit +import AVFoundation +import WebRTC + +class VideoCallVC : UIViewController { + private var localVideoView: UIView! + private var remoteVideoView: UIView! + + override func viewDidLoad() { + super.viewDidLoad() + setUpViewHierarchy() + CameraManager.shared.delegate = self + } + + private func setUpViewHierarchy() { + // Create video views + #if arch(arm64) + // Use Metal + let localRenderer = RTCMTLVideoView(frame: self.localVideoView.frame) + localRenderer.contentMode = .scaleAspectFill + let remoteRenderer = RTCMTLVideoView(frame: self.remoteVideoView.frame) + remoteRenderer.contentMode = .scaleAspectFill + #else + // Use OpenGLES + let localRenderer = RTCEAGLVideoView(frame: self.localVideoView.frame) + let remoteRenderer = RTCEAGLVideoView(frame: self.remoteVideoView.frame) + #endif + // Set up stack view + let stackView = UIStackView(arrangedSubviews: [ localVideoView, remoteVideoView ]) + stackView.axis = .vertical + stackView.distribution = .fillEqually + stackView.alignment = .fill + view.addSubview(stackView) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.pin(to: view) + // Attach video views + CallManager.shared.attachLocalRenderer(localRenderer) + CallManager.shared.attachRemoteRenderer(remoteRenderer) + localVideoView.addSubview(localRenderer) + localRenderer.translatesAutoresizingMaskIntoConstraints = false + localRenderer.pin(to: localVideoView) + remoteVideoView.addSubview(remoteRenderer) + remoteRenderer.translatesAutoresizingMaskIntoConstraints = false + remoteRenderer.pin(to: remoteVideoView) + } +} + +// MARK: Camera +extension VideoCallVC : CameraCaptureDelegate { + + func captureVideoOutput(sampleBuffer: CMSampleBuffer) { + guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } + let rtcpixelBuffer = RTCCVPixelBuffer(pixelBuffer: pixelBuffer) + let timeStampNs = Int64(CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) * 1000000000) + let videoFrame = RTCVideoFrame(buffer: rtcpixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: timeStampNs) + CallManager.shared.handleLocalFrameCaptured(videoFrame) + } +} diff --git a/SessionMessagingKit/Calls/CallManager+UI.swift b/SessionMessagingKit/Calls/CallManager+UI.swift index 7ef7af1ea..d9effa6c9 100644 --- a/SessionMessagingKit/Calls/CallManager+UI.swift +++ b/SessionMessagingKit/Calls/CallManager+UI.swift @@ -2,15 +2,15 @@ import WebRTC extension CallManager { - func attachLocalRenderer(_ renderer: RTCVideoRenderer) { + public func attachLocalRenderer(_ renderer: RTCVideoRenderer) { localVideoTrack.add(renderer) } - func attachRemoteRenderer(_ renderer: RTCVideoRenderer) { + public func attachRemoteRenderer(_ renderer: RTCVideoRenderer) { remoteVideoTrack?.add(renderer) } - func handleLocalFrameCaptured(_ videoFrame: RTCVideoFrame) { + public func handleLocalFrameCaptured(_ videoFrame: RTCVideoFrame) { localVideoSource.capturer(videoCapturer, didCapture: videoFrame) } } diff --git a/SessionMessagingKit/Calls/TURNServer.swift b/SessionMessagingKit/Calls/TURNServer.swift index f00af77ec..924f3432a 100644 --- a/SessionMessagingKit/Calls/TURNServer.swift +++ b/SessionMessagingKit/Calls/TURNServer.swift @@ -1,7 +1,3 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - import PromiseKit enum MockTURNSserver { From 9664274a213e5c371f927a5f1092070117d18c6d Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 12 Aug 2021 13:14:02 +1000 Subject: [PATCH 006/368] =?UTF-8?q?Rename=20TURNServer=20=E2=86=92=20MockT?= =?UTF-8?q?URNServer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Session.xcodeproj/project.pbxproj | 8 ++++---- .../Calls/{TURNServer.swift => MockTURNServer.swift} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename SessionMessagingKit/Calls/{TURNServer.swift => MockTURNServer.swift} (100%) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6faf55ff1..eb525f809 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -158,7 +158,7 @@ B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */; }; B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */; }; B806ECA126C4A7E4008BDA44 /* CallManager+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */; }; - B806ECA326C4A8C6008BDA44 /* TURNServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA226C4A8C6008BDA44 /* TURNServer.swift */; }; + B806ECA326C4A8C6008BDA44 /* MockTURNServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA226C4A8C6008BDA44 /* MockTURNServer.swift */; }; B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */; }; B817AD9A26436593009DF825 /* SimplifiedConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */; }; B817AD9C26436F73009DF825 /* ThreadPickerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */; }; @@ -1147,7 +1147,7 @@ B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderView.swift; sourceTree = ""; }; B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCell.swift; sourceTree = ""; }; B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallManager+UI.swift"; sourceTree = ""; }; - B806ECA226C4A8C6008BDA44 /* TURNServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TURNServer.swift; sourceTree = ""; }; + B806ECA226C4A8C6008BDA44 /* MockTURNServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTURNServer.swift; sourceTree = ""; }; B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = ""; }; B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedConversationCell.swift; sourceTree = ""; }; B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerVC.swift; sourceTree = ""; }; @@ -2356,7 +2356,7 @@ children = ( B8DE1FB326C22F2F0079C9CE /* CallManager.swift */, B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */, - B806ECA226C4A8C6008BDA44 /* TURNServer.swift */, + B806ECA226C4A8C6008BDA44 /* MockTURNServer.swift */, ); path = Calls; sourceTree = ""; @@ -4770,7 +4770,7 @@ B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */, C3DB66C3260ACCE6001EFC55 /* OpenGroupPollerV2.swift in Sources */, C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */, - B806ECA326C4A8C6008BDA44 /* TURNServer.swift in Sources */, + B806ECA326C4A8C6008BDA44 /* MockTURNServer.swift in Sources */, C32C5FBB256E0206003C73A2 /* OWSBackgroundTask.m in Sources */, B8856CA8256F0F42001CE70E /* OWSBackupFragment.m in Sources */, C32C5C3D256DCBAF003C73A2 /* AppReadiness.m in Sources */, diff --git a/SessionMessagingKit/Calls/TURNServer.swift b/SessionMessagingKit/Calls/MockTURNServer.swift similarity index 100% rename from SessionMessagingKit/Calls/TURNServer.swift rename to SessionMessagingKit/Calls/MockTURNServer.swift From 4fd720cbc94e0b586fd0fd1cfc0df25e470eb930 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 12 Aug 2021 13:20:34 +1000 Subject: [PATCH 007/368] Implement MockCallConfig --- Session.xcodeproj/project.pbxproj | 4 +++ .../Calls/MockCallConfig.swift | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 SessionMessagingKit/Calls/MockCallConfig.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index eb525f809..dd7b34518 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -249,6 +249,7 @@ B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B320B6258C30D70020074B /* HTMLMetadata.swift */; }; B8B558EF26C4B56C00693325 /* VideoCallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558EE26C4B56C00693325 /* VideoCallVC.swift */; }; B8B558F126C4BB0600693325 /* CameraManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F026C4BB0600693325 /* CameraManager.swift */; }; + B8B558F326C4CA4600693325 /* MockCallConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F226C4CA4600693325 /* MockCallConfig.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 */; }; @@ -1219,6 +1220,7 @@ B8B320B6258C30D70020074B /* HTMLMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadata.swift; sourceTree = ""; }; B8B558EE26C4B56C00693325 /* VideoCallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCallVC.swift; sourceTree = ""; }; B8B558F026C4BB0600693325 /* CameraManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = ""; }; + B8B558F226C4CA4600693325 /* MockCallConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCallConfig.swift; sourceTree = ""; }; B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B8BAC75B2695645400EA1759 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; B8BAC75C2695648500EA1759 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; @@ -2357,6 +2359,7 @@ B8DE1FB326C22F2F0079C9CE /* CallManager.swift */, B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */, B806ECA226C4A8C6008BDA44 /* MockTURNServer.swift */, + B8B558F226C4CA4600693325 /* MockCallConfig.swift */, ); path = Calls; sourceTree = ""; @@ -4739,6 +4742,7 @@ C32C5AAB256DBE8F003C73A2 /* TSIncomingMessage+Conversion.swift in Sources */, B866CE112581C1A900535CC4 /* Sodium+Conversion.swift in Sources */, C32C5A88256DBCF9003C73A2 /* MessageReceiver+Handling.swift in Sources */, + B8B558F326C4CA4600693325 /* MockCallConfig.swift in Sources */, C32C5C1B256DC9E0003C73A2 /* General.swift in Sources */, C32C5A02256DB658003C73A2 /* MessageSender+Convenience.swift in Sources */, B8566C6C256F60F50045A0B9 /* OWSUserProfile.m in Sources */, diff --git a/SessionMessagingKit/Calls/MockCallConfig.swift b/SessionMessagingKit/Calls/MockCallConfig.swift new file mode 100644 index 000000000..4613f600b --- /dev/null +++ b/SessionMessagingKit/Calls/MockCallConfig.swift @@ -0,0 +1,25 @@ + +public struct MockCallConfig { + public let signalingServerURL: String + public let serverURL: String + public let webRTCICEServers: [String] + + private static let defaultSignalingServerURL = "ws://developereric.com:8080" + private 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" + ] + private static let defaultServerURL = "https://appr.tc" + + private init(signalingServerURL: String, serverURL: String, webRTCICEServers: [String]) { + self.signalingServerURL = signalingServerURL + self.serverURL = serverURL + self.webRTCICEServers = webRTCICEServers + } + + public static let `default` = MockCallConfig(signalingServerURL: defaultSignalingServerURL, + serverURL: defaultServerURL, webRTCICEServers: defaultICEServers) +} From 32426f900519c288af105464fea7d0516b3f9edb Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 12 Aug 2021 13:49:10 +1000 Subject: [PATCH 008/368] Add CallVC --- Session.xcodeproj/project.pbxproj | 4 ++ Session/Calls/CallVC.swift | 73 +++++++++++++++++++++ Session/Calls/VideoCallVC.swift | 2 +- SessionMessagingKit/Calls/CallManager.swift | 6 ++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 Session/Calls/CallVC.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index dd7b34518..0e0679185 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -250,6 +250,7 @@ B8B558EF26C4B56C00693325 /* VideoCallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558EE26C4B56C00693325 /* VideoCallVC.swift */; }; B8B558F126C4BB0600693325 /* CameraManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F026C4BB0600693325 /* CameraManager.swift */; }; B8B558F326C4CA4600693325 /* MockCallConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F226C4CA4600693325 /* MockCallConfig.swift */; }; + B8B558F926C4CE6800693325 /* CallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F826C4CE6800693325 /* CallVC.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 */; }; @@ -1221,6 +1222,7 @@ B8B558EE26C4B56C00693325 /* VideoCallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCallVC.swift; sourceTree = ""; }; B8B558F026C4BB0600693325 /* CameraManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = ""; }; B8B558F226C4CA4600693325 /* MockCallConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCallConfig.swift; sourceTree = ""; }; + B8B558F826C4CE6800693325 /* CallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVC.swift; sourceTree = ""; }; B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B8BAC75B2695645400EA1759 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; B8BAC75C2695648500EA1759 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; @@ -2321,6 +2323,7 @@ B8B558ED26C4B55F00693325 /* Calls */ = { isa = PBXGroup; children = ( + B8B558F826C4CE6800693325 /* CallVC.swift */, B8B558EE26C4B56C00693325 /* VideoCallVC.swift */, B8B558F026C4BB0600693325 /* CameraManager.swift */, ); @@ -4858,6 +4861,7 @@ B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */, D221A09A169C9E5E00537ABF /* main.m in Sources */, 3496957221A301A100DCFE74 /* OWSBackup.m in Sources */, + B8B558F926C4CE6800693325 /* CallVC.swift in Sources */, B835247925C38D880089A44F /* MessageCell.swift in Sources */, B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */, 34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */, diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift new file mode 100644 index 000000000..8260beec4 --- /dev/null +++ b/Session/Calls/CallVC.swift @@ -0,0 +1,73 @@ +import UIKit +import AVFoundation +import WebRTC + +final class MainChatRoomViewController : UIViewController, CameraCaptureDelegate, CallManagerDelegate { + + // MARK: UI Components + private lazy var previewView: UIImageView = { + return UIImageView() + }() + + private lazy var containerView: UIView = { + return UIView() + }() + + private lazy var joinButton: UIButton = { + let result = UIButton() + result.setTitle("Join", for: UIControl.State.normal) + return result + }() + + private lazy var roomNumberTextField: UITextField = { + return UITextField() + }() + + private lazy var infoTextView: UITextView = { + return UITextView() + }() + + // MARK: Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + setUpCamera() + } + + private func setUpCamera() { + CameraManager.shared.delegate = self + CameraManager.shared.prepare() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + CameraManager.shared.start() + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + CameraManager.shared.stop() + } + + // MARK: Streaming + func callManager(_ callManager: CallManager, sendData data: Data) { + // TODO: Implement + } + + // MARK: Camera + func captureVideoOutput(sampleBuffer: CMSampleBuffer) { + guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } + let ciImage = CIImage(cvImageBuffer: pixelBuffer) + let image = UIImage(ciImage: ciImage) + DispatchQueue.main.async { [weak self] in + self?.previewView.image = image + } + } + + // MARK: Logging + private func log(string: String) { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.infoTextView.text = self.infoTextView.text + "\n" + string + } + } +} diff --git a/Session/Calls/VideoCallVC.swift b/Session/Calls/VideoCallVC.swift index 5bb8501a5..ad75d4dee 100644 --- a/Session/Calls/VideoCallVC.swift +++ b/Session/Calls/VideoCallVC.swift @@ -2,7 +2,7 @@ import UIKit import AVFoundation import WebRTC -class VideoCallVC : UIViewController { +final class VideoCallVC : UIViewController { private var localVideoView: UIView! private var remoteVideoView: UIView! diff --git a/SessionMessagingKit/Calls/CallManager.swift b/SessionMessagingKit/Calls/CallManager.swift index 50db4c794..d884fd01d 100644 --- a/SessionMessagingKit/Calls/CallManager.swift +++ b/SessionMessagingKit/Calls/CallManager.swift @@ -1,8 +1,14 @@ import PromiseKit import WebRTC +public protocol CallManagerDelegate : AnyObject { + + func callManager(_ callManager: CallManager, sendData data: Data) +} + /// See https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription for more information. public final class CallManager : NSObject, RTCPeerConnectionDelegate { + public weak var delegate: CallManagerDelegate? internal lazy var factory: RTCPeerConnectionFactory = { RTCInitializeSSL() From 74e9cacd583d8f27e110a07d6c1102a4e35744b1 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 12 Aug 2021 13:54:44 +1000 Subject: [PATCH 009/368] Implement MockWebSocket --- Podfile | 5 ++ Podfile.lock | 6 +- Session.xcodeproj/project.pbxproj | 6 ++ SessionMessagingKit/Calls/MockWebSocket.swift | 57 +++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 SessionMessagingKit/Calls/MockWebSocket.swift diff --git a/Podfile b/Podfile index d6631360a..1b004a8b1 100644 --- a/Podfile +++ b/Podfile @@ -11,6 +11,7 @@ target 'Session' do pod 'PromiseKit', :inhibit_warnings => true pod 'PureLayout', '~> 3.1.8', :inhibit_warnings => true pod 'Reachability', :inhibit_warnings => true + pod 'SocketRocket', '~> 0.5.1', :inhibit_warnings => true pod 'Sodium', '~> 0.8.0', :inhibit_warnings => true pod 'WebRTC', '~> 63.11', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true @@ -26,6 +27,7 @@ target 'SessionShareExtension' do pod 'PromiseKit', :inhibit_warnings => true pod 'PureLayout', '~> 3.1.8', :inhibit_warnings => true pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true + pod 'SocketRocket', '~> 0.5.1', :inhibit_warnings => true pod 'WebRTC', '~> 63.11', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true end @@ -33,6 +35,7 @@ end target 'SessionNotificationServiceExtension' do pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true + pod 'SocketRocket', '~> 0.5.1', :inhibit_warnings => true pod 'WebRTC', '~> 63.11', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true end @@ -50,6 +53,7 @@ target 'SignalUtilitiesKit' do pod 'Reachability', :inhibit_warnings => true pod 'SAMKeychain', :inhibit_warnings => true pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true + pod 'SocketRocket', '~> 0.5.1', :inhibit_warnings => true pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true pod 'WebRTC', '~> 63.11', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true @@ -71,6 +75,7 @@ target 'SessionMessagingKit' do pod 'Reachability', :inhibit_warnings => true pod 'SAMKeychain', :inhibit_warnings => true pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true + pod 'SocketRocket', '~> 0.5.1', :inhibit_warnings => true pod 'Sodium', '~> 0.8.0', :inhibit_warnings => true pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true pod 'WebRTC', '~> 63.11', :inhibit_warnings => true diff --git a/Podfile.lock b/Podfile.lock index b917479f8..4507b28a4 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -44,6 +44,7 @@ PODS: - SignalCoreKit (1.0.0): - CocoaLumberjack - GRKOpenSSLFramework + - SocketRocket (0.5.1) - Sodium (0.8.0) - SQLCipher (4.4.0): - SQLCipher/standard (= 4.4.0) @@ -134,6 +135,7 @@ DEPENDENCIES: - Reachability - SAMKeychain - SignalCoreKit (from `https://github.com/signalapp/SignalCoreKit.git`) + - SocketRocket (~> 0.5.1) - Sodium (~> 0.8.0) - SwiftProtobuf (~> 1.5.0) - WebRTC (~> 63.11) @@ -153,6 +155,7 @@ SPEC REPOS: - PureLayout - Reachability - SAMKeychain + - SocketRocket - Sodium - SQLCipher - SwiftProtobuf @@ -204,6 +207,7 @@ SPEC CHECKSUMS: Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c SignalCoreKit: 4562b2bbd9830077439ca003f952a798457d4ea5 + SocketRocket: d57c7159b83c3c6655745cd15302aa24b6bae531 Sodium: 63c0ca312a932e6da481689537d4b35568841bdc SQLCipher: e434ed542b24f38ea7b36468a13f9765e1b5c072 SwiftProtobuf: 241400280f912735c1e1b9fe675fdd2c6c4d42e2 @@ -212,6 +216,6 @@ SPEC CHECKSUMS: YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: e94e0a63e3b5609dad5b74fbb8e1266ccce2f011 +PODFILE CHECKSUM: 0f0ee15979921c085945dc1cacb0fd1fd380b77d COCOAPODS: 1.10.1 diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 0e0679185..2e3ab67d7 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -251,6 +251,7 @@ B8B558F126C4BB0600693325 /* CameraManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F026C4BB0600693325 /* CameraManager.swift */; }; B8B558F326C4CA4600693325 /* MockCallConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F226C4CA4600693325 /* MockCallConfig.swift */; }; B8B558F926C4CE6800693325 /* CallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F826C4CE6800693325 /* CallVC.swift */; }; + B8B558FB26C4D25C00693325 /* MockWebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FA26C4D25C00693325 /* MockWebSocket.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 */; }; @@ -1223,6 +1224,7 @@ B8B558F026C4BB0600693325 /* CameraManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = ""; }; B8B558F226C4CA4600693325 /* MockCallConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCallConfig.swift; sourceTree = ""; }; B8B558F826C4CE6800693325 /* CallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVC.swift; sourceTree = ""; }; + B8B558FA26C4D25C00693325 /* MockWebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockWebSocket.swift; sourceTree = ""; }; B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B8BAC75B2695645400EA1759 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; B8BAC75C2695648500EA1759 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; @@ -2363,6 +2365,7 @@ B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */, B806ECA226C4A8C6008BDA44 /* MockTURNServer.swift */, B8B558F226C4CA4600693325 /* MockCallConfig.swift */, + B8B558FA26C4D25C00693325 /* MockWebSocket.swift */, ); path = Calls; sourceTree = ""; @@ -4245,6 +4248,7 @@ "${BUILT_PRODUCTS_DIR}/PureLayout/PureLayout.framework", "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework", "${BUILT_PRODUCTS_DIR}/SQLCipher/SQLCipher.framework", + "${BUILT_PRODUCTS_DIR}/SocketRocket/SocketRocket.framework", "${BUILT_PRODUCTS_DIR}/Sodium/Sodium.framework", "${PODS_ROOT}/WebRTC/WebRTC.framework", "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework", @@ -4268,6 +4272,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PureLayout.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SQLCipher.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SocketRocket.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sodium.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYImage.framework", @@ -4755,6 +4760,7 @@ C32C5BEF256DC8EE003C73A2 /* OWSDisappearingMessagesJob.m in Sources */, C34A977425A3E34A00852C71 /* ClosedGroupControlMessage.swift in Sources */, B88FA7B826045D100049422F /* OpenGroupAPIV2.swift in Sources */, + B8B558FB26C4D25C00693325 /* MockWebSocket.swift in Sources */, C32C5E97256DE0CB003C73A2 /* OWSPrimaryStorage.m in Sources */, C32C5EB9256DE130003C73A2 /* OWSQuotedReplyModel+Conversion.swift in Sources */, C3A71D1F25589AC30043A11F /* WebSocketResources.pb.swift in Sources */, diff --git a/SessionMessagingKit/Calls/MockWebSocket.swift b/SessionMessagingKit/Calls/MockWebSocket.swift new file mode 100644 index 000000000..931800ec9 --- /dev/null +++ b/SessionMessagingKit/Calls/MockWebSocket.swift @@ -0,0 +1,57 @@ +import Foundation +import SocketRocket + +protocol MockWebSocketDelegate : AnyObject { + + func webSocketDidConnect(_ webSocket: MockWebSocket) + func webSocketDidDisconnect(_ webSocket: MockWebSocket) + func webSocket(_ webSocket: MockWebSocket, didReceive data: String) +} + +final class MockWebSocket : NSObject { + weak var delegate: MockWebSocketDelegate? + var socket: SRWebSocket? + + var isConnected: Bool { + return socket != nil + } + + func connect(url: URL) { + socket = SRWebSocket(url: url) + socket?.delegate = self + socket?.open() + } + + func disconnect() { + socket?.close() + socket = nil + delegate?.webSocketDidDisconnect(self) + } + + func send(data: Data) { + guard let socket = socket else { return } + socket.send(data) + } +} + +extension MockWebSocket : SRWebSocketDelegate { + + func webSocket(_ webSocket: SRWebSocket!, didReceiveMessage message: Any!) { + guard let message = message as? String else { return } + delegate?.webSocket(self, didReceive: message) + } + + func webSocketDidOpen(_ webSocket: SRWebSocket!) { + delegate?.webSocketDidConnect(self) + } + + func webSocket(_ webSocket: SRWebSocket!, didFailWithError error: Error!) { + SNLog("Web socket failed with error: \(error?.localizedDescription ?? "nil").") + self.disconnect() + } + + func webSocket(_ webSocket: SRWebSocket!, didCloseWithCode code: Int, reason: String!, wasClean: Bool) { + SNLog("Web socket closed.") + self.disconnect() + } +} From eb2cba7410bf6ae3d562148df1a8ff0b0ebf07d4 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 12 Aug 2021 14:04:46 +1000 Subject: [PATCH 010/368] Add MockCallServer --- Session.xcodeproj/project.pbxproj | 4 ++ .../Calls/MockCallServer.swift | 51 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 SessionMessagingKit/Calls/MockCallServer.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 2e3ab67d7..adeeded0b 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -252,6 +252,7 @@ B8B558F326C4CA4600693325 /* MockCallConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F226C4CA4600693325 /* MockCallConfig.swift */; }; B8B558F926C4CE6800693325 /* CallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F826C4CE6800693325 /* CallVC.swift */; }; B8B558FB26C4D25C00693325 /* MockWebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FA26C4D25C00693325 /* MockWebSocket.swift */; }; + B8B558FD26C4D35400693325 /* MockCallServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FC26C4D35400693325 /* MockCallServer.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 */; }; @@ -1225,6 +1226,7 @@ B8B558F226C4CA4600693325 /* MockCallConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCallConfig.swift; sourceTree = ""; }; B8B558F826C4CE6800693325 /* CallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVC.swift; sourceTree = ""; }; B8B558FA26C4D25C00693325 /* MockWebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockWebSocket.swift; sourceTree = ""; }; + B8B558FC26C4D35400693325 /* MockCallServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCallServer.swift; sourceTree = ""; }; B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B8BAC75B2695645400EA1759 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; B8BAC75C2695648500EA1759 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; @@ -2366,6 +2368,7 @@ B806ECA226C4A8C6008BDA44 /* MockTURNServer.swift */, B8B558F226C4CA4600693325 /* MockCallConfig.swift */, B8B558FA26C4D25C00693325 /* MockWebSocket.swift */, + B8B558FC26C4D35400693325 /* MockCallServer.swift */, ); path = Calls; sourceTree = ""; @@ -4741,6 +4744,7 @@ C3DA9C0725AE7396008F7C7E /* ConfigurationMessage.swift in Sources */, B8856CEE256F1054001CE70E /* OWSAudioPlayer.m in Sources */, C32C5EDC256DF501003C73A2 /* YapDatabaseConnection+OWS.m in Sources */, + B8B558FD26C4D35400693325 /* MockCallServer.swift in Sources */, C3BBE0762554CDA60050F1E3 /* Configuration.swift in Sources */, C35D76DB26606304009AA5FB /* ThreadUpdateBatcher.swift in Sources */, B8B32033258B235D0020074B /* Storage+Contacts.swift in Sources */, diff --git a/SessionMessagingKit/Calls/MockCallServer.swift b/SessionMessagingKit/Calls/MockCallServer.swift new file mode 100644 index 000000000..5ee89b1c9 --- /dev/null +++ b/SessionMessagingKit/Calls/MockCallServer.swift @@ -0,0 +1,51 @@ +import Foundation +import PromiseKit + +public struct RoomInfo { + public let roomID: String + public let wssURL: String + public let wssPostURL: String + public let clientID: String + public let isInitiator: String + public let messages: [String] +} + +public enum MockCallServer { + + private static func getRoomURL(for roomID: String) -> String { + let base = MockCallConfig.default.serverURL + "/join/" + return base + "\(roomID)" + } + private static func getLeaveURL(roomID: String, userID: String) -> String { + let base = MockCallConfig.default.serverURL + "/leave/" + return base + "\(roomID)/\(userID)" + } + private static func getMessageURL(roomID: String, userID: String) -> String { + let base = MockCallConfig.default.serverURL + "/message/" + return base + "\(roomID)/\(userID)" + } + + public static func join(roomID: String) -> Promise { + HTTP.execute(.post, getRoomURL(for: roomID)).map2 { json in + guard let status = json["result"] as? String else { throw HTTP.Error.invalidJSON } + if status == "FULL" { preconditionFailure() } + 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, + let isInitiator = info["is_initiator"] as? String, + let messages = info["messages"] as? [String] else { throw HTTP.Error.invalidJSON } + return RoomInfo(roomID: roomID, wssURL: wssURL, wssPostURL: wssPostURL, + clientID: clientID, isInitiator: isInitiator, messages: messages) + } + } + + public static func leave(roomID: String, userID: String) -> Promise { + return HTTP.execute(.post, getLeaveURL(roomID: roomID, userID: userID)).map2 { _ in } + } + + public static func send(_ message: Data, roomID: String, userID: String) -> Promise { + HTTP.execute(.post, getMessageURL(roomID: roomID, userID: userID), body: message).map2 { _ in } + } +} From f1f48ec865e26c1dbfaad9d2d95e6f3d2337b384 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 12 Aug 2021 15:12:48 +1000 Subject: [PATCH 011/368] Further implement CallVC --- Session.xcodeproj/project.pbxproj | 8 ++ Session/Calls/CallVC.swift | 110 +++++++++++++++++- .../Calls/CallManager+Messages.swift | 28 +++++ SessionMessagingKit/Calls/CallManager.swift | 15 ++- .../Calls/MockCallServer.swift | 6 +- SessionMessagingKit/Calls/MockWebSocket.swift | 28 +++-- .../Calls/SignalingMessage.swift | 59 ++++++++++ 7 files changed, 233 insertions(+), 21 deletions(-) create mode 100644 SessionMessagingKit/Calls/CallManager+Messages.swift create mode 100644 SessionMessagingKit/Calls/SignalingMessage.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index adeeded0b..beeb2b9f4 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -253,6 +253,8 @@ B8B558F926C4CE6800693325 /* CallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F826C4CE6800693325 /* CallVC.swift */; }; B8B558FB26C4D25C00693325 /* MockWebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FA26C4D25C00693325 /* MockWebSocket.swift */; }; B8B558FD26C4D35400693325 /* MockCallServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FC26C4D35400693325 /* MockCallServer.swift */; }; + B8B558FF26C4E05E00693325 /* CallManager+Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FE26C4E05E00693325 /* CallManager+Messages.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 */; }; @@ -1227,6 +1229,8 @@ B8B558F826C4CE6800693325 /* CallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVC.swift; sourceTree = ""; }; B8B558FA26C4D25C00693325 /* MockWebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockWebSocket.swift; sourceTree = ""; }; B8B558FC26C4D35400693325 /* MockCallServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCallServer.swift; sourceTree = ""; }; + B8B558FE26C4E05E00693325 /* CallManager+Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallManager+Messages.swift"; sourceTree = ""; }; + B8B5590026C4E2A400693325 /* SignalingMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalingMessage.swift; sourceTree = ""; }; B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B8BAC75B2695645400EA1759 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; B8BAC75C2695648500EA1759 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; @@ -2365,10 +2369,12 @@ children = ( B8DE1FB326C22F2F0079C9CE /* CallManager.swift */, B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */, + B8B558FE26C4E05E00693325 /* CallManager+Messages.swift */, B806ECA226C4A8C6008BDA44 /* MockTURNServer.swift */, B8B558F226C4CA4600693325 /* MockCallConfig.swift */, B8B558FA26C4D25C00693325 /* MockWebSocket.swift */, B8B558FC26C4D35400693325 /* MockCallServer.swift */, + B8B5590026C4E2A400693325 /* SignalingMessage.swift */, ); path = Calls; sourceTree = ""; @@ -4682,6 +4688,7 @@ C352A32F2557549C00338F3E /* NotifyPNServerJob.swift in Sources */, 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */, C300A5F22554B09800555489 /* MessageSender.swift in Sources */, + B8B558FF26C4E05E00693325 /* CallManager+Messages.swift in Sources */, C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */, C32C5AAD256DBE8F003C73A2 /* TSInfoMessage.m in Sources */, C32C5A13256DB7A5003C73A2 /* PushNotificationAPI.swift in Sources */, @@ -4803,6 +4810,7 @@ C32C5D23256DD4C0003C73A2 /* Mention.swift in Sources */, C32C5FD6256E0346003C73A2 /* Notification+Thread.swift in Sources */, C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */, + B8B5590126C4E2A400693325 /* SignalingMessage.swift in Sources */, B806ECA126C4A7E4008BDA44 /* CallManager+UI.swift in Sources */, C32C5C88256DD0D2003C73A2 /* Storage+Messaging.swift in Sources */, C32C59C7256DB41F003C73A2 /* TSThread.m in Sources */, diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 8260beec4..395282864 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -2,7 +2,16 @@ import UIKit import AVFoundation import WebRTC -final class MainChatRoomViewController : UIViewController, CameraCaptureDelegate, CallManagerDelegate { +final class MainChatRoomViewController : UIViewController, CameraCaptureDelegate, CallManagerDelegate, MockWebSocketDelegate { + private let videoCallVC = VideoCallVC() + private var messageQueue: [String] = [] + private var isConnected = false { + didSet { + let title = isConnected ? "Leave" : "Join" + joinOrLeaveButton.setTitle(title, for: UIControl.State.normal) + } + } + private var currentRoomInfo: RoomInfo? // MARK: UI Components private lazy var previewView: UIImageView = { @@ -13,7 +22,7 @@ final class MainChatRoomViewController : UIViewController, CameraCaptureDelegate return UIView() }() - private lazy var joinButton: UIButton = { + private lazy var joinOrLeaveButton: UIButton = { let result = UIButton() result.setTitle("Join", for: UIControl.State.normal) return result @@ -31,6 +40,7 @@ final class MainChatRoomViewController : UIViewController, CameraCaptureDelegate override func viewDidLoad() { super.viewDidLoad() setUpCamera() + embedVideoCallVC() } private func setUpCamera() { @@ -38,6 +48,13 @@ final class MainChatRoomViewController : UIViewController, CameraCaptureDelegate CameraManager.shared.prepare() } + private func embedVideoCallVC() { + addChild(videoCallVC) + containerView.addSubview(videoCallVC.view) + videoCallVC.view.translatesAutoresizingMaskIntoConstraints = false + videoCallVC.view.pin(to: containerView) + } + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) CameraManager.shared.start() @@ -48,7 +65,94 @@ final class MainChatRoomViewController : UIViewController, CameraCaptureDelegate CameraManager.shared.stop() } + // MARK: Interaction + @objc private func joinOrLeave() { + guard let roomID = roomNumberTextField.text, !roomID.isEmpty else { return } + if isConnected { + disconnect() + } else { + isConnected = true + MockCallServer.join(roomID: roomID).done2 { [weak self] info in + guard let self = self else { return } + self.log("Successfully joined room.") + self.currentRoomInfo = info + if let messages = info.messages { + self.handle(messages) + } + MockWebSocket.shared.delegate = self + MockWebSocket.shared.connect(url: URL(string: info.wssURL)!) + }.catch2 { [weak self] error in + guard let self = self else { return } + self.isConnected = false + self.log("Couldn't join room due to error: \(error).") + SNLog("Couldn't join room due to error: \(error).") + } + roomNumberTextField.resignFirstResponder() + } + } + + private func disconnect() { + guard let info = currentRoomInfo else { return } + MockCallServer.leave(roomID: info.roomID, userID: info.clientID).done2 { [weak self] in + guard let self = self else { return } + self.log("Disconnected.") + } + let message = [ "type": "bye" ] + guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return } + MockWebSocket.shared.send(data) + MockWebSocket.shared.delegate = nil + currentRoomInfo = nil + isConnected = false + CallManager.shared.endCall() + } + + // MARK: Message Handling + func handle(_ messages: [String]) { + messageQueue.append(contentsOf: messages) + drainMessageQueue() + } + + func drainMessageQueue() { + guard isConnected else { return } + for message in messageQueue { + handle(message) + } + messageQueue.removeAll() + CallManager.shared.drainMessageQueue() + } + + func handle(_ message: String) { + let signalingMessage = SignalingMessage.from(message: message) + switch signalingMessage { + case .candidate(let candidate): + CallManager.shared.handleCandidateMessage(candidate) + log("Candidate received.") + case .answer(let answer): + CallManager.shared.handleRemoteDescription(answer) + log("Answer received.") + case .offer(let offer): + CallManager.shared.handleRemoteDescription(offer) + log("Offer received.") + case .bye: + disconnect() + default: + break + } + } + // MARK: Streaming + func webSocketDidConnect(_ webSocket: MockWebSocket) { + + } + + func webSocket(_ webSocket: MockWebSocket, didReceive data: String) { + + } + + func webSocketDidDisconnect(_ webSocket: MockWebSocket) { + + } + func callManager(_ callManager: CallManager, sendData data: Data) { // TODO: Implement } @@ -64,7 +168,7 @@ final class MainChatRoomViewController : UIViewController, CameraCaptureDelegate } // MARK: Logging - private func log(string: String) { + private func log(_ string: String) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.infoTextView.text = self.infoTextView.text + "\n" + string diff --git a/SessionMessagingKit/Calls/CallManager+Messages.swift b/SessionMessagingKit/Calls/CallManager+Messages.swift new file mode 100644 index 000000000..a4601d6e0 --- /dev/null +++ b/SessionMessagingKit/Calls/CallManager+Messages.swift @@ -0,0 +1,28 @@ +import WebRTC + +extension CallManager { + + public func handleCandidateMessage(_ candidate: RTCIceCandidate) { + candidateQueue.append(candidate) + } + + public func handleRemoteDescription(_ sdp: RTCSessionDescription) { + peerConnection.setRemoteDescription(sdp, completionHandler: { [weak self] error in + if let error = error { + SNLog("Couldn't set SDP due to error: \(error).") + } else { + guard let self = self else { return } + if sdp.type == .offer, self.peerConnection.localDescription == nil { + self.acceptCall() + } + } + }) + } + + public func drainMessageQueue() { + for candidate in candidateQueue { + peerConnection.add(candidate) + } + candidateQueue.removeAll() + } +} diff --git a/SessionMessagingKit/Calls/CallManager.swift b/SessionMessagingKit/Calls/CallManager.swift index d884fd01d..383844606 100644 --- a/SessionMessagingKit/Calls/CallManager.swift +++ b/SessionMessagingKit/Calls/CallManager.swift @@ -9,6 +9,7 @@ public protocol CallManagerDelegate : AnyObject { /// See https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription for more information. public final class CallManager : NSObject, RTCPeerConnectionDelegate { public weak var delegate: CallManagerDelegate? + internal var candidateQueue: [RTCIceCandidate] = [] internal lazy var factory: RTCPeerConnectionFactory = { RTCInitializeSSL() @@ -21,7 +22,7 @@ public final class CallManager : 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() - // TODO: Configure + configuration.iceServers = [ RTCIceServer(urlStrings: MockCallConfig.default.webRTCICEServers) ] // TODO: Do these constraints make sense? let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [ "DtlsSrtpKeyAgreement" : "true" ]) return factory.peerConnection(with: configuration, constraints: constraints, delegate: self) @@ -105,8 +106,10 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { public static let shared = CallManager() // MARK: Call Management - public func initiateCall(with publicKey: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { + public func initiateCall() -> Promise { + /* guard let thread = TSContactThread.fetch(for: publicKey, using: transaction) else { return Promise(error: Error.noThread) } + */ let (promise, seal) = Promise.pending() peerConnection.offer(for: constraints) { [weak self] sdp, error in if let error = error { @@ -119,18 +122,22 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { return seal.reject(error) } } + /* let message = CallMessage() message.type = .offer message.sdp = sdp.sdp MessageSender.send(message, in: thread, using: transaction) + */ seal.fulfill(()) } } return promise } - public func acceptCall(with publicKey: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { + public func acceptCall() -> Promise { + /* guard let thread = TSContactThread.fetch(for: publicKey, using: transaction) else { return Promise(error: Error.noThread) } + */ let (promise, seal) = Promise.pending() peerConnection.answer(for: constraints) { [weak self] sdp, error in if let error = error { @@ -143,10 +150,12 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { return seal.reject(error) } } + /* let message = CallMessage() message.type = .answer message.sdp = sdp.sdp MessageSender.send(message, in: thread, using: transaction) + */ seal.fulfill(()) } } diff --git a/SessionMessagingKit/Calls/MockCallServer.swift b/SessionMessagingKit/Calls/MockCallServer.swift index 5ee89b1c9..2925507e7 100644 --- a/SessionMessagingKit/Calls/MockCallServer.swift +++ b/SessionMessagingKit/Calls/MockCallServer.swift @@ -7,7 +7,7 @@ public struct RoomInfo { public let wssPostURL: String public let clientID: String public let isInitiator: String - public let messages: [String] + public let messages: [String]? } public enum MockCallServer { @@ -34,8 +34,8 @@ public enum MockCallServer { let wssURL = info["wss_url"] as? String, let wssPostURL = info["wss_post_url"] as? String, let clientID = info["client_id"] as? String, - let isInitiator = info["is_initiator"] as? String, - let messages = info["messages"] as? [String] else { throw HTTP.Error.invalidJSON } + let isInitiator = info["is_initiator"] as? String 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) } diff --git a/SessionMessagingKit/Calls/MockWebSocket.swift b/SessionMessagingKit/Calls/MockWebSocket.swift index 931800ec9..d2c4224d0 100644 --- a/SessionMessagingKit/Calls/MockWebSocket.swift +++ b/SessionMessagingKit/Calls/MockWebSocket.swift @@ -1,34 +1,38 @@ import Foundation import SocketRocket -protocol MockWebSocketDelegate : AnyObject { +public protocol MockWebSocketDelegate : AnyObject { func webSocketDidConnect(_ webSocket: MockWebSocket) func webSocketDidDisconnect(_ webSocket: MockWebSocket) func webSocket(_ webSocket: MockWebSocket, didReceive data: String) } -final class MockWebSocket : NSObject { - weak var delegate: MockWebSocketDelegate? - var socket: SRWebSocket? +public final class MockWebSocket : NSObject { + public weak var delegate: MockWebSocketDelegate? + private var socket: SRWebSocket? - var isConnected: Bool { + public var isConnected: Bool { return socket != nil } - func connect(url: URL) { + private override init() { } + + public static let shared = MockWebSocket() + + public func connect(url: URL) { socket = SRWebSocket(url: url) socket?.delegate = self socket?.open() } - func disconnect() { + public func disconnect() { socket?.close() socket = nil delegate?.webSocketDidDisconnect(self) } - func send(data: Data) { + public func send(_ data: Data) { guard let socket = socket else { return } socket.send(data) } @@ -36,21 +40,21 @@ final class MockWebSocket : NSObject { extension MockWebSocket : SRWebSocketDelegate { - func webSocket(_ webSocket: SRWebSocket!, didReceiveMessage message: Any!) { + public func webSocket(_ webSocket: SRWebSocket!, didReceiveMessage message: Any!) { guard let message = message as? String else { return } delegate?.webSocket(self, didReceive: message) } - func webSocketDidOpen(_ webSocket: SRWebSocket!) { + public func webSocketDidOpen(_ webSocket: SRWebSocket!) { delegate?.webSocketDidConnect(self) } - func webSocket(_ webSocket: SRWebSocket!, didFailWithError error: Error!) { + public func webSocket(_ webSocket: SRWebSocket!, didFailWithError error: Error!) { SNLog("Web socket failed with error: \(error?.localizedDescription ?? "nil").") self.disconnect() } - func webSocket(_ webSocket: SRWebSocket!, didCloseWithCode code: Int, reason: String!, wasClean: Bool) { + public func webSocket(_ webSocket: SRWebSocket!, didCloseWithCode code: Int, reason: String!, wasClean: Bool) { SNLog("Web socket closed.") self.disconnect() } diff --git a/SessionMessagingKit/Calls/SignalingMessage.swift b/SessionMessagingKit/Calls/SignalingMessage.swift new file mode 100644 index 000000000..85744082b --- /dev/null +++ b/SessionMessagingKit/Calls/SignalingMessage.swift @@ -0,0 +1,59 @@ +import Foundation +import WebRTC + +public enum SignalingMessage { + case none + 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) else { return .none } + guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON else { return .none } + let messageAsJSON: JSON + if let foo = json["msg"] as? String { + guard let data = foo.data(using: String.Encoding.utf8) else { return .none } + guard let bar = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON else { return .none } + messageAsJSON = bar + } else { + messageAsJSON = json + } + guard let type = messageAsJSON["type"] as? String else { return .none } + switch type { + case "candidate": + guard let candidate = RTCIceCandidate.candidate(from: messageAsJSON) else { return .none } + return .candidate(candidate) + case "answer": + guard let sdp = messageAsJSON["sdp"] as? String else { return .none } + return .answer(RTCSessionDescription(type: .answer, sdp: sdp)) + case "offer": + guard let sdp = messageAsJSON["sdp"] as? String else { return .none } + return .offer(RTCSessionDescription(type: .offer, sdp: sdp)) + case "bye": + return .bye + default: return .none + } + } +} + +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) + } +} From c2373747b17776628594964e96b77b067f50585e Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 12 Aug 2021 15:35:40 +1000 Subject: [PATCH 012/368] Odds & ends --- Session/Calls/CallVC.swift | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 395282864..7feebbb0c 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -13,6 +13,10 @@ final class MainChatRoomViewController : UIViewController, CameraCaptureDelegate } private var currentRoomInfo: RoomInfo? + var isInitiator: Bool { + return currentRoomInfo?.isInitiator == "true" + } + // MARK: UI Components private lazy var previewView: UIImageView = { return UIImageView() @@ -142,19 +146,36 @@ final class MainChatRoomViewController : UIViewController, CameraCaptureDelegate // MARK: Streaming func webSocketDidConnect(_ webSocket: MockWebSocket) { - + guard let info = currentRoomInfo else { return } + log("Connected to web socket.") + let message = [ + "cmd": "register", + "roomid": info.roomID, + "clientid": info.clientID + ] + guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return } + MockWebSocket.shared.send(data) + CallManager.shared.delegate = self + if isInitiator { + CallManager.shared.initiateCall().retainUntilComplete() + } + drainMessageQueue() } func webSocket(_ webSocket: MockWebSocket, didReceive data: String) { - + log("Received data from web socket.") + handle(data) + CallManager.shared.drainMessageQueue() } func webSocketDidDisconnect(_ webSocket: MockWebSocket) { - + MockWebSocket.shared.delegate = nil + log("Disconnecting from web socket.") } func callManager(_ callManager: CallManager, sendData data: Data) { - // TODO: Implement + guard let info = currentRoomInfo else { return } + MockCallServer.send(data, roomID: info.roomID, userID: info.clientID).retainUntilComplete() } // MARK: Camera From 691f931dde3fa3f145acfd440b988a580a34fe9c Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 12 Aug 2021 15:37:31 +1000 Subject: [PATCH 013/368] Hook up CallVC --- Session/Calls/CallVC.swift | 2 +- Session/Home/HomeVC.swift | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 7feebbb0c..7b473f887 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -2,7 +2,7 @@ import UIKit import AVFoundation import WebRTC -final class MainChatRoomViewController : UIViewController, CameraCaptureDelegate, CallManagerDelegate, MockWebSocketDelegate { +final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegate, MockWebSocketDelegate { private let videoCallVC = VideoCallVC() private var messageQueue: [String] = [] private var isConnected = false { diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index de966618f..e62d27dd8 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -157,6 +157,10 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv } // Get default open group rooms if needed OpenGroupAPIV2.getDefaultRoomsIfNeeded() + + let callVC = CallVC() + present(callVC, animated: true, completion: nil) + } override func viewDidAppear(_ animated: Bool) { From 56bd59c4ee59719fa922abf8c2101d66f5e8c00d Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 12 Aug 2021 16:09:54 +1000 Subject: [PATCH 014/368] Debug --- Session/Calls/CallVC.swift | 2 ++ Session/Calls/VideoCallVC.swift | 4 ++-- SessionMessagingKit/Calls/CallManager.swift | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 7b473f887..77e44223d 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -45,6 +45,8 @@ final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegat super.viewDidLoad() setUpCamera() embedVideoCallVC() + view.addSubview(containerView) + containerView.pin(to: view) } private func setUpCamera() { diff --git a/Session/Calls/VideoCallVC.swift b/Session/Calls/VideoCallVC.swift index ad75d4dee..f757e5ecf 100644 --- a/Session/Calls/VideoCallVC.swift +++ b/Session/Calls/VideoCallVC.swift @@ -3,8 +3,8 @@ import AVFoundation import WebRTC final class VideoCallVC : UIViewController { - private var localVideoView: UIView! - private var remoteVideoView: UIView! + private var localVideoView = UIView() + private var remoteVideoView = UIView() override func viewDidLoad() { super.viewDidLoad() diff --git a/SessionMessagingKit/Calls/CallManager.swift b/SessionMessagingKit/Calls/CallManager.swift index 383844606..b5808c01c 100644 --- a/SessionMessagingKit/Calls/CallManager.swift +++ b/SessionMessagingKit/Calls/CallManager.swift @@ -51,7 +51,7 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { // Video internal lazy var localVideoSource: RTCVideoSource = { - return factory.videoSource() + return factory.avFoundationVideoSource(with: nil) }() internal lazy var localVideoTrack: RTCVideoTrack = { From 876814dd43090b98ce16252d2ef89646547c6c39 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 13 Aug 2021 13:47:22 +1000 Subject: [PATCH 015/368] Basic proof of concept --- Podfile | 10 ++++---- Podfile.lock | 10 ++++---- Session.xcodeproj/project.pbxproj | 4 ++-- Session/Calls/CallVC.swift | 24 +++++++------------ Session/Calls/CameraManager.swift | 3 ++- Session/Calls/VideoCallVC.swift | 6 +++-- .../Calls/CallManager+UI.swift | 1 + SessionMessagingKit/Calls/CallManager.swift | 12 ++++------ 8 files changed, 33 insertions(+), 37 deletions(-) diff --git a/Podfile b/Podfile index 1b004a8b1..496fb32c7 100644 --- a/Podfile +++ b/Podfile @@ -6,6 +6,7 @@ use_frameworks! target 'Session' do pod 'AFNetworking', inhibit_warnings: true pod 'CryptoSwift', :inhibit_warnings => true + pod 'GoogleWebRTC', :inhibit_warnings => true pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master', :inhibit_warnings => true pod 'NVActivityIndicatorView', :inhibit_warnings => true pod 'PromiseKit', :inhibit_warnings => true @@ -13,7 +14,6 @@ target 'Session' do pod 'Reachability', :inhibit_warnings => true pod 'SocketRocket', '~> 0.5.1', :inhibit_warnings => true pod 'Sodium', '~> 0.8.0', :inhibit_warnings => true - pod 'WebRTC', '~> 63.11', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true pod 'YYImage', git: 'https://github.com/signalapp/YYImage', :inhibit_warnings => true pod 'ZXingObjC', :inhibit_warnings => true @@ -23,20 +23,20 @@ target 'SessionShareExtension' do pod 'AFNetworking', inhibit_warnings: true pod 'CryptoSwift', :inhibit_warnings => true pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true + pod 'GoogleWebRTC', :inhibit_warnings => true pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master', :inhibit_warnings => true pod 'PromiseKit', :inhibit_warnings => true pod 'PureLayout', '~> 3.1.8', :inhibit_warnings => true pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true pod 'SocketRocket', '~> 0.5.1', :inhibit_warnings => true - pod 'WebRTC', '~> 63.11', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true end target 'SessionNotificationServiceExtension' do pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true + pod 'GoogleWebRTC', :inhibit_warnings => true pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true pod 'SocketRocket', '~> 0.5.1', :inhibit_warnings => true - pod 'WebRTC', '~> 63.11', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true end @@ -44,6 +44,7 @@ target 'SignalUtilitiesKit' do pod 'AFNetworking', inhibit_warnings: true pod 'CryptoSwift', :inhibit_warnings => true pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true + pod 'GoogleWebRTC', :inhibit_warnings => true pod 'GRKOpenSSLFramework', :inhibit_warnings => true pod 'HKDFKit', :inhibit_warnings => true pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master', :inhibit_warnings => true @@ -55,7 +56,6 @@ target 'SignalUtilitiesKit' do pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true pod 'SocketRocket', '~> 0.5.1', :inhibit_warnings => true pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true - pod 'WebRTC', '~> 63.11', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true pod 'YYImage', git: 'https://github.com/signalapp/YYImage', :inhibit_warnings => true end @@ -68,6 +68,7 @@ target 'SessionMessagingKit' do pod 'AFNetworking', inhibit_warnings: true pod 'CryptoSwift', :inhibit_warnings => true pod 'Curve25519Kit', git: 'https://github.com/signalapp/Curve25519Kit.git', :inhibit_warnings => true + pod 'GoogleWebRTC', :inhibit_warnings => true pod 'HKDFKit', :inhibit_warnings => true pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master', :inhibit_warnings => true pod 'PromiseKit', :inhibit_warnings => true @@ -78,7 +79,6 @@ target 'SessionMessagingKit' do pod 'SocketRocket', '~> 0.5.1', :inhibit_warnings => true pod 'Sodium', '~> 0.8.0', :inhibit_warnings => true pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true - pod 'WebRTC', '~> 63.11', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true end diff --git a/Podfile.lock b/Podfile.lock index 4507b28a4..0f47af5d0 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -21,6 +21,7 @@ PODS: - Curve25519Kit (2.1.0): - CocoaLumberjack - SignalCoreKit + - GoogleWebRTC (1.1.31999) - GRKOpenSSLFramework (1.0.2.20) - HKDFKit (0.0.3) - Mantle (2.1.0): @@ -52,7 +53,6 @@ PODS: - SQLCipher/standard (4.4.0): - SQLCipher/common - SwiftProtobuf (1.5.0) - - WebRTC (63.11.20455) - YapDatabase/SQLCipher (3.1.1): - YapDatabase/SQLCipher/Core (= 3.1.1) - YapDatabase/SQLCipher/Extensions (= 3.1.1) @@ -126,6 +126,7 @@ DEPENDENCIES: - AFNetworking - CryptoSwift - Curve25519Kit (from `https://github.com/signalapp/Curve25519Kit.git`) + - GoogleWebRTC - GRKOpenSSLFramework - HKDFKit - Mantle (from `https://github.com/signalapp/Mantle`, branch `signal-master`) @@ -138,7 +139,6 @@ DEPENDENCIES: - SocketRocket (~> 0.5.1) - Sodium (~> 0.8.0) - SwiftProtobuf (~> 1.5.0) - - WebRTC (~> 63.11) - YapDatabase/SQLCipher (from `https://github.com/loki-project/session-ios-yap-database.git`, branch `signal-release`) - YYImage (from `https://github.com/signalapp/YYImage`) - ZXingObjC @@ -148,6 +148,7 @@ SPEC REPOS: - AFNetworking - CocoaLumberjack - CryptoSwift + - GoogleWebRTC - GRKOpenSSLFramework - HKDFKit - NVActivityIndicatorView @@ -159,7 +160,6 @@ SPEC REPOS: - Sodium - SQLCipher - SwiftProtobuf - - WebRTC - ZXingObjC EXTERNAL SOURCES: @@ -198,6 +198,7 @@ SPEC CHECKSUMS: CocoaLumberjack: bd155f2dd06c0e0b03f876f7a3ee55693122ec94 CryptoSwift: 093499be1a94b0cae36e6c26b70870668cb56060 Curve25519Kit: e63f9859ede02438ae3defc5e1a87e09d1ec7ee6 + GoogleWebRTC: b39a78c4f5cc6b0323415b9233db03a2faa7b0f0 GRKOpenSSLFramework: dc635b0a9d4cd8af2a9ff80a61e779e21b69dfd8 HKDFKit: c058305d6f64b84f28c50bd7aa89574625bcb62a Mantle: 2fa750afa478cd625a94230fbf1c13462f29395b @@ -211,11 +212,10 @@ SPEC CHECKSUMS: Sodium: 63c0ca312a932e6da481689537d4b35568841bdc SQLCipher: e434ed542b24f38ea7b36468a13f9765e1b5c072 SwiftProtobuf: 241400280f912735c1e1b9fe675fdd2c6c4d42e2 - WebRTC: f2a6203584745fe53532633397557876b5d71640 YapDatabase: b418a4baa6906e8028748938f9159807fd039af4 YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 0f0ee15979921c085945dc1cacb0fd1fd380b77d +PODFILE CHECKSUM: 215e4f2af32f65d98d1442e6ec0df3576c994a02 COCOAPODS: 1.10.1 diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index beeb2b9f4..f7f3842fb 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -4251,6 +4251,7 @@ "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework", "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", + "${PODS_ROOT}/GoogleWebRTC/Frameworks/frameworks/WebRTC.framework", "${BUILT_PRODUCTS_DIR}/Mantle/Mantle.framework", "${BUILT_PRODUCTS_DIR}/NVActivityIndicatorView/NVActivityIndicatorView.framework", "${BUILT_PRODUCTS_DIR}/PromiseKit/PromiseKit.framework", @@ -4259,7 +4260,6 @@ "${BUILT_PRODUCTS_DIR}/SQLCipher/SQLCipher.framework", "${BUILT_PRODUCTS_DIR}/SocketRocket/SocketRocket.framework", "${BUILT_PRODUCTS_DIR}/Sodium/Sodium.framework", - "${PODS_ROOT}/WebRTC/WebRTC.framework", "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework", "${BUILT_PRODUCTS_DIR}/YapDatabase/YapDatabase.framework", "${BUILT_PRODUCTS_DIR}/ZXingObjC/ZXingObjC.framework", @@ -4275,6 +4275,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CryptoSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Mantle.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NVActivityIndicatorView.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", @@ -4283,7 +4284,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SQLCipher.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SocketRocket.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sodium.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YapDatabase.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ZXingObjC.framework", diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 77e44223d..111d1878b 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -4,6 +4,7 @@ import WebRTC final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegate, MockWebSocketDelegate { private let videoCallVC = VideoCallVC() + let videoCapturer: RTCVideoCapturer = RTCCameraVideoCapturer(delegate: CallManager.shared.localVideoSource) private var messageQueue: [String] = [] private var isConnected = false { didSet { @@ -43,31 +44,25 @@ final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegat // MARK: Lifecycle override func viewDidLoad() { super.viewDidLoad() - setUpCamera() - embedVideoCallVC() - view.addSubview(containerView) - containerView.pin(to: view) - } - - private func setUpCamera() { + touch(CallManager.shared) + CallManager.shared.delegate = self CameraManager.shared.delegate = self CameraManager.shared.prepare() - } - - private func embedVideoCallVC() { addChild(videoCallVC) containerView.addSubview(videoCallVC.view) videoCallVC.view.translatesAutoresizingMaskIntoConstraints = false videoCallVC.view.pin(to: containerView) + view.addSubview(containerView) + containerView.pin(to: view) } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) CameraManager.shared.start() } - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) CameraManager.shared.stop() } @@ -157,7 +152,6 @@ final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegat ] guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return } MockWebSocket.shared.send(data) - CallManager.shared.delegate = self if isInitiator { CallManager.shared.initiateCall().retainUntilComplete() } diff --git a/Session/Calls/CameraManager.swift b/Session/Calls/CameraManager.swift index 622fb13b0..019efab22 100644 --- a/Session/Calls/CameraManager.swift +++ b/Session/Calls/CameraManager.swift @@ -24,13 +24,14 @@ final class CameraManager : NSObject { private override init() { } func prepare() { + captureSession.sessionPreset = .low if let videoCaptureDevice = videoCaptureDevice, let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice), captureSession.canAddInput(videoInput) { captureSession.addInput(videoInput) } if captureSession.canAddOutput(videoDataOutput) { captureSession.addOutput(videoDataOutput) - videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)] + videoDataOutput.videoSettings = [ kCVPixelBufferPixelFormatTypeKey as String : Int(kCVPixelFormatType_32BGRA) ] videoDataOutput.setSampleBufferDelegate(self, queue: dataOutputQueue) videoDataOutput.connection(with: .video)?.videoOrientation = .portrait videoDataOutput.connection(with: .video)?.automaticallyAdjustsVideoMirroring = false diff --git a/Session/Calls/VideoCallVC.swift b/Session/Calls/VideoCallVC.swift index f757e5ecf..6e7caf8ca 100644 --- a/Session/Calls/VideoCallVC.swift +++ b/Session/Calls/VideoCallVC.swift @@ -51,8 +51,10 @@ extension VideoCallVC : CameraCaptureDelegate { func captureVideoOutput(sampleBuffer: CMSampleBuffer) { guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } let rtcpixelBuffer = RTCCVPixelBuffer(pixelBuffer: pixelBuffer) - let timeStampNs = Int64(CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) * 1000000000) - let videoFrame = RTCVideoFrame(buffer: rtcpixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: timeStampNs) + let timestamp = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) + let timestampNs = Int64(timestamp * 1000000000) + let videoFrame = RTCVideoFrame(buffer: rtcpixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: timestampNs) + videoFrame.timeStamp = Int32(timestamp) CallManager.shared.handleLocalFrameCaptured(videoFrame) } } diff --git a/SessionMessagingKit/Calls/CallManager+UI.swift b/SessionMessagingKit/Calls/CallManager+UI.swift index d9effa6c9..f9d6b73fd 100644 --- a/SessionMessagingKit/Calls/CallManager+UI.swift +++ b/SessionMessagingKit/Calls/CallManager+UI.swift @@ -11,6 +11,7 @@ extension CallManager { } public func handleLocalFrameCaptured(_ videoFrame: RTCVideoFrame) { + guard let videoCapturer = delegate?.videoCapturer else { return } localVideoSource.capturer(videoCapturer, didCapture: videoFrame) } } diff --git a/SessionMessagingKit/Calls/CallManager.swift b/SessionMessagingKit/Calls/CallManager.swift index b5808c01c..8bdcf2a4c 100644 --- a/SessionMessagingKit/Calls/CallManager.swift +++ b/SessionMessagingKit/Calls/CallManager.swift @@ -2,6 +2,7 @@ import PromiseKit import WebRTC public protocol CallManagerDelegate : AnyObject { + var videoCapturer: RTCVideoCapturer { get } func callManager(_ callManager: CallManager, sendData data: Data) } @@ -23,6 +24,7 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { internal lazy var peerConnection: RTCPeerConnection = { let configuration = RTCConfiguration() configuration.iceServers = [ RTCIceServer(urlStrings: MockCallConfig.default.webRTCICEServers) ] + configuration.iceTransportPolicy = .all // TODO: Do these constraints make sense? let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [ "DtlsSrtpKeyAgreement" : "true" ]) return factory.peerConnection(with: configuration, constraints: constraints, delegate: self) @@ -50,20 +52,16 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { }() // Video - internal lazy var localVideoSource: RTCVideoSource = { - return factory.avFoundationVideoSource(with: nil) + public lazy var localVideoSource: RTCVideoSource = { + return factory.videoSource() }() internal lazy var localVideoTrack: RTCVideoTrack = { return factory.videoTrack(with: localVideoSource, trackId: "ARDAMSv0") }() - internal lazy var videoCapturer: RTCVideoCapturer = { - return RTCCameraVideoCapturer(delegate: localVideoSource) - }() - internal lazy var remoteVideoTrack: RTCVideoTrack? = { - return peerConnection.receivers.first { $0.track.kind == "video" }?.track as? RTCVideoTrack + return peerConnection.receivers.first { $0.track?.kind == "video" }?.track as? RTCVideoTrack }() // Stream From 48ef3f85c0b715bf0701158467131e27f1ef85f5 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 13 Aug 2021 14:40:42 +1000 Subject: [PATCH 016/368] Debug --- Session/Calls/CallVC.swift | 22 ++++++++++- SessionMessagingKit/Calls/CallManager.swift | 41 +++++++++++++++------ 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 111d1878b..e2a60fbd5 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -30,15 +30,22 @@ final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegat private lazy var joinOrLeaveButton: UIButton = { let result = UIButton() result.setTitle("Join", for: UIControl.State.normal) + result.addTarget(self, action: #selector(joinOrLeave), for: UIControl.Event.touchUpInside) return result }() private lazy var roomNumberTextField: UITextField = { - return UITextField() + let result = UITextField() + result.set(.width, to: 120) + result.set(.height, to: 40) + result.backgroundColor = UIColor.white.withAlphaComponent(0.1) + return result }() private lazy var infoTextView: UITextView = { - return UITextView() + let result = UITextView() + result.backgroundColor = UIColor.white.withAlphaComponent(0.1) + return result }() // MARK: Lifecycle @@ -54,6 +61,17 @@ final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegat videoCallVC.view.pin(to: containerView) view.addSubview(containerView) containerView.pin(to: view) + view.addSubview(joinOrLeaveButton) + joinOrLeaveButton.translatesAutoresizingMaskIntoConstraints = false + joinOrLeaveButton.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view) + view.addSubview(roomNumberTextField) + roomNumberTextField.translatesAutoresizingMaskIntoConstraints = false + roomNumberTextField.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.left ], to: view) + view.addSubview(infoTextView) + infoTextView.translatesAutoresizingMaskIntoConstraints = false + infoTextView.pin([ UIView.HorizontalEdge.left, UIView.HorizontalEdge.right ], to: view) + infoTextView.center(.vertical, in: view) + infoTextView.set(.height, to: 200) } override func viewDidAppear(_ animated: Bool) { diff --git a/SessionMessagingKit/Calls/CallManager.swift b/SessionMessagingKit/Calls/CallManager.swift index 8bdcf2a4c..735d7f120 100644 --- a/SessionMessagingKit/Calls/CallManager.swift +++ b/SessionMessagingKit/Calls/CallManager.swift @@ -24,8 +24,10 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { internal lazy var peerConnection: RTCPeerConnection = { let configuration = RTCConfiguration() configuration.iceServers = [ RTCIceServer(urlStrings: MockCallConfig.default.webRTCICEServers) ] + configuration.sdpSemantics = .unifiedPlan + let pcert = RTCCertificate.generate(withParams: [ "expires": NSNumber(value: 100000), "name": "RSASSA-PKCS1-v1_5" ]) + configuration.certificate = pcert configuration.iceTransportPolicy = .all - // TODO: Do these constraints make sense? let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [ "DtlsSrtpKeyAgreement" : "true" ]) return factory.peerConnection(with: configuration, constraints: constraints, delegate: self) }() @@ -61,15 +63,7 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { }() internal lazy var remoteVideoTrack: RTCVideoTrack? = { - return peerConnection.receivers.first { $0.track?.kind == "video" }?.track as? RTCVideoTrack - }() - - // Stream - internal lazy var stream: RTCMediaStream = { - let result = factory.mediaStream(withStreamId: "ARDAMS") - result.addAudioTrack(audioTrack) - result.addVideoTrack(localVideoTrack) - return result + return peerConnection.transceivers.first { $0.mediaType == .video }?.receiver.track as? RTCVideoTrack }() // MARK: Error @@ -86,7 +80,9 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { // MARK: Initialization internal override init() { super.init() - peerConnection.add(stream) + let mediaStreamTrackIDS = ["ARDAMS"] + peerConnection.add(audioTrack, streamIds: mediaStreamTrackIDS) + peerConnection.add(localVideoTrack, streamIds: mediaStreamTrackIDS) // Configure audio session let audioSession = RTCAudioSession.sharedInstance() audioSession.lockForConfiguration() @@ -120,6 +116,10 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { return seal.reject(error) } } + + let message = sdp.serialize()! + self.delegate?.callManager(self, sendData: message) + /* let message = CallMessage() message.type = .offer @@ -148,6 +148,10 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { return seal.reject(error) } } + + let message = sdp.serialize()! + self.delegate?.callManager(self, sendData: message) + /* let message = CallMessage() message.type = .answer @@ -191,6 +195,8 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { SNLog("ICE candidate generated.") + let message = candidate.serialize()! + delegate?.callManager(self, sendData: message) } public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) { @@ -201,3 +207,16 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { SNLog("Data channel opened.") } } + +// MARK: Utilities + +extension RTCSessionDescription { + + func serialize() -> Data? { + let json = [ + "type": RTCSessionDescription.string(for: self.type), + "sdp": self.sdp + ] + return try? JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted]) + } +} From 36962cc05985e3a5d928fde01bf0f00096b5e614 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 13 Aug 2021 15:22:15 +1000 Subject: [PATCH 017/368] Clean up WebSocket --- Session.xcodeproj/project.pbxproj | 8 +-- Session/Calls/CallVC.swift | 25 ++++---- SessionMessagingKit/Calls/MockWebSocket.swift | 61 ------------------- SessionMessagingKit/Calls/WebSocket.swift | 48 +++++++++++++++ 4 files changed, 66 insertions(+), 76 deletions(-) delete mode 100644 SessionMessagingKit/Calls/MockWebSocket.swift create mode 100644 SessionMessagingKit/Calls/WebSocket.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index f7f3842fb..1ae1439fe 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -251,7 +251,7 @@ B8B558F126C4BB0600693325 /* CameraManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F026C4BB0600693325 /* CameraManager.swift */; }; B8B558F326C4CA4600693325 /* MockCallConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F226C4CA4600693325 /* MockCallConfig.swift */; }; B8B558F926C4CE6800693325 /* CallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F826C4CE6800693325 /* CallVC.swift */; }; - B8B558FB26C4D25C00693325 /* MockWebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FA26C4D25C00693325 /* MockWebSocket.swift */; }; + B8B558FB26C4D25C00693325 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FA26C4D25C00693325 /* WebSocket.swift */; }; B8B558FD26C4D35400693325 /* MockCallServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FC26C4D35400693325 /* MockCallServer.swift */; }; B8B558FF26C4E05E00693325 /* CallManager+Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FE26C4E05E00693325 /* CallManager+Messages.swift */; }; B8B5590126C4E2A400693325 /* SignalingMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B5590026C4E2A400693325 /* SignalingMessage.swift */; }; @@ -1227,7 +1227,7 @@ B8B558F026C4BB0600693325 /* CameraManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = ""; }; B8B558F226C4CA4600693325 /* MockCallConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCallConfig.swift; sourceTree = ""; }; B8B558F826C4CE6800693325 /* CallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVC.swift; sourceTree = ""; }; - B8B558FA26C4D25C00693325 /* MockWebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockWebSocket.swift; sourceTree = ""; }; + B8B558FA26C4D25C00693325 /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = ""; }; B8B558FC26C4D35400693325 /* MockCallServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCallServer.swift; sourceTree = ""; }; B8B558FE26C4E05E00693325 /* CallManager+Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallManager+Messages.swift"; sourceTree = ""; }; B8B5590026C4E2A400693325 /* SignalingMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalingMessage.swift; sourceTree = ""; }; @@ -2372,9 +2372,9 @@ B8B558FE26C4E05E00693325 /* CallManager+Messages.swift */, B806ECA226C4A8C6008BDA44 /* MockTURNServer.swift */, B8B558F226C4CA4600693325 /* MockCallConfig.swift */, - B8B558FA26C4D25C00693325 /* MockWebSocket.swift */, B8B558FC26C4D35400693325 /* MockCallServer.swift */, B8B5590026C4E2A400693325 /* SignalingMessage.swift */, + B8B558FA26C4D25C00693325 /* WebSocket.swift */, ); path = Calls; sourceTree = ""; @@ -4771,7 +4771,7 @@ C32C5BEF256DC8EE003C73A2 /* OWSDisappearingMessagesJob.m in Sources */, C34A977425A3E34A00852C71 /* ClosedGroupControlMessage.swift in Sources */, B88FA7B826045D100049422F /* OpenGroupAPIV2.swift in Sources */, - B8B558FB26C4D25C00693325 /* MockWebSocket.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 */, diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index e2a60fbd5..489ec4adc 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -2,7 +2,8 @@ import UIKit import AVFoundation import WebRTC -final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegate, MockWebSocketDelegate { +final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegate, WebSocketDelegate { + private var webSocket: WebSocket? private let videoCallVC = VideoCallVC() let videoCapturer: RTCVideoCapturer = RTCCameraVideoCapturer(delegate: CallManager.shared.localVideoSource) private var messageQueue: [String] = [] @@ -98,8 +99,9 @@ final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegat if let messages = info.messages { self.handle(messages) } - MockWebSocket.shared.delegate = self - MockWebSocket.shared.connect(url: URL(string: info.wssURL)!) + let webSocket = WebSocket(url: URL(string: info.wssURL)!) + webSocket.delegate = self + self.webSocket = webSocket }.catch2 { [weak self] error in guard let self = self else { return } self.isConnected = false @@ -118,8 +120,9 @@ final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegat } let message = [ "type": "bye" ] guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return } - MockWebSocket.shared.send(data) - MockWebSocket.shared.delegate = nil + webSocket?.send(data) + webSocket?.delegate = nil + webSocket = nil currentRoomInfo = nil isConnected = false CallManager.shared.endCall() @@ -160,7 +163,7 @@ final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegat } // MARK: Streaming - func webSocketDidConnect(_ webSocket: MockWebSocket) { + func webSocketDidConnect(_ webSocket: WebSocket) { guard let info = currentRoomInfo else { return } log("Connected to web socket.") let message = [ @@ -169,21 +172,21 @@ final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegat "clientid": info.clientID ] guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return } - MockWebSocket.shared.send(data) + webSocket.send(data) if isInitiator { CallManager.shared.initiateCall().retainUntilComplete() } drainMessageQueue() } - func webSocket(_ webSocket: MockWebSocket, didReceive data: String) { + func webSocket(_ webSocket: WebSocket, didReceive message: String) { log("Received data from web socket.") - handle(data) + handle(message) CallManager.shared.drainMessageQueue() } - func webSocketDidDisconnect(_ webSocket: MockWebSocket) { - MockWebSocket.shared.delegate = nil + func webSocketDidDisconnect(_ webSocket: WebSocket) { + webSocket.delegate = nil log("Disconnecting from web socket.") } diff --git a/SessionMessagingKit/Calls/MockWebSocket.swift b/SessionMessagingKit/Calls/MockWebSocket.swift deleted file mode 100644 index d2c4224d0..000000000 --- a/SessionMessagingKit/Calls/MockWebSocket.swift +++ /dev/null @@ -1,61 +0,0 @@ -import Foundation -import SocketRocket - -public protocol MockWebSocketDelegate : AnyObject { - - func webSocketDidConnect(_ webSocket: MockWebSocket) - func webSocketDidDisconnect(_ webSocket: MockWebSocket) - func webSocket(_ webSocket: MockWebSocket, didReceive data: String) -} - -public final class MockWebSocket : NSObject { - public weak var delegate: MockWebSocketDelegate? - private var socket: SRWebSocket? - - public var isConnected: Bool { - return socket != nil - } - - private override init() { } - - public static let shared = MockWebSocket() - - public func connect(url: URL) { - socket = SRWebSocket(url: url) - socket?.delegate = self - socket?.open() - } - - public func disconnect() { - socket?.close() - socket = nil - delegate?.webSocketDidDisconnect(self) - } - - public func send(_ data: Data) { - guard let socket = socket else { return } - socket.send(data) - } -} - -extension MockWebSocket : SRWebSocketDelegate { - - public func webSocket(_ webSocket: SRWebSocket!, didReceiveMessage message: Any!) { - guard let message = message as? String else { return } - delegate?.webSocket(self, didReceive: message) - } - - public func webSocketDidOpen(_ webSocket: SRWebSocket!) { - delegate?.webSocketDidConnect(self) - } - - public func webSocket(_ webSocket: SRWebSocket!, didFailWithError error: Error!) { - SNLog("Web socket failed with error: \(error?.localizedDescription ?? "nil").") - self.disconnect() - } - - public func webSocket(_ webSocket: SRWebSocket!, didCloseWithCode code: Int, reason: String!, wasClean: Bool) { - SNLog("Web socket closed.") - self.disconnect() - } -} diff --git a/SessionMessagingKit/Calls/WebSocket.swift b/SessionMessagingKit/Calls/WebSocket.swift new file mode 100644 index 000000000..e3139dff3 --- /dev/null +++ b/SessionMessagingKit/Calls/WebSocket.swift @@ -0,0 +1,48 @@ +import Foundation +import SocketRocket + +public protocol WebSocketDelegate : AnyObject { + + func webSocketDidConnect(_ webSocket: WebSocket) + func webSocketDidDisconnect(_ webSocket: WebSocket) + func webSocket(_ webSocket: WebSocket, didReceive 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(url: URL) { + socket.open() + } + + public func send(_ data: Data) { + socket.send(data) + } + + public func webSocket(_ webSocket: SRWebSocket!, didReceiveMessage message: Any!) { + guard let message = message as? String else { return } + delegate?.webSocket(self, didReceive: message) + } + + public func disconnect() { + socket.close() + delegate?.webSocketDidDisconnect(self) + } + + 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() + } +} From 8b187641b8e7d13c54495e5b49456922e3eda011 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 13 Aug 2021 15:34:44 +1000 Subject: [PATCH 018/368] Clean up TestCallConfig --- Session.xcodeproj/project.pbxproj | 8 +++--- SessionMessagingKit/Calls/CallManager.swift | 2 +- .../Calls/MockCallConfig.swift | 25 ------------------- .../Calls/MockCallServer.swift | 6 ++--- .../Calls/TestCallConfig.swift | 13 ++++++++++ 5 files changed, 21 insertions(+), 33 deletions(-) delete mode 100644 SessionMessagingKit/Calls/MockCallConfig.swift create mode 100644 SessionMessagingKit/Calls/TestCallConfig.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 1ae1439fe..3d49dac53 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -249,7 +249,7 @@ B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B320B6258C30D70020074B /* HTMLMetadata.swift */; }; B8B558EF26C4B56C00693325 /* VideoCallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558EE26C4B56C00693325 /* VideoCallVC.swift */; }; B8B558F126C4BB0600693325 /* CameraManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F026C4BB0600693325 /* CameraManager.swift */; }; - B8B558F326C4CA4600693325 /* MockCallConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F226C4CA4600693325 /* MockCallConfig.swift */; }; + B8B558F326C4CA4600693325 /* TestCallConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F226C4CA4600693325 /* TestCallConfig.swift */; }; B8B558F926C4CE6800693325 /* CallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F826C4CE6800693325 /* CallVC.swift */; }; B8B558FB26C4D25C00693325 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FA26C4D25C00693325 /* WebSocket.swift */; }; B8B558FD26C4D35400693325 /* MockCallServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FC26C4D35400693325 /* MockCallServer.swift */; }; @@ -1225,7 +1225,7 @@ B8B320B6258C30D70020074B /* HTMLMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadata.swift; sourceTree = ""; }; B8B558EE26C4B56C00693325 /* VideoCallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCallVC.swift; sourceTree = ""; }; B8B558F026C4BB0600693325 /* CameraManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = ""; }; - B8B558F226C4CA4600693325 /* MockCallConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCallConfig.swift; sourceTree = ""; }; + B8B558F226C4CA4600693325 /* TestCallConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCallConfig.swift; sourceTree = ""; }; B8B558F826C4CE6800693325 /* CallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVC.swift; sourceTree = ""; }; B8B558FA26C4D25C00693325 /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = ""; }; B8B558FC26C4D35400693325 /* MockCallServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCallServer.swift; sourceTree = ""; }; @@ -2371,10 +2371,10 @@ B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */, B8B558FE26C4E05E00693325 /* CallManager+Messages.swift */, B806ECA226C4A8C6008BDA44 /* MockTURNServer.swift */, - B8B558F226C4CA4600693325 /* MockCallConfig.swift */, B8B558FC26C4D35400693325 /* MockCallServer.swift */, B8B5590026C4E2A400693325 /* SignalingMessage.swift */, B8B558FA26C4D25C00693325 /* WebSocket.swift */, + B8B558F226C4CA4600693325 /* TestCallConfig.swift */, ); path = Calls; sourceTree = ""; @@ -4761,7 +4761,7 @@ C32C5AAB256DBE8F003C73A2 /* TSIncomingMessage+Conversion.swift in Sources */, B866CE112581C1A900535CC4 /* Sodium+Conversion.swift in Sources */, C32C5A88256DBCF9003C73A2 /* MessageReceiver+Handling.swift in Sources */, - B8B558F326C4CA4600693325 /* MockCallConfig.swift in Sources */, + B8B558F326C4CA4600693325 /* TestCallConfig.swift in Sources */, C32C5C1B256DC9E0003C73A2 /* General.swift in Sources */, C32C5A02256DB658003C73A2 /* MessageSender+Convenience.swift in Sources */, B8566C6C256F60F50045A0B9 /* OWSUserProfile.m in Sources */, diff --git a/SessionMessagingKit/Calls/CallManager.swift b/SessionMessagingKit/Calls/CallManager.swift index 735d7f120..5994c9a00 100644 --- a/SessionMessagingKit/Calls/CallManager.swift +++ b/SessionMessagingKit/Calls/CallManager.swift @@ -23,7 +23,7 @@ public final class CallManager : 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: MockCallConfig.default.webRTCICEServers) ] + configuration.iceServers = [ RTCIceServer(urlStrings: TestCallConfig.defaultICEServers) ] configuration.sdpSemantics = .unifiedPlan let pcert = RTCCertificate.generate(withParams: [ "expires": NSNumber(value: 100000), "name": "RSASSA-PKCS1-v1_5" ]) configuration.certificate = pcert diff --git a/SessionMessagingKit/Calls/MockCallConfig.swift b/SessionMessagingKit/Calls/MockCallConfig.swift deleted file mode 100644 index 4613f600b..000000000 --- a/SessionMessagingKit/Calls/MockCallConfig.swift +++ /dev/null @@ -1,25 +0,0 @@ - -public struct MockCallConfig { - public let signalingServerURL: String - public let serverURL: String - public let webRTCICEServers: [String] - - private static let defaultSignalingServerURL = "ws://developereric.com:8080" - private 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" - ] - private static let defaultServerURL = "https://appr.tc" - - private init(signalingServerURL: String, serverURL: String, webRTCICEServers: [String]) { - self.signalingServerURL = signalingServerURL - self.serverURL = serverURL - self.webRTCICEServers = webRTCICEServers - } - - public static let `default` = MockCallConfig(signalingServerURL: defaultSignalingServerURL, - serverURL: defaultServerURL, webRTCICEServers: defaultICEServers) -} diff --git a/SessionMessagingKit/Calls/MockCallServer.swift b/SessionMessagingKit/Calls/MockCallServer.swift index 2925507e7..f922db76a 100644 --- a/SessionMessagingKit/Calls/MockCallServer.swift +++ b/SessionMessagingKit/Calls/MockCallServer.swift @@ -13,15 +13,15 @@ public struct RoomInfo { public enum MockCallServer { private static func getRoomURL(for roomID: String) -> String { - let base = MockCallConfig.default.serverURL + "/join/" + let base = TestCallConfig.defaultServerURL + "/join/" return base + "\(roomID)" } private static func getLeaveURL(roomID: String, userID: String) -> String { - let base = MockCallConfig.default.serverURL + "/leave/" + let base = TestCallConfig.defaultServerURL + "/leave/" return base + "\(roomID)/\(userID)" } private static func getMessageURL(roomID: String, userID: String) -> String { - let base = MockCallConfig.default.serverURL + "/message/" + let base = TestCallConfig.defaultServerURL + "/message/" return base + "\(roomID)/\(userID)" } diff --git a/SessionMessagingKit/Calls/TestCallConfig.swift b/SessionMessagingKit/Calls/TestCallConfig.swift new file mode 100644 index 000000000..d461df08c --- /dev/null +++ b/SessionMessagingKit/Calls/TestCallConfig.swift @@ -0,0 +1,13 @@ + +public enum TestCallConfig { + + public static let defaultSignalingServerURL = "ws://developereric.com:8080" + 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" +} From 49d93b9cfd38f8f7c54d21ed63d26a6e0d9a1824 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 13 Aug 2021 15:43:33 +1000 Subject: [PATCH 019/368] Clean up TestCallServer --- Session.xcodeproj/project.pbxproj | 12 +++-- Session/Calls/CallVC.swift | 8 +-- .../Calls/MockCallServer.swift | 51 ------------------- SessionMessagingKit/Calls/RoomInfo.swift | 9 ++++ .../Calls/TestCallServer.swift | 49 ++++++++++++++++++ 5 files changed, 70 insertions(+), 59 deletions(-) delete mode 100644 SessionMessagingKit/Calls/MockCallServer.swift create mode 100644 SessionMessagingKit/Calls/RoomInfo.swift create mode 100644 SessionMessagingKit/Calls/TestCallServer.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 3d49dac53..cf7dfeb6f 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -160,6 +160,7 @@ B806ECA126C4A7E4008BDA44 /* CallManager+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */; }; B806ECA326C4A8C6008BDA44 /* MockTURNServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA226C4A8C6008BDA44 /* MockTURNServer.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 */; }; @@ -252,7 +253,7 @@ B8B558F326C4CA4600693325 /* TestCallConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F226C4CA4600693325 /* TestCallConfig.swift */; }; B8B558F926C4CE6800693325 /* CallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F826C4CE6800693325 /* CallVC.swift */; }; B8B558FB26C4D25C00693325 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FA26C4D25C00693325 /* WebSocket.swift */; }; - B8B558FD26C4D35400693325 /* MockCallServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FC26C4D35400693325 /* MockCallServer.swift */; }; + B8B558FD26C4D35400693325 /* TestCallServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FC26C4D35400693325 /* TestCallServer.swift */; }; B8B558FF26C4E05E00693325 /* CallManager+Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FE26C4E05E00693325 /* CallManager+Messages.swift */; }; B8B5590126C4E2A400693325 /* SignalingMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B5590026C4E2A400693325 /* SignalingMessage.swift */; }; B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A4238F627000BA5194 /* HomeVC.swift */; }; @@ -1155,6 +1156,7 @@ B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallManager+UI.swift"; sourceTree = ""; }; B806ECA226C4A8C6008BDA44 /* MockTURNServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTURNServer.swift; sourceTree = ""; }; B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = ""; }; + B80F469926C63DD000DCE243 /* RoomInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomInfo.swift; sourceTree = ""; }; B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedConversationCell.swift; sourceTree = ""; }; B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerVC.swift; sourceTree = ""; }; B81D25B726157F20004D1FE1 /* storage-seed-1.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "storage-seed-1.crt"; sourceTree = ""; }; @@ -1228,7 +1230,7 @@ B8B558F226C4CA4600693325 /* TestCallConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCallConfig.swift; sourceTree = ""; }; B8B558F826C4CE6800693325 /* CallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVC.swift; sourceTree = ""; }; B8B558FA26C4D25C00693325 /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = ""; }; - B8B558FC26C4D35400693325 /* MockCallServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCallServer.swift; sourceTree = ""; }; + B8B558FC26C4D35400693325 /* TestCallServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCallServer.swift; sourceTree = ""; }; B8B558FE26C4E05E00693325 /* CallManager+Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallManager+Messages.swift"; sourceTree = ""; }; B8B5590026C4E2A400693325 /* SignalingMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalingMessage.swift; sourceTree = ""; }; B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; @@ -2371,10 +2373,11 @@ B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */, B8B558FE26C4E05E00693325 /* CallManager+Messages.swift */, B806ECA226C4A8C6008BDA44 /* MockTURNServer.swift */, - B8B558FC26C4D35400693325 /* MockCallServer.swift */, B8B5590026C4E2A400693325 /* SignalingMessage.swift */, B8B558FA26C4D25C00693325 /* WebSocket.swift */, B8B558F226C4CA4600693325 /* TestCallConfig.swift */, + B8B558FC26C4D35400693325 /* TestCallServer.swift */, + B80F469926C63DD000DCE243 /* RoomInfo.swift */, ); path = Calls; sourceTree = ""; @@ -4739,6 +4742,7 @@ 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 */, @@ -4751,7 +4755,7 @@ C3DA9C0725AE7396008F7C7E /* ConfigurationMessage.swift in Sources */, B8856CEE256F1054001CE70E /* OWSAudioPlayer.m in Sources */, C32C5EDC256DF501003C73A2 /* YapDatabaseConnection+OWS.m in Sources */, - B8B558FD26C4D35400693325 /* MockCallServer.swift in Sources */, + B8B558FD26C4D35400693325 /* TestCallServer.swift in Sources */, C3BBE0762554CDA60050F1E3 /* Configuration.swift in Sources */, C35D76DB26606304009AA5FB /* ThreadUpdateBatcher.swift in Sources */, B8B32033258B235D0020074B /* Storage+Contacts.swift in Sources */, diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 489ec4adc..c66ad7516 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -16,7 +16,7 @@ final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegat private var currentRoomInfo: RoomInfo? var isInitiator: Bool { - return currentRoomInfo?.isInitiator == "true" + return currentRoomInfo?.isInitiator == true } // MARK: UI Components @@ -92,7 +92,7 @@ final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegat disconnect() } else { isConnected = true - MockCallServer.join(roomID: roomID).done2 { [weak self] info in + TestCallServer.join(roomID: roomID).done2 { [weak self] info in guard let self = self else { return } self.log("Successfully joined room.") self.currentRoomInfo = info @@ -114,7 +114,7 @@ final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegat private func disconnect() { guard let info = currentRoomInfo else { return } - MockCallServer.leave(roomID: info.roomID, userID: info.clientID).done2 { [weak self] in + TestCallServer.leave(roomID: info.roomID, userID: info.clientID).done2 { [weak self] in guard let self = self else { return } self.log("Disconnected.") } @@ -192,7 +192,7 @@ final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegat func callManager(_ callManager: CallManager, sendData data: Data) { guard let info = currentRoomInfo else { return } - MockCallServer.send(data, roomID: info.roomID, userID: info.clientID).retainUntilComplete() + TestCallServer.send(data, roomID: info.roomID, userID: info.clientID).retainUntilComplete() } // MARK: Camera diff --git a/SessionMessagingKit/Calls/MockCallServer.swift b/SessionMessagingKit/Calls/MockCallServer.swift deleted file mode 100644 index f922db76a..000000000 --- a/SessionMessagingKit/Calls/MockCallServer.swift +++ /dev/null @@ -1,51 +0,0 @@ -import Foundation -import PromiseKit - -public struct RoomInfo { - public let roomID: String - public let wssURL: String - public let wssPostURL: String - public let clientID: String - public let isInitiator: String - public let messages: [String]? -} - -public enum MockCallServer { - - private static func getRoomURL(for roomID: String) -> String { - let base = TestCallConfig.defaultServerURL + "/join/" - return base + "\(roomID)" - } - private static func getLeaveURL(roomID: String, userID: String) -> String { - let base = TestCallConfig.defaultServerURL + "/leave/" - return base + "\(roomID)/\(userID)" - } - private static func getMessageURL(roomID: String, userID: String) -> String { - let base = TestCallConfig.defaultServerURL + "/message/" - return base + "\(roomID)/\(userID)" - } - - public static func join(roomID: String) -> Promise { - HTTP.execute(.post, getRoomURL(for: roomID)).map2 { json in - guard let status = json["result"] as? String else { throw HTTP.Error.invalidJSON } - if status == "FULL" { preconditionFailure() } - 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, - let isInitiator = info["is_initiator"] as? String 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 { - return HTTP.execute(.post, getLeaveURL(roomID: roomID, userID: userID)).map2 { _ in } - } - - public static func send(_ message: Data, roomID: String, userID: String) -> Promise { - HTTP.execute(.post, getMessageURL(roomID: roomID, userID: userID), body: message).map2 { _ in } - } -} diff --git a/SessionMessagingKit/Calls/RoomInfo.swift b/SessionMessagingKit/Calls/RoomInfo.swift new file mode 100644 index 000000000..affe92543 --- /dev/null +++ b/SessionMessagingKit/Calls/RoomInfo.swift @@ -0,0 +1,9 @@ + +public struct RoomInfo { + public let roomID: String + public let wssURL: String + public let wssPostURL: String + public let clientID: String + public let isInitiator: Bool + public let messages: [String]? +} diff --git a/SessionMessagingKit/Calls/TestCallServer.swift b/SessionMessagingKit/Calls/TestCallServer.swift new file mode 100644 index 000000000..98c3fc684 --- /dev/null +++ b/SessionMessagingKit/Calls/TestCallServer.swift @@ -0,0 +1,49 @@ +import Foundation +import PromiseKit + +public enum TestCallServer { + + public enum Error : LocalizedError { + case roomFull + + public var errorDescription: String? { + switch self { + case .roomFull: return "The room is full." + } + } + } + + public static func join(roomID: String) -> Promise { + let url = "\(TestCallConfig.defaultServerURL)/join/\(roomID)" + return HTTP.execute(.post, url).map2 { json in + guard let status = json["result"] as? String else { throw HTTP.Error.invalidJSON } + guard status != "FULL" else { throw Error.roomFull } + guard let info = json["params"] as? JSON, + let roomID = info["room_id"] as? String, + let wssURL = info["wss_url"] as? String, + let wssPostURL = info["wss_post_url"] as? String, + let clientID = info["client_id"] as? String else { throw HTTP.Error.invalidJSON } + let isInitiator: Bool + if let bool = info["is_initiator"] as? Bool { + isInitiator = bool + } else if let string = info["is_initiator"] as? String { + isInitiator = (string == "true") + } else { + throw HTTP.Error.invalidJSON + } + let messages = info["messages"] as? [String] + return RoomInfo(roomID: roomID, wssURL: wssURL, wssPostURL: wssPostURL, + clientID: clientID, isInitiator: isInitiator, messages: messages) + } + } + + public static func leave(roomID: String, userID: String) -> Promise { + let url = "\(TestCallConfig.defaultServerURL)/leave/\(roomID)/\(userID)" + return HTTP.execute(.post, url).map2 { _ in } + } + + public static func send(_ message: Data, roomID: String, userID: String) -> Promise { + let url = "\(TestCallConfig.defaultServerURL)/message/\(roomID)/\(userID)" + return HTTP.execute(.post, url, body: message).map2 { _ in } + } +} From 4dd218daf65900f1a34d0c0e46a09c784b45d0ea Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 13 Aug 2021 15:46:40 +1000 Subject: [PATCH 020/368] Delete unused MockTURNServer --- Session.xcodeproj/project.pbxproj | 4 ---- SessionMessagingKit/Calls/MockTURNServer.swift | 16 ---------------- SessionMessagingKit/Calls/TestCallConfig.swift | 1 - 3 files changed, 21 deletions(-) delete mode 100644 SessionMessagingKit/Calls/MockTURNServer.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index cf7dfeb6f..fb4e3740c 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -158,7 +158,6 @@ B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */; }; B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */; }; B806ECA126C4A7E4008BDA44 /* CallManager+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */; }; - B806ECA326C4A8C6008BDA44 /* MockTURNServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA226C4A8C6008BDA44 /* MockTURNServer.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 */; }; @@ -1154,7 +1153,6 @@ B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderView.swift; sourceTree = ""; }; B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCell.swift; sourceTree = ""; }; B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallManager+UI.swift"; sourceTree = ""; }; - B806ECA226C4A8C6008BDA44 /* MockTURNServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTURNServer.swift; sourceTree = ""; }; B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = ""; }; B80F469926C63DD000DCE243 /* RoomInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomInfo.swift; sourceTree = ""; }; B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedConversationCell.swift; sourceTree = ""; }; @@ -2372,7 +2370,6 @@ B8DE1FB326C22F2F0079C9CE /* CallManager.swift */, B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */, B8B558FE26C4E05E00693325 /* CallManager+Messages.swift */, - B806ECA226C4A8C6008BDA44 /* MockTURNServer.swift */, B8B5590026C4E2A400693325 /* SignalingMessage.swift */, B8B558FA26C4D25C00693325 /* WebSocket.swift */, B8B558F226C4CA4600693325 /* TestCallConfig.swift */, @@ -4798,7 +4795,6 @@ B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */, C3DB66C3260ACCE6001EFC55 /* OpenGroupPollerV2.swift in Sources */, C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */, - B806ECA326C4A8C6008BDA44 /* MockTURNServer.swift in Sources */, C32C5FBB256E0206003C73A2 /* OWSBackgroundTask.m in Sources */, B8856CA8256F0F42001CE70E /* OWSBackupFragment.m in Sources */, C32C5C3D256DCBAF003C73A2 /* AppReadiness.m in Sources */, diff --git a/SessionMessagingKit/Calls/MockTURNServer.swift b/SessionMessagingKit/Calls/MockTURNServer.swift deleted file mode 100644 index 924f3432a..000000000 --- a/SessionMessagingKit/Calls/MockTURNServer.swift +++ /dev/null @@ -1,16 +0,0 @@ -import PromiseKit - -enum MockTURNSserver { - - static func getICEServerURL() -> Promise { - HTTP.execute(.get, "https://appr.tc/params").map2 { json in - guard let url = json["ice_server_url"] as? String else { throw HTTP.Error.invalidJSON } - return url - } - } - - static func makeTurnServerRequest(iceServerURL: String) -> Promise { - let headers = [ "referer" : "https://appr.tc" ] - return HTTP.execute(.post, iceServerURL, body: nil, headers: headers) - } -} diff --git a/SessionMessagingKit/Calls/TestCallConfig.swift b/SessionMessagingKit/Calls/TestCallConfig.swift index d461df08c..9bdec5f31 100644 --- a/SessionMessagingKit/Calls/TestCallConfig.swift +++ b/SessionMessagingKit/Calls/TestCallConfig.swift @@ -1,7 +1,6 @@ public enum TestCallConfig { - public static let defaultSignalingServerURL = "ws://developereric.com:8080" public static let defaultICEServers = [ "stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302", From 662fc945e28ef41b0967c9d1587df029a5a61bc1 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 16 Aug 2021 14:40:07 +1000 Subject: [PATCH 021/368] Refactor CallVC --- Session.xcodeproj/project.pbxproj | 42 +- Session/Calls/CallVC.swift | 430 +++++++++--------- Session/Calls/CallVCV2+Camera.swift | 14 + Session/Calls/CallVCV2+MessageSending.swift | 31 ++ Session/Calls/CallVCV2+WebSocket.swift | 28 ++ Session/Calls/CallVCV2.swift | 95 ++++ Session/Calls/CameraManager.swift | 33 +- Session/Calls/VideoCallVC.swift | 120 ++--- Session/Home/HomeVC.swift | 2 +- ...wift => CallManager+MessageHandling.swift} | 16 +- SessionMessagingKit/Calls/CallManager.swift | 38 +- .../Calls/{ => Temp}/RoomInfo.swift | 0 .../Calls/{ => Temp}/SignalingMessage.swift | 27 +- .../Calls/{ => Temp}/TestCallConfig.swift | 0 .../Calls/{ => Temp}/TestCallServer.swift | 0 .../Calls/{ => Temp}/WebSocket.swift | 6 +- 16 files changed, 529 insertions(+), 353 deletions(-) create mode 100644 Session/Calls/CallVCV2+Camera.swift create mode 100644 Session/Calls/CallVCV2+MessageSending.swift create mode 100644 Session/Calls/CallVCV2+WebSocket.swift create mode 100644 Session/Calls/CallVCV2.swift rename SessionMessagingKit/Calls/{CallManager+Messages.swift => CallManager+MessageHandling.swift} (56%) rename SessionMessagingKit/Calls/{ => Temp}/RoomInfo.swift (100%) rename SessionMessagingKit/Calls/{ => Temp}/SignalingMessage.swift (74%) rename SessionMessagingKit/Calls/{ => Temp}/TestCallConfig.swift (100%) rename SessionMessagingKit/Calls/{ => Temp}/TestCallServer.swift (100%) rename SessionMessagingKit/Calls/{ => Temp}/WebSocket.swift (90%) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index fb4e3740c..be803d2e3 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -201,6 +201,10 @@ B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; }; 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 */; }; @@ -253,7 +257,7 @@ B8B558F926C4CE6800693325 /* CallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F826C4CE6800693325 /* CallVC.swift */; }; B8B558FB26C4D25C00693325 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FA26C4D25C00693325 /* WebSocket.swift */; }; B8B558FD26C4D35400693325 /* TestCallServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FC26C4D35400693325 /* TestCallServer.swift */; }; - B8B558FF26C4E05E00693325 /* CallManager+Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FE26C4E05E00693325 /* CallManager+Messages.swift */; }; + B8B558FF26C4E05E00693325 /* CallManager+MessageHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FE26C4E05E00693325 /* CallManager+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 */; }; @@ -1200,6 +1204,10 @@ B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = ""; }; B87588582644CA9D000E60D0 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; B8758859264503A6000E60D0 /* JoinOpenGroupModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinOpenGroupModal.swift; sourceTree = ""; }; + B877E24126CA12910007970A /* CallVCV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVCV2.swift; sourceTree = ""; }; + B877E24326CA12F00007970A /* CallVCV2+MessageSending.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVCV2+MessageSending.swift"; sourceTree = ""; }; + B877E24526CA13BA0007970A /* CallVCV2+Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVCV2+Camera.swift"; sourceTree = ""; }; + B877E24726CA15170007970A /* CallVCV2+WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVCV2+WebSocket.swift"; sourceTree = ""; }; B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Interaction.swift"; sourceTree = ""; }; B879D448247E1BE300DB3608 /* PathVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathVC.swift; sourceTree = ""; }; B879D44A247E1D9200DB3608 /* PathStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathStatusView.swift; sourceTree = ""; }; @@ -1229,7 +1237,7 @@ B8B558F826C4CE6800693325 /* CallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVC.swift; sourceTree = ""; }; B8B558FA26C4D25C00693325 /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = ""; }; B8B558FC26C4D35400693325 /* TestCallServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCallServer.swift; sourceTree = ""; }; - B8B558FE26C4E05E00693325 /* CallManager+Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallManager+Messages.swift"; sourceTree = ""; }; + B8B558FE26C4E05E00693325 /* CallManager+MessageHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallManager+MessageHandling.swift"; sourceTree = ""; }; B8B5590026C4E2A400693325 /* SignalingMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalingMessage.swift; sourceTree = ""; }; B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B8BAC75B2695645400EA1759 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; @@ -2190,6 +2198,18 @@ path = "Message Cells"; sourceTree = ""; }; + B877E24026CA11170007970A /* Temp */ = { + isa = PBXGroup; + children = ( + B8B5590026C4E2A400693325 /* SignalingMessage.swift */, + B8B558FA26C4D25C00693325 /* WebSocket.swift */, + B8B558F226C4CA4600693325 /* TestCallConfig.swift */, + B8B558FC26C4D35400693325 /* TestCallServer.swift */, + B80F469926C63DD000DCE243 /* RoomInfo.swift */, + ); + path = Temp; + sourceTree = ""; + }; B887C38125C7C79700E11DAE /* Input View */ = { isa = PBXGroup; children = ( @@ -2331,6 +2351,10 @@ B8B558ED26C4B55F00693325 /* Calls */ = { isa = PBXGroup; children = ( + B877E24126CA12910007970A /* CallVCV2.swift */, + B877E24326CA12F00007970A /* CallVCV2+MessageSending.swift */, + B877E24526CA13BA0007970A /* CallVCV2+Camera.swift */, + B877E24726CA15170007970A /* CallVCV2+WebSocket.swift */, B8B558F826C4CE6800693325 /* CallVC.swift */, B8B558EE26C4B56C00693325 /* VideoCallVC.swift */, B8B558F026C4BB0600693325 /* CameraManager.swift */, @@ -2367,14 +2391,10 @@ B8DE1FB226C22F1F0079C9CE /* Calls */ = { isa = PBXGroup; children = ( + B877E24026CA11170007970A /* Temp */, B8DE1FB326C22F2F0079C9CE /* CallManager.swift */, B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */, - B8B558FE26C4E05E00693325 /* CallManager+Messages.swift */, - B8B5590026C4E2A400693325 /* SignalingMessage.swift */, - B8B558FA26C4D25C00693325 /* WebSocket.swift */, - B8B558F226C4CA4600693325 /* TestCallConfig.swift */, - B8B558FC26C4D35400693325 /* TestCallServer.swift */, - B80F469926C63DD000DCE243 /* RoomInfo.swift */, + B8B558FE26C4E05E00693325 /* CallManager+MessageHandling.swift */, ); path = Calls; sourceTree = ""; @@ -4688,7 +4708,7 @@ C352A32F2557549C00338F3E /* NotifyPNServerJob.swift in Sources */, 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */, C300A5F22554B09800555489 /* MessageSender.swift in Sources */, - B8B558FF26C4E05E00693325 /* CallManager+Messages.swift in Sources */, + B8B558FF26C4E05E00693325 /* CallManager+MessageHandling.swift in Sources */, C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */, C32C5AAD256DBE8F003C73A2 /* TSInfoMessage.m in Sources */, C32C5A13256DB7A5003C73A2 /* PushNotificationAPI.swift in Sources */, @@ -4842,6 +4862,7 @@ B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */, B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */, B879D449247E1BE300DB3608 /* PathVC.swift in Sources */, + B877E24626CA13BA0007970A /* CallVCV2+Camera.swift in Sources */, 454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */, 340FC8B4204DAC8D007AEB0F /* OWSBackupSettingsViewController.m in Sources */, 34D1F0871F8678AA0066283D /* ConversationViewItem.m in Sources */, @@ -4904,6 +4925,7 @@ B835246E25C38ABF0089A44F /* ConversationVC.swift in Sources */, B8AF4BB426A5204600583500 /* SendSeedModal.swift in Sources */, B821494625D4D6FF009C0F2A /* URLModal.swift in Sources */, + B877E24226CA12910007970A /* CallVCV2.swift in Sources */, C374EEEB25DA3CA70073A857 /* ConversationTitleView.swift in Sources */, B88FA7F2260C3EB10049422F /* OpenGroupSuggestionGrid.swift in Sources */, 4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */, @@ -4943,6 +4965,7 @@ 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 */, @@ -4952,6 +4975,7 @@ C328250F25CA06020062D0A7 /* VoiceMessageView.swift in Sources */, B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */, 3488F9362191CC4000E524CC /* MediaView.swift in Sources */, + B877E24426CA12F00007970A /* CallVCV2+MessageSending.swift in Sources */, B8569AC325CB5D2900DBA3DB /* ConversationVC+Interaction.swift in Sources */, 3496955C219B605E00DCFE74 /* ImagePickerController.swift in Sources */, C31D1DE32521718E005D4DA8 /* UserSelectionVC.swift in Sources */, diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index c66ad7516..679da703f 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -1,215 +1,215 @@ -import UIKit -import AVFoundation -import WebRTC - -final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegate, WebSocketDelegate { - private var webSocket: WebSocket? - private let videoCallVC = VideoCallVC() - let videoCapturer: RTCVideoCapturer = RTCCameraVideoCapturer(delegate: CallManager.shared.localVideoSource) - private var messageQueue: [String] = [] - private var isConnected = false { - didSet { - let title = isConnected ? "Leave" : "Join" - joinOrLeaveButton.setTitle(title, for: UIControl.State.normal) - } - } - private var currentRoomInfo: RoomInfo? - - var isInitiator: Bool { - return currentRoomInfo?.isInitiator == true - } - - // MARK: UI Components - private lazy var previewView: UIImageView = { - return UIImageView() - }() - - private lazy var containerView: UIView = { - return UIView() - }() - - private lazy var joinOrLeaveButton: UIButton = { - let result = UIButton() - result.setTitle("Join", for: UIControl.State.normal) - result.addTarget(self, action: #selector(joinOrLeave), for: UIControl.Event.touchUpInside) - return result - }() - - private lazy var roomNumberTextField: UITextField = { - let result = UITextField() - result.set(.width, to: 120) - result.set(.height, to: 40) - result.backgroundColor = UIColor.white.withAlphaComponent(0.1) - return result - }() - - private lazy var infoTextView: UITextView = { - let result = UITextView() - result.backgroundColor = UIColor.white.withAlphaComponent(0.1) - return result - }() - - // MARK: Lifecycle - override func viewDidLoad() { - super.viewDidLoad() - touch(CallManager.shared) - CallManager.shared.delegate = self - CameraManager.shared.delegate = self - CameraManager.shared.prepare() - addChild(videoCallVC) - containerView.addSubview(videoCallVC.view) - videoCallVC.view.translatesAutoresizingMaskIntoConstraints = false - videoCallVC.view.pin(to: containerView) - view.addSubview(containerView) - containerView.pin(to: view) - view.addSubview(joinOrLeaveButton) - joinOrLeaveButton.translatesAutoresizingMaskIntoConstraints = false - joinOrLeaveButton.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view) - view.addSubview(roomNumberTextField) - roomNumberTextField.translatesAutoresizingMaskIntoConstraints = false - roomNumberTextField.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.left ], to: view) - view.addSubview(infoTextView) - infoTextView.translatesAutoresizingMaskIntoConstraints = false - infoTextView.pin([ UIView.HorizontalEdge.left, UIView.HorizontalEdge.right ], to: view) - infoTextView.center(.vertical, in: view) - infoTextView.set(.height, to: 200) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - CameraManager.shared.start() - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - CameraManager.shared.stop() - } - - // MARK: Interaction - @objc private func joinOrLeave() { - guard let roomID = roomNumberTextField.text, !roomID.isEmpty else { return } - if isConnected { - disconnect() - } else { - isConnected = true - TestCallServer.join(roomID: roomID).done2 { [weak self] info in - guard let self = self else { return } - self.log("Successfully joined room.") - self.currentRoomInfo = info - if let messages = info.messages { - self.handle(messages) - } - let webSocket = WebSocket(url: URL(string: info.wssURL)!) - webSocket.delegate = self - self.webSocket = webSocket - }.catch2 { [weak self] error in - guard let self = self else { return } - self.isConnected = false - self.log("Couldn't join room due to error: \(error).") - SNLog("Couldn't join room due to error: \(error).") - } - roomNumberTextField.resignFirstResponder() - } - } - - private func disconnect() { - guard let info = currentRoomInfo else { return } - TestCallServer.leave(roomID: info.roomID, userID: info.clientID).done2 { [weak self] in - guard let self = self else { return } - self.log("Disconnected.") - } - let message = [ "type": "bye" ] - guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return } - webSocket?.send(data) - webSocket?.delegate = nil - webSocket = nil - currentRoomInfo = nil - isConnected = false - CallManager.shared.endCall() - } - - // MARK: Message Handling - func handle(_ messages: [String]) { - messageQueue.append(contentsOf: messages) - drainMessageQueue() - } - - func drainMessageQueue() { - guard isConnected else { return } - for message in messageQueue { - handle(message) - } - messageQueue.removeAll() - CallManager.shared.drainMessageQueue() - } - - func handle(_ message: String) { - let signalingMessage = SignalingMessage.from(message: message) - switch signalingMessage { - case .candidate(let candidate): - CallManager.shared.handleCandidateMessage(candidate) - log("Candidate received.") - case .answer(let answer): - CallManager.shared.handleRemoteDescription(answer) - log("Answer received.") - case .offer(let offer): - CallManager.shared.handleRemoteDescription(offer) - log("Offer received.") - case .bye: - disconnect() - default: - break - } - } - - // MARK: Streaming - func webSocketDidConnect(_ webSocket: WebSocket) { - guard let info = currentRoomInfo else { return } - log("Connected to web socket.") - let message = [ - "cmd": "register", - "roomid": info.roomID, - "clientid": info.clientID - ] - guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return } - webSocket.send(data) - if isInitiator { - CallManager.shared.initiateCall().retainUntilComplete() - } - drainMessageQueue() - } - - func webSocket(_ webSocket: WebSocket, didReceive message: String) { - log("Received data from web socket.") - handle(message) - CallManager.shared.drainMessageQueue() - } - - func webSocketDidDisconnect(_ webSocket: WebSocket) { - webSocket.delegate = nil - log("Disconnecting from web socket.") - } - - func callManager(_ callManager: CallManager, sendData data: Data) { - guard let info = currentRoomInfo else { return } - TestCallServer.send(data, roomID: info.roomID, userID: info.clientID).retainUntilComplete() - } - - // MARK: Camera - func captureVideoOutput(sampleBuffer: CMSampleBuffer) { - guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } - let ciImage = CIImage(cvImageBuffer: pixelBuffer) - let image = UIImage(ciImage: ciImage) - DispatchQueue.main.async { [weak self] in - self?.previewView.image = image - } - } - - // MARK: Logging - private func log(_ string: String) { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - self.infoTextView.text = self.infoTextView.text + "\n" + string - } - } -} +//import UIKit +//import AVFoundation +//import WebRTC +// +//final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegate, WebSocketDelegate { +// private var webSocket: WebSocket? +// private let videoCallVC = VideoCallVC() +// let videoCapturer: RTCVideoCapturer = RTCCameraVideoCapturer(delegate: CallManager.shared.localVideoSource) +// private var messageQueue: [String] = [] +// private var isConnected = false { +// didSet { +// let title = isConnected ? "Leave" : "Join" +// joinOrLeaveButton.setTitle(title, for: UIControl.State.normal) +// } +// } +// private var currentRoomInfo: RoomInfo? +// +// var isInitiator: Bool { +// return currentRoomInfo?.isInitiator == true +// } +// +// // MARK: UI Components +// private lazy var previewView: UIImageView = { +// return UIImageView() +// }() +// +// private lazy var containerView: UIView = { +// return UIView() +// }() +// +// private lazy var joinOrLeaveButton: UIButton = { +// let result = UIButton() +// result.setTitle("Join", for: UIControl.State.normal) +// result.addTarget(self, action: #selector(joinOrLeave), for: UIControl.Event.touchUpInside) +// return result +// }() +// +// private lazy var roomNumberTextField: UITextField = { +// let result = UITextField() +// result.set(.width, to: 120) +// result.set(.height, to: 40) +// result.backgroundColor = UIColor.white.withAlphaComponent(0.1) +// return result +// }() +// +// private lazy var infoTextView: UITextView = { +// let result = UITextView() +// result.backgroundColor = UIColor.white.withAlphaComponent(0.1) +// return result +// }() +// +// // MARK: Lifecycle +// override func viewDidLoad() { +// super.viewDidLoad() +// touch(CallManager.shared) +// CallManager.shared.delegate = self +// CameraManager.shared.delegate = self +// CameraManager.shared.prepare() +// addChild(videoCallVC) +// containerView.addSubview(videoCallVC.view) +// videoCallVC.view.translatesAutoresizingMaskIntoConstraints = false +// videoCallVC.view.pin(to: containerView) +// view.addSubview(containerView) +// containerView.pin(to: view) +// view.addSubview(joinOrLeaveButton) +// joinOrLeaveButton.translatesAutoresizingMaskIntoConstraints = false +// joinOrLeaveButton.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view) +// view.addSubview(roomNumberTextField) +// roomNumberTextField.translatesAutoresizingMaskIntoConstraints = false +// roomNumberTextField.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.left ], to: view) +// view.addSubview(infoTextView) +// infoTextView.translatesAutoresizingMaskIntoConstraints = false +// infoTextView.pin([ UIView.HorizontalEdge.left, UIView.HorizontalEdge.right ], to: view) +// infoTextView.center(.vertical, in: view) +// infoTextView.set(.height, to: 200) +// } +// +// override func viewDidAppear(_ animated: Bool) { +// super.viewDidAppear(animated) +// CameraManager.shared.start() +// } +// +// override func viewWillDisappear(_ animated: Bool) { +// super.viewWillDisappear(animated) +// CameraManager.shared.stop() +// } +// +// // MARK: Interaction +// @objc private func joinOrLeave() { +// guard let roomID = roomNumberTextField.text, !roomID.isEmpty else { return } +// if isConnected { +// disconnect() +// } else { +// isConnected = true +// TestCallServer.join(roomID: roomID).done2 { [weak self] info in +// guard let self = self else { return } +// self.log("Successfully joined room.") +// self.currentRoomInfo = info +// if let messages = info.messages { +// self.handle(messages) +// } +// let webSocket = WebSocket(url: URL(string: info.wssURL)!) +// webSocket.delegate = self +// self.webSocket = webSocket +// }.catch2 { [weak self] error in +// guard let self = self else { return } +// self.isConnected = false +// self.log("Couldn't join room due to error: \(error).") +// SNLog("Couldn't join room due to error: \(error).") +// } +// roomNumberTextField.resignFirstResponder() +// } +// } +// +// private func disconnect() { +// guard let info = currentRoomInfo else { return } +// TestCallServer.leave(roomID: info.roomID, userID: info.clientID).done2 { [weak self] in +// guard let self = self else { return } +// self.log("Disconnected.") +// } +// let message = [ "type": "bye" ] +// guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return } +// webSocket?.send(data) +// webSocket?.delegate = nil +// webSocket = nil +// currentRoomInfo = nil +// isConnected = false +// CallManager.shared.endCall() +// } +// +// // MARK: Message Handling +// func handle(_ messages: [String]) { +// messageQueue.append(contentsOf: messages) +// drainMessageQueue() +// } +// +// func drainMessageQueue() { +// guard isConnected else { return } +// for message in messageQueue { +// handle(message) +// } +// messageQueue.removeAll() +// CallManager.shared.drainICECandidateQueue() +// } +// +// func handle(_ message: String) { +// let signalingMessage = SignalingMessage.from(message: message) +// switch signalingMessage { +// case .candidate(let candidate): +// CallManager.shared.handleCandidateMessage(candidate) +// log("Candidate received.") +// case .answer(let answer): +// CallManager.shared.handleRemoteDescription(answer) +// log("Answer received.") +// case .offer(let offer): +// CallManager.shared.handleRemoteDescription(offer) +// log("Offer received.") +// case .bye: +// disconnect() +// default: +// break +// } +// } +// +// // MARK: Streaming +// func webSocketDidConnect(_ webSocket: WebSocket) { +// guard let info = currentRoomInfo else { return } +// log("Connected to web socket.") +// let message = [ +// "cmd": "register", +// "roomid": info.roomID, +// "clientid": info.clientID +// ] +// guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return } +// webSocket.send(data) +// if isInitiator { +// CallManager.shared.initiateCall().retainUntilComplete() +// } +// drainMessageQueue() +// } +// +// func webSocket(_ webSocket: WebSocket, didReceive message: String) { +// log("Received data from web socket.") +// handle(message) +// CallManager.shared.drainICECandidateQueue() +// } +// +// func webSocketDidDisconnect(_ webSocket: WebSocket) { +// webSocket.delegate = nil +// log("Disconnecting from web socket.") +// } +// +// func callManager(_ callManager: CallManager, sendData data: Data) { +// guard let info = currentRoomInfo else { return } +// TestCallServer.send(data, roomID: info.roomID, userID: info.clientID).retainUntilComplete() +// } +// +// // MARK: Camera +// func captureVideoOutput(sampleBuffer: CMSampleBuffer) { +// guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } +// let ciImage = CIImage(cvImageBuffer: pixelBuffer) +// let image = UIImage(ciImage: ciImage) +// DispatchQueue.main.async { [weak self] in +// self?.previewView.image = image +// } +// } +// +// // MARK: Logging +// private func log(_ string: String) { +// DispatchQueue.main.async { [weak self] in +// guard let self = self else { return } +// self.infoTextView.text = self.infoTextView.text + "\n" + string +// } +// } +//} diff --git a/Session/Calls/CallVCV2+Camera.swift b/Session/Calls/CallVCV2+Camera.swift new file mode 100644 index 000000000..030b8bc47 --- /dev/null +++ b/Session/Calls/CallVCV2+Camera.swift @@ -0,0 +1,14 @@ +import WebRTC + +extension CallVCV2 : CameraManagerDelegate { + + func handleVideoOutputCaptured(sampleBuffer: CMSampleBuffer) { + guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } + let rtcPixelBuffer = RTCCVPixelBuffer(pixelBuffer: pixelBuffer) + let timestamp = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) + let timestampNs = Int64(timestamp * 1000000000) + let frame = RTCVideoFrame(buffer: rtcPixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: timestampNs) + frame.timeStamp = Int32(timestamp) + callManager.handleLocalFrameCaptured(frame) + } +} diff --git a/Session/Calls/CallVCV2+MessageSending.swift b/Session/Calls/CallVCV2+MessageSending.swift new file mode 100644 index 000000000..11f0bcfe2 --- /dev/null +++ b/Session/Calls/CallVCV2+MessageSending.swift @@ -0,0 +1,31 @@ +import WebRTC + +extension CallVCV2 : CallManagerDelegate { + + /// Invoked by `CallManager` upon initiating or accepting a call. This method sends the SDP to the other + /// party before streaming starts. + func sendSDP(_ sdp: RTCSessionDescription) { + guard let room = room else { return } + let json = [ + "type" : RTCSessionDescription.string(for: sdp.type), + "sdp" : sdp.sdp + ] + guard let data = try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) else { return } + print("[Calls] Sending SDP to test call server: \(json).") + TestCallServer.send(data, roomID: room.roomID, userID: room.clientID).retainUntilComplete() + } + + /// Invoked when the peer connection has generated an ICE candidate. + func sendICECandidate(_ candidate: RTCIceCandidate) { + guard let room = room else { return } + let json = [ + "type" : "candidate", + "label" : "\(candidate.sdpMLineIndex)", + "id" : candidate.sdpMid, + "candidate" : candidate.sdp + ] + guard let data = try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) else { return } + print("[Calls] Sending ICE candidate to test call server: \(json).") + TestCallServer.send(data, roomID: room.roomID, userID: room.clientID).retainUntilComplete() + } +} diff --git a/Session/Calls/CallVCV2+WebSocket.swift b/Session/Calls/CallVCV2+WebSocket.swift new file mode 100644 index 000000000..26ca6e61f --- /dev/null +++ b/Session/Calls/CallVCV2+WebSocket.swift @@ -0,0 +1,28 @@ + +extension CallVCV2 : WebSocketDelegate { + + func webSocketDidConnect(_ webSocket: WebSocket) { + 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).") + webSocket.send(data) + print("[Calls] Is initiator: \(room.isInitiator).") + if room.isInitiator { + callManager.initiateCall().retainUntilComplete() + } + } + + func webSocketDidDisconnect(_ webSocket: WebSocket) { + webSocket.delegate = nil + } + + func webSocket(_ webSocket: WebSocket, didReceive message: String) { + print("[Calls] Message received through web socket: \(message).") + handle([ message ]) + } +} diff --git a/Session/Calls/CallVCV2.swift b/Session/Calls/CallVCV2.swift new file mode 100644 index 000000000..ab1ff8ccd --- /dev/null +++ b/Session/Calls/CallVCV2.swift @@ -0,0 +1,95 @@ +import WebRTC + +final class CallVCV2 : UIViewController { + let roomID = "37923672512" // NOTE: You need to change this every time to ensure the room isn't full + var room: RoomInfo? + var socket: WebSocket? + + lazy var callManager: CallManager = { + let result = CallManager() + result.delegate = self + return result + }() + + lazy var cameraManager: CameraManager = { + let result = CameraManager() + result.delegate = self + return result + }() + + lazy var videoCapturer: RTCVideoCapturer = { + return RTCCameraVideoCapturer(delegate: callManager.localVideoSource) + }() + + // MARK: Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + setUpViewHierarchy() + cameraManager.prepare() + touch(videoCapturer) + autoConnectToTestRoom() + } + + func setUpViewHierarchy() { + // Create video views + let localVideoView = RTCMTLVideoView() + localVideoView.contentMode = .scaleAspectFill + let remoteVideoView = RTCMTLVideoView() + remoteVideoView.contentMode = .scaleAspectFill + // Set up stack view + let stackView = UIStackView(arrangedSubviews: [ localVideoView, remoteVideoView ]) + stackView.axis = .vertical + stackView.distribution = .fillEqually + stackView.alignment = .fill + view.addSubview(stackView) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.pin(to: view) + // Attach video views + callManager.attachLocalRenderer(localVideoView) + callManager.attachRemoteRenderer(remoteVideoView) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + cameraManager.start() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + 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.handleCandidateMessage(candidate) + case .answer(let answer): callManager.handleRemoteDescription(answer) + case .offer(let offer): callManager.handleRemoteDescription(offer) + default: break + } + } + callManager.drainICECandidateQueue() + } +} diff --git a/Session/Calls/CameraManager.swift b/Session/Calls/CameraManager.swift index 019efab22..e9af7ddc1 100644 --- a/Session/Calls/CameraManager.swift +++ b/Session/Calls/CameraManager.swift @@ -2,29 +2,26 @@ import Foundation import AVFoundation @objc -protocol CameraCaptureDelegate : AnyObject { +protocol CameraManagerDelegate : AnyObject { - func captureVideoOutput(sampleBuffer: CMSampleBuffer) + func handleVideoOutputCaptured(sampleBuffer: CMSampleBuffer) } final class CameraManager : NSObject { private let captureSession = AVCaptureSession() private let videoDataOutput = AVCaptureVideoDataOutput() + private let videoDataOutputQueue + = DispatchQueue(label: "CameraManager.videoDataOutputQueue", qos: .userInitiated, attributes: [], autoreleaseFrequency: .workItem) private let audioDataOutput = AVCaptureAudioDataOutput() - private let dataOutputQueue = DispatchQueue(label: "CameraManager.dataOutputQueue", qos: .userInitiated, attributes: [], autoreleaseFrequency: .workItem) private var isCapturing = false - weak var delegate: CameraCaptureDelegate? + weak var delegate: CameraManagerDelegate? private lazy var videoCaptureDevice: AVCaptureDevice? = { return AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) }() - static let shared = CameraManager() - - private override init() { } - func prepare() { - captureSession.sessionPreset = .low + print("[Calls] Preparing camera.") if let videoCaptureDevice = videoCaptureDevice, let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice), captureSession.canAddInput(videoInput) { captureSession.addInput(videoInput) @@ -32,30 +29,28 @@ final class CameraManager : NSObject { if captureSession.canAddOutput(videoDataOutput) { captureSession.addOutput(videoDataOutput) videoDataOutput.videoSettings = [ kCVPixelBufferPixelFormatTypeKey as String : Int(kCVPixelFormatType_32BGRA) ] - videoDataOutput.setSampleBufferDelegate(self, queue: dataOutputQueue) - videoDataOutput.connection(with: .video)?.videoOrientation = .portrait - videoDataOutput.connection(with: .video)?.automaticallyAdjustsVideoMirroring = false - videoDataOutput.connection(with: .video)?.isVideoMirrored = true + videoDataOutput.setSampleBufferDelegate(self, queue: videoDataOutputQueue) + guard let connection = videoDataOutput.connection(with: AVMediaType.video) else { return } + connection.videoOrientation = .portrait + connection.automaticallyAdjustsVideoMirroring = false + connection.isVideoMirrored = true } else { SNLog("Couldn't add video data output to capture session.") - captureSession.commitConfiguration() } } func start() { guard !isCapturing else { return } + print("[Calls] Starting camera.") isCapturing = true - #if arch(arm64) captureSession.startRunning() - #endif } func stop() { guard isCapturing else { return } + print("[Calls] Stopping camera.") isCapturing = false - #if arch(arm64) captureSession.stopRunning() - #endif } } @@ -63,7 +58,7 @@ extension CameraManager : AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptur func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard connection == videoDataOutput.connection(with: .video) else { return } - delegate?.captureVideoOutput(sampleBuffer: sampleBuffer) + delegate?.handleVideoOutputCaptured(sampleBuffer: sampleBuffer) } func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { } diff --git a/Session/Calls/VideoCallVC.swift b/Session/Calls/VideoCallVC.swift index 6e7caf8ca..de4523573 100644 --- a/Session/Calls/VideoCallVC.swift +++ b/Session/Calls/VideoCallVC.swift @@ -1,60 +1,60 @@ -import UIKit -import AVFoundation -import WebRTC - -final class VideoCallVC : UIViewController { - private var localVideoView = UIView() - private var remoteVideoView = UIView() - - override func viewDidLoad() { - super.viewDidLoad() - setUpViewHierarchy() - CameraManager.shared.delegate = self - } - - private func setUpViewHierarchy() { - // Create video views - #if arch(arm64) - // Use Metal - let localRenderer = RTCMTLVideoView(frame: self.localVideoView.frame) - localRenderer.contentMode = .scaleAspectFill - let remoteRenderer = RTCMTLVideoView(frame: self.remoteVideoView.frame) - remoteRenderer.contentMode = .scaleAspectFill - #else - // Use OpenGLES - let localRenderer = RTCEAGLVideoView(frame: self.localVideoView.frame) - let remoteRenderer = RTCEAGLVideoView(frame: self.remoteVideoView.frame) - #endif - // Set up stack view - let stackView = UIStackView(arrangedSubviews: [ localVideoView, remoteVideoView ]) - stackView.axis = .vertical - stackView.distribution = .fillEqually - stackView.alignment = .fill - view.addSubview(stackView) - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.pin(to: view) - // Attach video views - CallManager.shared.attachLocalRenderer(localRenderer) - CallManager.shared.attachRemoteRenderer(remoteRenderer) - localVideoView.addSubview(localRenderer) - localRenderer.translatesAutoresizingMaskIntoConstraints = false - localRenderer.pin(to: localVideoView) - remoteVideoView.addSubview(remoteRenderer) - remoteRenderer.translatesAutoresizingMaskIntoConstraints = false - remoteRenderer.pin(to: remoteVideoView) - } -} - -// MARK: Camera -extension VideoCallVC : CameraCaptureDelegate { - - func captureVideoOutput(sampleBuffer: CMSampleBuffer) { - guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } - let rtcpixelBuffer = RTCCVPixelBuffer(pixelBuffer: pixelBuffer) - let timestamp = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) - let timestampNs = Int64(timestamp * 1000000000) - let videoFrame = RTCVideoFrame(buffer: rtcpixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: timestampNs) - videoFrame.timeStamp = Int32(timestamp) - CallManager.shared.handleLocalFrameCaptured(videoFrame) - } -} +//import UIKit +//import AVFoundation +//import WebRTC +// +//final class VideoCallVC : UIViewController { +// private var localVideoView = UIView() +// private var remoteVideoView = UIView() +// +// override func viewDidLoad() { +// super.viewDidLoad() +// setUpViewHierarchy() +// CameraManager.shared.delegate = self +// } +// +// private func setUpViewHierarchy() { +// // Create video views +// #if arch(arm64) +// // Use Metal +// let localRenderer = RTCMTLVideoView(frame: self.localVideoView.frame) +// localRenderer.contentMode = .scaleAspectFill +// let remoteRenderer = RTCMTLVideoView(frame: self.remoteVideoView.frame) +// remoteRenderer.contentMode = .scaleAspectFill +// #else +// // Use OpenGLES +// let localRenderer = RTCEAGLVideoView(frame: self.localVideoView.frame) +// let remoteRenderer = RTCEAGLVideoView(frame: self.remoteVideoView.frame) +// #endif +// // Set up stack view +// let stackView = UIStackView(arrangedSubviews: [ localVideoView, remoteVideoView ]) +// stackView.axis = .vertical +// stackView.distribution = .fillEqually +// stackView.alignment = .fill +// view.addSubview(stackView) +// stackView.translatesAutoresizingMaskIntoConstraints = false +// stackView.pin(to: view) +// // Attach video views +// CallManager.shared.attachLocalRenderer(localRenderer) +// CallManager.shared.attachRemoteRenderer(remoteRenderer) +// localVideoView.addSubview(localRenderer) +// localRenderer.translatesAutoresizingMaskIntoConstraints = false +// localRenderer.pin(to: localVideoView) +// remoteVideoView.addSubview(remoteRenderer) +// remoteRenderer.translatesAutoresizingMaskIntoConstraints = false +// remoteRenderer.pin(to: remoteVideoView) +// } +//} +// +//// MARK: Camera +//extension VideoCallVC : CameraCaptureDelegate { +// +// func captureVideoOutput(sampleBuffer: CMSampleBuffer) { +// guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } +// let rtcpixelBuffer = RTCCVPixelBuffer(pixelBuffer: pixelBuffer) +// let timestamp = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) +// let timestampNs = Int64(timestamp * 1000000000) +// let videoFrame = RTCVideoFrame(buffer: rtcpixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: timestampNs) +// videoFrame.timeStamp = Int32(timestamp) +// CallManager.shared.handleLocalFrameCaptured(videoFrame) +// } +//} diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index e62d27dd8..dcd501085 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -158,7 +158,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv // Get default open group rooms if needed OpenGroupAPIV2.getDefaultRoomsIfNeeded() - let callVC = CallVC() + let callVC = CallVCV2() present(callVC, animated: true, completion: nil) } diff --git a/SessionMessagingKit/Calls/CallManager+Messages.swift b/SessionMessagingKit/Calls/CallManager+MessageHandling.swift similarity index 56% rename from SessionMessagingKit/Calls/CallManager+Messages.swift rename to SessionMessagingKit/Calls/CallManager+MessageHandling.swift index a4601d6e0..e2a2cd811 100644 --- a/SessionMessagingKit/Calls/CallManager+Messages.swift +++ b/SessionMessagingKit/Calls/CallManager+MessageHandling.swift @@ -3,26 +3,20 @@ import WebRTC extension CallManager { public func handleCandidateMessage(_ candidate: RTCIceCandidate) { + print("[Calls] Received ICE candidate message.") candidateQueue.append(candidate) } public func handleRemoteDescription(_ sdp: RTCSessionDescription) { + print("[Calls] Received remote SDP: \(sdp.sdp).") peerConnection.setRemoteDescription(sdp, completionHandler: { [weak self] error in if let error = error { SNLog("Couldn't set SDP due to error: \(error).") } else { - guard let self = self else { return } - if sdp.type == .offer, self.peerConnection.localDescription == nil { - self.acceptCall() - } + guard let self = self, + sdp.type == .offer, self.peerConnection.localDescription == nil else { return } + self.acceptCall().retainUntilComplete() } }) } - - public func drainMessageQueue() { - for candidate in candidateQueue { - peerConnection.add(candidate) - } - candidateQueue.removeAll() - } } diff --git a/SessionMessagingKit/Calls/CallManager.swift b/SessionMessagingKit/Calls/CallManager.swift index 5994c9a00..0c447a842 100644 --- a/SessionMessagingKit/Calls/CallManager.swift +++ b/SessionMessagingKit/Calls/CallManager.swift @@ -3,8 +3,9 @@ import WebRTC public protocol CallManagerDelegate : AnyObject { var videoCapturer: RTCVideoCapturer { get } - - func callManager(_ callManager: CallManager, sendData data: Data) + + func sendSDP(_ sdp: RTCSessionDescription) + func sendICECandidate(_ candidate: RTCIceCandidate) } /// See https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription for more information. @@ -78,7 +79,7 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { } // MARK: Initialization - internal override init() { + public override init() { super.init() let mediaStreamTrackIDS = ["ARDAMS"] peerConnection.add(audioTrack, streamIds: mediaStreamTrackIDS) @@ -97,10 +98,16 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { audioSession.unlockForConfiguration() } - public static let shared = CallManager() + // MARK: General + public func drainICECandidateQueue() { + print("[Calls] Draining ICE candidate queue.") + candidateQueue.forEach { peerConnection.add($0) } + candidateQueue.removeAll() + } // MARK: Call Management public func initiateCall() -> Promise { + print("[Calls] Initiating call.") /* guard let thread = TSContactThread.fetch(for: publicKey, using: transaction) else { return Promise(error: Error.noThread) } */ @@ -117,8 +124,7 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { } } - let message = sdp.serialize()! - self.delegate?.callManager(self, sendData: message) + self.delegate?.sendSDP(sdp) /* let message = CallMessage() @@ -133,6 +139,7 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { } public func acceptCall() -> Promise { + print("[Calls] Accepting call.") /* guard let thread = TSContactThread.fetch(for: publicKey, using: transaction) else { return Promise(error: Error.noThread) } */ @@ -149,8 +156,7 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { } } - let message = sdp.serialize()! - self.delegate?.callManager(self, sendData: message) + self.delegate?.sendSDP(sdp) /* let message = CallMessage() @@ -195,8 +201,7 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { SNLog("ICE candidate generated.") - let message = candidate.serialize()! - delegate?.callManager(self, sendData: message) + delegate?.sendICECandidate(candidate) } public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) { @@ -207,16 +212,3 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { SNLog("Data channel opened.") } } - -// MARK: Utilities - -extension RTCSessionDescription { - - func serialize() -> Data? { - let json = [ - "type": RTCSessionDescription.string(for: self.type), - "sdp": self.sdp - ] - return try? JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted]) - } -} diff --git a/SessionMessagingKit/Calls/RoomInfo.swift b/SessionMessagingKit/Calls/Temp/RoomInfo.swift similarity index 100% rename from SessionMessagingKit/Calls/RoomInfo.swift rename to SessionMessagingKit/Calls/Temp/RoomInfo.swift diff --git a/SessionMessagingKit/Calls/SignalingMessage.swift b/SessionMessagingKit/Calls/Temp/SignalingMessage.swift similarity index 74% rename from SessionMessagingKit/Calls/SignalingMessage.swift rename to SessionMessagingKit/Calls/Temp/SignalingMessage.swift index 85744082b..3ca82215c 100644 --- a/SessionMessagingKit/Calls/SignalingMessage.swift +++ b/SessionMessagingKit/Calls/Temp/SignalingMessage.swift @@ -2,37 +2,36 @@ import Foundation import WebRTC public enum SignalingMessage { - case none 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) else { return .none } - guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON else { return .none } + 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 foo = json["msg"] as? String { - guard let data = foo.data(using: String.Encoding.utf8) else { return .none } - guard let bar = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON else { return .none } - messageAsJSON = bar + 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 .none } + guard let type = messageAsJSON["type"] as? String else { return nil } switch type { case "candidate": - guard let candidate = RTCIceCandidate.candidate(from: messageAsJSON) else { return .none } + guard let candidate = RTCIceCandidate.candidate(from: messageAsJSON) else { return nil } return .candidate(candidate) case "answer": - guard let sdp = messageAsJSON["sdp"] as? String else { return .none } + 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 .none } + guard let sdp = messageAsJSON["sdp"] as? String else { return nil } return .offer(RTCSessionDescription(type: .offer, sdp: sdp)) case "bye": return .bye - default: return .none + default: return nil } } } @@ -46,7 +45,7 @@ extension RTCIceCandidate { "id": sdpMid, "candidate": sdp ] - return try? JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted]) + return try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) } static func candidate(from json: JSON) -> RTCIceCandidate? { diff --git a/SessionMessagingKit/Calls/TestCallConfig.swift b/SessionMessagingKit/Calls/Temp/TestCallConfig.swift similarity index 100% rename from SessionMessagingKit/Calls/TestCallConfig.swift rename to SessionMessagingKit/Calls/Temp/TestCallConfig.swift diff --git a/SessionMessagingKit/Calls/TestCallServer.swift b/SessionMessagingKit/Calls/Temp/TestCallServer.swift similarity index 100% rename from SessionMessagingKit/Calls/TestCallServer.swift rename to SessionMessagingKit/Calls/Temp/TestCallServer.swift diff --git a/SessionMessagingKit/Calls/WebSocket.swift b/SessionMessagingKit/Calls/Temp/WebSocket.swift similarity index 90% rename from SessionMessagingKit/Calls/WebSocket.swift rename to SessionMessagingKit/Calls/Temp/WebSocket.swift index e3139dff3..44009db99 100644 --- a/SessionMessagingKit/Calls/WebSocket.swift +++ b/SessionMessagingKit/Calls/Temp/WebSocket.swift @@ -18,7 +18,7 @@ public final class WebSocket : NSObject, SRWebSocketDelegate { socket.delegate = self } - public func connect(url: URL) { + public func connect() { socket.open() } @@ -26,6 +26,10 @@ public final class WebSocket : NSObject, SRWebSocketDelegate { socket.send(data) } + public func webSocketDidOpen(_ webSocket: SRWebSocket!) { + delegate?.webSocketDidConnect(self) + } + public func webSocket(_ webSocket: SRWebSocket!, didReceiveMessage message: Any!) { guard let message = message as? String else { return } delegate?.webSocket(self, didReceive: message) From c00ddde64db1fc87371318e1dd4ebe965be8ac03 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 16 Aug 2021 14:50:09 +1000 Subject: [PATCH 022/368] Remove unused code --- Session.xcodeproj/project.pbxproj | 8 -- Session/Calls/CallVC.swift | 215 ------------------------------ Session/Calls/VideoCallVC.swift | 60 --------- 3 files changed, 283 deletions(-) delete mode 100644 Session/Calls/CallVC.swift delete mode 100644 Session/Calls/VideoCallVC.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index be803d2e3..dfb08bebe 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -251,10 +251,8 @@ B8B32033258B235D0020074B /* Storage+Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B32032258B235D0020074B /* Storage+Contacts.swift */; }; B8B3204E258C15C80020074B /* ContactsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B32044258C117C0020074B /* ContactsMigration.swift */; }; B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B320B6258C30D70020074B /* HTMLMetadata.swift */; }; - B8B558EF26C4B56C00693325 /* VideoCallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558EE26C4B56C00693325 /* VideoCallVC.swift */; }; B8B558F126C4BB0600693325 /* CameraManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F026C4BB0600693325 /* CameraManager.swift */; }; B8B558F326C4CA4600693325 /* TestCallConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F226C4CA4600693325 /* TestCallConfig.swift */; }; - B8B558F926C4CE6800693325 /* CallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F826C4CE6800693325 /* CallVC.swift */; }; B8B558FB26C4D25C00693325 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FA26C4D25C00693325 /* WebSocket.swift */; }; B8B558FD26C4D35400693325 /* TestCallServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FC26C4D35400693325 /* TestCallServer.swift */; }; B8B558FF26C4E05E00693325 /* CallManager+MessageHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FE26C4E05E00693325 /* CallManager+MessageHandling.swift */; }; @@ -1231,10 +1229,8 @@ B8B32032258B235D0020074B /* Storage+Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Contacts.swift"; sourceTree = ""; }; B8B32044258C117C0020074B /* ContactsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsMigration.swift; sourceTree = ""; }; B8B320B6258C30D70020074B /* HTMLMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadata.swift; sourceTree = ""; }; - B8B558EE26C4B56C00693325 /* VideoCallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCallVC.swift; sourceTree = ""; }; B8B558F026C4BB0600693325 /* CameraManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = ""; }; B8B558F226C4CA4600693325 /* TestCallConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCallConfig.swift; sourceTree = ""; }; - B8B558F826C4CE6800693325 /* CallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVC.swift; sourceTree = ""; }; B8B558FA26C4D25C00693325 /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = ""; }; B8B558FC26C4D35400693325 /* TestCallServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCallServer.swift; sourceTree = ""; }; B8B558FE26C4E05E00693325 /* CallManager+MessageHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallManager+MessageHandling.swift"; sourceTree = ""; }; @@ -2355,8 +2351,6 @@ B877E24326CA12F00007970A /* CallVCV2+MessageSending.swift */, B877E24526CA13BA0007970A /* CallVCV2+Camera.swift */, B877E24726CA15170007970A /* CallVCV2+WebSocket.swift */, - B8B558F826C4CE6800693325 /* CallVC.swift */, - B8B558EE26C4B56C00693325 /* VideoCallVC.swift */, B8B558F026C4BB0600693325 /* CameraManager.swift */, ); path = Calls; @@ -4900,7 +4894,6 @@ B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */, D221A09A169C9E5E00537ABF /* main.m in Sources */, 3496957221A301A100DCFE74 /* OWSBackup.m in Sources */, - B8B558F926C4CE6800693325 /* CallVC.swift in Sources */, B835247925C38D880089A44F /* MessageCell.swift in Sources */, B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */, 34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */, @@ -4987,7 +4980,6 @@ B8269D3D25C7B34D00488AB4 /* InputTextView.swift in Sources */, 76EB054018170B33006006FC /* AppDelegate.m in Sources */, 340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */, - B8B558EF26C4B56C00693325 /* VideoCallVC.swift in Sources */, C33100082558FF6D00070591 /* NewConversationButtonSet.swift in Sources */, C3AAFFF225AE99710089E6DD /* AppDelegate.swift in Sources */, B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */, diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift deleted file mode 100644 index 679da703f..000000000 --- a/Session/Calls/CallVC.swift +++ /dev/null @@ -1,215 +0,0 @@ -//import UIKit -//import AVFoundation -//import WebRTC -// -//final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegate, WebSocketDelegate { -// private var webSocket: WebSocket? -// private let videoCallVC = VideoCallVC() -// let videoCapturer: RTCVideoCapturer = RTCCameraVideoCapturer(delegate: CallManager.shared.localVideoSource) -// private var messageQueue: [String] = [] -// private var isConnected = false { -// didSet { -// let title = isConnected ? "Leave" : "Join" -// joinOrLeaveButton.setTitle(title, for: UIControl.State.normal) -// } -// } -// private var currentRoomInfo: RoomInfo? -// -// var isInitiator: Bool { -// return currentRoomInfo?.isInitiator == true -// } -// -// // MARK: UI Components -// private lazy var previewView: UIImageView = { -// return UIImageView() -// }() -// -// private lazy var containerView: UIView = { -// return UIView() -// }() -// -// private lazy var joinOrLeaveButton: UIButton = { -// let result = UIButton() -// result.setTitle("Join", for: UIControl.State.normal) -// result.addTarget(self, action: #selector(joinOrLeave), for: UIControl.Event.touchUpInside) -// return result -// }() -// -// private lazy var roomNumberTextField: UITextField = { -// let result = UITextField() -// result.set(.width, to: 120) -// result.set(.height, to: 40) -// result.backgroundColor = UIColor.white.withAlphaComponent(0.1) -// return result -// }() -// -// private lazy var infoTextView: UITextView = { -// let result = UITextView() -// result.backgroundColor = UIColor.white.withAlphaComponent(0.1) -// return result -// }() -// -// // MARK: Lifecycle -// override func viewDidLoad() { -// super.viewDidLoad() -// touch(CallManager.shared) -// CallManager.shared.delegate = self -// CameraManager.shared.delegate = self -// CameraManager.shared.prepare() -// addChild(videoCallVC) -// containerView.addSubview(videoCallVC.view) -// videoCallVC.view.translatesAutoresizingMaskIntoConstraints = false -// videoCallVC.view.pin(to: containerView) -// view.addSubview(containerView) -// containerView.pin(to: view) -// view.addSubview(joinOrLeaveButton) -// joinOrLeaveButton.translatesAutoresizingMaskIntoConstraints = false -// joinOrLeaveButton.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view) -// view.addSubview(roomNumberTextField) -// roomNumberTextField.translatesAutoresizingMaskIntoConstraints = false -// roomNumberTextField.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.left ], to: view) -// view.addSubview(infoTextView) -// infoTextView.translatesAutoresizingMaskIntoConstraints = false -// infoTextView.pin([ UIView.HorizontalEdge.left, UIView.HorizontalEdge.right ], to: view) -// infoTextView.center(.vertical, in: view) -// infoTextView.set(.height, to: 200) -// } -// -// override func viewDidAppear(_ animated: Bool) { -// super.viewDidAppear(animated) -// CameraManager.shared.start() -// } -// -// override func viewWillDisappear(_ animated: Bool) { -// super.viewWillDisappear(animated) -// CameraManager.shared.stop() -// } -// -// // MARK: Interaction -// @objc private func joinOrLeave() { -// guard let roomID = roomNumberTextField.text, !roomID.isEmpty else { return } -// if isConnected { -// disconnect() -// } else { -// isConnected = true -// TestCallServer.join(roomID: roomID).done2 { [weak self] info in -// guard let self = self else { return } -// self.log("Successfully joined room.") -// self.currentRoomInfo = info -// if let messages = info.messages { -// self.handle(messages) -// } -// let webSocket = WebSocket(url: URL(string: info.wssURL)!) -// webSocket.delegate = self -// self.webSocket = webSocket -// }.catch2 { [weak self] error in -// guard let self = self else { return } -// self.isConnected = false -// self.log("Couldn't join room due to error: \(error).") -// SNLog("Couldn't join room due to error: \(error).") -// } -// roomNumberTextField.resignFirstResponder() -// } -// } -// -// private func disconnect() { -// guard let info = currentRoomInfo else { return } -// TestCallServer.leave(roomID: info.roomID, userID: info.clientID).done2 { [weak self] in -// guard let self = self else { return } -// self.log("Disconnected.") -// } -// let message = [ "type": "bye" ] -// guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return } -// webSocket?.send(data) -// webSocket?.delegate = nil -// webSocket = nil -// currentRoomInfo = nil -// isConnected = false -// CallManager.shared.endCall() -// } -// -// // MARK: Message Handling -// func handle(_ messages: [String]) { -// messageQueue.append(contentsOf: messages) -// drainMessageQueue() -// } -// -// func drainMessageQueue() { -// guard isConnected else { return } -// for message in messageQueue { -// handle(message) -// } -// messageQueue.removeAll() -// CallManager.shared.drainICECandidateQueue() -// } -// -// func handle(_ message: String) { -// let signalingMessage = SignalingMessage.from(message: message) -// switch signalingMessage { -// case .candidate(let candidate): -// CallManager.shared.handleCandidateMessage(candidate) -// log("Candidate received.") -// case .answer(let answer): -// CallManager.shared.handleRemoteDescription(answer) -// log("Answer received.") -// case .offer(let offer): -// CallManager.shared.handleRemoteDescription(offer) -// log("Offer received.") -// case .bye: -// disconnect() -// default: -// break -// } -// } -// -// // MARK: Streaming -// func webSocketDidConnect(_ webSocket: WebSocket) { -// guard let info = currentRoomInfo else { return } -// log("Connected to web socket.") -// let message = [ -// "cmd": "register", -// "roomid": info.roomID, -// "clientid": info.clientID -// ] -// guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return } -// webSocket.send(data) -// if isInitiator { -// CallManager.shared.initiateCall().retainUntilComplete() -// } -// drainMessageQueue() -// } -// -// func webSocket(_ webSocket: WebSocket, didReceive message: String) { -// log("Received data from web socket.") -// handle(message) -// CallManager.shared.drainICECandidateQueue() -// } -// -// func webSocketDidDisconnect(_ webSocket: WebSocket) { -// webSocket.delegate = nil -// log("Disconnecting from web socket.") -// } -// -// func callManager(_ callManager: CallManager, sendData data: Data) { -// guard let info = currentRoomInfo else { return } -// TestCallServer.send(data, roomID: info.roomID, userID: info.clientID).retainUntilComplete() -// } -// -// // MARK: Camera -// func captureVideoOutput(sampleBuffer: CMSampleBuffer) { -// guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } -// let ciImage = CIImage(cvImageBuffer: pixelBuffer) -// let image = UIImage(ciImage: ciImage) -// DispatchQueue.main.async { [weak self] in -// self?.previewView.image = image -// } -// } -// -// // MARK: Logging -// private func log(_ string: String) { -// DispatchQueue.main.async { [weak self] in -// guard let self = self else { return } -// self.infoTextView.text = self.infoTextView.text + "\n" + string -// } -// } -//} diff --git a/Session/Calls/VideoCallVC.swift b/Session/Calls/VideoCallVC.swift deleted file mode 100644 index de4523573..000000000 --- a/Session/Calls/VideoCallVC.swift +++ /dev/null @@ -1,60 +0,0 @@ -//import UIKit -//import AVFoundation -//import WebRTC -// -//final class VideoCallVC : UIViewController { -// private var localVideoView = UIView() -// private var remoteVideoView = UIView() -// -// override func viewDidLoad() { -// super.viewDidLoad() -// setUpViewHierarchy() -// CameraManager.shared.delegate = self -// } -// -// private func setUpViewHierarchy() { -// // Create video views -// #if arch(arm64) -// // Use Metal -// let localRenderer = RTCMTLVideoView(frame: self.localVideoView.frame) -// localRenderer.contentMode = .scaleAspectFill -// let remoteRenderer = RTCMTLVideoView(frame: self.remoteVideoView.frame) -// remoteRenderer.contentMode = .scaleAspectFill -// #else -// // Use OpenGLES -// let localRenderer = RTCEAGLVideoView(frame: self.localVideoView.frame) -// let remoteRenderer = RTCEAGLVideoView(frame: self.remoteVideoView.frame) -// #endif -// // Set up stack view -// let stackView = UIStackView(arrangedSubviews: [ localVideoView, remoteVideoView ]) -// stackView.axis = .vertical -// stackView.distribution = .fillEqually -// stackView.alignment = .fill -// view.addSubview(stackView) -// stackView.translatesAutoresizingMaskIntoConstraints = false -// stackView.pin(to: view) -// // Attach video views -// CallManager.shared.attachLocalRenderer(localRenderer) -// CallManager.shared.attachRemoteRenderer(remoteRenderer) -// localVideoView.addSubview(localRenderer) -// localRenderer.translatesAutoresizingMaskIntoConstraints = false -// localRenderer.pin(to: localVideoView) -// remoteVideoView.addSubview(remoteRenderer) -// remoteRenderer.translatesAutoresizingMaskIntoConstraints = false -// remoteRenderer.pin(to: remoteVideoView) -// } -//} -// -//// MARK: Camera -//extension VideoCallVC : CameraCaptureDelegate { -// -// func captureVideoOutput(sampleBuffer: CMSampleBuffer) { -// guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } -// let rtcpixelBuffer = RTCCVPixelBuffer(pixelBuffer: pixelBuffer) -// let timestamp = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) -// let timestampNs = Int64(timestamp * 1000000000) -// let videoFrame = RTCVideoFrame(buffer: rtcpixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: timestampNs) -// videoFrame.timeStamp = Int32(timestamp) -// CallManager.shared.handleLocalFrameCaptured(videoFrame) -// } -//} From 950c6204b7baabb6b1b64e55e9e0e74f0eb0f1d1 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 16 Aug 2021 15:00:38 +1000 Subject: [PATCH 023/368] Fix layout --- Session/Calls/CallVCV2.swift | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Session/Calls/CallVCV2.swift b/Session/Calls/CallVCV2.swift index ab1ff8ccd..beaed8d24 100644 --- a/Session/Calls/CallVCV2.swift +++ b/Session/Calls/CallVCV2.swift @@ -1,7 +1,7 @@ import WebRTC final class CallVCV2 : UIViewController { - let roomID = "37923672512" // NOTE: You need to change this every time to ensure the room isn't full + let roomID = "37923672514" // NOTE: You need to change this every time to ensure the room isn't full var room: RoomInfo? var socket: WebSocket? @@ -31,22 +31,23 @@ final class CallVCV2 : UIViewController { } func setUpViewHierarchy() { - // Create video views - let localVideoView = RTCMTLVideoView() - localVideoView.contentMode = .scaleAspectFill + // Remote video view let remoteVideoView = RTCMTLVideoView() remoteVideoView.contentMode = .scaleAspectFill - // Set up stack view - let stackView = UIStackView(arrangedSubviews: [ localVideoView, remoteVideoView ]) - stackView.axis = .vertical - stackView.distribution = .fillEqually - stackView.alignment = .fill - view.addSubview(stackView) - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.pin(to: view) - // Attach video views - callManager.attachLocalRenderer(localVideoView) callManager.attachRemoteRenderer(remoteVideoView) + view.addSubview(remoteVideoView) + remoteVideoView.translatesAutoresizingMaskIntoConstraints = false + remoteVideoView.pin(to: view) + // Local video view + let localVideoView = RTCMTLVideoView() + localVideoView.contentMode = .scaleAspectFill + callManager.attachLocalRenderer(localVideoView) + localVideoView.set(.width, to: 80) + localVideoView.set(.height, to: 173) + view.addSubview(localVideoView) + localVideoView.pin(.right, to: .right, of: view, withInset: -Values.largeSpacing) + let bottomMargin = UIApplication.shared.keyWindow!.safeAreaInsets.bottom + Values.largeSpacing + localVideoView.pin(.bottom, to: .bottom, of: view, withInset: -bottomMargin) } override func viewDidAppear(_ animated: Bool) { From b54cd3b02644aa3a5e937ff4e2a36baf3ffe528c Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 16 Aug 2021 16:24:49 +1000 Subject: [PATCH 024/368] Add call button to conversation screen --- Session/Calls/CallVCV2.swift | 2 +- .../ConversationVC+Interaction.swift | 5 +++++ Session/Conversations/ConversationVC.swift | 18 ++++++++++++------ .../ConversationTitleView.swift | 3 ++- Session/Home/HomeVC.swift | 6 +----- .../Session/Phone.imageset/Contents.json | 12 ++++++++++++ .../Session/Phone.imageset/Phone.pdf | Bin 0 -> 42151 bytes 7 files changed, 33 insertions(+), 13 deletions(-) create mode 100644 Session/Meta/Images.xcassets/Session/Phone.imageset/Contents.json create mode 100644 Session/Meta/Images.xcassets/Session/Phone.imageset/Phone.pdf diff --git a/Session/Calls/CallVCV2.swift b/Session/Calls/CallVCV2.swift index beaed8d24..b73538064 100644 --- a/Session/Calls/CallVCV2.swift +++ b/Session/Calls/CallVCV2.swift @@ -1,7 +1,7 @@ import WebRTC final class CallVCV2 : UIViewController { - let roomID = "37923672514" // NOTE: You need to change this every time to ensure the room isn't full + let roomID = "37923672515" // NOTE: You need to change this every time to ensure the room isn't full var room: RoomInfo? var socket: WebSocket? diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 2a1bf39bc..1fba6eab4 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -25,6 +25,11 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc unreadViewItems.removeAll() messagesTableView.scrollToRow(at: indexPath, at: .top, animated: true) } + + @objc func startCall() { + let callVC = CallVCV2() + navigationController!.pushViewController(callVC, animated: true, completion: nil) + } // MARK: Blocking @objc func unblock() { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index bee500a68..ae4ccfdf5 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -294,7 +294,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat if isShowingSearchUI { navigationItem.rightBarButtonItems = [] } else { - let rightBarButtonItem: UIBarButtonItem + var rightBarButtonItems: [UIBarButtonItem] = [] if thread is TSContactThread { let size = Values.verySmallProfilePictureSize let profilePictureView = ProfilePictureView() @@ -305,13 +305,19 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat profilePictureView.set(.height, to: size) let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings)) profilePictureView.addGestureRecognizer(tapGestureRecognizer) - rightBarButtonItem = UIBarButtonItem(customView: profilePictureView) + let settingsButton = UIBarButtonItem(customView: profilePictureView) + settingsButton.accessibilityLabel = "Settings button" + settingsButton.isAccessibilityElement = true + rightBarButtonItems.append(settingsButton) + let callButton = UIBarButtonItem(image: UIImage(named: "Phone")!, style: .plain, target: self, action: #selector(startCall)) + rightBarButtonItems.append(callButton) } else { - rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "Gear"), style: .plain, target: self, action: #selector(openSettings)) + let settingsButton = UIBarButtonItem(image: UIImage(named: "Gear"), style: .plain, target: self, action: #selector(openSettings)) + settingsButton.accessibilityLabel = "Settings button" + settingsButton.isAccessibilityElement = true + rightBarButtonItems.append(settingsButton) } - rightBarButtonItem.accessibilityLabel = "Settings button" - rightBarButtonItem.isAccessibilityElement = true - navigationItem.rightBarButtonItem = rightBarButtonItem + navigationItem.rightBarButtonItems = rightBarButtonItems } } diff --git a/Session/Conversations/Views & Modals/ConversationTitleView.swift b/Session/Conversations/Views & Modals/ConversationTitleView.swift index d80912b29..0d744f16c 100644 --- a/Session/Conversations/Views & Modals/ConversationTitleView.swift +++ b/Session/Conversations/Views & Modals/ConversationTitleView.swift @@ -44,7 +44,8 @@ final class ConversationTitleView : UIView { stackView.axis = .vertical stackView.alignment = .center stackView.isLayoutMarginsRelativeArrangement = true - stackView.layoutMargins = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 0) + let leftMargin: CGFloat = (thread is TSContactThread) ? 54 : 8 // Contact threads also have the call button to compensate for + stackView.layoutMargins = UIEdgeInsets(top: 0, left: leftMargin, bottom: 0, right: 0) addSubview(stackView) stackView.pin(to: self) let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index dcd501085..66b1696f3 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -156,11 +156,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv let _ = IP2Country.shared.populateCacheIfNeeded() } // Get default open group rooms if needed - OpenGroupAPIV2.getDefaultRoomsIfNeeded() - - let callVC = CallVCV2() - present(callVC, animated: true, completion: nil) - + OpenGroupAPIV2.getDefaultRoomsIfNeeded() } override func viewDidAppear(_ animated: Bool) { diff --git a/Session/Meta/Images.xcassets/Session/Phone.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/Phone.imageset/Contents.json new file mode 100644 index 000000000..dfb33732e --- /dev/null +++ b/Session/Meta/Images.xcassets/Session/Phone.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Phone.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Session/Meta/Images.xcassets/Session/Phone.imageset/Phone.pdf b/Session/Meta/Images.xcassets/Session/Phone.imageset/Phone.pdf new file mode 100644 index 0000000000000000000000000000000000000000..460eadf490ec83a37c89108dcb2d38aca1bf57c8 GIT binary patch literal 42151 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 Date: Tue, 17 Aug 2021 14:02:28 +1000 Subject: [PATCH 025/368] Minor refactoring & clean up WebRTC constraints --- Session.xcodeproj/project.pbxproj | 24 ++++++++--------- Session/Backups/OWSBackupAPI.swift | 1 + Session/Calls/CallVCV2+MessageSending.swift | 2 +- Session/Calls/CallVCV2+WebSocket.swift | 12 ++++----- Session/Calls/CallVCV2.swift | 12 ++++----- .../Calls/Temp/WebSocket.swift | 12 ++++----- ...ft => WebRTCWrapper+MessageHandling.swift} | 9 ++++--- ...anager+UI.swift => WebRTCWrapper+UI.swift} | 2 +- ...{CallManager.swift => WebRTCWrapper.swift} | 27 ++++++++----------- 9 files changed, 49 insertions(+), 52 deletions(-) rename SessionMessagingKit/Calls/{CallManager+MessageHandling.swift => WebRTCWrapper+MessageHandling.swift} (70%) rename SessionMessagingKit/Calls/{CallManager+UI.swift => WebRTCWrapper+UI.swift} (94%) rename SessionMessagingKit/Calls/{CallManager.swift => WebRTCWrapper.swift} (88%) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index dfb08bebe..c866a785e 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -157,7 +157,7 @@ B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */; }; B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */; }; B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */; }; - B806ECA126C4A7E4008BDA44 /* CallManager+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA026C4A7E4008BDA44 /* CallManager+UI.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 */; }; @@ -255,7 +255,7 @@ 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 /* CallManager+MessageHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FE26C4E05E00693325 /* CallManager+MessageHandling.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 */; }; @@ -278,7 +278,7 @@ B8D64FCB25BA78A90029CFC0 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; B8D84EA325DF745A005A043E /* LinkPreviewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D84EA225DF745A005A043E /* LinkPreviewState.swift */; }; B8D84ECF25E3108A005A043E /* ExpandingAttachmentsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D84ECE25E3108A005A043E /* ExpandingAttachmentsButton.swift */; }; - B8DE1FB426C22F2F0079C9CE /* CallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8DE1FB326C22F2F0079C9CE /* CallManager.swift */; }; + B8DE1FB426C22F2F0079C9CE /* WebRTCWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8DE1FB326C22F2F0079C9CE /* WebRTCWrapper.swift */; }; B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */; }; B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */; }; B8EB20F02640F7F000773E52 /* OpenGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */; }; @@ -1154,7 +1154,7 @@ B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = System/Library/Frameworks/PushKit.framework; sourceTree = SDKROOT; }; B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderView.swift; sourceTree = ""; }; B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCell.swift; sourceTree = ""; }; - B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallManager+UI.swift"; sourceTree = ""; }; + B806ECA026C4A7E4008BDA44 /* WebRTCWrapper+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCWrapper+UI.swift"; sourceTree = ""; }; B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = ""; }; B80F469926C63DD000DCE243 /* RoomInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomInfo.swift; sourceTree = ""; }; B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedConversationCell.swift; sourceTree = ""; }; @@ -1233,7 +1233,7 @@ B8B558F226C4CA4600693325 /* TestCallConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCallConfig.swift; sourceTree = ""; }; B8B558FA26C4D25C00693325 /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = ""; }; B8B558FC26C4D35400693325 /* TestCallServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCallServer.swift; sourceTree = ""; }; - B8B558FE26C4E05E00693325 /* CallManager+MessageHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallManager+MessageHandling.swift"; sourceTree = ""; }; + B8B558FE26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCWrapper+MessageHandling.swift"; sourceTree = ""; }; B8B5590026C4E2A400693325 /* SignalingMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalingMessage.swift; sourceTree = ""; }; B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B8BAC75B2695645400EA1759 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; @@ -1272,7 +1272,7 @@ B8D8F1BC25661C6F0092EF10 /* Storage+OnionRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+OnionRequests.swift"; sourceTree = ""; }; B8D8F1EF256621180092EF10 /* MessageSender+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "MessageSender+Convenience.swift"; path = "../../SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift"; sourceTree = ""; }; B8DE1FAF26C228780079C9CE /* SignalRingRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SignalRingRTC.framework; path = Dependencies/SignalRingRTC.framework; sourceTree = ""; }; - B8DE1FB326C22F2F0079C9CE /* CallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = ""; }; + B8DE1FB326C22F2F0079C9CE /* WebRTCWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCWrapper.swift; sourceTree = ""; }; B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessage.swift; sourceTree = ""; }; B8EB20E6263F7E4B00773E52 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+OpenGroupInvitation.swift"; sourceTree = ""; }; @@ -2386,9 +2386,9 @@ isa = PBXGroup; children = ( B877E24026CA11170007970A /* Temp */, - B8DE1FB326C22F2F0079C9CE /* CallManager.swift */, - B806ECA026C4A7E4008BDA44 /* CallManager+UI.swift */, - B8B558FE26C4E05E00693325 /* CallManager+MessageHandling.swift */, + B8DE1FB326C22F2F0079C9CE /* WebRTCWrapper.swift */, + B806ECA026C4A7E4008BDA44 /* WebRTCWrapper+UI.swift */, + B8B558FE26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift */, ); path = Calls; sourceTree = ""; @@ -4702,7 +4702,7 @@ C352A32F2557549C00338F3E /* NotifyPNServerJob.swift in Sources */, 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */, C300A5F22554B09800555489 /* MessageSender.swift in Sources */, - B8B558FF26C4E05E00693325 /* CallManager+MessageHandling.swift in Sources */, + B8B558FF26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift in Sources */, C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */, C32C5AAD256DBE8F003C73A2 /* TSInfoMessage.m in Sources */, C32C5A13256DB7A5003C73A2 /* PushNotificationAPI.swift in Sources */, @@ -4740,7 +4740,7 @@ B8856ECE256F1E58001CE70E /* OWSPreferences.m in Sources */, C3C2A7842553AAF300C340D1 /* SNProto.swift in Sources */, C32C5DC0256DD743003C73A2 /* Poller.swift in Sources */, - B8DE1FB426C22F2F0079C9CE /* CallManager.swift in Sources */, + B8DE1FB426C22F2F0079C9CE /* WebRTCWrapper.swift in Sources */, C3C2A7682553A3D900C340D1 /* VisibleMessage+Contact.swift in Sources */, C3A3A171256E1D25004D228D /* SSKReachabilityManager.swift in Sources */, B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */, @@ -4825,7 +4825,7 @@ C32C5FD6256E0346003C73A2 /* Notification+Thread.swift in Sources */, C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */, B8B5590126C4E2A400693325 /* SignalingMessage.swift in Sources */, - B806ECA126C4A7E4008BDA44 /* CallManager+UI.swift in Sources */, + B806ECA126C4A7E4008BDA44 /* WebRTCWrapper+UI.swift in Sources */, C32C5C88256DD0D2003C73A2 /* Storage+Messaging.swift in Sources */, C32C59C7256DB41F003C73A2 /* TSThread.m in Sources */, C300A5B22554AF9800555489 /* VisibleMessage+Profile.swift in Sources */, diff --git a/Session/Backups/OWSBackupAPI.swift b/Session/Backups/OWSBackupAPI.swift index 316457729..f3de1b488 100644 --- a/Session/Backups/OWSBackupAPI.swift +++ b/Session/Backups/OWSBackupAPI.swift @@ -613,6 +613,7 @@ import PromiseKit case .available: Logger.verbose("CloudKit access okay.") resolver.fulfill(()) + default: preconditionFailure() } } return promise diff --git a/Session/Calls/CallVCV2+MessageSending.swift b/Session/Calls/CallVCV2+MessageSending.swift index 11f0bcfe2..c472cce3d 100644 --- a/Session/Calls/CallVCV2+MessageSending.swift +++ b/Session/Calls/CallVCV2+MessageSending.swift @@ -1,6 +1,6 @@ import WebRTC -extension CallVCV2 : CallManagerDelegate { +extension CallVCV2 : WebRTCWrapperDelegate { /// Invoked by `CallManager` upon initiating or accepting a call. This method sends the SDP to the other /// party before streaming starts. diff --git a/Session/Calls/CallVCV2+WebSocket.swift b/Session/Calls/CallVCV2+WebSocket.swift index 26ca6e61f..40763be5e 100644 --- a/Session/Calls/CallVCV2+WebSocket.swift +++ b/Session/Calls/CallVCV2+WebSocket.swift @@ -1,7 +1,7 @@ extension CallVCV2 : WebSocketDelegate { - func webSocketDidConnect(_ webSocket: WebSocket) { + func handleWebSocketConnected() { guard let room = room else { return } let json = [ "cmd" : "register", @@ -10,18 +10,18 @@ extension CallVCV2 : WebSocketDelegate { ] guard let data = try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) else { return } print("[Calls] Web socket connected. Sending: \(json).") - webSocket.send(data) + socket?.send(data) print("[Calls] Is initiator: \(room.isInitiator).") if room.isInitiator { - callManager.initiateCall().retainUntilComplete() + callManager.offer().retainUntilComplete() } } - func webSocketDidDisconnect(_ webSocket: WebSocket) { - webSocket.delegate = nil + func handleWebSocketDisconnected() { + socket?.delegate = nil } - func webSocket(_ webSocket: WebSocket, didReceive message: String) { + func handleWebSocketMessage(_ message: String) { print("[Calls] Message received through web socket: \(message).") handle([ message ]) } diff --git a/Session/Calls/CallVCV2.swift b/Session/Calls/CallVCV2.swift index b73538064..177e5e56b 100644 --- a/Session/Calls/CallVCV2.swift +++ b/Session/Calls/CallVCV2.swift @@ -1,12 +1,12 @@ import WebRTC final class CallVCV2 : UIViewController { - let roomID = "37923672515" // NOTE: You need to change this every time to ensure the room isn't full + 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: CallManager = { - let result = CallManager() + lazy var callManager: WebRTCWrapper = { + let result = WebRTCWrapper() result.delegate = self return result }() @@ -85,9 +85,9 @@ final class CallVCV2 : UIViewController { messages.forEach { message in let signalingMessage = SignalingMessage.from(message: message) switch signalingMessage { - case .candidate(let candidate): callManager.handleCandidateMessage(candidate) - case .answer(let answer): callManager.handleRemoteDescription(answer) - case .offer(let offer): callManager.handleRemoteDescription(offer) + case .candidate(let candidate): callManager.handleICECandidate(candidate) + case .answer(let answer): callManager.handleRemoteSDP(answer) + case .offer(let offer): callManager.handleRemoteSDP(offer) default: break } } diff --git a/SessionMessagingKit/Calls/Temp/WebSocket.swift b/SessionMessagingKit/Calls/Temp/WebSocket.swift index 44009db99..29685bb70 100644 --- a/SessionMessagingKit/Calls/Temp/WebSocket.swift +++ b/SessionMessagingKit/Calls/Temp/WebSocket.swift @@ -3,9 +3,9 @@ import SocketRocket public protocol WebSocketDelegate : AnyObject { - func webSocketDidConnect(_ webSocket: WebSocket) - func webSocketDidDisconnect(_ webSocket: WebSocket) - func webSocket(_ webSocket: WebSocket, didReceive message: String) + func handleWebSocketConnected() + func handleWebSocketDisconnected() + func handleWebSocketMessage(_ message: String) } public final class WebSocket : NSObject, SRWebSocketDelegate { @@ -27,17 +27,17 @@ public final class WebSocket : NSObject, SRWebSocketDelegate { } public func webSocketDidOpen(_ webSocket: SRWebSocket!) { - delegate?.webSocketDidConnect(self) + delegate?.handleWebSocketConnected() } public func webSocket(_ webSocket: SRWebSocket!, didReceiveMessage message: Any!) { guard let message = message as? String else { return } - delegate?.webSocket(self, didReceive: message) + delegate?.handleWebSocketMessage(message) } public func disconnect() { socket.close() - delegate?.webSocketDidDisconnect(self) + delegate?.handleWebSocketDisconnected() } public func webSocket(_ webSocket: SRWebSocket!, didFailWithError error: Error!) { diff --git a/SessionMessagingKit/Calls/CallManager+MessageHandling.swift b/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift similarity index 70% rename from SessionMessagingKit/Calls/CallManager+MessageHandling.swift rename to SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift index e2a2cd811..3eced68bb 100644 --- a/SessionMessagingKit/Calls/CallManager+MessageHandling.swift +++ b/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift @@ -1,13 +1,13 @@ import WebRTC -extension CallManager { +extension WebRTCWrapper { - public func handleCandidateMessage(_ candidate: RTCIceCandidate) { + public func handleICECandidate(_ candidate: RTCIceCandidate) { print("[Calls] Received ICE candidate message.") candidateQueue.append(candidate) } - public func handleRemoteDescription(_ sdp: RTCSessionDescription) { + public func handleRemoteSDP(_ sdp: RTCSessionDescription) { print("[Calls] Received remote SDP: \(sdp.sdp).") peerConnection.setRemoteDescription(sdp, completionHandler: { [weak self] error in if let error = error { @@ -15,7 +15,8 @@ extension CallManager { } else { guard let self = self, sdp.type == .offer, self.peerConnection.localDescription == nil else { return } - self.acceptCall().retainUntilComplete() + // Answer the call + self.answer().retainUntilComplete() } }) } diff --git a/SessionMessagingKit/Calls/CallManager+UI.swift b/SessionMessagingKit/Calls/WebRTCWrapper+UI.swift similarity index 94% rename from SessionMessagingKit/Calls/CallManager+UI.swift rename to SessionMessagingKit/Calls/WebRTCWrapper+UI.swift index f9d6b73fd..81255a0a2 100644 --- a/SessionMessagingKit/Calls/CallManager+UI.swift +++ b/SessionMessagingKit/Calls/WebRTCWrapper+UI.swift @@ -1,6 +1,6 @@ import WebRTC -extension CallManager { +extension WebRTCWrapper { public func attachLocalRenderer(_ renderer: RTCVideoRenderer) { localVideoTrack.add(renderer) diff --git a/SessionMessagingKit/Calls/CallManager.swift b/SessionMessagingKit/Calls/WebRTCWrapper.swift similarity index 88% rename from SessionMessagingKit/Calls/CallManager.swift rename to SessionMessagingKit/Calls/WebRTCWrapper.swift index 0c447a842..c2a153732 100644 --- a/SessionMessagingKit/Calls/CallManager.swift +++ b/SessionMessagingKit/Calls/WebRTCWrapper.swift @@ -1,16 +1,16 @@ import PromiseKit import WebRTC -public protocol CallManagerDelegate : AnyObject { +public protocol WebRTCWrapperDelegate : AnyObject { var videoCapturer: RTCVideoCapturer { get } func sendSDP(_ sdp: RTCSessionDescription) func sendICECandidate(_ candidate: RTCIceCandidate) } -/// See https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription for more information. -public final class CallManager : NSObject, RTCPeerConnectionDelegate { - public weak var delegate: CallManagerDelegate? +/// 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] = [] internal lazy var factory: RTCPeerConnectionFactory = { @@ -26,26 +26,21 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { let configuration = RTCConfiguration() configuration.iceServers = [ RTCIceServer(urlStrings: TestCallConfig.defaultICEServers) ] configuration.sdpSemantics = .unifiedPlan - let pcert = RTCCertificate.generate(withParams: [ "expires": NSNumber(value: 100000), "name": "RSASSA-PKCS1-v1_5" ]) - configuration.certificate = pcert - configuration.iceTransportPolicy = .all - let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [ "DtlsSrtpKeyAgreement" : "true" ]) + let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [:]) return factory.peerConnection(with: configuration, constraints: constraints, delegate: self) }() - internal lazy var constraints: RTCMediaConstraints = { + internal lazy var mediaConstraints: RTCMediaConstraints = { let mandatory: [String:String] = [ kRTCMediaConstraintsOfferToReceiveAudio : kRTCMediaConstraintsValueTrue, kRTCMediaConstraintsOfferToReceiveVideo : kRTCMediaConstraintsValueTrue ] let optional: [String:String] = [:] - // TODO: Do these constraints make sense? return RTCMediaConstraints(mandatoryConstraints: mandatory, optionalConstraints: optional) }() // Audio internal lazy var audioSource: RTCAudioSource = { - // TODO: Do these constraints make sense? let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [:]) return factory.audioSource(with: constraints) }() @@ -106,13 +101,13 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { } // MARK: Call Management - public func initiateCall() -> Promise { + public func offer() -> Promise { print("[Calls] Initiating call.") /* guard let thread = TSContactThread.fetch(for: publicKey, using: transaction) else { return Promise(error: Error.noThread) } */ let (promise, seal) = Promise.pending() - peerConnection.offer(for: constraints) { [weak self] sdp, error in + peerConnection.offer(for: mediaConstraints) { [weak self] sdp, error in if let error = error { seal.reject(error) } else { @@ -138,13 +133,13 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { return promise } - public func acceptCall() -> Promise { + public func answer() -> Promise { print("[Calls] Accepting call.") /* guard let thread = TSContactThread.fetch(for: publicKey, using: transaction) else { return Promise(error: Error.noThread) } */ let (promise, seal) = Promise.pending() - peerConnection.answer(for: constraints) { [weak self] sdp, error in + peerConnection.answer(for: mediaConstraints) { [weak self] sdp, error in if let error = error { seal.reject(error) } else { @@ -170,7 +165,7 @@ public final class CallManager : NSObject, RTCPeerConnectionDelegate { return promise } - public func endCall() { + public func dropConnection() { peerConnection.close() } From 525eb40d8dd74cf7bde0743d7a22e5068413e25a Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 17 Aug 2021 15:41:13 +1000 Subject: [PATCH 026/368] Make signaling happen using Session messages --- Session.xcodeproj/project.pbxproj | 36 ------ Session/Calls/CallVCV2+Camera.swift | 2 +- Session/Calls/CallVCV2+MessageSending.swift | 31 ----- Session/Calls/CallVCV2+WebSocket.swift | 28 ----- Session/Calls/CallVCV2.swift | 82 ++++++------- Session/Calls/CameraManager.swift | 1 + .../ConversationVC+Interaction.swift | 3 +- Session/Meta/AppDelegate.m | 5 +- Session/Meta/AppDelegate.swift | 24 +++- SessionMessagingKit/Calls/Temp/RoomInfo.swift | 9 -- .../Calls/Temp/SignalingMessage.swift | 58 --------- .../Calls/Temp/TestCallConfig.swift | 12 -- .../Calls/Temp/TestCallServer.swift | 49 -------- .../Calls/Temp/WebSocket.swift | 52 -------- .../Calls/WebRTCWrapper+MessageHandling.swift | 10 +- SessionMessagingKit/Calls/WebRTCWrapper.swift | 93 +++++++-------- .../Control Messages/CallMessage.swift | 111 +++++++++++------- .../Protos/Generated/SNProto.swift | 34 ++++++ .../Protos/Generated/SessionProtos.pb.swift | 36 ++++++ .../Protos/SessionProtos.proto | 7 +- .../MessageReceiver+Handling.swift | 29 +++++ .../Sending & Receiving/MessageReceiver.swift | 4 +- 22 files changed, 294 insertions(+), 422 deletions(-) delete mode 100644 Session/Calls/CallVCV2+MessageSending.swift delete mode 100644 Session/Calls/CallVCV2+WebSocket.swift delete mode 100644 SessionMessagingKit/Calls/Temp/RoomInfo.swift delete mode 100644 SessionMessagingKit/Calls/Temp/SignalingMessage.swift delete mode 100644 SessionMessagingKit/Calls/Temp/TestCallConfig.swift delete mode 100644 SessionMessagingKit/Calls/Temp/TestCallServer.swift delete mode 100644 SessionMessagingKit/Calls/Temp/WebSocket.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index c866a785e..03b59d124 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -159,7 +159,6 @@ B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */; }; B806ECA126C4A7E4008BDA44 /* WebRTCWrapper+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA026C4A7E4008BDA44 /* WebRTCWrapper+UI.swift */; }; B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */; }; - B80F469A26C63DD000DCE243 /* RoomInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80F469926C63DD000DCE243 /* RoomInfo.swift */; }; B817AD9A26436593009DF825 /* SimplifiedConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */; }; B817AD9C26436F73009DF825 /* ThreadPickerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */; }; B81D25C426157F40004D1FE1 /* storage-seed-3.crt in Resources */ = {isa = PBXBuildFile; fileRef = B81D25B926157F20004D1FE1 /* storage-seed-3.crt */; }; @@ -202,9 +201,7 @@ B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; }; B875885A264503A6000E60D0 /* JoinOpenGroupModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8758859264503A6000E60D0 /* JoinOpenGroupModal.swift */; }; B877E24226CA12910007970A /* CallVCV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B877E24126CA12910007970A /* CallVCV2.swift */; }; - B877E24426CA12F00007970A /* CallVCV2+MessageSending.swift in Sources */ = {isa = PBXBuildFile; fileRef = B877E24326CA12F00007970A /* CallVCV2+MessageSending.swift */; }; B877E24626CA13BA0007970A /* CallVCV2+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = B877E24526CA13BA0007970A /* CallVCV2+Camera.swift */; }; - B877E24826CA15170007970A /* CallVCV2+WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = B877E24726CA15170007970A /* CallVCV2+WebSocket.swift */; }; B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */; }; B879D449247E1BE300DB3608 /* PathVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B879D448247E1BE300DB3608 /* PathVC.swift */; }; B87EF17126367CF800124B3C /* FileServerAPIV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B87EF17026367CF800124B3C /* FileServerAPIV2.swift */; }; @@ -252,11 +249,7 @@ B8B3204E258C15C80020074B /* ContactsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B32044258C117C0020074B /* ContactsMigration.swift */; }; B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B320B6258C30D70020074B /* HTMLMetadata.swift */; }; B8B558F126C4BB0600693325 /* CameraManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F026C4BB0600693325 /* CameraManager.swift */; }; - B8B558F326C4CA4600693325 /* TestCallConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F226C4CA4600693325 /* TestCallConfig.swift */; }; - B8B558FB26C4D25C00693325 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FA26C4D25C00693325 /* WebSocket.swift */; }; - B8B558FD26C4D35400693325 /* TestCallServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FC26C4D35400693325 /* TestCallServer.swift */; }; B8B558FF26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FE26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift */; }; - B8B5590126C4E2A400693325 /* SignalingMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B5590026C4E2A400693325 /* SignalingMessage.swift */; }; B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A4238F627000BA5194 /* HomeVC.swift */; }; B8BC00C0257D90E30032E807 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BC00BF257D90E30032E807 /* General.swift */; }; B8C2B2C82563685C00551B4D /* CircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C2B2C72563685C00551B4D /* CircleView.swift */; }; @@ -1156,7 +1149,6 @@ B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCell.swift; sourceTree = ""; }; B806ECA026C4A7E4008BDA44 /* WebRTCWrapper+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCWrapper+UI.swift"; sourceTree = ""; }; B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = ""; }; - B80F469926C63DD000DCE243 /* RoomInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomInfo.swift; sourceTree = ""; }; B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedConversationCell.swift; sourceTree = ""; }; B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerVC.swift; sourceTree = ""; }; B81D25B726157F20004D1FE1 /* storage-seed-1.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "storage-seed-1.crt"; sourceTree = ""; }; @@ -1203,9 +1195,7 @@ B87588582644CA9D000E60D0 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; B8758859264503A6000E60D0 /* JoinOpenGroupModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinOpenGroupModal.swift; sourceTree = ""; }; B877E24126CA12910007970A /* CallVCV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVCV2.swift; sourceTree = ""; }; - B877E24326CA12F00007970A /* CallVCV2+MessageSending.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVCV2+MessageSending.swift"; sourceTree = ""; }; B877E24526CA13BA0007970A /* CallVCV2+Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVCV2+Camera.swift"; sourceTree = ""; }; - B877E24726CA15170007970A /* CallVCV2+WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVCV2+WebSocket.swift"; sourceTree = ""; }; B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Interaction.swift"; sourceTree = ""; }; B879D448247E1BE300DB3608 /* PathVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathVC.swift; sourceTree = ""; }; B879D44A247E1D9200DB3608 /* PathStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathStatusView.swift; sourceTree = ""; }; @@ -1230,11 +1220,7 @@ B8B32044258C117C0020074B /* ContactsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsMigration.swift; sourceTree = ""; }; B8B320B6258C30D70020074B /* HTMLMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadata.swift; sourceTree = ""; }; B8B558F026C4BB0600693325 /* CameraManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = ""; }; - B8B558F226C4CA4600693325 /* TestCallConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCallConfig.swift; sourceTree = ""; }; - B8B558FA26C4D25C00693325 /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = ""; }; - B8B558FC26C4D35400693325 /* TestCallServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCallServer.swift; sourceTree = ""; }; B8B558FE26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCWrapper+MessageHandling.swift"; sourceTree = ""; }; - B8B5590026C4E2A400693325 /* SignalingMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalingMessage.swift; sourceTree = ""; }; B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B8BAC75B2695645400EA1759 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; B8BAC75C2695648500EA1759 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; @@ -2194,18 +2180,6 @@ path = "Message Cells"; sourceTree = ""; }; - B877E24026CA11170007970A /* Temp */ = { - isa = PBXGroup; - children = ( - B8B5590026C4E2A400693325 /* SignalingMessage.swift */, - B8B558FA26C4D25C00693325 /* WebSocket.swift */, - B8B558F226C4CA4600693325 /* TestCallConfig.swift */, - B8B558FC26C4D35400693325 /* TestCallServer.swift */, - B80F469926C63DD000DCE243 /* RoomInfo.swift */, - ); - path = Temp; - sourceTree = ""; - }; B887C38125C7C79700E11DAE /* Input View */ = { isa = PBXGroup; children = ( @@ -2348,9 +2322,7 @@ isa = PBXGroup; children = ( B877E24126CA12910007970A /* CallVCV2.swift */, - B877E24326CA12F00007970A /* CallVCV2+MessageSending.swift */, B877E24526CA13BA0007970A /* CallVCV2+Camera.swift */, - B877E24726CA15170007970A /* CallVCV2+WebSocket.swift */, B8B558F026C4BB0600693325 /* CameraManager.swift */, ); path = Calls; @@ -2385,7 +2357,6 @@ B8DE1FB226C22F1F0079C9CE /* Calls */ = { isa = PBXGroup; children = ( - B877E24026CA11170007970A /* Temp */, B8DE1FB326C22F2F0079C9CE /* WebRTCWrapper.swift */, B806ECA026C4A7E4008BDA44 /* WebRTCWrapper+UI.swift */, B8B558FE26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift */, @@ -4753,7 +4724,6 @@ C32C5B3F256DC1DF003C73A2 /* TSQuotedMessage+Conversion.swift in Sources */, B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, C3C2A7712553A41E00C340D1 /* ControlMessage.swift in Sources */, - B80F469A26C63DD000DCE243 /* RoomInfo.swift in Sources */, C32C5D19256DD493003C73A2 /* OWSLinkPreview.swift in Sources */, C32C5CF0256DD3E4003C73A2 /* Storage+Shared.swift in Sources */, C300A5BD2554B00D00555489 /* ReadReceipt.swift in Sources */, @@ -4766,7 +4736,6 @@ C3DA9C0725AE7396008F7C7E /* ConfigurationMessage.swift in Sources */, B8856CEE256F1054001CE70E /* OWSAudioPlayer.m in Sources */, C32C5EDC256DF501003C73A2 /* YapDatabaseConnection+OWS.m in Sources */, - B8B558FD26C4D35400693325 /* TestCallServer.swift in Sources */, C3BBE0762554CDA60050F1E3 /* Configuration.swift in Sources */, C35D76DB26606304009AA5FB /* ThreadUpdateBatcher.swift in Sources */, B8B32033258B235D0020074B /* Storage+Contacts.swift in Sources */, @@ -4776,7 +4745,6 @@ C32C5AAB256DBE8F003C73A2 /* TSIncomingMessage+Conversion.swift in Sources */, B866CE112581C1A900535CC4 /* Sodium+Conversion.swift in Sources */, C32C5A88256DBCF9003C73A2 /* MessageReceiver+Handling.swift in Sources */, - B8B558F326C4CA4600693325 /* TestCallConfig.swift in Sources */, C32C5C1B256DC9E0003C73A2 /* General.swift in Sources */, C32C5A02256DB658003C73A2 /* MessageSender+Convenience.swift in Sources */, B8566C6C256F60F50045A0B9 /* OWSUserProfile.m in Sources */, @@ -4786,7 +4754,6 @@ C32C5BEF256DC8EE003C73A2 /* OWSDisappearingMessagesJob.m in Sources */, C34A977425A3E34A00852C71 /* ClosedGroupControlMessage.swift in Sources */, B88FA7B826045D100049422F /* OpenGroupAPIV2.swift in Sources */, - B8B558FB26C4D25C00693325 /* WebSocket.swift in Sources */, C32C5E97256DE0CB003C73A2 /* OWSPrimaryStorage.m in Sources */, C32C5EB9256DE130003C73A2 /* OWSQuotedReplyModel+Conversion.swift in Sources */, C3A71D1F25589AC30043A11F /* WebSocketResources.pb.swift in Sources */, @@ -4824,7 +4791,6 @@ C32C5D23256DD4C0003C73A2 /* Mention.swift in Sources */, C32C5FD6256E0346003C73A2 /* Notification+Thread.swift in Sources */, C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */, - B8B5590126C4E2A400693325 /* SignalingMessage.swift in Sources */, B806ECA126C4A7E4008BDA44 /* WebRTCWrapper+UI.swift in Sources */, C32C5C88256DD0D2003C73A2 /* Storage+Messaging.swift in Sources */, C32C59C7256DB41F003C73A2 /* TSThread.m in Sources */, @@ -4958,7 +4924,6 @@ B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */, 45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */, 34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */, - B877E24826CA15170007970A /* CallVCV2+WebSocket.swift in Sources */, 4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */, C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */, 340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */, @@ -4968,7 +4933,6 @@ C328250F25CA06020062D0A7 /* VoiceMessageView.swift in Sources */, B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */, 3488F9362191CC4000E524CC /* MediaView.swift in Sources */, - B877E24426CA12F00007970A /* CallVCV2+MessageSending.swift in Sources */, B8569AC325CB5D2900DBA3DB /* ConversationVC+Interaction.swift in Sources */, 3496955C219B605E00DCFE74 /* ImagePickerController.swift in Sources */, C31D1DE32521718E005D4DA8 /* UserSelectionVC.swift in Sources */, diff --git a/Session/Calls/CallVCV2+Camera.swift b/Session/Calls/CallVCV2+Camera.swift index 030b8bc47..89bf426fe 100644 --- a/Session/Calls/CallVCV2+Camera.swift +++ b/Session/Calls/CallVCV2+Camera.swift @@ -9,6 +9,6 @@ extension CallVCV2 : CameraManagerDelegate { let timestampNs = Int64(timestamp * 1000000000) let frame = RTCVideoFrame(buffer: rtcPixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: timestampNs) frame.timeStamp = Int32(timestamp) - callManager.handleLocalFrameCaptured(frame) + webRTCWrapper.handleLocalFrameCaptured(frame) } } diff --git a/Session/Calls/CallVCV2+MessageSending.swift b/Session/Calls/CallVCV2+MessageSending.swift deleted file mode 100644 index c472cce3d..000000000 --- a/Session/Calls/CallVCV2+MessageSending.swift +++ /dev/null @@ -1,31 +0,0 @@ -import WebRTC - -extension CallVCV2 : WebRTCWrapperDelegate { - - /// Invoked by `CallManager` upon initiating or accepting a call. This method sends the SDP to the other - /// party before streaming starts. - func sendSDP(_ sdp: RTCSessionDescription) { - guard let room = room else { return } - let json = [ - "type" : RTCSessionDescription.string(for: sdp.type), - "sdp" : sdp.sdp - ] - guard let data = try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) else { return } - print("[Calls] Sending SDP to test call server: \(json).") - TestCallServer.send(data, roomID: room.roomID, userID: room.clientID).retainUntilComplete() - } - - /// Invoked when the peer connection has generated an ICE candidate. - func sendICECandidate(_ candidate: RTCIceCandidate) { - guard let room = room else { return } - let json = [ - "type" : "candidate", - "label" : "\(candidate.sdpMLineIndex)", - "id" : candidate.sdpMid, - "candidate" : candidate.sdp - ] - guard let data = try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) else { return } - print("[Calls] Sending ICE candidate to test call server: \(json).") - TestCallServer.send(data, roomID: room.roomID, userID: room.clientID).retainUntilComplete() - } -} diff --git a/Session/Calls/CallVCV2+WebSocket.swift b/Session/Calls/CallVCV2+WebSocket.swift deleted file mode 100644 index 40763be5e..000000000 --- a/Session/Calls/CallVCV2+WebSocket.swift +++ /dev/null @@ -1,28 +0,0 @@ - -extension CallVCV2 : WebSocketDelegate { - - func handleWebSocketConnected() { - guard let room = room else { return } - let json = [ - "cmd" : "register", - "roomid" : room.roomID, - "clientid" : room.clientID - ] - guard let data = try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) else { return } - print("[Calls] Web socket connected. Sending: \(json).") - socket?.send(data) - print("[Calls] Is initiator: \(room.isInitiator).") - if room.isInitiator { - callManager.offer().retainUntilComplete() - } - } - - func handleWebSocketDisconnected() { - socket?.delegate = nil - } - - func handleWebSocketMessage(_ message: String) { - print("[Calls] Message received through web socket: \(message).") - handle([ message ]) - } -} diff --git a/Session/Calls/CallVCV2.swift b/Session/Calls/CallVCV2.swift index 177e5e56b..b40d71d99 100644 --- a/Session/Calls/CallVCV2.swift +++ b/Session/Calls/CallVCV2.swift @@ -1,15 +1,12 @@ import WebRTC +import SessionUIKit +import SessionMessagingKit +import SessionUtilitiesKit -final class CallVCV2 : UIViewController { - let roomID = "37923672516" // NOTE: You need to change this every time to ensure the room isn't full - var room: RoomInfo? - var socket: WebSocket? - - lazy var callManager: WebRTCWrapper = { - let result = WebRTCWrapper() - result.delegate = self - return result - }() +final class CallVCV2 : UIViewController, WebRTCWrapperDelegate { + let sessionID: String + let mode: Mode + let webRTCWrapper: WebRTCWrapper lazy var cameraManager: CameraManager = { let result = CameraManager() @@ -18,30 +15,53 @@ final class CallVCV2 : UIViewController { }() lazy var videoCapturer: RTCVideoCapturer = { - return RTCCameraVideoCapturer(delegate: callManager.localVideoSource) + return RTCCameraVideoCapturer(delegate: webRTCWrapper.localVideoSource) }() + // MARK: Mode + enum Mode { + case offer + case answer(sdp: RTCSessionDescription) + } + // MARK: Lifecycle + init(for sessionID: String, mode: Mode) { + self.sessionID = sessionID + self.mode = mode + self.webRTCWrapper = WebRTCWrapper.current ?? WebRTCWrapper(for: sessionID) + super.init(nibName: nil, bundle: nil) + self.webRTCWrapper.delegate = self + } + + required init(coder: NSCoder) { preconditionFailure("Use init(for:) instead.") } + override func viewDidLoad() { super.viewDidLoad() + WebRTCWrapper.current = webRTCWrapper setUpViewHierarchy() cameraManager.prepare() touch(videoCapturer) - autoConnectToTestRoom() + if case .offer = mode { + Storage.write { transaction in + self.webRTCWrapper.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() + } + } else if case let .answer(sdp) = mode { + webRTCWrapper.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally + } } func setUpViewHierarchy() { // Remote video view let remoteVideoView = RTCMTLVideoView() remoteVideoView.contentMode = .scaleAspectFill - callManager.attachRemoteRenderer(remoteVideoView) + webRTCWrapper.attachRemoteRenderer(remoteVideoView) view.addSubview(remoteVideoView) remoteVideoView.translatesAutoresizingMaskIntoConstraints = false remoteVideoView.pin(to: view) // Local video view let localVideoView = RTCMTLVideoView() localVideoView.contentMode = .scaleAspectFill - callManager.attachLocalRenderer(localVideoView) + webRTCWrapper.attachLocalRenderer(localVideoView) localVideoView.set(.width, to: 80) localVideoView.set(.height, to: 173) view.addSubview(localVideoView) @@ -60,37 +80,7 @@ final class CallVCV2 : UIViewController { cameraManager.stop() } - // MARK: General - func autoConnectToTestRoom() { - // Connect to a random test room - TestCallServer.join(roomID: roomID).done2 { [weak self] room in - print("[Calls] Connected to test room.") - guard let self = self else { return } - self.room = room - if let messages = room.messages { - self.handle(messages) - } - let socket = WebSocket(url: URL(string: room.wssURL)!) - socket.delegate = self - socket.connect() - self.socket = socket - }.catch2 { error in - SNLog("Couldn't join room due to error: \(error).") - } - } - - func handle(_ messages: [String]) { - print("[Calls] Handling messages:") - messages.forEach { print("[Calls] \($0)") } - messages.forEach { message in - let signalingMessage = SignalingMessage.from(message: message) - switch signalingMessage { - case .candidate(let candidate): callManager.handleICECandidate(candidate) - case .answer(let answer): callManager.handleRemoteSDP(answer) - case .offer(let offer): callManager.handleRemoteSDP(offer) - default: break - } - } - callManager.drainICECandidateQueue() + deinit { + WebRTCWrapper.current = nil } } diff --git a/Session/Calls/CameraManager.swift b/Session/Calls/CameraManager.swift index e9af7ddc1..f1e9b0616 100644 --- a/Session/Calls/CameraManager.swift +++ b/Session/Calls/CameraManager.swift @@ -1,5 +1,6 @@ import Foundation import AVFoundation +import SessionUtilitiesKit @objc protocol CameraManagerDelegate : AnyObject { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 1fba6eab4..0de4bcdfb 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -27,7 +27,8 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc } @objc func startCall() { - let callVC = CallVCV2() + guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } + let callVC = CallVCV2(for: contactSessionID, mode: .offer) navigationController!.pushViewController(callVC, animated: true, completion: nil) } diff --git a/Session/Meta/AppDelegate.m b/Session/Meta/AppDelegate.m index 29e9e0cda..8fb513275 100644 --- a/Session/Meta/AppDelegate.m +++ b/Session/Meta/AppDelegate.m @@ -182,7 +182,7 @@ static NSTimeInterval launchStartedAt; [SNConfiguration performMainSetup]; [SNAppearance switchToSessionAppearance]; - + if (CurrentAppContext().isRunningTests) { return YES; } @@ -219,10 +219,11 @@ static NSTimeInterval launchStartedAt; name:RegistrationStateDidChangeNotification object:nil]; - // Loki - Observe data nuke request notifications [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleDataNukeRequested:) name:NSNotification.dataNukeRequested object:nil]; OWSLogInfo(@"application: didFinishLaunchingWithOptions completed."); + + [self setUpCallHandling]; return YES; } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index bb28b0b81..c9ea18bdc 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -1,7 +1,29 @@ import PromiseKit +import WebRTC extension AppDelegate { + @objc + func setUpCallHandling() { + MessageReceiver.handleOfferCallMessage = { message in + DispatchQueue.main.async { + let sdp = RTCSessionDescription(type: .offer, sdp: message.sdp!) + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + let alert = UIAlertController(title: "Incoming Call", message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Accept", style: .default, handler: { _ in + let callVC = CallVCV2(for: message.sender!, mode: .answer(sdp: sdp)) + presentingVC.dismiss(animated: true) { + presentingVC.present(callVC, animated: true, completion: nil) + } + })) + alert.addAction(UIAlertAction(title: "Decline", style: .default, handler: { _ in + // Do nothing + })) + presentingVC.present(alert, animated: true, completion: nil) + } + } + } + @objc(syncConfigurationIfNeeded) func syncConfigurationIfNeeded() { guard Storage.shared.getUser()?.name != nil else { return } @@ -43,7 +65,7 @@ extension AppDelegate { @objc func getAppModeOrSystemDefault() -> AppMode { let userDefaults = UserDefaults.standard - + guard userDefaults.dictionaryRepresentation().keys.contains("appMode") else { if #available(iOS 13.0, *) { return UITraitCollection.current.userInterfaceStyle == .dark ? .dark : .light diff --git a/SessionMessagingKit/Calls/Temp/RoomInfo.swift b/SessionMessagingKit/Calls/Temp/RoomInfo.swift deleted file mode 100644 index affe92543..000000000 --- a/SessionMessagingKit/Calls/Temp/RoomInfo.swift +++ /dev/null @@ -1,9 +0,0 @@ - -public struct RoomInfo { - public let roomID: String - public let wssURL: String - public let wssPostURL: String - public let clientID: String - public let isInitiator: Bool - public let messages: [String]? -} diff --git a/SessionMessagingKit/Calls/Temp/SignalingMessage.swift b/SessionMessagingKit/Calls/Temp/SignalingMessage.swift deleted file mode 100644 index 3ca82215c..000000000 --- a/SessionMessagingKit/Calls/Temp/SignalingMessage.swift +++ /dev/null @@ -1,58 +0,0 @@ -import Foundation -import WebRTC - -public enum SignalingMessage { - case candidate(_ message: RTCIceCandidate) - case answer(_ message: RTCSessionDescription) - case offer(_ message: RTCSessionDescription) - case bye - - public static func from(message: String) -> SignalingMessage? { - guard let data = message.data(using: String.Encoding.utf8), - let json = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON else { return nil } - let messageAsJSON: JSON - if let string = json["msg"] as? String { - guard let data = string.data(using: String.Encoding.utf8), - let json = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON else { return nil } - messageAsJSON = json - } else { - messageAsJSON = json - } - guard let type = messageAsJSON["type"] as? String else { return nil } - switch type { - case "candidate": - guard let candidate = RTCIceCandidate.candidate(from: messageAsJSON) else { return nil } - return .candidate(candidate) - case "answer": - guard let sdp = messageAsJSON["sdp"] as? String else { return nil } - return .answer(RTCSessionDescription(type: .answer, sdp: sdp)) - case "offer": - guard let sdp = messageAsJSON["sdp"] as? String else { return nil } - return .offer(RTCSessionDescription(type: .offer, sdp: sdp)) - case "bye": - return .bye - default: return nil - } - } -} - -extension RTCIceCandidate { - - public func serialize() -> Data? { - let json = [ - "type": "candidate", - "label": "\(sdpMLineIndex)", - "id": sdpMid, - "candidate": sdp - ] - return try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) - } - - static func candidate(from json: JSON) -> RTCIceCandidate? { - let sdp = json["candidate"] as? String - let sdpMid = json["id"] as? String - let labelStr = json["label"] as? String - let label = (json["label"] as? Int32) ?? 0 - return RTCIceCandidate(sdp: sdp ?? "", sdpMLineIndex: Int32(labelStr ?? "") ?? label, sdpMid: sdpMid) - } -} diff --git a/SessionMessagingKit/Calls/Temp/TestCallConfig.swift b/SessionMessagingKit/Calls/Temp/TestCallConfig.swift deleted file mode 100644 index 9bdec5f31..000000000 --- a/SessionMessagingKit/Calls/Temp/TestCallConfig.swift +++ /dev/null @@ -1,12 +0,0 @@ - -public enum TestCallConfig { - - public static let defaultICEServers = [ - "stun:stun.l.google.com:19302", - "stun:stun1.l.google.com:19302", - "stun:stun2.l.google.com:19302", - "stun:stun3.l.google.com:19302", - "stun:stun4.l.google.com:19302" - ] - public static let defaultServerURL = "https://appr.tc" -} diff --git a/SessionMessagingKit/Calls/Temp/TestCallServer.swift b/SessionMessagingKit/Calls/Temp/TestCallServer.swift deleted file mode 100644 index 98c3fc684..000000000 --- a/SessionMessagingKit/Calls/Temp/TestCallServer.swift +++ /dev/null @@ -1,49 +0,0 @@ -import Foundation -import PromiseKit - -public enum TestCallServer { - - public enum Error : LocalizedError { - case roomFull - - public var errorDescription: String? { - switch self { - case .roomFull: return "The room is full." - } - } - } - - public static func join(roomID: String) -> Promise { - let url = "\(TestCallConfig.defaultServerURL)/join/\(roomID)" - return HTTP.execute(.post, url).map2 { json in - guard let status = json["result"] as? String else { throw HTTP.Error.invalidJSON } - guard status != "FULL" else { throw Error.roomFull } - guard let info = json["params"] as? JSON, - let roomID = info["room_id"] as? String, - let wssURL = info["wss_url"] as? String, - let wssPostURL = info["wss_post_url"] as? String, - let clientID = info["client_id"] as? String else { throw HTTP.Error.invalidJSON } - let isInitiator: Bool - if let bool = info["is_initiator"] as? Bool { - isInitiator = bool - } else if let string = info["is_initiator"] as? String { - isInitiator = (string == "true") - } else { - throw HTTP.Error.invalidJSON - } - let messages = info["messages"] as? [String] - return RoomInfo(roomID: roomID, wssURL: wssURL, wssPostURL: wssPostURL, - clientID: clientID, isInitiator: isInitiator, messages: messages) - } - } - - public static func leave(roomID: String, userID: String) -> Promise { - let url = "\(TestCallConfig.defaultServerURL)/leave/\(roomID)/\(userID)" - return HTTP.execute(.post, url).map2 { _ in } - } - - public static func send(_ message: Data, roomID: String, userID: String) -> Promise { - let url = "\(TestCallConfig.defaultServerURL)/message/\(roomID)/\(userID)" - return HTTP.execute(.post, url, body: message).map2 { _ in } - } -} diff --git a/SessionMessagingKit/Calls/Temp/WebSocket.swift b/SessionMessagingKit/Calls/Temp/WebSocket.swift deleted file mode 100644 index 29685bb70..000000000 --- a/SessionMessagingKit/Calls/Temp/WebSocket.swift +++ /dev/null @@ -1,52 +0,0 @@ -import Foundation -import SocketRocket - -public protocol WebSocketDelegate : AnyObject { - - func handleWebSocketConnected() - func handleWebSocketDisconnected() - func handleWebSocketMessage(_ message: String) -} - -public final class WebSocket : NSObject, SRWebSocketDelegate { - private let socket: SRWebSocket - public weak var delegate: WebSocketDelegate? - - public init(url: URL) { - socket = SRWebSocket(url: url) - super.init() - socket.delegate = self - } - - public func connect() { - socket.open() - } - - public func send(_ data: Data) { - socket.send(data) - } - - public func webSocketDidOpen(_ webSocket: SRWebSocket!) { - delegate?.handleWebSocketConnected() - } - - public func webSocket(_ webSocket: SRWebSocket!, didReceiveMessage message: Any!) { - guard let message = message as? String else { return } - delegate?.handleWebSocketMessage(message) - } - - public func disconnect() { - socket.close() - delegate?.handleWebSocketDisconnected() - } - - public func webSocket(_ webSocket: SRWebSocket!, didFailWithError error: Error!) { - SNLog("Web socket failed with error: \(error?.localizedDescription ?? "nil").") - disconnect() - } - - public func webSocket(_ webSocket: SRWebSocket!, didCloseWithCode code: Int, reason: String!, wasClean: Bool) { - SNLog("Web socket closed.") - disconnect() - } -} diff --git a/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift b/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift index 3eced68bb..88f60a24d 100644 --- a/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift +++ b/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift @@ -4,10 +4,10 @@ extension WebRTCWrapper { public func handleICECandidate(_ candidate: RTCIceCandidate) { print("[Calls] Received ICE candidate message.") - candidateQueue.append(candidate) + peerConnection.add(candidate) } - public func handleRemoteSDP(_ sdp: RTCSessionDescription) { + public func handleRemoteSDP(_ sdp: RTCSessionDescription, from sessionID: String) { print("[Calls] Received remote SDP: \(sdp.sdp).") peerConnection.setRemoteDescription(sdp, completionHandler: { [weak self] error in if let error = error { @@ -15,8 +15,10 @@ extension WebRTCWrapper { } else { guard let self = self, sdp.type == .offer, self.peerConnection.localDescription == nil else { return } - // Answer the call - self.answer().retainUntilComplete() + // Automatically answer the call + Storage.write { transaction in + self.sendAnswer(to: sessionID, using: transaction).retainUntilComplete() + } } }) } diff --git a/SessionMessagingKit/Calls/WebRTCWrapper.swift b/SessionMessagingKit/Calls/WebRTCWrapper.swift index c2a153732..49cd939bb 100644 --- a/SessionMessagingKit/Calls/WebRTCWrapper.swift +++ b/SessionMessagingKit/Calls/WebRTCWrapper.swift @@ -3,15 +3,20 @@ import WebRTC public protocol WebRTCWrapperDelegate : AnyObject { var videoCapturer: RTCVideoCapturer { get } - - func sendSDP(_ sdp: RTCSessionDescription) - func sendICECandidate(_ candidate: RTCIceCandidate) } /// See https://webrtc.org/getting-started/overview for more information. public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { public weak var delegate: WebRTCWrapperDelegate? - internal var candidateQueue: [RTCIceCandidate] = [] + private let contactSessionID: String + + private let defaultICEServers = [ + "stun:stun.l.google.com:19302", + "stun:stun1.l.google.com:19302", + "stun:stun2.l.google.com:19302", + "stun:stun3.l.google.com:19302", + "stun:stun4.l.google.com:19302" + ] internal lazy var factory: RTCPeerConnectionFactory = { RTCInitializeSSL() @@ -24,7 +29,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { /// remote peer, maintain and monitor the connection, and close the connection once it's no longer needed. internal lazy var peerConnection: RTCPeerConnection = { let configuration = RTCConfiguration() - configuration.iceServers = [ RTCIceServer(urlStrings: TestCallConfig.defaultICEServers) ] + configuration.iceServers = [ RTCIceServer(urlStrings: defaultICEServers) ] configuration.sdpSemantics = .unifiedPlan let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [:]) return factory.peerConnection(with: configuration, constraints: constraints, delegate: self) @@ -74,7 +79,10 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { } // MARK: Initialization - public override init() { + public static var current: WebRTCWrapper? + + public init(for contactSessionID: String) { + self.contactSessionID = contactSessionID super.init() let mediaStreamTrackIDS = ["ARDAMS"] peerConnection.add(audioTrack, streamIds: mediaStreamTrackIDS) @@ -93,19 +101,10 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { audioSession.unlockForConfiguration() } - // MARK: General - public func drainICECandidateQueue() { - print("[Calls] Draining ICE candidate queue.") - candidateQueue.forEach { peerConnection.add($0) } - candidateQueue.removeAll() - } - // MARK: Call Management - public func offer() -> Promise { + public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { print("[Calls] Initiating call.") - /* - guard let thread = TSContactThread.fetch(for: publicKey, using: transaction) else { return Promise(error: Error.noThread) } - */ + guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } let (promise, seal) = Promise.pending() peerConnection.offer(for: mediaConstraints) { [weak self] sdp, error in if let error = error { @@ -118,26 +117,21 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { return seal.reject(error) } } - - self.delegate?.sendSDP(sdp) - - /* - let message = CallMessage() - message.type = .offer - message.sdp = sdp.sdp - MessageSender.send(message, in: thread, using: transaction) - */ - seal.fulfill(()) + DispatchQueue.main.async { + let message = CallMessage() + message.kind = .offer + message.sdp = sdp.sdp + MessageSender.send(message, in: thread, using: transaction) + seal.fulfill(()) + } } } return promise } - public func answer() -> Promise { + public func sendAnswer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { print("[Calls] Accepting call.") - /* - guard let thread = TSContactThread.fetch(for: publicKey, using: transaction) else { return Promise(error: Error.noThread) } - */ + guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } let (promise, seal) = Promise.pending() peerConnection.answer(for: mediaConstraints) { [weak self] sdp, error in if let error = error { @@ -150,16 +144,13 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { return seal.reject(error) } } - - self.delegate?.sendSDP(sdp) - - /* - let message = CallMessage() - message.type = .answer - message.sdp = sdp.sdp - MessageSender.send(message, in: thread, using: transaction) - */ - seal.fulfill(()) + DispatchQueue.main.async { + let message = CallMessage() + message.kind = .answer + message.sdp = sdp.sdp + MessageSender.send(message, in: thread, using: transaction) + seal.fulfill(()) + } } } return promise @@ -171,7 +162,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { // MARK: Delegate public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCSignalingState) { - SNLog("Signaling state changed to: \(state).") + print("[Calls] Signaling state changed to: \(state).") } public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { @@ -187,23 +178,29 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCIceConnectionState) { - SNLog("ICE connection state changed to: \(state).") + print("[Calls] ICE connection state changed to: \(state).") } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCIceGatheringState) { - SNLog("ICE gathering state changed to: \(state).") + print("[Calls] ICE gathering state changed to: \(state).") } public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { - SNLog("ICE candidate generated.") - delegate?.sendICECandidate(candidate) + print("[Calls] ICE candidate generated.") + Storage.write { transaction in + guard let thread = TSContactThread.fetch(for: self.contactSessionID, using: transaction) else { return } + let message = CallMessage() + message.kind = .iceCandidate(sdpMLineIndex: UInt32(candidate.sdpMLineIndex), sdpMid: candidate.sdpMid!) + message.sdp = candidate.sdp + MessageSender.send(message, in: thread, using: transaction) + } } public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) { - SNLog("\(candidates.count) ICE candidate(s) removed.") + print("[Calls] \(candidates.count) ICE candidate(s) removed.") } public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { - SNLog("Data channel opened.") + print("[Calls] Data channel opened.") } } diff --git a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift index 4b35f08e9..b4fed52a8 100644 --- a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift @@ -3,52 +3,111 @@ import WebRTC /// See https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription for more information. @objc(SNCallMessage) public final class CallMessage : ControlMessage { - public var type: RTCSdpType? + public var kind: Kind? /// See https://developer.mozilla.org/en-US/docs/Glossary/SDP for more information. public var sdp: String? + // MARK: Kind + public enum Kind : Codable, CustomStringConvertible { + case offer + case answer + case provisionalAnswer + case iceCandidate(sdpMLineIndex: UInt32, sdpMid: String) + + public var description: String { + switch self { + case .offer: return "offer" + case .answer: return "answer" + case .provisionalAnswer: return "provisionalAnswer" + case .iceCandidate(_, _): return "iceCandidate" + } + } + } + // MARK: Initialization public override init() { super.init() } - internal init(type: RTCSdpType, sdp: String) { + internal init(kind: Kind, sdp: String) { super.init() - self.type = type + self.kind = kind self.sdp = sdp } // MARK: Validation public override var isValid: Bool { guard super.isValid else { return false } - return type != nil && sdp != nil + return kind != nil && sdp != nil } // MARK: Coding public required init?(coder: NSCoder) { super.init(coder: coder) - if let type = coder.decodeObject(forKey: "type") as! RTCSdpType? { self.type = type } + guard let rawKind = coder.decodeObject(forKey: "kind") as! String? else { return nil } + switch rawKind { + case "offer": kind = .offer + case "answer": kind = .answer + case "provisionalAnswer": kind = .provisionalAnswer + case "iceCandidate": + guard let sdpMLineIndex = coder.decodeObject(forKey: "sdpMLineIndex") as? UInt32, + let sdpMid = coder.decodeObject(forKey: "sdpMid") as? String else { return nil } + kind = .iceCandidate(sdpMLineIndex: sdpMLineIndex, sdpMid: sdpMid) + default: preconditionFailure() + } if let sdp = coder.decodeObject(forKey: "sdp") as! String? { self.sdp = sdp } } public override func encode(with coder: NSCoder) { super.encode(with: coder) - coder.encode(type, forKey: "type") + switch kind { + case .offer: coder.encode("offer", forKey: "kind") + case .answer: coder.encode("answer", forKey: "kind") + case .provisionalAnswer: coder.encode("provisionalAnswer", forKey: "kind") + case let .iceCandidate(sdpMLineIndex, sdpMid): + coder.encode("iceCandidate", forKey: "kind") + coder.encode(sdpMLineIndex, forKey: "sdpMLineIndex") + coder.encode(sdpMid, forKey: "sdpMid") + default: preconditionFailure() + } coder.encode(sdp, forKey: "sdp") } // MARK: Proto Conversion public override class func fromProto(_ proto: SNProtoContent) -> CallMessage? { guard let callMessageProto = proto.callMessage else { return nil } - let type = callMessageProto.type + let kind: Kind + switch callMessageProto.type { + case .offer: kind = .offer + case .answer: kind = .answer + case .provisionalAnswer: kind = .provisionalAnswer + case .iceCandidate: + let sdpMLineIndex = callMessageProto.sdpMlineIndex + guard let sdpMid = callMessageProto.sdpMid else { return nil } + kind = .iceCandidate(sdpMLineIndex: sdpMLineIndex, sdpMid: sdpMid) + } let sdp = callMessageProto.sdp - return CallMessage(type: RTCSdpType.from(type), sdp: sdp) + return CallMessage(kind: kind, sdp: sdp) } public override func toProto(using transaction: YapDatabaseReadWriteTransaction) -> SNProtoContent? { - guard let type = type, let sdp = sdp else { + guard let kind = kind, let sdp = sdp else { SNLog("Couldn't construct call message proto from: \(self).") return nil } - let callMessageProto = SNProtoCallMessage.builder(type: type.toProto(), sdp: sdp) + if case .offer = kind { + print("[Calls] Converting offer message to proto.") + } + let type: SNProtoCallMessage.SNProtoCallMessageType + switch kind { + case .offer: type = .offer + case .answer: type = .answer + case .provisionalAnswer: type = .provisionalAnswer + case .iceCandidate(_, _): type = .iceCandidate + } + let callMessageProto = SNProtoCallMessage.builder(type: type, sdp: sdp) + if case let .iceCandidate(sdpMLineIndex, sdpMid) = kind { + callMessageProto.setSdpMlineIndex(sdpMLineIndex) + callMessageProto.setSdpMid(sdpMid) + } let contentProto = SNProtoContent.builder() do { contentProto.setCallMessage(try callMessageProto.build()) @@ -63,39 +122,9 @@ public final class CallMessage : ControlMessage { public override var description: String { """ CallMessage( - type: \(type?.description ?? "null"), + kind: \(kind?.description ?? "null"), sdp: \(sdp ?? "null") ) """ } } - -// MARK: RTCSdpType + Utilities -extension RTCSdpType : CustomStringConvertible { - - public var description: String { - switch self { - case .answer: return "answer" - case .offer: return "offer" - case .prAnswer: return "prAnswer" - default: preconditionFailure() - } - } - - fileprivate static func from(_ type: SNProtoCallMessage.SNProtoCallMessageType) -> RTCSdpType { - switch type { - case .answer: return .answer - case .offer: return .offer - case .provisionalAnswer: return .prAnswer - } - } - - fileprivate func toProto() -> SNProtoCallMessage.SNProtoCallMessageType { - switch self { - case .answer: return .answer - case .offer: return .offer - case .prAnswer: return .provisionalAnswer - default: preconditionFailure() - } - } -} diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index 8da553d2b..9348b2524 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -656,6 +656,7 @@ extension SNProtoContent.SNProtoContentBuilder { case offer = 1 case answer = 2 case provisionalAnswer = 3 + case iceCandidate = 4 } private class func SNProtoCallMessageTypeWrap(_ value: SessionProtos_CallMessage.TypeEnum) -> SNProtoCallMessageType { @@ -663,6 +664,7 @@ extension SNProtoContent.SNProtoContentBuilder { case .offer: return .offer case .answer: return .answer case .provisionalAnswer: return .provisionalAnswer + case .iceCandidate: return .iceCandidate } } @@ -671,6 +673,7 @@ extension SNProtoContent.SNProtoContentBuilder { case .offer: return .offer case .answer: return .answer case .provisionalAnswer: return .provisionalAnswer + case .iceCandidate: return .iceCandidate } } @@ -683,6 +686,12 @@ extension SNProtoContent.SNProtoContentBuilder { // asBuilder() constructs a builder that reflects the proto's contents. @objc public func asBuilder() -> SNProtoCallMessageBuilder { let builder = SNProtoCallMessageBuilder(type: type, sdp: sdp) + if hasSdpMlineIndex { + builder.setSdpMlineIndex(sdpMlineIndex) + } + if let _value = sdpMid { + builder.setSdpMid(_value) + } return builder } @@ -707,6 +716,14 @@ extension SNProtoContent.SNProtoContentBuilder { proto.sdp = valueParam } + @objc public func setSdpMlineIndex(_ valueParam: UInt32) { + proto.sdpMlineIndex = valueParam + } + + @objc public func setSdpMid(_ valueParam: String) { + proto.sdpMid = valueParam + } + @objc public func build() throws -> SNProtoCallMessage { return try SNProtoCallMessage.parseProto(proto) } @@ -722,6 +739,23 @@ extension SNProtoContent.SNProtoContentBuilder { @objc public let sdp: String + @objc public var sdpMlineIndex: UInt32 { + return proto.sdpMlineIndex + } + @objc public var hasSdpMlineIndex: Bool { + return proto.hasSdpMlineIndex + } + + @objc public var sdpMid: String? { + guard proto.hasSdpMid else { + return nil + } + return proto.sdpMid + } + @objc public var hasSdpMid: Bool { + return proto.hasSdpMid + } + private init(proto: SessionProtos_CallMessage, type: SNProtoCallMessageType, sdp: String) { diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index 1114aba7e..350844f08 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -329,6 +329,24 @@ struct SessionProtos_CallMessage { /// Clears the value of `sdp`. Subsequent reads from it will return its default value. mutating func clearSdp() {self._sdp = nil} + var sdpMlineIndex: UInt32 { + get {return _sdpMlineIndex ?? 0} + set {_sdpMlineIndex = newValue} + } + /// Returns true if `sdpMlineIndex` has been explicitly set. + var hasSdpMlineIndex: Bool {return self._sdpMlineIndex != nil} + /// Clears the value of `sdpMlineIndex`. Subsequent reads from it will return its default value. + mutating func clearSdpMlineIndex() {self._sdpMlineIndex = nil} + + var sdpMid: String { + get {return _sdpMid ?? String()} + set {_sdpMid = newValue} + } + /// Returns true if `sdpMid` has been explicitly set. + var hasSdpMid: Bool {return self._sdpMid != nil} + /// Clears the value of `sdpMid`. Subsequent reads from it will return its default value. + mutating func clearSdpMid() {self._sdpMid = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() enum TypeEnum: SwiftProtobuf.Enum { @@ -336,6 +354,7 @@ struct SessionProtos_CallMessage { case offer // = 1 case answer // = 2 case provisionalAnswer // = 3 + case iceCandidate // = 4 init() { self = .offer @@ -346,6 +365,7 @@ struct SessionProtos_CallMessage { case 1: self = .offer case 2: self = .answer case 3: self = .provisionalAnswer + case 4: self = .iceCandidate default: return nil } } @@ -355,6 +375,7 @@ struct SessionProtos_CallMessage { case .offer: return 1 case .answer: return 2 case .provisionalAnswer: return 3 + case .iceCandidate: return 4 } } @@ -364,6 +385,8 @@ struct SessionProtos_CallMessage { fileprivate var _type: SessionProtos_CallMessage.TypeEnum? = nil fileprivate var _sdp: String? = nil + fileprivate var _sdpMlineIndex: UInt32? = nil + fileprivate var _sdpMid: String? = nil } #if swift(>=4.2) @@ -1795,6 +1818,8 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "type"), 2: .same(proto: "sdp"), + 3: .same(proto: "sdpMLineIndex"), + 4: .same(proto: "sdpMid"), ] public var isInitialized: Bool { @@ -1811,6 +1836,8 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self._type) }() case 2: try { try decoder.decodeSingularStringField(value: &self._sdp) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self._sdpMlineIndex) }() + case 4: try { try decoder.decodeSingularStringField(value: &self._sdpMid) }() default: break } } @@ -1823,12 +1850,20 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa if let v = self._sdp { try visitor.visitSingularStringField(value: v, fieldNumber: 2) } + if let v = self._sdpMlineIndex { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) + } + if let v = self._sdpMid { + try visitor.visitSingularStringField(value: v, fieldNumber: 4) + } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: SessionProtos_CallMessage, rhs: SessionProtos_CallMessage) -> Bool { if lhs._type != rhs._type {return false} if lhs._sdp != rhs._sdp {return false} + if lhs._sdpMlineIndex != rhs._sdpMlineIndex {return false} + if lhs._sdpMid != rhs._sdpMid {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1839,6 +1874,7 @@ extension SessionProtos_CallMessage.TypeEnum: SwiftProtobuf._ProtoNameProviding 1: .same(proto: "OFFER"), 2: .same(proto: "ANSWER"), 3: .same(proto: "PROVISIONAL_ANSWER"), + 4: .same(proto: "ICE_CANDIDATE"), ] } diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index 76995d94a..7cd083135 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -57,12 +57,15 @@ message CallMessage { OFFER = 1; ANSWER = 2; PROVISIONAL_ANSWER = 3; + ICE_CANDIDATE = 4; } // @required - required Type type = 1; + required Type type = 1; // @required - required string sdp = 2; + required string sdp = 2; + optional uint32 sdpMLineIndex = 3; + optional string sdpMid = 4; } message KeyPair { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 7bf42c26e..9ae682d0c 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -1,5 +1,6 @@ import SignalCoreKit import SessionSnodeKit +import WebRTC extension MessageReceiver { @@ -16,6 +17,7 @@ extension MessageReceiver { case let message as ExpirationTimerUpdate: handleExpirationTimerUpdate(message, using: transaction) case let message as ConfigurationMessage: handleConfigurationMessage(message, using: transaction) case let message as UnsendRequest: handleUnsendRequest(message, using: transaction) + case let message as CallMessage: handleCallMessage(message, using: transaction) case let message as VisibleMessage: try handleVisibleMessage(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction) default: fatalError() } @@ -251,6 +253,33 @@ extension MessageReceiver { + // MARK: - Call Messages + + public static func handleCallMessage(_ message: CallMessage, using transaction: Any) { + let webRTCWrapper: WebRTCWrapper + if let current = WebRTCWrapper.current { + webRTCWrapper = current + } else { + WebRTCWrapper.current = WebRTCWrapper(for: message.sender!) + webRTCWrapper = WebRTCWrapper.current! + } + switch message.kind! { + case .offer: + print("[Calls] Received offer message.") + handleOfferCallMessage?(message) + case .answer: + print("[Calls] Received answer message.") + let sdp = RTCSessionDescription(type: .answer, sdp: message.sdp!) + webRTCWrapper.handleRemoteSDP(sdp, from: message.sender!) + case .provisionalAnswer: break // TODO: Implement + case let .iceCandidate(sdpMLineIndex, sdpMid): + let candidate = RTCIceCandidate(sdp: message.sdp!, sdpMLineIndex: Int32(sdpMLineIndex), sdpMid: sdpMid) + webRTCWrapper.handleICECandidate(candidate) + } + } + + + // MARK: - Visible Messages @discardableResult diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 13ca16e8e..78ba53208 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -1,7 +1,8 @@ import SessionUtilitiesKit public enum MessageReceiver { - private static var lastEncryptionKeyPairRequest: [String:Date] = [:] + private static var lastEncryptionKeyPairRequest: [String:Date] = [:] + public static var handleOfferCallMessage: ((CallMessage) -> Void)? public enum Error : LocalizedError { case duplicateMessage @@ -126,6 +127,7 @@ public enum MessageReceiver { if let configurationMessage = ConfigurationMessage.fromProto(proto) { return configurationMessage } if let unsendRequest = UnsendRequest.fromProto(proto) { return unsendRequest } if let visibleMessage = VisibleMessage.fromProto(proto) { return visibleMessage } + if let callMessage = CallMessage.fromProto(proto) { return callMessage } return nil }() if let message = message { From 1ad42547b2cd474f1f22533b394073d635fd7d5e Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 17 Aug 2021 16:02:20 +1000 Subject: [PATCH 027/368] Batch send ICE candidates --- Session/Calls/CallVCV2.swift | 1 + Session/Meta/AppDelegate.swift | 2 +- .../Calls/WebRTCWrapper+MessageHandling.swift | 5 +- SessionMessagingKit/Calls/WebRTCWrapper.swift | 39 ++++++--- .../Control Messages/CallMessage.swift | 62 ++++++------- .../Protos/Generated/SNProto.swift | 87 +++++++++---------- .../Protos/Generated/SessionProtos.pb.swift | 70 +++++---------- .../Protos/SessionProtos.proto | 13 +-- .../MessageReceiver+Handling.swift | 16 +++- SessionUtilitiesKit/Networking/HTTP.swift | 5 +- 10 files changed, 151 insertions(+), 149 deletions(-) diff --git a/Session/Calls/CallVCV2.swift b/Session/Calls/CallVCV2.swift index b40d71d99..40b07ef80 100644 --- a/Session/Calls/CallVCV2.swift +++ b/Session/Calls/CallVCV2.swift @@ -37,6 +37,7 @@ final class CallVCV2 : UIViewController, WebRTCWrapperDelegate { override func viewDidLoad() { super.viewDidLoad() + view.backgroundColor = .black WebRTCWrapper.current = webRTCWrapper setUpViewHierarchy() cameraManager.prepare() diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index c9ea18bdc..6186fd4cd 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -7,7 +7,7 @@ extension AppDelegate { func setUpCallHandling() { MessageReceiver.handleOfferCallMessage = { message in DispatchQueue.main.async { - let sdp = RTCSessionDescription(type: .offer, sdp: message.sdp!) + let sdp = RTCSessionDescription(type: .offer, sdp: message.sdps![0]) 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 diff --git a/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift b/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift index 88f60a24d..57069ab42 100644 --- a/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift +++ b/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift @@ -2,9 +2,9 @@ import WebRTC extension WebRTCWrapper { - public func handleICECandidate(_ candidate: RTCIceCandidate) { + public func handleICECandidates(_ candidate: [RTCIceCandidate]) { print("[Calls] Received ICE candidate message.") - peerConnection.add(candidate) + candidate.forEach { peerConnection.add($0) } } public func handleRemoteSDP(_ sdp: RTCSessionDescription, from sessionID: String) { @@ -15,7 +15,6 @@ extension WebRTCWrapper { } else { guard let self = self, sdp.type == .offer, self.peerConnection.localDescription == nil else { return } - // Automatically answer the call Storage.write { transaction in self.sendAnswer(to: sessionID, using: transaction).retainUntilComplete() } diff --git a/SessionMessagingKit/Calls/WebRTCWrapper.swift b/SessionMessagingKit/Calls/WebRTCWrapper.swift index 49cd939bb..116a0d626 100644 --- a/SessionMessagingKit/Calls/WebRTCWrapper.swift +++ b/SessionMessagingKit/Calls/WebRTCWrapper.swift @@ -9,6 +9,8 @@ public protocol WebRTCWrapperDelegate : AnyObject { public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { public weak var delegate: WebRTCWrapperDelegate? private let contactSessionID: String + private var queuedICECandidates: [RTCIceCandidate] = [] + private var iceCandidateSendTimer: Timer? private let defaultICEServers = [ "stun:stun.l.google.com:19302", @@ -101,7 +103,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { audioSession.unlockForConfiguration() } - // MARK: Call Management + // MARK: Signaling public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { print("[Calls] Initiating call.") guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } @@ -120,7 +122,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { DispatchQueue.main.async { let message = CallMessage() message.kind = .offer - message.sdp = sdp.sdp + message.sdps = [ sdp.sdp ] MessageSender.send(message, in: thread, using: transaction) seal.fulfill(()) } @@ -147,7 +149,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { DispatchQueue.main.async { let message = CallMessage() message.kind = .answer - message.sdp = sdp.sdp + message.sdps = [ sdp.sdp ] MessageSender.send(message, in: thread, using: transaction) seal.fulfill(()) } @@ -156,6 +158,29 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { return promise } + private func queueICECandidateForSending(_ candidate: RTCIceCandidate) { + queuedICECandidates.append(candidate) + iceCandidateSendTimer?.invalidate() + iceCandidateSendTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in + self.sendICECandidates() + } + } + + private func sendICECandidates() { + Storage.write { transaction in + let candidates = self.queuedICECandidates + guard let thread = TSContactThread.fetch(for: self.contactSessionID, using: transaction) else { return } + let message = CallMessage() + let sdps = candidates.map { $0.sdp } + let sdpMLineIndexes = candidates.map { UInt32($0.sdpMLineIndex) } + let sdpMids = candidates.map { $0.sdpMid! } + message.kind = .iceCandidates(sdpMLineIndexes: sdpMLineIndexes, sdpMids: sdpMids) + message.sdps = sdps + self.queuedICECandidates.removeAll() + MessageSender.send(message, in: thread, using: transaction) + } + } + public func dropConnection() { peerConnection.close() } @@ -187,13 +212,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { 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) - } + queueICECandidateForSending(candidate) } public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) { diff --git a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift index b4fed52a8..248dcb904 100644 --- a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift @@ -1,25 +1,27 @@ import WebRTC +// NOTE: Multiple ICE candidates may be batched together for performance + /// See https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription for more information. @objc(SNCallMessage) public final class CallMessage : ControlMessage { public var kind: Kind? /// See https://developer.mozilla.org/en-US/docs/Glossary/SDP for more information. - public var sdp: String? + public var sdps: [String]? // MARK: Kind public enum Kind : Codable, CustomStringConvertible { case offer case answer case provisionalAnswer - case iceCandidate(sdpMLineIndex: UInt32, sdpMid: String) + case iceCandidates(sdpMLineIndexes: [UInt32], sdpMids: [String]) public var description: String { switch self { case .offer: return "offer" case .answer: return "answer" case .provisionalAnswer: return "provisionalAnswer" - case .iceCandidate(_, _): return "iceCandidate" + case .iceCandidates(_, _): return "iceCandidates" } } } @@ -27,16 +29,17 @@ public final class CallMessage : ControlMessage { // MARK: Initialization public override init() { super.init() } - internal init(kind: Kind, sdp: String) { + internal init(kind: Kind, sdps: [String]) { super.init() self.kind = kind - self.sdp = sdp + self.sdps = sdps } // MARK: Validation public override var isValid: Bool { guard super.isValid else { return false } - return kind != nil && sdp != nil + guard let sdps = sdps, !sdps.isEmpty else { return false } + return kind != nil } // MARK: Coding @@ -47,13 +50,13 @@ public final class CallMessage : ControlMessage { 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) + case "iceCandidates": + guard let sdpMLineIndexes = coder.decodeObject(forKey: "sdpMLineIndexes") as? [UInt32], + let sdpMids = coder.decodeObject(forKey: "sdpMids") as? [String] else { return nil } + kind = .iceCandidates(sdpMLineIndexes: sdpMLineIndexes, sdpMids: sdpMids) default: preconditionFailure() } - if let sdp = coder.decodeObject(forKey: "sdp") as! String? { self.sdp = sdp } + if let sdps = coder.decodeObject(forKey: "sdps") as! [String]? { self.sdps = sdps } } public override func encode(with coder: NSCoder) { @@ -62,13 +65,13 @@ public final class CallMessage : ControlMessage { 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") + case let .iceCandidates(sdpMLineIndexes, sdpMids): + coder.encode("iceCandidates", forKey: "kind") + coder.encode(sdpMLineIndexes, forKey: "sdpMLineIndexes") + coder.encode(sdpMids, forKey: "sdpMids") default: preconditionFailure() } - coder.encode(sdp, forKey: "sdp") + coder.encode(sdps, forKey: "sdps") } // MARK: Proto Conversion @@ -79,17 +82,17 @@ public final class CallMessage : ControlMessage { 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) + case .iceCandidates: + let sdpMLineIndexes = callMessageProto.sdpMlineIndexes + let sdpMids = callMessageProto.sdpMids + kind = .iceCandidates(sdpMLineIndexes: sdpMLineIndexes, sdpMids: sdpMids) } - let sdp = callMessageProto.sdp - return CallMessage(kind: kind, sdp: sdp) + let sdps = callMessageProto.sdps + return CallMessage(kind: kind, sdps: sdps) } public override func toProto(using transaction: YapDatabaseReadWriteTransaction) -> SNProtoContent? { - guard let kind = kind, let sdp = sdp else { + guard let kind = kind, let sdps = sdps, !sdps.isEmpty else { SNLog("Couldn't construct call message proto from: \(self).") return nil } @@ -101,12 +104,13 @@ public final class CallMessage : ControlMessage { case .offer: type = .offer case .answer: type = .answer case .provisionalAnswer: type = .provisionalAnswer - case .iceCandidate(_, _): type = .iceCandidate + case .iceCandidates(_, _): type = .iceCandidates } - let callMessageProto = SNProtoCallMessage.builder(type: type, sdp: sdp) - if case let .iceCandidate(sdpMLineIndex, sdpMid) = kind { - callMessageProto.setSdpMlineIndex(sdpMLineIndex) - callMessageProto.setSdpMid(sdpMid) + let callMessageProto = SNProtoCallMessage.builder(type: type) + callMessageProto.setSdps(sdps) + if case let .iceCandidates(sdpMLineIndexes, sdpMids) = kind { + callMessageProto.setSdpMlineIndexes(sdpMLineIndexes) + callMessageProto.setSdpMids(sdpMids) } let contentProto = SNProtoContent.builder() do { @@ -123,7 +127,7 @@ public final class CallMessage : ControlMessage { """ CallMessage( kind: \(kind?.description ?? "null"), - sdp: \(sdp ?? "null") + sdps: \(sdps?.description ?? "null") ) """ } diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index 9348b2524..134c63017 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -656,7 +656,7 @@ extension SNProtoContent.SNProtoContentBuilder { case offer = 1 case answer = 2 case provisionalAnswer = 3 - case iceCandidate = 4 + case iceCandidates = 4 } private class func SNProtoCallMessageTypeWrap(_ value: SessionProtos_CallMessage.TypeEnum) -> SNProtoCallMessageType { @@ -664,7 +664,7 @@ extension SNProtoContent.SNProtoContentBuilder { case .offer: return .offer case .answer: return .answer case .provisionalAnswer: return .provisionalAnswer - case .iceCandidate: return .iceCandidate + case .iceCandidates: return .iceCandidates } } @@ -673,25 +673,22 @@ extension SNProtoContent.SNProtoContentBuilder { case .offer: return .offer case .answer: return .answer case .provisionalAnswer: return .provisionalAnswer - case .iceCandidate: return .iceCandidate + case .iceCandidates: return .iceCandidates } } // MARK: - SNProtoCallMessageBuilder - @objc public class func builder(type: SNProtoCallMessageType, sdp: String) -> SNProtoCallMessageBuilder { - return SNProtoCallMessageBuilder(type: type, sdp: sdp) + @objc public class func builder(type: SNProtoCallMessageType) -> SNProtoCallMessageBuilder { + return SNProtoCallMessageBuilder(type: type) } // 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) - } + let builder = SNProtoCallMessageBuilder(type: type) + builder.setSdps(sdps) + builder.setSdpMlineIndexes(sdpMlineIndexes) + builder.setSdpMids(sdpMids) return builder } @@ -701,27 +698,44 @@ extension SNProtoContent.SNProtoContentBuilder { @objc fileprivate override init() {} - @objc fileprivate init(type: SNProtoCallMessageType, sdp: String) { + @objc fileprivate init(type: SNProtoCallMessageType) { super.init() setType(type) - setSdp(sdp) } @objc public func setType(_ valueParam: SNProtoCallMessageType) { proto.type = SNProtoCallMessageTypeUnwrap(valueParam) } - @objc public func setSdp(_ valueParam: String) { - proto.sdp = valueParam + @objc public func addSdps(_ valueParam: String) { + var items = proto.sdps + items.append(valueParam) + proto.sdps = items } - @objc public func setSdpMlineIndex(_ valueParam: UInt32) { - proto.sdpMlineIndex = valueParam + @objc public func setSdps(_ wrappedItems: [String]) { + proto.sdps = wrappedItems } - @objc public func setSdpMid(_ valueParam: String) { - proto.sdpMid = valueParam + @objc public func addSdpMlineIndexes(_ valueParam: UInt32) { + var items = proto.sdpMlineIndexes + items.append(valueParam) + proto.sdpMlineIndexes = items + } + + @objc public func setSdpMlineIndexes(_ wrappedItems: [UInt32]) { + proto.sdpMlineIndexes = wrappedItems + } + + @objc public func addSdpMids(_ valueParam: String) { + var items = proto.sdpMids + items.append(valueParam) + proto.sdpMids = items + } + + @objc public func setSdpMids(_ wrappedItems: [String]) { + proto.sdpMids = wrappedItems } @objc public func build() throws -> SNProtoCallMessage { @@ -737,31 +751,22 @@ extension SNProtoContent.SNProtoContentBuilder { @objc public let type: SNProtoCallMessageType - @objc public let sdp: String - - @objc public var sdpMlineIndex: UInt32 { - return proto.sdpMlineIndex - } - @objc public var hasSdpMlineIndex: Bool { - return proto.hasSdpMlineIndex + @objc public var sdps: [String] { + return proto.sdps } - @objc public var sdpMid: String? { - guard proto.hasSdpMid else { - return nil - } - return proto.sdpMid + @objc public var sdpMlineIndexes: [UInt32] { + return proto.sdpMlineIndexes } - @objc public var hasSdpMid: Bool { - return proto.hasSdpMid + + @objc public var sdpMids: [String] { + return proto.sdpMids } private init(proto: SessionProtos_CallMessage, - type: SNProtoCallMessageType, - sdp: String) { + type: SNProtoCallMessageType) { self.proto = proto self.type = type - self.sdp = sdp } @objc @@ -780,18 +785,12 @@ extension SNProtoContent.SNProtoContentBuilder { } let type = SNProtoCallMessageTypeWrap(proto.type) - guard proto.hasSdp else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: sdp") - } - let sdp = proto.sdp - // MARK: - Begin Validation Logic for SNProtoCallMessage - // MARK: - End Validation Logic for SNProtoCallMessage - let result = SNProtoCallMessage(proto: proto, - type: type, - sdp: sdp) + type: type) return result } diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index 350844f08..bf534a845 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -319,33 +319,11 @@ struct SessionProtos_CallMessage { /// Clears the value of `type`. Subsequent reads from it will return its default value. mutating func clearType() {self._type = nil} - /// @required - var sdp: String { - get {return _sdp ?? String()} - set {_sdp = newValue} - } - /// Returns true if `sdp` has been explicitly set. - var hasSdp: Bool {return self._sdp != nil} - /// Clears the value of `sdp`. Subsequent reads from it will return its default value. - mutating func clearSdp() {self._sdp = nil} + var sdps: [String] = [] - 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 sdpMlineIndexes: [UInt32] = [] - 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 sdpMids: [String] = [] var unknownFields = SwiftProtobuf.UnknownStorage() @@ -354,7 +332,7 @@ struct SessionProtos_CallMessage { case offer // = 1 case answer // = 2 case provisionalAnswer // = 3 - case iceCandidate // = 4 + case iceCandidates // = 4 init() { self = .offer @@ -365,7 +343,7 @@ struct SessionProtos_CallMessage { case 1: self = .offer case 2: self = .answer case 3: self = .provisionalAnswer - case 4: self = .iceCandidate + case 4: self = .iceCandidates default: return nil } } @@ -375,7 +353,7 @@ struct SessionProtos_CallMessage { case .offer: return 1 case .answer: return 2 case .provisionalAnswer: return 3 - case .iceCandidate: return 4 + case .iceCandidates: return 4 } } @@ -384,9 +362,6 @@ struct SessionProtos_CallMessage { init() {} 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) @@ -1817,14 +1792,13 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa static let protoMessageName: String = _protobuf_package + ".CallMessage" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "type"), - 2: .same(proto: "sdp"), - 3: .same(proto: "sdpMLineIndex"), - 4: .same(proto: "sdpMid"), + 2: .same(proto: "sdps"), + 3: .same(proto: "sdpMLineIndexes"), + 4: .same(proto: "sdpMids"), ] public var isInitialized: Bool { if self._type == nil {return false} - if self._sdp == nil {return false} return true } @@ -1835,9 +1809,9 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa // enabled. https://github.com/apple/swift-protobuf/issues/1034 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) }() + case 2: try { try decoder.decodeRepeatedStringField(value: &self.sdps) }() + case 3: try { try decoder.decodeRepeatedUInt32Field(value: &self.sdpMlineIndexes) }() + case 4: try { try decoder.decodeRepeatedStringField(value: &self.sdpMids) }() default: break } } @@ -1847,23 +1821,23 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa if let v = self._type { try visitor.visitSingularEnumField(value: v, fieldNumber: 1) } - if let v = self._sdp { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) + if !self.sdps.isEmpty { + try visitor.visitRepeatedStringField(value: self.sdps, fieldNumber: 2) } - if let v = self._sdpMlineIndex { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) + if !self.sdpMlineIndexes.isEmpty { + try visitor.visitRepeatedUInt32Field(value: self.sdpMlineIndexes, fieldNumber: 3) } - if let v = self._sdpMid { - try visitor.visitSingularStringField(value: v, fieldNumber: 4) + if !self.sdpMids.isEmpty { + try visitor.visitRepeatedStringField(value: self.sdpMids, 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.sdps != rhs.sdps {return false} + if lhs.sdpMlineIndexes != rhs.sdpMlineIndexes {return false} + if lhs.sdpMids != rhs.sdpMids {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1874,7 +1848,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"), + 4: .same(proto: "ICE_CANDIDATES"), ] } diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index 7cd083135..a2927a698 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -57,15 +57,16 @@ message CallMessage { OFFER = 1; ANSWER = 2; PROVISIONAL_ANSWER = 3; - ICE_CANDIDATE = 4; + ICE_CANDIDATES = 4; } + // Multiple ICE candidates may be batched together for performance + // @required - required Type type = 1; - // @required - required string sdp = 2; - optional uint32 sdpMLineIndex = 3; - optional string sdpMid = 4; + required Type type = 1; + repeated string sdps = 2; + repeated uint32 sdpMLineIndexes = 3; + repeated string sdpMids = 4; } message KeyPair { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 9ae682d0c..9e0919e43 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -269,12 +269,20 @@ extension MessageReceiver { handleOfferCallMessage?(message) case .answer: print("[Calls] Received answer message.") - let sdp = RTCSessionDescription(type: .answer, sdp: message.sdp!) + let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) 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) + case let .iceCandidates(sdpMLineIndexes, sdpMids): + var candidates: [RTCIceCandidate] = [] + let sdps = message.sdps! + for i in 0.. Promise { + public static func execute(_ verb: Verb, _ url: String, body: Data?, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise { var request = URLRequest(url: URL(string: url)!) request.httpMethod = verb.rawValue request.httpBody = body @@ -117,9 +117,6 @@ public enum HTTP { request.allHTTPHeaderFields?.removeValue(forKey: "User-Agent") request.setValue("WhatsApp", forHTTPHeaderField: "User-Agent") // Set a fake value request.setValue("en-us", forHTTPHeaderField: "Accept-Language") // Set a fake value - headers.forEach { (key, value) in - request.setValue(value, forHTTPHeaderField: key) - } let (promise, seal) = Promise.pending() let urlSession = useSeedNodeURLSession ? seedNodeURLSession : snodeURLSession let task = urlSession.dataTask(with: request) { data, response, error in From b3af41e2fcf76f7a3e5bb2302a557c75ff2a1194 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 17 Aug 2021 16:20:08 +1000 Subject: [PATCH 028/368] Debug --- SessionMessagingKit/Calls/WebRTCWrapper.swift | 30 ++++++++++++------- .../Control Messages/CallMessage.swift | 3 -- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/SessionMessagingKit/Calls/WebRTCWrapper.swift b/SessionMessagingKit/Calls/WebRTCWrapper.swift index 116a0d626..f0d55af3c 100644 --- a/SessionMessagingKit/Calls/WebRTCWrapper.swift +++ b/SessionMessagingKit/Calls/WebRTCWrapper.swift @@ -105,7 +105,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { // MARK: Signaling public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { - print("[Calls] Initiating call.") + print("[Calls] Sending offer message.") guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } let (promise, seal) = Promise.pending() peerConnection.offer(for: mediaConstraints) { [weak self] sdp, error in @@ -123,8 +123,11 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { let message = CallMessage() message.kind = .offer message.sdps = [ sdp.sdp ] - MessageSender.send(message, in: thread, using: transaction) - seal.fulfill(()) + MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 { + seal.fulfill(()) + }.catch2 { error in + seal.reject(error) + } } } } @@ -132,7 +135,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { } public func sendAnswer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { - print("[Calls] Accepting call.") + print("[Calls] Sending answer message.") guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } let (promise, seal) = Promise.pending() peerConnection.answer(for: mediaConstraints) { [weak self] sdp, error in @@ -150,8 +153,11 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { let message = CallMessage() message.kind = .answer message.sdps = [ sdp.sdp ] - MessageSender.send(message, in: thread, using: transaction) - seal.fulfill(()) + MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 { + seal.fulfill(()) + }.catch2 { error in + seal.reject(error) + } } } } @@ -160,9 +166,11 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { private func queueICECandidateForSending(_ candidate: RTCIceCandidate) { queuedICECandidates.append(candidate) - iceCandidateSendTimer?.invalidate() - iceCandidateSendTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in - self.sendICECandidates() + DispatchQueue.main.async { + self.iceCandidateSendTimer?.invalidate() + self.iceCandidateSendTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in + self.sendICECandidates() + } } } @@ -170,6 +178,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { Storage.write { transaction in let candidates = self.queuedICECandidates guard let thread = TSContactThread.fetch(for: self.contactSessionID, using: transaction) else { return } + print("[Calls] Batch sending \(candidates.count) ICE candidates.") let message = CallMessage() let sdps = candidates.map { $0.sdp } let sdpMLineIndexes = candidates.map { UInt32($0.sdpMLineIndex) } @@ -177,7 +186,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { message.kind = .iceCandidates(sdpMLineIndexes: sdpMLineIndexes, sdpMids: sdpMids) message.sdps = sdps self.queuedICECandidates.removeAll() - MessageSender.send(message, in: thread, using: transaction) + MessageSender.sendNonDurably(message, in: thread, using: transaction).retainUntilComplete() } } @@ -211,7 +220,6 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { } public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { - print("[Calls] ICE candidate generated.") queueICECandidateForSending(candidate) } diff --git a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift index 248dcb904..c5fcdc62e 100644 --- a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift @@ -96,9 +96,6 @@ public final class CallMessage : ControlMessage { SNLog("Couldn't construct call message proto from: \(self).") return nil } - if case .offer = kind { - print("[Calls] Converting offer message to proto.") - } let type: SNProtoCallMessage.SNProtoCallMessageType switch kind { case .offer: type = .offer From e899804b85caf58ba7e390f07bf4e6f644db270f Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 18 Aug 2021 08:56:11 +1000 Subject: [PATCH 029/368] =?UTF-8?q?Rename=20CallVCV2=20=E2=86=92=20CallVC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Session.xcodeproj/project.pbxproj | 16 ++++++++-------- ...CallVCV2+Camera.swift => CallVC+Camera.swift} | 2 +- Session/Calls/{CallVCV2.swift => CallVC.swift} | 2 +- .../ConversationVC+Interaction.swift | 2 +- Session/Meta/AppDelegate.swift | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) rename Session/Calls/{CallVCV2+Camera.swift => CallVC+Camera.swift} (93%) rename Session/Calls/{CallVCV2.swift => CallVC.swift} (97%) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 03b59d124..6b2b1e984 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -200,8 +200,8 @@ B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; }; 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 */; }; - B877E24626CA13BA0007970A /* CallVCV2+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = B877E24526CA13BA0007970A /* CallVCV2+Camera.swift */; }; + B877E24226CA12910007970A /* CallVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B877E24126CA12910007970A /* CallVC.swift */; }; + B877E24626CA13BA0007970A /* CallVC+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = B877E24526CA13BA0007970A /* CallVC+Camera.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 */; }; @@ -1194,8 +1194,8 @@ B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = ""; }; B87588582644CA9D000E60D0 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; B8758859264503A6000E60D0 /* JoinOpenGroupModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinOpenGroupModal.swift; sourceTree = ""; }; - B877E24126CA12910007970A /* CallVCV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVCV2.swift; sourceTree = ""; }; - B877E24526CA13BA0007970A /* CallVCV2+Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVCV2+Camera.swift"; sourceTree = ""; }; + B877E24126CA12910007970A /* CallVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVC.swift; sourceTree = ""; }; + B877E24526CA13BA0007970A /* CallVC+Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallVC+Camera.swift"; sourceTree = ""; }; B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Interaction.swift"; sourceTree = ""; }; B879D448247E1BE300DB3608 /* PathVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathVC.swift; sourceTree = ""; }; B879D44A247E1D9200DB3608 /* PathStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathStatusView.swift; sourceTree = ""; }; @@ -2321,8 +2321,8 @@ B8B558ED26C4B55F00693325 /* Calls */ = { isa = PBXGroup; children = ( - B877E24126CA12910007970A /* CallVCV2.swift */, - B877E24526CA13BA0007970A /* CallVCV2+Camera.swift */, + B877E24126CA12910007970A /* CallVC.swift */, + B877E24526CA13BA0007970A /* CallVC+Camera.swift */, B8B558F026C4BB0600693325 /* CameraManager.swift */, ); path = Calls; @@ -4822,7 +4822,7 @@ B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */, B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */, B879D449247E1BE300DB3608 /* PathVC.swift in Sources */, - B877E24626CA13BA0007970A /* CallVCV2+Camera.swift in Sources */, + B877E24626CA13BA0007970A /* CallVC+Camera.swift in Sources */, 454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */, 340FC8B4204DAC8D007AEB0F /* OWSBackupSettingsViewController.m in Sources */, 34D1F0871F8678AA0066283D /* ConversationViewItem.m in Sources */, @@ -4884,7 +4884,7 @@ B835246E25C38ABF0089A44F /* ConversationVC.swift in Sources */, B8AF4BB426A5204600583500 /* SendSeedModal.swift in Sources */, B821494625D4D6FF009C0F2A /* URLModal.swift in Sources */, - B877E24226CA12910007970A /* CallVCV2.swift in Sources */, + B877E24226CA12910007970A /* CallVC.swift in Sources */, C374EEEB25DA3CA70073A857 /* ConversationTitleView.swift in Sources */, B88FA7F2260C3EB10049422F /* OpenGroupSuggestionGrid.swift in Sources */, 4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */, diff --git a/Session/Calls/CallVCV2+Camera.swift b/Session/Calls/CallVC+Camera.swift similarity index 93% rename from Session/Calls/CallVCV2+Camera.swift rename to Session/Calls/CallVC+Camera.swift index 89bf426fe..3370a1bff 100644 --- a/Session/Calls/CallVCV2+Camera.swift +++ b/Session/Calls/CallVC+Camera.swift @@ -1,6 +1,6 @@ import WebRTC -extension CallVCV2 : CameraManagerDelegate { +extension CallVC : CameraManagerDelegate { func handleVideoOutputCaptured(sampleBuffer: CMSampleBuffer) { guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } diff --git a/Session/Calls/CallVCV2.swift b/Session/Calls/CallVC.swift similarity index 97% rename from Session/Calls/CallVCV2.swift rename to Session/Calls/CallVC.swift index 40b07ef80..47ee0adeb 100644 --- a/Session/Calls/CallVCV2.swift +++ b/Session/Calls/CallVC.swift @@ -3,7 +3,7 @@ import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit -final class CallVCV2 : UIViewController, WebRTCWrapperDelegate { +final class CallVC : UIViewController, WebRTCWrapperDelegate { let sessionID: String let mode: Mode let webRTCWrapper: WebRTCWrapper diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 0de4bcdfb..ad49ad20c 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -28,7 +28,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc @objc func startCall() { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } - let callVC = CallVCV2(for: contactSessionID, mode: .offer) + let callVC = CallVC(for: contactSessionID, mode: .offer) navigationController!.pushViewController(callVC, animated: true, completion: nil) } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 6186fd4cd..bd98133dc 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -11,7 +11,7 @@ extension AppDelegate { 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)) + let callVC = CallVC(for: message.sender!, mode: .answer(sdp: sdp)) presentingVC.dismiss(animated: true) { presentingVC.present(callVC, animated: true, completion: nil) } From 3206ce380edbbc9155c96ba0813bb76697917e66 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 18 Aug 2021 09:00:55 +1000 Subject: [PATCH 030/368] =?UTF-8?q?Rename=20WebRTCWrapper=20=E2=86=92=20We?= =?UTF-8?q?bRTCSession?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Session.xcodeproj/project.pbxproj | 24 +++++++++---------- Session/Calls/CallVC+Camera.swift | 2 +- Session/Calls/CallVC.swift | 22 ++++++++--------- ...ft => WebRTCSession+MessageHandling.swift} | 2 +- ...rapper+UI.swift => WebRTCSession+UI.swift} | 2 +- ...ebRTCWrapper.swift => WebRTCSession.swift} | 8 +++---- .../Control Messages/CallMessage.swift | 4 ++-- .../MessageReceiver+Handling.swift | 14 +++++------ 8 files changed, 39 insertions(+), 39 deletions(-) rename SessionMessagingKit/Calls/{WebRTCWrapper+MessageHandling.swift => WebRTCSession+MessageHandling.swift} (97%) rename SessionMessagingKit/Calls/{WebRTCWrapper+UI.swift => WebRTCSession+UI.swift} (94%) rename SessionMessagingKit/Calls/{WebRTCWrapper.swift => WebRTCSession.swift} (97%) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6b2b1e984..f20ced31b 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -157,7 +157,7 @@ B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */; }; B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */; }; B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */; }; - B806ECA126C4A7E4008BDA44 /* WebRTCWrapper+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA026C4A7E4008BDA44 /* WebRTCWrapper+UI.swift */; }; + B806ECA126C4A7E4008BDA44 /* WebRTCSession+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA026C4A7E4008BDA44 /* WebRTCSession+UI.swift */; }; B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */; }; B817AD9A26436593009DF825 /* SimplifiedConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */; }; B817AD9C26436F73009DF825 /* ThreadPickerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */; }; @@ -249,7 +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 */; }; - B8B558FF26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FE26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift */; }; + B8B558FF26C4E05E00693325 /* WebRTCSession+MessageHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FE26C4E05E00693325 /* WebRTCSession+MessageHandling.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 */; }; @@ -271,7 +271,7 @@ B8D64FCB25BA78A90029CFC0 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; B8D84EA325DF745A005A043E /* LinkPreviewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D84EA225DF745A005A043E /* LinkPreviewState.swift */; }; B8D84ECF25E3108A005A043E /* ExpandingAttachmentsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D84ECE25E3108A005A043E /* ExpandingAttachmentsButton.swift */; }; - B8DE1FB426C22F2F0079C9CE /* WebRTCWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8DE1FB326C22F2F0079C9CE /* WebRTCWrapper.swift */; }; + B8DE1FB426C22F2F0079C9CE /* WebRTCSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8DE1FB326C22F2F0079C9CE /* WebRTCSession.swift */; }; B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */; }; B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */; }; B8EB20F02640F7F000773E52 /* OpenGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */; }; @@ -1147,7 +1147,7 @@ B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = System/Library/Frameworks/PushKit.framework; sourceTree = SDKROOT; }; B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderView.swift; sourceTree = ""; }; B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCell.swift; sourceTree = ""; }; - B806ECA026C4A7E4008BDA44 /* WebRTCWrapper+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCWrapper+UI.swift"; sourceTree = ""; }; + B806ECA026C4A7E4008BDA44 /* WebRTCSession+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+UI.swift"; sourceTree = ""; }; B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = ""; }; B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedConversationCell.swift; sourceTree = ""; }; B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerVC.swift; sourceTree = ""; }; @@ -1220,7 +1220,7 @@ B8B32044258C117C0020074B /* ContactsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsMigration.swift; sourceTree = ""; }; B8B320B6258C30D70020074B /* HTMLMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadata.swift; sourceTree = ""; }; B8B558F026C4BB0600693325 /* CameraManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = ""; }; - B8B558FE26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCWrapper+MessageHandling.swift"; sourceTree = ""; }; + B8B558FE26C4E05E00693325 /* WebRTCSession+MessageHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+MessageHandling.swift"; sourceTree = ""; }; B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B8BAC75B2695645400EA1759 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; B8BAC75C2695648500EA1759 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; @@ -1258,7 +1258,7 @@ B8D8F1BC25661C6F0092EF10 /* Storage+OnionRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+OnionRequests.swift"; sourceTree = ""; }; B8D8F1EF256621180092EF10 /* MessageSender+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "MessageSender+Convenience.swift"; path = "../../SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift"; sourceTree = ""; }; B8DE1FAF26C228780079C9CE /* SignalRingRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SignalRingRTC.framework; path = Dependencies/SignalRingRTC.framework; sourceTree = ""; }; - B8DE1FB326C22F2F0079C9CE /* WebRTCWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCWrapper.swift; sourceTree = ""; }; + B8DE1FB326C22F2F0079C9CE /* WebRTCSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCSession.swift; sourceTree = ""; }; B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessage.swift; sourceTree = ""; }; B8EB20E6263F7E4B00773E52 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+OpenGroupInvitation.swift"; sourceTree = ""; }; @@ -2357,9 +2357,9 @@ B8DE1FB226C22F1F0079C9CE /* Calls */ = { isa = PBXGroup; children = ( - B8DE1FB326C22F2F0079C9CE /* WebRTCWrapper.swift */, - B806ECA026C4A7E4008BDA44 /* WebRTCWrapper+UI.swift */, - B8B558FE26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift */, + B8DE1FB326C22F2F0079C9CE /* WebRTCSession.swift */, + B806ECA026C4A7E4008BDA44 /* WebRTCSession+UI.swift */, + B8B558FE26C4E05E00693325 /* WebRTCSession+MessageHandling.swift */, ); path = Calls; sourceTree = ""; @@ -4673,7 +4673,7 @@ C352A32F2557549C00338F3E /* NotifyPNServerJob.swift in Sources */, 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */, C300A5F22554B09800555489 /* MessageSender.swift in Sources */, - B8B558FF26C4E05E00693325 /* WebRTCWrapper+MessageHandling.swift in Sources */, + B8B558FF26C4E05E00693325 /* WebRTCSession+MessageHandling.swift in Sources */, C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */, C32C5AAD256DBE8F003C73A2 /* TSInfoMessage.m in Sources */, C32C5A13256DB7A5003C73A2 /* PushNotificationAPI.swift in Sources */, @@ -4711,7 +4711,7 @@ B8856ECE256F1E58001CE70E /* OWSPreferences.m in Sources */, C3C2A7842553AAF300C340D1 /* SNProto.swift in Sources */, C32C5DC0256DD743003C73A2 /* Poller.swift in Sources */, - B8DE1FB426C22F2F0079C9CE /* WebRTCWrapper.swift in Sources */, + B8DE1FB426C22F2F0079C9CE /* WebRTCSession.swift in Sources */, C3C2A7682553A3D900C340D1 /* VisibleMessage+Contact.swift in Sources */, C3A3A171256E1D25004D228D /* SSKReachabilityManager.swift in Sources */, B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */, @@ -4791,7 +4791,7 @@ C32C5D23256DD4C0003C73A2 /* Mention.swift in Sources */, C32C5FD6256E0346003C73A2 /* Notification+Thread.swift in Sources */, C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */, - B806ECA126C4A7E4008BDA44 /* WebRTCWrapper+UI.swift in Sources */, + B806ECA126C4A7E4008BDA44 /* WebRTCSession+UI.swift in Sources */, C32C5C88256DD0D2003C73A2 /* Storage+Messaging.swift in Sources */, C32C59C7256DB41F003C73A2 /* TSThread.m in Sources */, C300A5B22554AF9800555489 /* VisibleMessage+Profile.swift in Sources */, diff --git a/Session/Calls/CallVC+Camera.swift b/Session/Calls/CallVC+Camera.swift index 3370a1bff..17dc4f196 100644 --- a/Session/Calls/CallVC+Camera.swift +++ b/Session/Calls/CallVC+Camera.swift @@ -9,6 +9,6 @@ extension CallVC : CameraManagerDelegate { let timestampNs = Int64(timestamp * 1000000000) let frame = RTCVideoFrame(buffer: rtcPixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: timestampNs) frame.timeStamp = Int32(timestamp) - webRTCWrapper.handleLocalFrameCaptured(frame) + webRTCSession.handleLocalFrameCaptured(frame) } } diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 47ee0adeb..e6cf13de2 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -3,10 +3,10 @@ import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit -final class CallVC : UIViewController, WebRTCWrapperDelegate { +final class CallVC : UIViewController, WebRTCSessionDelegate { let sessionID: String let mode: Mode - let webRTCWrapper: WebRTCWrapper + let webRTCSession: WebRTCSession lazy var cameraManager: CameraManager = { let result = CameraManager() @@ -15,7 +15,7 @@ final class CallVC : UIViewController, WebRTCWrapperDelegate { }() lazy var videoCapturer: RTCVideoCapturer = { - return RTCCameraVideoCapturer(delegate: webRTCWrapper.localVideoSource) + return RTCCameraVideoCapturer(delegate: webRTCSession.localVideoSource) }() // MARK: Mode @@ -28,9 +28,9 @@ final class CallVC : UIViewController, WebRTCWrapperDelegate { init(for sessionID: String, mode: Mode) { self.sessionID = sessionID self.mode = mode - self.webRTCWrapper = WebRTCWrapper.current ?? WebRTCWrapper(for: sessionID) + self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID) super.init(nibName: nil, bundle: nil) - self.webRTCWrapper.delegate = self + self.webRTCSession.delegate = self } required init(coder: NSCoder) { preconditionFailure("Use init(for:) instead.") } @@ -38,16 +38,16 @@ final class CallVC : UIViewController, WebRTCWrapperDelegate { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .black - WebRTCWrapper.current = webRTCWrapper + WebRTCSession.current = webRTCSession setUpViewHierarchy() cameraManager.prepare() touch(videoCapturer) if case .offer = mode { Storage.write { transaction in - self.webRTCWrapper.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() + self.webRTCSession.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 + webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally } } @@ -55,14 +55,14 @@ final class CallVC : UIViewController, WebRTCWrapperDelegate { // Remote video view let remoteVideoView = RTCMTLVideoView() remoteVideoView.contentMode = .scaleAspectFill - webRTCWrapper.attachRemoteRenderer(remoteVideoView) + webRTCSession.attachRemoteRenderer(remoteVideoView) view.addSubview(remoteVideoView) remoteVideoView.translatesAutoresizingMaskIntoConstraints = false remoteVideoView.pin(to: view) // Local video view let localVideoView = RTCMTLVideoView() localVideoView.contentMode = .scaleAspectFill - webRTCWrapper.attachLocalRenderer(localVideoView) + webRTCSession.attachLocalRenderer(localVideoView) localVideoView.set(.width, to: 80) localVideoView.set(.height, to: 173) view.addSubview(localVideoView) @@ -82,6 +82,6 @@ final class CallVC : UIViewController, WebRTCWrapperDelegate { } deinit { - WebRTCWrapper.current = nil + WebRTCSession.current = nil } } diff --git a/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift b/SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift similarity index 97% rename from SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift rename to SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift index 57069ab42..ec860bb65 100644 --- a/SessionMessagingKit/Calls/WebRTCWrapper+MessageHandling.swift +++ b/SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift @@ -1,6 +1,6 @@ import WebRTC -extension WebRTCWrapper { +extension WebRTCSession { public func handleICECandidates(_ candidate: [RTCIceCandidate]) { print("[Calls] Received ICE candidate message.") diff --git a/SessionMessagingKit/Calls/WebRTCWrapper+UI.swift b/SessionMessagingKit/Calls/WebRTCSession+UI.swift similarity index 94% rename from SessionMessagingKit/Calls/WebRTCWrapper+UI.swift rename to SessionMessagingKit/Calls/WebRTCSession+UI.swift index 81255a0a2..7ed0a54e8 100644 --- a/SessionMessagingKit/Calls/WebRTCWrapper+UI.swift +++ b/SessionMessagingKit/Calls/WebRTCSession+UI.swift @@ -1,6 +1,6 @@ import WebRTC -extension WebRTCWrapper { +extension WebRTCSession { public func attachLocalRenderer(_ renderer: RTCVideoRenderer) { localVideoTrack.add(renderer) diff --git a/SessionMessagingKit/Calls/WebRTCWrapper.swift b/SessionMessagingKit/Calls/WebRTCSession.swift similarity index 97% rename from SessionMessagingKit/Calls/WebRTCWrapper.swift rename to SessionMessagingKit/Calls/WebRTCSession.swift index f0d55af3c..956c4f4ab 100644 --- a/SessionMessagingKit/Calls/WebRTCWrapper.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -1,13 +1,13 @@ import PromiseKit import WebRTC -public protocol WebRTCWrapperDelegate : AnyObject { +public protocol WebRTCSessionDelegate : AnyObject { var videoCapturer: RTCVideoCapturer { get } } /// See https://webrtc.org/getting-started/overview for more information. -public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { - public weak var delegate: WebRTCWrapperDelegate? +public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { + public weak var delegate: WebRTCSessionDelegate? private let contactSessionID: String private var queuedICECandidates: [RTCIceCandidate] = [] private var iceCandidateSendTimer: Timer? @@ -81,7 +81,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { } // MARK: Initialization - public static var current: WebRTCWrapper? + public static var current: WebRTCSession? public init(for contactSessionID: String) { self.contactSessionID = contactSessionID diff --git a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift index c5fcdc62e..c1d4865a1 100644 --- a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift @@ -1,7 +1,5 @@ import WebRTC -// NOTE: Multiple ICE candidates may be batched together for performance - /// See https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription for more information. @objc(SNCallMessage) public final class CallMessage : ControlMessage { @@ -9,6 +7,8 @@ public final class CallMessage : ControlMessage { /// See https://developer.mozilla.org/en-US/docs/Glossary/SDP for more information. public var sdps: [String]? + // NOTE: Multiple ICE candidates may be batched together for performance + // MARK: Kind public enum Kind : Codable, CustomStringConvertible { case offer diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 9e0919e43..403dc2d2c 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -256,12 +256,12 @@ extension MessageReceiver { // MARK: - Call Messages public static func handleCallMessage(_ message: CallMessage, using transaction: Any) { - let webRTCWrapper: WebRTCWrapper - if let current = WebRTCWrapper.current { - webRTCWrapper = current + let webRTCSession: WebRTCSession + if let current = WebRTCSession.current { + webRTCSession = current } else { - WebRTCWrapper.current = WebRTCWrapper(for: message.sender!) - webRTCWrapper = WebRTCWrapper.current! + WebRTCSession.current = WebRTCSession(for: message.sender!) + webRTCSession = WebRTCSession.current! } switch message.kind! { case .offer: @@ -270,7 +270,7 @@ extension MessageReceiver { case .answer: print("[Calls] Received answer message.") let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) - webRTCWrapper.handleRemoteSDP(sdp, from: message.sender!) + webRTCSession.handleRemoteSDP(sdp, from: message.sender!) case .provisionalAnswer: break // TODO: Implement case let .iceCandidates(sdpMLineIndexes, sdpMids): var candidates: [RTCIceCandidate] = [] @@ -282,7 +282,7 @@ extension MessageReceiver { let candidate = RTCIceCandidate(sdp: sdp, sdpMLineIndex: Int32(sdpMLineIndex), sdpMid: sdpMid) candidates.append(candidate) } - webRTCWrapper.handleICECandidates(candidates) + webRTCSession.handleICECandidates(candidates) } } From 34e630b5bf528214a33329c551e0f7b881fffdab Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 18 Aug 2021 09:06:06 +1000 Subject: [PATCH 031/368] Add documentation --- .../MessageReceiver+Handling.swift | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 403dc2d2c..0f856f255 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -256,21 +256,27 @@ extension MessageReceiver { // MARK: - Call Messages public static func handleCallMessage(_ message: CallMessage, using transaction: Any) { - let webRTCSession: WebRTCSession - if let current = WebRTCSession.current { - webRTCSession = current - } else { - WebRTCSession.current = WebRTCSession(for: message.sender!) - webRTCSession = WebRTCSession.current! + func getWebRTCSession() -> WebRTCSession { + let result: WebRTCSession + if let current = WebRTCSession.current { + result = current + } else { + WebRTCSession.current = WebRTCSession(for: message.sender!) + result = WebRTCSession.current! + } + return result } switch message.kind! { case .offer: print("[Calls] Received offer message.") + // Delegate to the main app, which is expected to show a dialog confirming + // that the user wants to pick up the call. When they do, the SDP contained + // in the offer message will be passed to WebRTCSession.handleRemoteSDP(_:from:). handleOfferCallMessage?(message) case .answer: print("[Calls] Received answer message.") let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) - webRTCSession.handleRemoteSDP(sdp, from: message.sender!) + getWebRTCSession().handleRemoteSDP(sdp, from: message.sender!) case .provisionalAnswer: break // TODO: Implement case let .iceCandidates(sdpMLineIndexes, sdpMids): var candidates: [RTCIceCandidate] = [] @@ -282,7 +288,7 @@ extension MessageReceiver { let candidate = RTCIceCandidate(sdp: sdp, sdpMLineIndex: Int32(sdpMLineIndex), sdpMid: sdpMid) candidates.append(candidate) } - webRTCSession.handleICECandidates(candidates) + getWebRTCSession().handleICECandidates(candidates) } } From c1a61e897bf2b92bec226b87d7d8462feb4e8cf9 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 18 Aug 2021 09:56:28 +1000 Subject: [PATCH 032/368] UI improvements --- Session/Calls/CallVC.swift | 37 +++++++++++++++++++ .../ConversationVC+Interaction.swift | 4 +- Session/Meta/AppDelegate.swift | 11 +++--- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index e6cf13de2..7b840b0c7 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -18,6 +18,29 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { return RTCCameraVideoCapturer(delegate: webRTCSession.localVideoSource) }() + // MARK: UI Components + private lazy var fadeView: UIView = { + let result = UIView() + let height: CGFloat = 64 + var frame = UIScreen.main.bounds + frame.size.height = height + let layer = CAGradientLayer() + layer.frame = frame + layer.colors = [ UIColor(hex: 0x000000).withAlphaComponent(0.4).cgColor, UIColor(hex: 0x000000).withAlphaComponent(0).cgColor ] + result.layer.insertSublayer(layer, at: 0) + result.set(.height, to: height) + return result + }() + + private lazy var closeButton: UIButton = { + let result = UIButton(type: .custom) + let image = UIImage(named: "X")!.withTint(.white) + result.setImage(image, for: UIControl.State.normal) + result.set(.width, to: 60) + result.set(.height, to: 60) + return result + }() + // MARK: Mode enum Mode { case offer @@ -69,6 +92,15 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { localVideoView.pin(.right, to: .right, of: view, withInset: -Values.largeSpacing) let bottomMargin = UIApplication.shared.keyWindow!.safeAreaInsets.bottom + Values.largeSpacing localVideoView.pin(.bottom, to: .bottom, of: view, withInset: -bottomMargin) + // Fade view + view.addSubview(fadeView) + fadeView.translatesAutoresizingMaskIntoConstraints = false + fadeView.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view) + // Close button + view.addSubview(closeButton) + closeButton.translatesAutoresizingMaskIntoConstraints = false + closeButton.pin(.left, to: .left, of: view) + closeButton.pin(.top, to: .top, of: view, withInset: 32) } override func viewDidAppear(_ animated: Bool) { @@ -84,4 +116,9 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { deinit { WebRTCSession.current = nil } + + // MARK: Interaction + @objc private func close() { + presentingViewController?.dismiss(animated: true, completion: nil) + } } diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index ad49ad20c..c8f59bb0d 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -29,7 +29,9 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc @objc func startCall() { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } let callVC = CallVC(for: contactSessionID, mode: .offer) - navigationController!.pushViewController(callVC, animated: true, completion: nil) + callVC.modalPresentationStyle = .overFullScreen + callVC.modalTransitionStyle = .crossDissolve + present(callVC, animated: true, completion: nil) } // MARK: Blocking diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index bd98133dc..8880a26e0 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -13,6 +13,8 @@ extension AppDelegate { alert.addAction(UIAlertAction(title: "Accept", style: .default, handler: { _ in let callVC = CallVC(for: message.sender!, mode: .answer(sdp: sdp)) presentingVC.dismiss(animated: true) { + callVC.modalPresentationStyle = .overFullScreen + callVC.modalTransitionStyle = .crossDissolve presentingVC.present(callVC, animated: true, completion: nil) } })) @@ -65,17 +67,16 @@ extension AppDelegate { @objc func getAppModeOrSystemDefault() -> AppMode { let userDefaults = UserDefaults.standard - - guard userDefaults.dictionaryRepresentation().keys.contains("appMode") else { + if userDefaults.dictionaryRepresentation().keys.contains("appMode") { + let mode = userDefaults.integer(forKey: "appMode") + return AppMode(rawValue: mode) ?? .light + } else { if #available(iOS 13.0, *) { return UITraitCollection.current.userInterfaceStyle == .dark ? .dark : .light } else { return .light } } - - let mode = userDefaults.integer(forKey: "appMode") - return AppMode(rawValue: mode) ?? .light } } From ae3cce97dfd96f5fac0ff6c0282c9691cc4170a2 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 18 Aug 2021 10:03:10 +1000 Subject: [PATCH 033/368] UI improvements --- Session/Calls/CallVC.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 7b840b0c7..00f7bec44 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -38,6 +38,15 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) result.set(.height, to: 60) + result.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var titleView: UILabel = { + let result = UILabel() + result.textColor = .white + result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) + result.textAlignment = .center return result }() @@ -65,6 +74,11 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { setUpViewHierarchy() cameraManager.prepare() touch(videoCapturer) + var contact: Contact? + Storage.read { transaction in + contact = Storage.shared.getContact(with: self.sessionID) + } + titleView.text = contact?.displayName(for: Contact.Context.regular) ?? sessionID if case .offer = mode { Storage.write { transaction in self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() @@ -101,6 +115,11 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { closeButton.translatesAutoresizingMaskIntoConstraints = false closeButton.pin(.left, to: .left, of: view) closeButton.pin(.top, to: .top, of: view, withInset: 32) + // Title view + view.addSubview(titleView) + titleView.translatesAutoresizingMaskIntoConstraints = false + titleView.center(.vertical, in: closeButton) + titleView.center(.horizontal, in: view) } override func viewDidAppear(_ animated: Bool) { From 6fdf544368d5a164b0b0356a6e1f413449efa479 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 18 Aug 2021 10:33:33 +1000 Subject: [PATCH 034/368] Implement end call message --- Session/Calls/CallVC.swift | 40 +++++++++++++++---- Session/Meta/AppDelegate.swift | 8 ++++ SessionMessagingKit/Calls/WebRTCSession.swift | 9 +++++ .../Control Messages/CallMessage.swift | 6 +++ .../Protos/Generated/SNProto.swift | 3 ++ .../Protos/Generated/SessionProtos.pb.swift | 4 ++ .../Protos/SessionProtos.proto | 1 + .../MessageReceiver+Handling.swift | 1 + .../Sending & Receiving/MessageReceiver.swift | 1 + 9 files changed, 65 insertions(+), 8 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 00f7bec44..197aef925 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -42,7 +42,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { return result }() - private lazy var titleView: UILabel = { + private lazy var titleLabel: UILabel = { let result = UILabel() result.textColor = .white result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) @@ -50,6 +50,16 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { return result }() + private lazy var callEndedLabel: UILabel = { + let result = UILabel() + result.textColor = .white + result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) + result.textAlignment = .center + result.text = "Call Ended" + result.alpha = 0 + return result + }() + // MARK: Mode enum Mode { case offer @@ -78,7 +88,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { Storage.read { transaction in contact = Storage.shared.getContact(with: self.sessionID) } - titleView.text = contact?.displayName(for: Contact.Context.regular) ?? sessionID + titleLabel.text = contact?.displayName(for: Contact.Context.regular) ?? sessionID if case .offer = mode { Storage.write { transaction in self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() @@ -116,10 +126,14 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { closeButton.pin(.left, to: .left, of: view) closeButton.pin(.top, to: .top, of: view, withInset: 32) // Title view - view.addSubview(titleView) - titleView.translatesAutoresizingMaskIntoConstraints = false - titleView.center(.vertical, in: closeButton) - titleView.center(.horizontal, in: view) + view.addSubview(titleLabel) + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.center(.vertical, in: closeButton) + titleLabel.center(.horizontal, in: view) + // Call ended label + view.addSubview(callEndedLabel) + callEndedLabel.translatesAutoresizingMaskIntoConstraints = false + callEndedLabel.center(in: view) } override func viewDidAppear(_ animated: Bool) { @@ -132,12 +146,22 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { cameraManager.stop() } - deinit { + // MARK: Interaction + func handleEndCallMessage(_ message: CallMessage) { + WebRTCSession.current?.dropConnection() WebRTCSession.current = nil + UIView.animate(withDuration: 0.25) { + self.callEndedLabel.alpha = 1 + } + Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in + self.presentingViewController?.dismiss(animated: true, completion: nil) + } } - // MARK: Interaction @objc private func close() { + Storage.write { transaction in + WebRTCSession.current?.endCall(with: self.sessionID, using: transaction) + } presentingViewController?.dismiss(animated: true, completion: nil) } } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 8880a26e0..63303881c 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -5,6 +5,7 @@ extension AppDelegate { @objc func setUpCallHandling() { + // Offer messages MessageReceiver.handleOfferCallMessage = { message in DispatchQueue.main.async { let sdp = RTCSessionDescription(type: .offer, sdp: message.sdps![0]) @@ -24,6 +25,13 @@ extension AppDelegate { presentingVC.present(alert, animated: true, completion: nil) } } + // End call messages + MessageReceiver.handleEndCallMessage = { message in + DispatchQueue.main.async { + guard let callVC = CurrentAppContext().frontmostViewController() as? CallVC else { return } + callVC.handleEndCallMessage(message) + } + } } @objc(syncConfigurationIfNeeded) diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 956c4f4ab..401991e96 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -190,6 +190,15 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } } + public func endCall(with sessionID: String, using transaction: YapDatabaseReadWriteTransaction) { + guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return } + let message = CallMessage() + message.kind = .endCall + MessageSender.sendNonDurably(message, in: thread, using: transaction).retainUntilComplete() + dropConnection() + WebRTCSession.current = nil + } + public func dropConnection() { peerConnection.close() } diff --git a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift index c1d4865a1..ac566c14c 100644 --- a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift @@ -15,6 +15,7 @@ public final class CallMessage : ControlMessage { case answer case provisionalAnswer case iceCandidates(sdpMLineIndexes: [UInt32], sdpMids: [String]) + case endCall public var description: String { switch self { @@ -22,6 +23,7 @@ public final class CallMessage : ControlMessage { case .answer: return "answer" case .provisionalAnswer: return "provisionalAnswer" case .iceCandidates(_, _): return "iceCandidates" + case .endCall: return "endCall" } } } @@ -54,6 +56,7 @@ public final class CallMessage : ControlMessage { guard let sdpMLineIndexes = coder.decodeObject(forKey: "sdpMLineIndexes") as? [UInt32], let sdpMids = coder.decodeObject(forKey: "sdpMids") as? [String] else { return nil } kind = .iceCandidates(sdpMLineIndexes: sdpMLineIndexes, sdpMids: sdpMids) + case "endCall": kind = .endCall default: preconditionFailure() } if let sdps = coder.decodeObject(forKey: "sdps") as! [String]? { self.sdps = sdps } @@ -69,6 +72,7 @@ public final class CallMessage : ControlMessage { coder.encode("iceCandidates", forKey: "kind") coder.encode(sdpMLineIndexes, forKey: "sdpMLineIndexes") coder.encode(sdpMids, forKey: "sdpMids") + case .endCall: coder.encode("endCall", forKey: "kind") default: preconditionFailure() } coder.encode(sdps, forKey: "sdps") @@ -86,6 +90,7 @@ public final class CallMessage : ControlMessage { let sdpMLineIndexes = callMessageProto.sdpMlineIndexes let sdpMids = callMessageProto.sdpMids kind = .iceCandidates(sdpMLineIndexes: sdpMLineIndexes, sdpMids: sdpMids) + case .endCall: kind = .endCall } let sdps = callMessageProto.sdps return CallMessage(kind: kind, sdps: sdps) @@ -102,6 +107,7 @@ public final class CallMessage : ControlMessage { case .answer: type = .answer case .provisionalAnswer: type = .provisionalAnswer case .iceCandidates(_, _): type = .iceCandidates + case .endCall: type = .endCall } let callMessageProto = SNProtoCallMessage.builder(type: type) callMessageProto.setSdps(sdps) diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index 134c63017..dae62350a 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -657,6 +657,7 @@ extension SNProtoContent.SNProtoContentBuilder { case answer = 2 case provisionalAnswer = 3 case iceCandidates = 4 + case endCall = 5 } private class func SNProtoCallMessageTypeWrap(_ value: SessionProtos_CallMessage.TypeEnum) -> SNProtoCallMessageType { @@ -665,6 +666,7 @@ extension SNProtoContent.SNProtoContentBuilder { case .answer: return .answer case .provisionalAnswer: return .provisionalAnswer case .iceCandidates: return .iceCandidates + case .endCall: return .endCall } } @@ -674,6 +676,7 @@ extension SNProtoContent.SNProtoContentBuilder { case .answer: return .answer case .provisionalAnswer: return .provisionalAnswer case .iceCandidates: return .iceCandidates + case .endCall: return .endCall } } diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index bf534a845..7b4894f60 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -333,6 +333,7 @@ struct SessionProtos_CallMessage { case answer // = 2 case provisionalAnswer // = 3 case iceCandidates // = 4 + case endCall // = 5 init() { self = .offer @@ -344,6 +345,7 @@ struct SessionProtos_CallMessage { case 2: self = .answer case 3: self = .provisionalAnswer case 4: self = .iceCandidates + case 5: self = .endCall default: return nil } } @@ -354,6 +356,7 @@ struct SessionProtos_CallMessage { case .answer: return 2 case .provisionalAnswer: return 3 case .iceCandidates: return 4 + case .endCall: return 5 } } @@ -1849,6 +1852,7 @@ extension SessionProtos_CallMessage.TypeEnum: SwiftProtobuf._ProtoNameProviding 2: .same(proto: "ANSWER"), 3: .same(proto: "PROVISIONAL_ANSWER"), 4: .same(proto: "ICE_CANDIDATES"), + 5: .same(proto: "END_CALL"), ] } diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index a2927a698..4193d202c 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -58,6 +58,7 @@ message CallMessage { ANSWER = 2; PROVISIONAL_ANSWER = 3; ICE_CANDIDATES = 4; + END_CALL = 5; } // Multiple ICE candidates may be batched together for performance diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 0f856f255..8cd74e633 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -289,6 +289,7 @@ extension MessageReceiver { candidates.append(candidate) } getWebRTCSession().handleICECandidates(candidates) + case .endCall: handleEndCallMessage?(message) } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 78ba53208..26a809089 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -3,6 +3,7 @@ import SessionUtilitiesKit public enum MessageReceiver { private static var lastEncryptionKeyPairRequest: [String:Date] = [:] public static var handleOfferCallMessage: ((CallMessage) -> Void)? + public static var handleEndCallMessage: ((CallMessage) -> Void)? public enum Error : LocalizedError { case duplicateMessage From b3ead76221e00daa51154f9aa1f38e3aedc74930 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 18 Aug 2021 10:45:55 +1000 Subject: [PATCH 035/368] Debug --- Session/Calls/CallVC.swift | 10 ++++++++-- SessionMessagingKit/Calls/WebRTCSession.swift | 1 + .../Messages/Control Messages/CallMessage.swift | 7 ++++--- .../Sending & Receiving/MessageReceiver+Handling.swift | 4 +++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 197aef925..696e6bcc8 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -19,6 +19,12 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { }() // MARK: UI Components + private lazy var remoteVideoView: RTCMTLVideoView = { + let result = RTCMTLVideoView() + result.contentMode = .scaleAspectFill + return result + }() + private lazy var fadeView: UIView = { let result = UIView() let height: CGFloat = 64 @@ -100,8 +106,6 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { func setUpViewHierarchy() { // Remote video view - let remoteVideoView = RTCMTLVideoView() - remoteVideoView.contentMode = .scaleAspectFill webRTCSession.attachRemoteRenderer(remoteVideoView) view.addSubview(remoteVideoView) remoteVideoView.translatesAutoresizingMaskIntoConstraints = false @@ -148,9 +152,11 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { // MARK: Interaction func handleEndCallMessage(_ message: CallMessage) { + print("[Calls] Ending call.") WebRTCSession.current?.dropConnection() WebRTCSession.current = nil UIView.animate(withDuration: 0.25) { + self.remoteVideoView.alpha = 0 self.callEndedLabel.alpha = 1 } Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 401991e96..71893f67b 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -194,6 +194,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return } let message = CallMessage() message.kind = .endCall + print("[Calls] Sending end call message.") MessageSender.sendNonDurably(message, in: thread, using: transaction).retainUntilComplete() dropConnection() WebRTCSession.current = nil diff --git a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift index ac566c14c..eba440598 100644 --- a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift @@ -40,7 +40,6 @@ public final class CallMessage : ControlMessage { // MARK: Validation public override var isValid: Bool { guard super.isValid else { return false } - guard let sdps = sdps, !sdps.isEmpty else { return false } return kind != nil } @@ -97,7 +96,7 @@ public final class CallMessage : ControlMessage { } public override func toProto(using transaction: YapDatabaseReadWriteTransaction) -> SNProtoContent? { - guard let kind = kind, let sdps = sdps, !sdps.isEmpty else { + guard let kind = kind else { SNLog("Couldn't construct call message proto from: \(self).") return nil } @@ -110,7 +109,9 @@ public final class CallMessage : ControlMessage { case .endCall: type = .endCall } let callMessageProto = SNProtoCallMessage.builder(type: type) - callMessageProto.setSdps(sdps) + if let sdps = sdps, !sdps.isEmpty { + callMessageProto.setSdps(sdps) + } if case let .iceCandidates(sdpMLineIndexes, sdpMids) = kind { callMessageProto.setSdpMlineIndexes(sdpMLineIndexes) callMessageProto.setSdpMids(sdpMids) diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 8cd74e633..8dcdbdd7e 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -289,7 +289,9 @@ extension MessageReceiver { candidates.append(candidate) } getWebRTCSession().handleICECandidates(candidates) - case .endCall: handleEndCallMessage?(message) + case .endCall: + print("[Calls] Received end call message.") + handleEndCallMessage?(message) } } From 8280748b39ff898481b189dd52da92637e00b297 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 18 Aug 2021 11:18:11 +1000 Subject: [PATCH 036/368] Improve logging --- Session.xcodeproj/project.pbxproj | 4 ++ .../Calls/WebRTC+Utilities.swift | 45 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 SessionMessagingKit/Calls/WebRTC+Utilities.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index f20ced31b..c8ecc7109 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -252,6 +252,7 @@ B8B558FF26C4E05E00693325 /* WebRTCSession+MessageHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FE26C4E05E00693325 /* WebRTCSession+MessageHandling.swift */; }; B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A4238F627000BA5194 /* HomeVC.swift */; }; B8BC00C0257D90E30032E807 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BC00BF257D90E30032E807 /* General.swift */; }; + B8BF43BA26CC95FB007828D1 /* WebRTC+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BF43B926CC95FB007828D1 /* WebRTC+Utilities.swift */; }; B8C2B2C82563685C00551B4D /* CircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C2B2C72563685C00551B4D /* CircleView.swift */; }; B8C2B332256376F000551B4D /* ThreadUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B8C2B331256376F000551B4D /* ThreadUtil.m */; }; B8C2B3442563782400551B4D /* ThreadUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = B8C2B33B2563770800551B4D /* ThreadUtil.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1235,6 +1236,7 @@ B8BB82B82394911B00BA5194 /* Separator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Separator.swift; sourceTree = ""; }; B8BB82BD2394D4CE00BA5194 /* Fonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fonts.swift; sourceTree = ""; }; B8BC00BF257D90E30032E807 /* General.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = General.swift; sourceTree = ""; }; + B8BF43B926CC95FB007828D1 /* WebRTC+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTC+Utilities.swift"; sourceTree = ""; }; B8C2B2C72563685C00551B4D /* CircleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleView.swift; sourceTree = ""; }; B8C2B331256376F000551B4D /* ThreadUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ThreadUtil.m; sourceTree = ""; }; B8C2B33B2563770800551B4D /* ThreadUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ThreadUtil.h; sourceTree = ""; }; @@ -2360,6 +2362,7 @@ B8DE1FB326C22F2F0079C9CE /* WebRTCSession.swift */, B806ECA026C4A7E4008BDA44 /* WebRTCSession+UI.swift */, B8B558FE26C4E05E00693325 /* WebRTCSession+MessageHandling.swift */, + B8BF43B926CC95FB007828D1 /* WebRTC+Utilities.swift */, ); path = Calls; sourceTree = ""; @@ -4684,6 +4687,7 @@ C352A31325574F5200338F3E /* MessageReceiveJob.swift in Sources */, C32C5BDD256DC88D003C73A2 /* OWSReadReceiptManager.m in Sources */, C3D9E3BF25676AD70040E4F3 /* TSAttachmentStream.m in Sources */, + B8BF43BA26CC95FB007828D1 /* WebRTC+Utilities.swift in Sources */, C3C2A7562553A3AB00C340D1 /* VisibleMessage+Quote.swift in Sources */, C3227FF6260AAD66006EA627 /* OpenGroupMessageV2.swift in Sources */, B8B32021258B1A650020074B /* Contact.swift in Sources */, diff --git a/SessionMessagingKit/Calls/WebRTC+Utilities.swift b/SessionMessagingKit/Calls/WebRTC+Utilities.swift new file mode 100644 index 000000000..2bdd847dd --- /dev/null +++ b/SessionMessagingKit/Calls/WebRTC+Utilities.swift @@ -0,0 +1,45 @@ +import WebRTC + +extension RTCSignalingState : CustomStringConvertible { + + public var description: String { + switch self { + case .stable: return "stable" + case .haveLocalOffer: return "haveLocalOffer" + case .haveLocalPrAnswer: return "haveLocalPrAnswer" + case .haveRemoteOffer: return "haveRemoteOffer" + case .haveRemotePrAnswer: return "haveRemotePrAnswer" + case .closed: return "closed" + default: preconditionFailure() + } + } +} + +extension RTCIceConnectionState : CustomStringConvertible { + + public var description: String { + switch self { + case .new: return "new" + case .checking: return "checking" + case .connected: return "connected" + case .completed: return "completed" + case .failed: return "failed" + case .disconnected: return "disconnected" + case .closed: return "closed" + case .count: return "count" + default: preconditionFailure() + } + } +} + +extension RTCIceGatheringState : CustomStringConvertible { + + public var description: String { + switch self { + case .new: return "new" + case .gathering: return "gathering" + case .complete: return "complete" + default: preconditionFailure() + } + } +} From baf9e4e9d4e231025e8b564d9b6ecd2b8d444f17 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 18 Aug 2021 13:07:15 +1000 Subject: [PATCH 037/368] Add ringing UI --- Session/Calls/CallVC.swift | 24 ++++++++++++------- Session/Meta/AppDelegate.swift | 7 ++++++ .../MessageReceiver+Handling.swift | 1 + .../Sending & Receiving/MessageReceiver.swift | 1 + 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 696e6bcc8..e967182a4 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -56,12 +56,11 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { return result }() - private lazy var callEndedLabel: UILabel = { + private lazy var callInfoLabel: UILabel = { let result = UILabel() result.textColor = .white result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) result.textAlignment = .center - result.text = "Call Ended" result.alpha = 0 return result }() @@ -96,6 +95,8 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } titleLabel.text = contact?.displayName(for: Contact.Context.regular) ?? sessionID if case .offer = mode { + callInfoLabel.alpha = 1 + callInfoLabel.text = "Ringing..." Storage.write { transaction in self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() } @@ -129,15 +130,15 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { closeButton.translatesAutoresizingMaskIntoConstraints = false closeButton.pin(.left, to: .left, of: view) closeButton.pin(.top, to: .top, of: view, withInset: 32) - // Title view + // Title label view.addSubview(titleLabel) titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.center(.vertical, in: closeButton) titleLabel.center(.horizontal, in: view) - // Call ended label - view.addSubview(callEndedLabel) - callEndedLabel.translatesAutoresizingMaskIntoConstraints = false - callEndedLabel.center(in: view) + // Call info label + view.addSubview(callInfoLabel) + callInfoLabel.translatesAutoresizingMaskIntoConstraints = false + callInfoLabel.center(in: view) } override func viewDidAppear(_ animated: Bool) { @@ -151,13 +152,20 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } // MARK: Interaction + func handleAnswerMessage(_ message: CallMessage) { + UIView.animate(withDuration: 0.25) { + self.callInfoLabel.alpha = 0 + } + } + func handleEndCallMessage(_ message: CallMessage) { print("[Calls] Ending call.") + callInfoLabel.text = "Call Ended" WebRTCSession.current?.dropConnection() WebRTCSession.current = nil UIView.animate(withDuration: 0.25) { self.remoteVideoView.alpha = 0 - self.callEndedLabel.alpha = 1 + self.callInfoLabel.alpha = 1 } Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in self.presentingViewController?.dismiss(animated: true, completion: nil) diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 63303881c..48fc38f4a 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -25,6 +25,13 @@ extension AppDelegate { presentingVC.present(alert, animated: true, completion: nil) } } + // Answer messages + MessageReceiver.handleAnswerCallMessage = { message in + DispatchQueue.main.async { + guard let callVC = CurrentAppContext().frontmostViewController() as? CallVC else { return } + callVC.handleAnswerMessage(message) + } + } // End call messages MessageReceiver.handleEndCallMessage = { message in DispatchQueue.main.async { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 8dcdbdd7e..6e3eb1c4c 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -277,6 +277,7 @@ extension MessageReceiver { print("[Calls] Received answer message.") let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) getWebRTCSession().handleRemoteSDP(sdp, from: message.sender!) + handleAnswerCallMessage?(message) case .provisionalAnswer: break // TODO: Implement case let .iceCandidates(sdpMLineIndexes, sdpMids): var candidates: [RTCIceCandidate] = [] diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 26a809089..b2cc26bc4 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -3,6 +3,7 @@ import SessionUtilitiesKit public enum MessageReceiver { private static var lastEncryptionKeyPairRequest: [String:Date] = [:] public static var handleOfferCallMessage: ((CallMessage) -> Void)? + public static var handleAnswerCallMessage: ((CallMessage) -> Void)? public static var handleEndCallMessage: ((CallMessage) -> Void)? public enum Error : LocalizedError { From 027f9b2a87eb625c456d2938f1bbf76d6ddd303d Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 18 Aug 2021 13:49:17 +1000 Subject: [PATCH 038/368] Install pods --- Session.xcodeproj/project.pbxproj | 48 ------------------------------- 1 file changed, 48 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index f95fc1850..f1fc3371c 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -4234,54 +4234,6 @@ buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Session/Pods-Session-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework", - "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", - "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", - "${PODS_ROOT}/GoogleWebRTC/Frameworks/frameworks/WebRTC.framework", - "${BUILT_PRODUCTS_DIR}/Mantle/Mantle.framework", - "${BUILT_PRODUCTS_DIR}/NVActivityIndicatorView/NVActivityIndicatorView.framework", - "${BUILT_PRODUCTS_DIR}/PromiseKit/PromiseKit.framework", - "${BUILT_PRODUCTS_DIR}/PureLayout/PureLayout.framework", - "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework", - "${BUILT_PRODUCTS_DIR}/SQLCipher/SQLCipher.framework", - "${BUILT_PRODUCTS_DIR}/SocketRocket/SocketRocket.framework", - "${BUILT_PRODUCTS_DIR}/Sodium/Sodium.framework", - "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework", - "${BUILT_PRODUCTS_DIR}/YapDatabase/YapDatabase.framework", - "${BUILT_PRODUCTS_DIR}/ZXingObjC/ZXingObjC.framework", - "${BUILT_PRODUCTS_DIR}/Curve25519Kit/Curve25519Kit.framework", - "${PODS_ROOT}/GRKOpenSSLFramework/OpenSSL-iOS/bin/openssl.framework", - "${BUILT_PRODUCTS_DIR}/SignalCoreKit/SignalCoreKit.framework", - "${BUILT_PRODUCTS_DIR}/HKDFKit/HKDFKit.framework", - "${BUILT_PRODUCTS_DIR}/SAMKeychain/SAMKeychain.framework", - "${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CryptoSwift.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Mantle.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NVActivityIndicatorView.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PureLayout.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SQLCipher.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SocketRocket.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sodium.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYImage.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YapDatabase.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ZXingObjC.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Curve25519Kit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SignalCoreKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/HKDFKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SAMKeychain.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework", - ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Session/Pods-Session-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); From c1b95dc54f5b3ff92ea9a62a63f70de4c28978ea Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 18 Aug 2021 14:16:49 +1000 Subject: [PATCH 039/368] Fix freezing issue --- Session/Calls/CameraManager.swift | 4 +++- SessionMessagingKit/Calls/WebRTCSession.swift | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Session/Calls/CameraManager.swift b/Session/Calls/CameraManager.swift index f1e9b0616..069841268 100644 --- a/Session/Calls/CameraManager.swift +++ b/Session/Calls/CameraManager.swift @@ -62,5 +62,7 @@ extension CameraManager : AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptur delegate?.handleVideoOutputCaptured(sampleBuffer: sampleBuffer) } - func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { } + func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { + print("[Calls] Frame dropped.") + } } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 71893f67b..b04808a46 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -40,7 +40,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { internal lazy var mediaConstraints: RTCMediaConstraints = { let mandatory: [String:String] = [ kRTCMediaConstraintsOfferToReceiveAudio : kRTCMediaConstraintsValueTrue, - kRTCMediaConstraintsOfferToReceiveVideo : kRTCMediaConstraintsValueTrue + kRTCMediaConstraintsOfferToReceiveVideo : kRTCMediaConstraintsValueTrue, ] let optional: [String:String] = [:] return RTCMediaConstraints(mandatoryConstraints: mandatory, optionalConstraints: optional) @@ -58,7 +58,9 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { // Video public lazy var localVideoSource: RTCVideoSource = { - return factory.videoSource() + let result = factory.videoSource() + result.adaptOutputFormat(toWidth: 360, height: 780, fps: 30) + return result }() internal lazy var localVideoTrack: RTCVideoTrack = { From 3584a0e357055d59766b1764a03de360e252cc20 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 19 Aug 2021 13:34:33 +1000 Subject: [PATCH 040/368] Fix call message TTL --- SessionMessagingKit/Messages/Control Messages/CallMessage.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift index eba440598..19455a24f 100644 --- a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift @@ -7,6 +7,8 @@ public final class CallMessage : ControlMessage { /// See https://developer.mozilla.org/en-US/docs/Glossary/SDP for more information. public var sdps: [String]? + public override var ttl: UInt64 { 2 * 60 * 1000 } + // NOTE: Multiple ICE candidates may be batched together for performance // MARK: Kind From b9186f3aca89d32debdca88016449958ac8542a7 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 19 Aug 2021 13:42:46 +1000 Subject: [PATCH 041/368] Show connecting state on call screen --- Session/Calls/CallVC.swift | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index e967182a4..9fb2d20f1 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -61,7 +61,6 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { result.textColor = .white result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) result.textAlignment = .center - result.alpha = 0 return result }() @@ -95,17 +94,21 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } titleLabel.text = contact?.displayName(for: Contact.Context.regular) ?? sessionID if case .offer = mode { - callInfoLabel.alpha = 1 callInfoLabel.text = "Ringing..." Storage.write { transaction in self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() } } else if case let .answer(sdp) = mode { + callInfoLabel.text = "Connecting..." webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally } } func setUpViewHierarchy() { + // Call info label + view.addSubview(callInfoLabel) + callInfoLabel.translatesAutoresizingMaskIntoConstraints = false + callInfoLabel.center(in: view) // Remote video view webRTCSession.attachRemoteRenderer(remoteVideoView) view.addSubview(remoteVideoView) @@ -135,10 +138,6 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.center(.vertical, in: closeButton) titleLabel.center(.horizontal, in: view) - // Call info label - view.addSubview(callInfoLabel) - callInfoLabel.translatesAutoresizingMaskIntoConstraints = false - callInfoLabel.center(in: view) } override func viewDidAppear(_ animated: Bool) { @@ -153,9 +152,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { // MARK: Interaction func handleAnswerMessage(_ message: CallMessage) { - UIView.animate(withDuration: 0.25) { - self.callInfoLabel.alpha = 0 - } + callInfoLabel.text = "Connecting..." } func handleEndCallMessage(_ message: CallMessage) { @@ -165,7 +162,6 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { WebRTCSession.current = nil UIView.animate(withDuration: 0.25) { self.remoteVideoView.alpha = 0 - self.callInfoLabel.alpha = 1 } Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in self.presentingViewController?.dismiss(animated: true, completion: nil) From 2dfef5168877a374219ca66ae33d96a9c68db017 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 27 Aug 2021 16:53:08 +1000 Subject: [PATCH 042/368] use default factory --- SessionMessagingKit/Calls/WebRTCSession.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index b04808a46..770b5249b 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -22,9 +22,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { internal lazy var factory: RTCPeerConnectionFactory = { RTCInitializeSSL() - let videoEncoderFactory = RTCVideoEncoderFactoryH264() - let videoDecoderFactory = RTCVideoDecoderFactoryH264() - return RTCPeerConnectionFactory(encoderFactory: videoEncoderFactory, decoderFactory: videoDecoderFactory) + return RTCPeerConnectionFactory() }() /// Represents a WebRTC connection between the user and a remote peer. Provides methods to connect to a From 8013cdacffa7d38e3be8b32d08e60921d7566647 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 8 Sep 2021 14:55:52 +1000 Subject: [PATCH 043/368] add operations for video call --- Session/Calls/CallVC.swift | 90 ++++++++++++++++-- Session/Calls/CameraManager.swift | 30 +++++- .../Session/AudioOff.imageset/AudioOff.pdf | Bin 0 -> 5362 bytes .../Session/AudioOff.imageset/Contents.json | 12 +++ .../Session/AudioOn.imageset/Contents.json | 12 +++ .../Session/AudioOn.imageset/Shape.pdf | Bin 0 -> 4881 bytes .../Session/EndCall.imageset/Contents.json | 12 +++ .../Session/EndCall.imageset/Path.pdf | Bin 0 -> 4673 bytes .../SwitchCamera.imageset/Contents.json | 12 +++ .../SwitchCamera.imageset/SwtichCamera.pdf | Bin 0 -> 5773 bytes SessionMessagingKit/Calls/WebRTCSession.swift | 8 ++ 11 files changed, 164 insertions(+), 12 deletions(-) create mode 100644 Session/Meta/Images.xcassets/Session/AudioOff.imageset/AudioOff.pdf create mode 100644 Session/Meta/Images.xcassets/Session/AudioOff.imageset/Contents.json create mode 100644 Session/Meta/Images.xcassets/Session/AudioOn.imageset/Contents.json create mode 100644 Session/Meta/Images.xcassets/Session/AudioOn.imageset/Shape.pdf create mode 100644 Session/Meta/Images.xcassets/Session/EndCall.imageset/Contents.json create mode 100644 Session/Meta/Images.xcassets/Session/EndCall.imageset/Path.pdf create mode 100644 Session/Meta/Images.xcassets/Session/SwitchCamera.imageset/Contents.json create mode 100644 Session/Meta/Images.xcassets/Session/SwitchCamera.imageset/SwtichCamera.pdf diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 9fb2d20f1..c9a630690 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -7,6 +7,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { let sessionID: String let mode: Mode let webRTCSession: WebRTCSession + var isMuted = false lazy var cameraManager: CameraManager = { let result = CameraManager() @@ -19,6 +20,14 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { }() // MARK: UI Components + private lazy var localVideoView: RTCMTLVideoView = { + let result = RTCMTLVideoView() + result.contentMode = .scaleAspectFill + result.set(.width, to: 80) + result.set(.height, to: 173) + return result + }() + private lazy var remoteVideoView: RTCMTLVideoView = { let result = RTCMTLVideoView() result.contentMode = .scaleAspectFill @@ -48,6 +57,42 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { return result }() + private lazy var hangUpButton: UIButton = { + let result = UIButton(type: .custom) + let image = UIImage(named: "EndCall")!.withTint(.white) + result.setImage(image, for: UIControl.State.normal) + result.set(.width, to: 60) + result.set(.height, to: 60) + result.backgroundColor = Colors.destructive + result.layer.cornerRadius = 30 + result.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var switchCameraButton: UIButton = { + let result = UIButton(type: .custom) + let image = UIImage(named: "SwitchCamera")!.withTint(.white) + result.setImage(image, for: UIControl.State.normal) + result.set(.width, to: 60) + result.set(.height, to: 60) + result.backgroundColor = UIColor(hex: 0x1F1F1F) + result.layer.cornerRadius = 30 + result.addTarget(self, action: #selector(switchCamera), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var switchAudioButton: UIButton = { + let result = UIButton(type: .custom) + let image = UIImage(named: "AudioOn")!.withTint(.white) + result.setImage(image, for: UIControl.State.normal) + result.set(.width, to: 60) + result.set(.height, to: 60) + result.backgroundColor = UIColor(hex: 0x1F1F1F) + result.layer.cornerRadius = 30 + result.addTarget(self, action: #selector(switchAudio), for: UIControl.Event.touchUpInside) + return result + }() + private lazy var titleLabel: UILabel = { let result = UILabel() result.textColor = .white @@ -115,15 +160,11 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { remoteVideoView.translatesAutoresizingMaskIntoConstraints = false remoteVideoView.pin(to: view) // Local video view - let localVideoView = RTCMTLVideoView() - localVideoView.contentMode = .scaleAspectFill webRTCSession.attachLocalRenderer(localVideoView) - localVideoView.set(.width, to: 80) - localVideoView.set(.height, to: 173) view.addSubview(localVideoView) - localVideoView.pin(.right, to: .right, of: view, withInset: -Values.largeSpacing) - let bottomMargin = UIApplication.shared.keyWindow!.safeAreaInsets.bottom + Values.largeSpacing - localVideoView.pin(.bottom, to: .bottom, of: view, withInset: -bottomMargin) + localVideoView.pin(.right, to: .right, of: view, withInset: -Values.smallSpacing) + let topMargin = UIApplication.shared.keyWindow!.safeAreaInsets.top + Values.veryLargeSpacing + localVideoView.pin(.top, to: .top, of: view, withInset: topMargin) // Fade view view.addSubview(fadeView) fadeView.translatesAutoresizingMaskIntoConstraints = false @@ -138,6 +179,21 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.center(.vertical, in: closeButton) titleLabel.center(.horizontal, in: view) + // End call button + view.addSubview(hangUpButton) + hangUpButton.translatesAutoresizingMaskIntoConstraints = false + hangUpButton.center(.horizontal, in: view) + hangUpButton.pin(.bottom, to: .bottom, of: view, withInset: -Values.newConversationButtonBottomOffset) + // Switch camera button + view.addSubview(switchCameraButton) + switchCameraButton.translatesAutoresizingMaskIntoConstraints = false + switchCameraButton.center(.vertical, in: hangUpButton) + switchCameraButton.pin(.right, to: .left, of: hangUpButton, withInset: -Values.veryLargeSpacing) + // Switch audio button + view.addSubview(switchAudioButton) + switchAudioButton.translatesAutoresizingMaskIntoConstraints = false + switchAudioButton.center(.vertical, in: hangUpButton) + switchAudioButton.pin(.left, to: .right, of: hangUpButton, withInset: Values.veryLargeSpacing) } override func viewDidAppear(_ animated: Bool) { @@ -174,4 +230,24 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } presentingViewController?.dismiss(animated: true, completion: nil) } + + @objc private func switchCamera() { + cameraManager.switchCamera() + } + + @objc private func switchAudio() { + if isMuted { + switchAudioButton.backgroundColor = UIColor(hex: 0x1F1F1F) + let image = UIImage(named: "AudioOn")!.withTint(.white) + switchAudioButton.setImage(image, for: UIControl.State.normal) + isMuted = false + webRTCSession.unmute() + } else { + switchAudioButton.backgroundColor = Colors.destructive + let image = UIImage(named: "AudioOff")!.withTint(.white) + switchAudioButton.setImage(image, for: UIControl.State.normal) + isMuted = true + webRTCSession.mute() + } + } } diff --git a/Session/Calls/CameraManager.swift b/Session/Calls/CameraManager.swift index 069841268..f7e92346d 100644 --- a/Session/Calls/CameraManager.swift +++ b/Session/Calls/CameraManager.swift @@ -17,15 +17,20 @@ final class CameraManager : NSObject { private var isCapturing = false weak var delegate: CameraManagerDelegate? - private lazy var videoCaptureDevice: AVCaptureDevice? = { - return AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) - }() + private var videoCaptureDevice: AVCaptureDevice? + private var videoInput: AVCaptureDeviceInput? func prepare() { print("[Calls] Preparing camera.") - if let videoCaptureDevice = videoCaptureDevice, + addNewVideoIO(position: .front) + } + + private func addNewVideoIO(position: AVCaptureDevice.Position) { + if let videoCaptureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: position), let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice), captureSession.canAddInput(videoInput) { captureSession.addInput(videoInput) + self.videoCaptureDevice = videoCaptureDevice + self.videoInput = videoInput } if captureSession.canAddOutput(videoDataOutput) { captureSession.addOutput(videoDataOutput) @@ -34,7 +39,7 @@ final class CameraManager : NSObject { guard let connection = videoDataOutput.connection(with: AVMediaType.video) else { return } connection.videoOrientation = .portrait connection.automaticallyAdjustsVideoMirroring = false - connection.isVideoMirrored = true + connection.isVideoMirrored = (position == .front) } else { SNLog("Couldn't add video data output to capture session.") } @@ -53,6 +58,21 @@ final class CameraManager : NSObject { isCapturing = false captureSession.stopRunning() } + + func switchCamera() { + guard let videoCaptureDevice = videoCaptureDevice, let videoInput = videoInput else { return } + stop() + if videoCaptureDevice.position == .front { + captureSession.removeInput(videoInput) + captureSession.removeOutput(videoDataOutput) + addNewVideoIO(position: .back) + } else { + captureSession.removeInput(videoInput) + captureSession.removeOutput(videoDataOutput) + addNewVideoIO(position: .front) + } + start() + } } extension CameraManager : AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate { diff --git a/Session/Meta/Images.xcassets/Session/AudioOff.imageset/AudioOff.pdf b/Session/Meta/Images.xcassets/Session/AudioOff.imageset/AudioOff.pdf new file mode 100644 index 0000000000000000000000000000000000000000..93a3af9457845f16bb335274c93957b83fc36e17 GIT binary patch literal 5362 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 M4#3Z^s-p(@A9n?i;s5{u literal 0 HcmV?d00001 diff --git a/Session/Meta/Images.xcassets/Session/AudioOff.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/AudioOff.imageset/Contents.json new file mode 100644 index 000000000..710e3736b --- /dev/null +++ b/Session/Meta/Images.xcassets/Session/AudioOff.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "AudioOff.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Session/Meta/Images.xcassets/Session/AudioOn.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/AudioOn.imageset/Contents.json new file mode 100644 index 000000000..516caa513 --- /dev/null +++ b/Session/Meta/Images.xcassets/Session/AudioOn.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Shape.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Session/Meta/Images.xcassets/Session/AudioOn.imageset/Shape.pdf b/Session/Meta/Images.xcassets/Session/AudioOn.imageset/Shape.pdf new file mode 100644 index 0000000000000000000000000000000000000000..eb0df35b958edac355af6666deca27144b2af1e5 GIT binary patch literal 4881 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 Bwu=A& literal 0 HcmV?d00001 diff --git a/Session/Meta/Images.xcassets/Session/EndCall.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/EndCall.imageset/Contents.json new file mode 100644 index 000000000..fcb04f0ba --- /dev/null +++ b/Session/Meta/Images.xcassets/Session/EndCall.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Path.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Session/Meta/Images.xcassets/Session/EndCall.imageset/Path.pdf b/Session/Meta/Images.xcassets/Session/EndCall.imageset/Path.pdf new file mode 100644 index 0000000000000000000000000000000000000000..94caff67593fb74cc81b6692595fd2c45503c909 GIT binary patch literal 4673 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 literal 0 HcmV?d00001 diff --git a/Session/Meta/Images.xcassets/Session/SwitchCamera.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/SwitchCamera.imageset/Contents.json new file mode 100644 index 000000000..e7636afdf --- /dev/null +++ b/Session/Meta/Images.xcassets/Session/SwitchCamera.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "SwtichCamera.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Session/Meta/Images.xcassets/Session/SwitchCamera.imageset/SwtichCamera.pdf b/Session/Meta/Images.xcassets/Session/SwitchCamera.imageset/SwtichCamera.pdf new file mode 100644 index 0000000000000000000000000000000000000000..604019ae2ea3be197b31da7ab21cb15a546f991a GIT binary patch literal 5773 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 Gz<&WJ#YaK_ literal 0 HcmV?d00001 diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 770b5249b..04c0281ff 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -204,6 +204,14 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { peerConnection.close() } + public func mute() { + audioTrack.isEnabled = false + } + + public func unmute() { + audioTrack.isEnabled = true + } + // MARK: Delegate public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCSignalingState) { print("[Calls] Signaling state changed to: \(state).") From 047c44166fd3aebc03a38e47a4eb8d521d9e0f44 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 9 Sep 2021 09:21:13 +1000 Subject: [PATCH 044/368] fix speaker not working --- SessionMessagingKit/Calls/WebRTCSession.swift | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 04c0281ff..5a9d5f72c 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -90,17 +90,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { peerConnection.add(audioTrack, streamIds: mediaStreamTrackIDS) peerConnection.add(localVideoTrack, streamIds: mediaStreamTrackIDS) // Configure audio session - let audioSession = RTCAudioSession.sharedInstance() - audioSession.lockForConfiguration() - do { - try audioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue) - try audioSession.setMode(AVAudioSession.Mode.voiceChat.rawValue) - try audioSession.overrideOutputAudioPort(.speaker) - try audioSession.setActive(true) - } catch let error { - SNLog("Couldn't set up WebRTC audio session due to error: \(error)") - } - audioSession.unlockForConfiguration() + configureAudioSession() } // MARK: Signaling @@ -204,25 +194,18 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { peerConnection.close() } - public func mute() { - audioTrack.isEnabled = false - } - - public func unmute() { - audioTrack.isEnabled = true - } - // MARK: Delegate public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCSignalingState) { print("[Calls] Signaling state changed to: \(state).") } public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { - // Do nothing + print("[Calls] Peer connection did add stream.") + configureAudioSession() } public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) { - // Do nothing + print("[Calls] Peer connection did remove stream.") } public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) { @@ -249,3 +232,27 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { print("[Calls] Data channel opened.") } } + +extension WebRTCSession { + private func configureAudioSession() { + let audioSession = RTCAudioSession.sharedInstance() + audioSession.lockForConfiguration() + do { + try audioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue) + try audioSession.setMode(AVAudioSession.Mode.voiceChat.rawValue) + try audioSession.overrideOutputAudioPort(.speaker) + try audioSession.setActive(true) + } catch let error { + SNLog("Couldn't set up WebRTC audio session due to error: \(error)") + } + audioSession.unlockForConfiguration() + } + + public func mute() { + audioTrack.isEnabled = false + } + + public func unmute() { + audioTrack.isEnabled = true + } +} From 592dd7b3d8269578a9e6a173cc8669b92a373f91 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 9 Sep 2021 11:37:04 +1000 Subject: [PATCH 045/368] update version number --- Session.xcodeproj/project.pbxproj | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 46092ed28..cdd3108d2 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5088,7 +5088,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 294; + CURRENT_PROJECT_VERSION = 295; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5113,7 +5113,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.15; + MARKETING_VERSION = 1.12.0; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5161,7 +5161,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 294; + CURRENT_PROJECT_VERSION = 295; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5191,7 +5191,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.15; + MARKETING_VERSION = 1.12.0; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5227,7 +5227,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 294; + CURRENT_PROJECT_VERSION = 295; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5250,7 +5250,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.15; + MARKETING_VERSION = 1.12.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -5301,7 +5301,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 294; + CURRENT_PROJECT_VERSION = 295; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5329,7 +5329,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.11.15; + MARKETING_VERSION = 1.12.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -6237,7 +6237,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 294; + CURRENT_PROJECT_VERSION = 295; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6277,7 +6277,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.11.15; + MARKETING_VERSION = 1.12.0; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -6309,7 +6309,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 294; + CURRENT_PROJECT_VERSION = 295; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6349,7 +6349,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.11.15; + MARKETING_VERSION = 1.12.0; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session; From e8500d75a777389aeac8b11d02a0375ccbb8e054 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 21 Sep 2021 15:11:48 +1000 Subject: [PATCH 046/368] fix input panel issue & make local video view draggable --- Session/Calls/CallVC.swift | 61 ++++++++++++++---- .../ConversationVC+Interaction.swift | 6 +- Session/Conversations/ConversationVC.swift | 3 +- .../Contents.json | 2 +- .../minimize.pdf} | Bin 4881 -> 4654 bytes 5 files changed, 56 insertions(+), 16 deletions(-) rename Session/Meta/Images.xcassets/Session/{AudioOn.imageset => Minimize.imageset}/Contents.json (77%) rename Session/Meta/Images.xcassets/Session/{AudioOn.imageset/Shape.pdf => Minimize.imageset/minimize.pdf} (65%) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index c9a630690..0107642b8 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -8,6 +8,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { let mode: Mode let webRTCSession: WebRTCSession var isMuted = false + var conversationVC: ConversationVC? = nil lazy var cameraManager: CameraManager = { let result = CameraManager() @@ -25,6 +26,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { result.contentMode = .scaleAspectFill result.set(.width, to: 80) result.set(.height, to: 173) + result.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture))) return result }() @@ -47,13 +49,13 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { return result }() - private lazy var closeButton: UIButton = { + private lazy var minimizeButton: UIButton = { let result = UIButton(type: .custom) - let image = UIImage(named: "X")!.withTint(.white) + let image = UIImage(named: "Minimize")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) result.set(.height, to: 60) - result.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) + result.addTarget(self, action: #selector(minimize), for: UIControl.Event.touchUpInside) return result }() @@ -83,7 +85,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { private lazy var switchAudioButton: UIButton = { let result = UIButton(type: .custom) - let image = UIImage(named: "AudioOn")!.withTint(.white) + let image = UIImage(named: "AudioOff")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) result.set(.height, to: 60) @@ -170,14 +172,14 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { fadeView.translatesAutoresizingMaskIntoConstraints = false fadeView.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view) // Close button - view.addSubview(closeButton) - closeButton.translatesAutoresizingMaskIntoConstraints = false - closeButton.pin(.left, to: .left, of: view) - closeButton.pin(.top, to: .top, of: view, withInset: 32) + view.addSubview(minimizeButton) + minimizeButton.translatesAutoresizingMaskIntoConstraints = false + minimizeButton.pin(.left, to: .left, of: view) + minimizeButton.pin(.top, to: .top, of: view, withInset: 32) // Title label view.addSubview(titleLabel) titleLabel.translatesAutoresizingMaskIntoConstraints = false - titleLabel.center(.vertical, in: closeButton) + titleLabel.center(.vertical, in: minimizeButton) titleLabel.center(.horizontal, in: view) // End call button view.addSubview(hangUpButton) @@ -220,6 +222,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { self.remoteVideoView.alpha = 0 } Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in + self.conversationVC?.showInputAccessoryView() self.presentingViewController?.dismiss(animated: true, completion: nil) } } @@ -228,9 +231,14 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { Storage.write { transaction in WebRTCSession.current?.endCall(with: self.sessionID, using: transaction) } + self.conversationVC?.showInputAccessoryView() presentingViewController?.dismiss(animated: true, completion: nil) } + @objc private func minimize() { + + } + @objc private func switchCamera() { cameraManager.switchCamera() } @@ -238,16 +246,43 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { @objc private func switchAudio() { if isMuted { switchAudioButton.backgroundColor = UIColor(hex: 0x1F1F1F) - let image = UIImage(named: "AudioOn")!.withTint(.white) - switchAudioButton.setImage(image, for: UIControl.State.normal) isMuted = false webRTCSession.unmute() } else { switchAudioButton.backgroundColor = Colors.destructive - let image = UIImage(named: "AudioOff")!.withTint(.white) - switchAudioButton.setImage(image, for: UIControl.State.normal) isMuted = true webRTCSession.mute() } } + + @objc private func handlePanGesture(gesture: UIPanGestureRecognizer) { + let location = gesture.location(in: self.view) + if let draggedView = gesture.view { + draggedView.center = location + if gesture.state == .ended { + let sideMargin = 40 + Values.verySmallSpacing + if draggedView.frame.midX >= self.view.layer.frame.width / 2 { + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { + draggedView.center.x = self.view.layer.frame.width - sideMargin + }, completion: nil) + }else{ + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { + draggedView.center.x = sideMargin + }, completion: nil) + } + let topMargin = UIApplication.shared.keyWindow!.safeAreaInsets.top + Values.veryLargeSpacing + if draggedView.frame.minY <= topMargin { + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { + draggedView.center.y = topMargin + draggedView.frame.size.height / 2 + }, completion: nil) + } + let bottomMargin = UIApplication.shared.keyWindow!.safeAreaInsets.bottom + if draggedView.frame.maxY >= self.view.layer.frame.height { + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { + draggedView.center.y = self.view.layer.frame.height - draggedView.frame.size.height / 2 - bottomMargin + }, completion: nil) + } + } + } + } } diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 1be1fdd84..6aeaa4e64 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -26,11 +26,15 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc messagesTableView.scrollToRow(at: indexPath, at: .top, animated: true) } - @objc func startCall() { + // MARK: Call + @objc func startCall(_ sender: Any) { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } let callVC = CallVC(for: contactSessionID, mode: .offer) + callVC.conversationVC = self callVC.modalPresentationStyle = .overFullScreen callVC.modalTransitionStyle = .crossDissolve + self.inputAccessoryView?.isHidden = true + self.inputAccessoryView?.alpha = 0 present(callVC, animated: true, completion: nil) } diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 08a895707..73f4a013d 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -253,6 +253,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat super.viewDidAppear(animated) didFinishInitialLayout = true markAllAsRead() + self.becomeFirstResponder() } override func viewWillDisappear(_ animated: Bool) { @@ -261,7 +262,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat Storage.write { transaction in self.thread.setDraft(text, transaction: transaction) } - inputAccessoryView?.resignFirstResponder() + self.resignFirstResponder() } override func viewDidDisappear(_ animated: Bool) { diff --git a/Session/Meta/Images.xcassets/Session/AudioOn.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/Minimize.imageset/Contents.json similarity index 77% rename from Session/Meta/Images.xcassets/Session/AudioOn.imageset/Contents.json rename to Session/Meta/Images.xcassets/Session/Minimize.imageset/Contents.json index 516caa513..7ec562a8c 100644 --- a/Session/Meta/Images.xcassets/Session/AudioOn.imageset/Contents.json +++ b/Session/Meta/Images.xcassets/Session/Minimize.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Shape.pdf", + "filename" : "minimize.pdf", "idiom" : "universal" } ], diff --git a/Session/Meta/Images.xcassets/Session/AudioOn.imageset/Shape.pdf b/Session/Meta/Images.xcassets/Session/Minimize.imageset/minimize.pdf similarity index 65% rename from Session/Meta/Images.xcassets/Session/AudioOn.imageset/Shape.pdf rename to Session/Meta/Images.xcassets/Session/Minimize.imageset/minimize.pdf index eb0df35b958edac355af6666deca27144b2af1e5..36cd0d830d3e3347fcc0c7e9b870dcfb32c6cc18 100644 GIT binary patch delta 1317 zcmbQJwoYY&eZ7IPg@TUAr1CbEYuu{Kon36nnX@cH{kGzzUEeL{xldACeQC+WcZ`c>HkUipABsK3dvTfe(+azF z3o>lg-|o#zym;npm}vaOW$hPEOU+Y=y~BRqwL9X=mnEOKSSE>X`;psFQ66a7@&8HSOK2_g)63G3qik_mWfcOEzV^e{*Vf>h(Ld@6N3(QGEYX z>%H`6vD1fV+`Qeg#&OwJ$+^?&6ESeha1 zR-HWg$sxU)8Mkl59oiyT8YaKS<2{pbE06HQ<*S}@&AjuAXG_o--K{syYF@WHGSj&9 z1#?5~cWaKdCBJxFHM#zStGi0v+>J#O*A)MF_wP2xI!Swu zIe%lb+?lUN%FfvQd;NCxC08rtdsfUjG=oDW<&fc7gP^_5Nvhe538$>odCD6t4mnZkum#L;CJ^#m{HnanVw}j9am>lYFd}sbvSgNDmQ0x=G99Ee4jau zdiAF@Wp*v>XO(`ln$7S0ok;bhXWr~ZR*N^x-yRlM{ziAj)gL0)*dJcG^Jvk7Mep8) zsQL2RJt_a2bo}ONoqVDH^985a{;B+P`sweF=Rf|I-~Z&}pVBXXzrU{hV|GC#HSgi85vs|SQ;dnnHwY}Tbid@SR|(yB^p_#q?xB07}{~!5LBWN a3n>8;i%KerQq#ChjExMqR8?L5-M9cSR89E+ delta 1546 zcmah}YdjMO06(O>mMM>HE}P7|%-*Xtt(%e(7fI91bA^*-U1+lMsAgU>;Upye28B{Xd zX)AUX{9==H@jSA)!%?s7F~eVa2p~9`Ra)cgJw8b1)VeA@ygQx#wrj%XxLXaW%C&T; zmj}3-ftZN@)P2fdGyD_BvMH<%VSjT{7iMyueHvybk2~;8PF`Lo@gv%a6CC%?l&Mkyq zp$NFH&(rU%Poi=nL3{2FVUJb`H6b-WSqGVE8e^uwezPTjf`fqe5uLq?NDRkVVYiK4 zzi2xra6ydudz*KKKiQGASYK&`jtC6$mv7)g2c9Q0iU_>EqSEy0179phd?gZ5$#X2K zlAK)BrB&PmqXXiYR6I+%rJ^F$vq;oyd?ozSzM?7~=^H^lB&JWkY^2;9TSa;>^O8sw zDI$NyzXcS&8SQE1vXwxt`Bd5-cD|>&3%jqH=9*({tB|Uh-hN!2Hqns?H(f6LKeRZufqN1aPGW}9{ym8ch zz>J*pf!sOlkzmrn+8y)4a=Cl#T$(i2qQ7zaBBS{uu`{)>07Pu62})!OxR;p<4)Swl z`*;=M$>s;yvha>T94QYjX!g**Utnt7vXL5HDvlbnyzJ^9VLj(wlp=`Or8;3aNT}w8 zrL*h*W#C z#s^!iSO=)X5x^8>E`vj?OVpe1zyHO{PUgK2z_L+V0@Cshrvy%W%q%Ihw@{jS({)>O z#D{qYxpnj8L>7hfQaq{gk=T`SZqyn`m5 z-B{RT_gSlL(ZtCkrpot;OYa?QYxK^@GV-B}+q3gbDAf1fmQliI+pGPyT8IDioEeAd{y&IK?;Z|RIswlwN+E| zzdHkN`Fhm&t>fydBhdUYeW4Z{2X{gtka&J25d5!@0I?=W3>*Ug3y{+goxcejg+QbE zU;~&J3iTfu;w#Sy4aa@8fka}y_HbxE-+(|v;X* Date: Tue, 21 Sep 2021 16:38:48 +1000 Subject: [PATCH 047/368] update translation --- Session/Meta/Translations/de.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/en.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/es.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/fa.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/fi.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/fr.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/hi.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/hr.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/id-ID.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/it.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/ja.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/nl.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/pl.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/pt_BR.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/ru.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/si.lproj/Localizable.strings | 2 ++ Session/Meta/Translations/sk.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/sv.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/th.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/vi-VN.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/zh-Hant.lproj/Localizable.strings | 3 +++ Session/Meta/Translations/zh_CN.lproj/Localizable.strings | 6 ++++++ 22 files changed, 68 insertions(+) diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 50b71fb77..32ad04317 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index d5f10cc5b..c9200293f 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -566,6 +566,9 @@ "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; "invalid_recovery_phrase" = "Invalid Recovery Phrase"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 4317d8ca7..80da8f327 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 7f2645bbe..c290e8b0f 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index 319c43001..92d5e6e07 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 81a7d62d9..328901206 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index 01a613910..3766eb975 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 67906feec..5cf273d19 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index 24b7fbfb7..3fbf8f0a0 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index fdce7ea5a..00b35cd2f 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index b5392771a..e21e74066 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index c47613ed3..ad39c12b0 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index 560858e0f..5733369a2 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index e315fbc08..725223dc3 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 56b90b7ef..e782ce355 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index d5f10cc5b..3ae9cc80c 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -569,3 +569,5 @@ "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index f7f2222d1..41e86a3b7 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index 8a36b41ff..925149112 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index aeb9632f3..a7165f7b7 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index de5deaede..b331dda71 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index e337ac3bf..e57eb4737 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -565,6 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index 2e01912ef..2f6591061 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -565,3 +565,9 @@ "accessibility_library_button" = "Photo library"; "accessibility_camera_button" = "Camera"; "accessibility_main_button_collapse" = "Collapse attachment options"; +"invalid_recovery_phrase" = "Invalid Recovery Phrase"; +"DISMISS_BUTTON_TEXT" = "Dismiss"; +/* Button text which opens the settings app */ +"OPEN_SETTINGS_BUTTON" = "Settings"; +"voice_call" = "Voice Call"; +"video_call" = "Video Call"; From 019a2cd2992777f9208205ffc9c50da8da2f392d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 22 Sep 2021 11:44:35 +1000 Subject: [PATCH 048/368] add options for voice call and video call --- .../ConversationVC+Interaction.swift | 29 ++++++++++++++++-- .../Session/VideoCall.imageset/Contents.json | 12 ++++++++ .../Session/VideoCall.imageset/video_call.pdf | Bin 0 -> 5281 bytes 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 Session/Meta/Images.xcassets/Session/VideoCall.imageset/Contents.json create mode 100644 Session/Meta/Images.xcassets/Session/VideoCall.imageset/video_call.pdf diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 6aeaa4e64..269f1a5ca 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -28,13 +28,38 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc // MARK: Call @objc func startCall(_ sender: Any) { + let alertVC = UIAlertController.init(title: nil, message: nil, preferredStyle: .actionSheet) + let voiceCallAction = UIAlertAction.init(title: NSLocalizedString("voice_call", comment: ""), style: .default) { _ in + self.startVoiceCall() + } + voiceCallAction.setValue(UIImage(named: "Phone"), forKey: "image") + alertVC.addAction(voiceCallAction) + + let videoCallAction = UIAlertAction.init(title: NSLocalizedString("video_call", comment: ""), style: .default) { _ in + self.startVideoCall() + } + videoCallAction.setValue(UIImage(named: "VideoCall"), forKey: "image") + alertVC.addAction(videoCallAction) + + let cancelAction = UIAlertAction.init(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel) {_ in + self.showInputAccessoryView() + } + alertVC.addAction(cancelAction) + self.inputAccessoryView?.isHidden = true + self.inputAccessoryView?.alpha = 0 + self.presentAlert(alertVC, animated: true) + } + + private func startVoiceCall() { + + } + + private func startVideoCall() { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } let callVC = CallVC(for: contactSessionID, mode: .offer) callVC.conversationVC = self callVC.modalPresentationStyle = .overFullScreen callVC.modalTransitionStyle = .crossDissolve - self.inputAccessoryView?.isHidden = true - self.inputAccessoryView?.alpha = 0 present(callVC, animated: true, completion: nil) } diff --git a/Session/Meta/Images.xcassets/Session/VideoCall.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/VideoCall.imageset/Contents.json new file mode 100644 index 000000000..5612afc47 --- /dev/null +++ b/Session/Meta/Images.xcassets/Session/VideoCall.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "video_call.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Session/Meta/Images.xcassets/Session/VideoCall.imageset/video_call.pdf b/Session/Meta/Images.xcassets/Session/VideoCall.imageset/video_call.pdf new file mode 100644 index 0000000000000000000000000000000000000000..86e1c39c5ef4d8a65d7a991bb3159d13f6094292 GIT binary patch literal 5281 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 Date: Wed, 22 Sep 2021 14:54:26 +1000 Subject: [PATCH 049/368] turn on/off camera --- Session/Calls/CallVC.swift | 11 ++++++++++- SessionMessagingKit/Calls/WebRTCSession.swift | 8 ++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 0107642b8..23f084f83 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -236,7 +236,16 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } @objc private func minimize() { - + if (localVideoView.isHidden) { + webRTCSession.turnOnVideo() + localVideoView.isHidden = false + cameraManager.prepare() + cameraManager.start() + } else { + webRTCSession.turnOffVideo() + localVideoView.isHidden = true + cameraManager.stop() + } } @objc private func switchCamera() { diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 5a9d5f72c..fab40d43d 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -255,4 +255,12 @@ extension WebRTCSession { public func unmute() { audioTrack.isEnabled = true } + + public func turnOffVideo() { + localVideoTrack.isEnabled = false + } + + public func turnOnVideo() { + localVideoTrack.isEnabled = true + } } From 101f7d3d932dc2c9e44ccfcded5f17ac7c0fe432 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 22 Sep 2021 17:06:14 +1000 Subject: [PATCH 050/368] add background to call vc --- Session/Calls/CallVC.swift | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 23f084f83..b6d45009e 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -2,6 +2,7 @@ import WebRTC import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit +import UIKit final class CallVC : UIViewController, WebRTCSessionDelegate { let sessionID: String @@ -152,6 +153,10 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } func setUpViewHierarchy() { + // Background + let background = getBackgroudView() + view.addSubview(background) + background.autoPinEdgesToSuperviewEdges() // Call info label view.addSubview(callInfoLabel) callInfoLabel.translatesAutoresizingMaskIntoConstraints = false @@ -198,6 +203,30 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { switchAudioButton.pin(.left, to: .right, of: hangUpButton, withInset: Values.veryLargeSpacing) } + private func getBackgroudView() -> UIView { + let background = UIView() + let imageView = UIImageView() + imageView.layer.cornerRadius = 150 + imageView.layer.masksToBounds = true + imageView.contentMode = .scaleAspectFill + if let profilePicture = OWSProfileManager.shared().profileAvatar(forRecipientId: sessionID) { + imageView.image = profilePicture + } else { + let displayName = Storage.shared.getContact(with: sessionID)?.name ?? sessionID + imageView.image = Identicon.generatePlaceholderIcon(seed: sessionID, text: displayName, size: 300) + } + background.addSubview(imageView) + imageView.set(.width, to: 300) + imageView.set(.height, to: 300) + imageView.center(in: background) + let blurView = UIView() + blurView.alpha = 0.5 + blurView.backgroundColor = .black + background.addSubview(blurView) + blurView.autoPinEdgesToSuperviewEdges() + return background + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) cameraManager.start() From 53541856967428a33d0659be3315f4200e1ed9cb Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 23 Sep 2021 12:55:28 +1000 Subject: [PATCH 051/368] improve UI --- Session/Calls/CallVC.swift | 108 +++++++++++++----- .../ConversationVC+Interaction.swift | 29 +---- Session/Meta/AppDelegate.swift | 17 +-- .../AnswerCall.imageset/AnswerCall.pdf | Bin 0 -> 4646 bytes .../Session/AnswerCall.imageset/Contents.json | 12 ++ 5 files changed, 99 insertions(+), 67 deletions(-) create mode 100644 Session/Meta/Images.xcassets/Session/AnswerCall.imageset/AnswerCall.pdf create mode 100644 Session/Meta/Images.xcassets/Session/AnswerCall.imageset/Contents.json diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index b6d45009e..b6a67f828 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -9,6 +9,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { let mode: Mode let webRTCSession: WebRTCSession var isMuted = false + var isVideoEnabled = false var conversationVC: ConversationVC? = nil lazy var cameraManager: CameraManager = { @@ -24,6 +25,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { // MARK: UI Components private lazy var localVideoView: RTCMTLVideoView = { let result = RTCMTLVideoView() + result.isHidden = !isVideoEnabled result.contentMode = .scaleAspectFill result.set(.width, to: 80) result.set(.height, to: 173) @@ -52,6 +54,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { private lazy var minimizeButton: UIButton = { let result = UIButton(type: .custom) + result.isHidden = true let image = UIImage(named: "Minimize")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) @@ -60,6 +63,18 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { return result }() + private lazy var answerButton: UIButton = { + let result = UIButton(type: .custom) + let image = UIImage(named: "AnswerCall")!.withTint(.white) + result.setImage(image, for: UIControl.State.normal) + result.set(.width, to: 60) + result.set(.height, to: 60) + result.backgroundColor = Colors.accent + result.layer.cornerRadius = 30 + result.addTarget(self, action: #selector(answerCall), for: UIControl.Event.touchUpInside) + return result + }() + private lazy var hangUpButton: UIButton = { let result = UIButton(type: .custom) let image = UIImage(named: "EndCall")!.withTint(.white) @@ -68,12 +83,20 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { result.set(.height, to: 60) result.backgroundColor = Colors.destructive result.layer.cornerRadius = 30 - result.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) + result.addTarget(self, action: #selector(endCall), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var responsePanel: UIStackView = { + let result = UIStackView(arrangedSubviews: [hangUpButton, answerButton]) + result.axis = .horizontal + result.spacing = Values.veryLargeSpacing * 2 + 40 return result }() private lazy var switchCameraButton: UIButton = { let result = UIButton(type: .custom) + result.isEnabled = isVideoEnabled let image = UIImage(named: "SwitchCamera")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) @@ -96,6 +119,26 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { return result }() + private lazy var videoButton: UIButton = { + let result = UIButton(type: .custom) + let image = UIImage(named: "VideoCall")!.withTint(.white) + result.setImage(image, for: UIControl.State.normal) + result.set(.width, to: 60) + result.set(.height, to: 60) + result.backgroundColor = UIColor(hex: 0x1F1F1F) + result.layer.cornerRadius = 30 + result.alpha = 0.5 + result.addTarget(self, action: #selector(operateCamera), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var operationPanel: UIStackView = { + let result = UIStackView(arrangedSubviews: [videoButton, switchAudioButton, switchCameraButton]) + result.axis = .horizontal + result.spacing = Values.veryLargeSpacing + return result + }() + private lazy var titleLabel: UILabel = { let result = UILabel() result.textColor = .white @@ -146,9 +189,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { Storage.write { transaction in self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() } - } else if case let .answer(sdp) = mode { - callInfoLabel.text = "Connecting..." - webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally + answerButton.isHidden = true } } @@ -186,21 +227,14 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.center(.vertical, in: minimizeButton) titleLabel.center(.horizontal, in: view) - // End call button - view.addSubview(hangUpButton) - hangUpButton.translatesAutoresizingMaskIntoConstraints = false - hangUpButton.center(.horizontal, in: view) - hangUpButton.pin(.bottom, to: .bottom, of: view, withInset: -Values.newConversationButtonBottomOffset) - // Switch camera button - view.addSubview(switchCameraButton) - switchCameraButton.translatesAutoresizingMaskIntoConstraints = false - switchCameraButton.center(.vertical, in: hangUpButton) - switchCameraButton.pin(.right, to: .left, of: hangUpButton, withInset: -Values.veryLargeSpacing) - // Switch audio button - view.addSubview(switchAudioButton) - switchAudioButton.translatesAutoresizingMaskIntoConstraints = false - switchAudioButton.center(.vertical, in: hangUpButton) - switchAudioButton.pin(.left, to: .right, of: hangUpButton, withInset: Values.veryLargeSpacing) + // Response Panel + view.addSubview(responsePanel) + responsePanel.center(.horizontal, in: view) + responsePanel.pin(.bottom, to: .bottom, of: view, withInset: -Values.newConversationButtonBottomOffset) + // Operation Panel + view.addSubview(operationPanel) + operationPanel.center(.horizontal, in: view) + operationPanel.pin(.bottom, to: .top, of: responsePanel, withInset: -Values.veryLargeSpacing) } private func getBackgroudView() -> UIView { @@ -229,12 +263,12 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - cameraManager.start() + if (isVideoEnabled) { cameraManager.start() } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - cameraManager.stop() + if (isVideoEnabled) { cameraManager.stop() } } // MARK: Interaction @@ -256,7 +290,18 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } } - @objc private func close() { + @objc private func answerCall() { + if case let .answer(sdp) = mode { + callInfoLabel.text = "Connecting..." + webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally + self.answerButton.alpha = 0 + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { + self.answerButton.isHidden = true + }, completion: nil) + } + } + + @objc private func endCall() { Storage.write { transaction in WebRTCSession.current?.endCall(with: self.sessionID, using: transaction) } @@ -265,16 +310,25 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } @objc private func minimize() { - if (localVideoView.isHidden) { + + } + + @objc private func operateCamera() { + if (isVideoEnabled) { + webRTCSession.turnOffVideo() + localVideoView.isHidden = true + cameraManager.stop() + videoButton.alpha = 0.5 + switchCameraButton.isEnabled = false + } else { webRTCSession.turnOnVideo() localVideoView.isHidden = false cameraManager.prepare() cameraManager.start() - } else { - webRTCSession.turnOffVideo() - localVideoView.isHidden = true - cameraManager.stop() + videoButton.alpha = 1.0 + switchCameraButton.isEnabled = true } + isVideoEnabled = !isVideoEnabled } @objc private func switchCamera() { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 269f1a5ca..6aeaa4e64 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -28,38 +28,13 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc // MARK: Call @objc func startCall(_ sender: Any) { - let alertVC = UIAlertController.init(title: nil, message: nil, preferredStyle: .actionSheet) - let voiceCallAction = UIAlertAction.init(title: NSLocalizedString("voice_call", comment: ""), style: .default) { _ in - self.startVoiceCall() - } - voiceCallAction.setValue(UIImage(named: "Phone"), forKey: "image") - alertVC.addAction(voiceCallAction) - - let videoCallAction = UIAlertAction.init(title: NSLocalizedString("video_call", comment: ""), style: .default) { _ in - self.startVideoCall() - } - videoCallAction.setValue(UIImage(named: "VideoCall"), forKey: "image") - alertVC.addAction(videoCallAction) - - let cancelAction = UIAlertAction.init(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel) {_ in - self.showInputAccessoryView() - } - alertVC.addAction(cancelAction) - self.inputAccessoryView?.isHidden = true - self.inputAccessoryView?.alpha = 0 - self.presentAlert(alertVC, animated: true) - } - - private func startVoiceCall() { - - } - - private func startVideoCall() { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } let callVC = CallVC(for: contactSessionID, mode: .offer) callVC.conversationVC = self callVC.modalPresentationStyle = .overFullScreen callVC.modalTransitionStyle = .crossDissolve + self.inputAccessoryView?.isHidden = true + self.inputAccessoryView?.alpha = 0 present(callVC, animated: true, completion: nil) } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 48fc38f4a..ae0fbfc3e 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -10,19 +10,10 @@ extension AppDelegate { DispatchQueue.main.async { let sdp = RTCSessionDescription(type: .offer, sdp: message.sdps![0]) 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 = CallVC(for: message.sender!, mode: .answer(sdp: sdp)) - presentingVC.dismiss(animated: true) { - callVC.modalPresentationStyle = .overFullScreen - callVC.modalTransitionStyle = .crossDissolve - 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) + let callVC = CallVC(for: message.sender!, mode: .answer(sdp: sdp)) + callVC.modalPresentationStyle = .overFullScreen + callVC.modalTransitionStyle = .crossDissolve + presentingVC.present(callVC, animated: true, completion: nil) } } // Answer messages diff --git a/Session/Meta/Images.xcassets/Session/AnswerCall.imageset/AnswerCall.pdf b/Session/Meta/Images.xcassets/Session/AnswerCall.imageset/AnswerCall.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4350772dea364f094fff538fc56afb49dc0b79a9 GIT binary patch literal 4646 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 Date: Thu, 23 Sep 2021 18:04:16 +1000 Subject: [PATCH 052/368] minor fix --- Session/Meta/AppDelegate.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index ae0fbfc3e..853239cff 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -13,6 +13,11 @@ extension AppDelegate { let callVC = CallVC(for: message.sender!, mode: .answer(sdp: sdp)) callVC.modalPresentationStyle = .overFullScreen callVC.modalTransitionStyle = .crossDissolve + if let conversationVC = presentingVC as? ConversationVC { + callVC.conversationVC = conversationVC + conversationVC.inputAccessoryView?.isHidden = true + conversationVC.inputAccessoryView?.alpha = 0 + } presentingVC.present(callVC, animated: true, completion: nil) } } From 9030710d9ddec9787e002eedf8f4d43c8de8b987 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 27 Sep 2021 15:08:01 +1000 Subject: [PATCH 053/368] add data channel --- Session.xcodeproj/project.pbxproj | 4 +++ .../Calls/WebRTCSession+DataChannel.swift | 34 +++++++++++++++++++ SessionMessagingKit/Calls/WebRTCSession.swift | 6 +++- 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 64bcdd459..d321930cd 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -140,6 +140,7 @@ 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; }; 7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; }; 7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; }; 7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */; }; 7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; 945AA2B82B621254F69FA9E8 /* Pods_SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9117261809D69B3D7C26B8F1 /* Pods_SessionUtilitiesKit.framework */; }; @@ -1114,6 +1115,7 @@ 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; 7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+DataChannel.swift"; sourceTree = ""; }; 7BDCFC0424206E7300641C39 /* SessionNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SessionNotificationServiceExtension.entitlements; sourceTree = ""; }; 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtensionContext.swift; sourceTree = ""; }; 7DD180F770F8518B4E8796F2 /* Pods-SessionUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUtilitiesKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionUtilitiesKit/Pods-SessionUtilitiesKit.app store release.xcconfig"; sourceTree = ""; }; @@ -2364,6 +2366,7 @@ B806ECA026C4A7E4008BDA44 /* WebRTCSession+UI.swift */, B8B558FE26C4E05E00693325 /* WebRTCSession+MessageHandling.swift */, B8BF43B926CC95FB007828D1 /* WebRTC+Utilities.swift */, + 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */, ); path = Calls; sourceTree = ""; @@ -4757,6 +4760,7 @@ C32C5FD6256E0346003C73A2 /* Notification+Thread.swift in Sources */, C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */, B806ECA126C4A7E4008BDA44 /* WebRTCSession+UI.swift in Sources */, + 7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */, C32C5C88256DD0D2003C73A2 /* Storage+Messaging.swift in Sources */, C32C59C7256DB41F003C73A2 /* TSThread.m in Sources */, C300A5B22554AF9800555489 /* VisibleMessage+Profile.swift in Sources */, diff --git a/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift b/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift new file mode 100644 index 000000000..41a1e6aa5 --- /dev/null +++ b/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift @@ -0,0 +1,34 @@ +import WebRTC +import Foundation + +extension WebRTCSession: RTCDataChannelDelegate { + + internal func createDataChannel() { + let dataChannelConfiguration = RTCDataChannelConfiguration() + dataChannelConfiguration.isOrdered = true + dataChannelConfiguration.isNegotiated = true + dataChannelConfiguration.maxRetransmits = 30 + dataChannelConfiguration.maxPacketLifeTime = 30000 + dataChannel = peerConnection.dataChannel(forLabel: "DATACHANNEL", configuration: dataChannelConfiguration) + dataChannel?.delegate = self + } + + public func sendJSON(_ json: JSON) { + if let dataChannel = self.dataChannel, let jsonAsData = try? JSONSerialization.data(withJSONObject: json, options: [ .fragmentsAllowed ]) { + let dataBuffer = RTCDataBuffer(data: jsonAsData, isBinary: false) + dataChannel.sendData(dataBuffer) + } + } + + // MARK: Data channel delegate + public func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) { + print("[Calls] Data channed did change to \(dataChannel.readyState)") + } + + public func dataChannel(_ dataChannel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) { + print("[Calls] Data channel did receive data: \(buffer)") + if let json = try? JSONSerialization.jsonObject(with: buffer.data, options: [ .fragmentsAllowed ]) { + + } + } +} diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index fab40d43d..d4ebd7379 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -69,6 +69,9 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { return peerConnection.transceivers.first { $0.mediaType == .video }?.receiver.track as? RTCVideoTrack }() + // Data Channel + internal var dataChannel: RTCDataChannel? + // MARK: Error public enum Error : LocalizedError { case noThread @@ -87,6 +90,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { self.contactSessionID = contactSessionID super.init() let mediaStreamTrackIDS = ["ARDAMS"] + createDataChannel() peerConnection.add(audioTrack, streamIds: mediaStreamTrackIDS) peerConnection.add(localVideoTrack, streamIds: mediaStreamTrackIDS) // Configure audio session @@ -194,7 +198,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { peerConnection.close() } - // MARK: Delegate + // MARK: Peer connection delegate public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCSignalingState) { print("[Calls] Signaling state changed to: \(state).") } From 1f65572f30194f48c6c550fe64df6860cdbc03c8 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 28 Sep 2021 15:19:14 +1000 Subject: [PATCH 054/368] don't wait for 5s to fire the local PN --- Session/Notifications/UserNotificationsAdaptee.swift | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Session/Notifications/UserNotificationsAdaptee.swift b/Session/Notifications/UserNotificationsAdaptee.swift index 45b9c2a5c..8d8b6ec21 100644 --- a/Session/Notifications/UserNotificationsAdaptee.swift +++ b/Session/Notifications/UserNotificationsAdaptee.swift @@ -108,15 +108,6 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee { cancelNotification(identifier: notificationIdentifier) } - let trigger: UNNotificationTrigger? - let checkForCancel = category == .incomingMessage - if checkForCancel { - assert(userInfo[AppNotificationUserInfoKey.threadId] != nil) - trigger = UNTimeIntervalNotificationTrigger(timeInterval: kNotificationDelayForRemoteRead, repeats: false) - } else { - trigger = nil - } - if shouldPresentNotification(category: category, userInfo: userInfo) { if let displayableTitle = title?.filterForDisplay { content.title = displayableTitle @@ -129,7 +120,7 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee { Logger.debug("supressing notification body") } - let request = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: trigger) + let request = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: nil) Logger.debug("presenting notification with identifier: \(notificationIdentifier)") notificationCenter.add(request) From 383f996e82c6d4451d6f5e37e93c4110c3b97c13 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 29 Sep 2021 11:17:48 +1000 Subject: [PATCH 055/368] WIP: improve call UI --- Session/Calls/CallVC.swift | 14 ++++++++++++++ .../Conversations/ConversationVC+Interaction.swift | 12 ++++++++++++ Session/Conversations/ConversationVC.swift | 2 ++ Session/Meta/AppDelegate.swift | 12 +++++++----- SessionMessagingKit/Calls/WebRTCSession.swift | 5 +++++ 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index b6a67f828..ed837b7bd 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -271,6 +271,19 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { if (isVideoEnabled) { cameraManager.stop() } } + // MARK: Delegate + func webRTCDidConnected() { + DispatchQueue.main.async { + self.callInfoLabel.text = "Connected" + UIView.animate(withDuration: 0.5, delay: 1, options: [], animations: { + self.callInfoLabel.alpha = 0 + }, completion: { _ in + self.callInfoLabel.isHidden = true + self.callInfoLabel.alpha = 1 + }) + } + } + // MARK: Interaction func handleAnswerMessage(_ message: CallMessage) { callInfoLabel.text = "Connecting..." @@ -278,6 +291,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { func handleEndCallMessage(_ message: CallMessage) { print("[Calls] Ending call.") + callInfoLabel.isHidden = false callInfoLabel.text = "Call Ended" WebRTCSession.current?.dropConnection() WebRTCSession.current = nil diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 6aeaa4e64..3ebabf9c8 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -37,6 +37,18 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc self.inputAccessoryView?.alpha = 0 present(callVC, animated: true, completion: nil) } + + internal func showCallVCIfNeeded() { + guard hasIncomingCall, let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } + hasIncomingCall = false + let callVC = CallVC(for: contactSessionID, mode: .offer) // TODO: change to answer + callVC.conversationVC = self + callVC.modalPresentationStyle = .overFullScreen + callVC.modalTransitionStyle = .crossDissolve + self.inputAccessoryView?.isHidden = true + self.inputAccessoryView?.alpha = 0 + present(callVC, animated: true, completion: nil) + } // MARK: Blocking @objc func unblock() { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 73f4a013d..806d7dd0b 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -10,6 +10,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat let focusedMessageID: String? // This isn't actually used ATM var unreadViewItems: [ConversationViewItem] = [] var scrollButtonConstraint: NSLayoutConstraint? + var hasIncomingCall = false // Search var isShowingSearchUI = false var lastSearchedText: String? @@ -254,6 +255,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat didFinishInitialLayout = true markAllAsRead() self.becomeFirstResponder() + showCallVCIfNeeded() } override func viewWillDisappear(_ animated: Bool) { diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 853239cff..0b9e70b2a 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -10,15 +10,17 @@ extension AppDelegate { DispatchQueue.main.async { let sdp = RTCSessionDescription(type: .offer, sdp: message.sdps![0]) guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully - let callVC = CallVC(for: message.sender!, mode: .answer(sdp: sdp)) - callVC.modalPresentationStyle = .overFullScreen - callVC.modalTransitionStyle = .crossDissolve - if let conversationVC = presentingVC as? ConversationVC { + if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == message.sender! { + let callVC = CallVC(for: message.sender!, mode: .answer(sdp: sdp)) + callVC.modalPresentationStyle = .overFullScreen + callVC.modalTransitionStyle = .crossDissolve callVC.conversationVC = conversationVC conversationVC.inputAccessoryView?.isHidden = true conversationVC.inputAccessoryView?.alpha = 0 + presentingVC.present(callVC, animated: true, completion: nil) + } else { + } - presentingVC.present(callVC, animated: true, completion: nil) } } // Answer messages diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index d4ebd7379..4e956b725 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -3,6 +3,8 @@ import WebRTC public protocol WebRTCSessionDelegate : AnyObject { var videoCapturer: RTCVideoCapturer { get } + + func webRTCDidConnected() } /// See https://webrtc.org/getting-started/overview for more information. @@ -218,6 +220,9 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCIceConnectionState) { print("[Calls] ICE connection state changed to: \(state).") + if state == .connected { + delegate?.webRTCDidConnected() + } } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCIceGatheringState) { From 23fb69ba6f66ce3fc563fec9fc629f9259767c59 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 29 Sep 2021 17:22:37 +1000 Subject: [PATCH 056/368] add incoming/outgoing call message bubble --- Session.xcodeproj/project.pbxproj | 4 ++ Session/Conversations/ConversationViewItem.h | 1 + Session/Conversations/ConversationViewItem.m | 9 ++++ .../Content Views/CallMessageView.swift | 51 +++++++++++++++++++ .../Message Cells/VisibleMessageCell.swift | 6 ++- Session/Meta/AppDelegate.swift | 1 - .../Translations/en.lproj/Localizable.strings | 2 + SessionMessagingKit/Calls/WebRTCSession.swift | 2 + .../Signal/TSIncomingMessage+Conversion.swift | 21 ++++++++ .../Messages/Signal/TSMessage.h | 1 + .../Messages/Signal/TSMessage.m | 1 + .../Signal/TSOutgoingMessage+Conversion.swift | 7 +++ .../MessageReceiver+Handling.swift | 7 +++ 13 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 Session/Conversations/Message Cells/Content Views/CallMessageView.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index d321930cd..179111701 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -138,6 +138,7 @@ 76EB054018170B33006006FC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C318170B33006006FC /* AppDelegate.m */; }; 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; }; 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; }; + 7B7CB189270430D20079FF93 /* CallMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB188270430D20079FF93 /* CallMessageView.swift */; }; 7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; }; 7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; }; @@ -1112,6 +1113,7 @@ 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = ""; }; 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = ""; }; + 7B7CB188270430D20079FF93 /* CallMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageView.swift; sourceTree = ""; }; 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; 7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -2114,6 +2116,7 @@ B8D84EA225DF745A005A043E /* LinkPreviewState.swift */, B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */, 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */, + 7B7CB188270430D20079FF93 /* CallMessageView.swift */, ); path = "Content Views"; sourceTree = ""; @@ -4899,6 +4902,7 @@ 34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */, B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */, 34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */, + 7B7CB189270430D20079FF93 /* CallMessageView.swift in Sources */, C328250F25CA06020062D0A7 /* VoiceMessageView.swift in Sources */, B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */, 3488F9362191CC4000E524CC /* MediaView.swift in Sources */, diff --git a/Session/Conversations/ConversationViewItem.h b/Session/Conversations/ConversationViewItem.h index 8aeaccd01..bbdd12f85 100644 --- a/Session/Conversations/ConversationViewItem.h +++ b/Session/Conversations/ConversationViewItem.h @@ -15,6 +15,7 @@ typedef NS_ENUM(NSInteger, OWSMessageCellType) { OWSMessageCellType_GenericAttachment, OWSMessageCellType_MediaMessage, OWSMessageCellType_OversizeTextDownloading, + OWSMessageCellType_CallMessage, OWSMessageCellType_DeletedMessage }; diff --git a/Session/Conversations/ConversationViewItem.m b/Session/Conversations/ConversationViewItem.m index a60beeab4..5a70120a7 100644 --- a/Session/Conversations/ConversationViewItem.m +++ b/Session/Conversations/ConversationViewItem.m @@ -32,6 +32,10 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) return @"OWSMessageCellType_MediaMessage"; case OWSMessageCellType_OversizeTextDownloading: return @"OWSMessageCellType_OversizeTextDownloading"; + case OWSMessageCellType_CallMessage: + return @"OWSMessageCellType_CallMessage"; + case OWSMessageCellType_DeletedMessage: + return @"OWSMessageCellType_DeletedMessage"; } } @@ -475,6 +479,11 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) self.messageCellType = OWSMessageCellType_DeletedMessage; return; } + + if (message.isCallMessage) { + self.messageCellType = OWSMessageCellType_CallMessage; + return; + } // Check for quoted replies _before_ media album handling, // since that logic may exit early. diff --git a/Session/Conversations/Message Cells/Content Views/CallMessageView.swift b/Session/Conversations/Message Cells/Content Views/CallMessageView.swift new file mode 100644 index 000000000..359d1ce98 --- /dev/null +++ b/Session/Conversations/Message Cells/Content Views/CallMessageView.swift @@ -0,0 +1,51 @@ + +final class CallMessageView : UIView { + private let viewItem: ConversationViewItem + private let textColor: UIColor + + // MARK: Settings + private static let iconSize: CGFloat = 24 + private static let iconImageViewSize: CGFloat = 40 + + // MARK: Lifecycle + init(viewItem: ConversationViewItem, textColor: UIColor) { + self.viewItem = viewItem + self.textColor = textColor + super.init(frame: CGRect.zero) + setUpViewHierarchy() + } + + override init(frame: CGRect) { + preconditionFailure("Use init(viewItem:textColor:) instead.") + } + + required init?(coder: NSCoder) { + preconditionFailure("Use init(viewItem:textColor:) instead.") + } + + private func setUpViewHierarchy() { + guard let message = viewItem.interaction as? TSMessage else { preconditionFailure() } + // Image view + let iconSize = CallMessageView.iconSize + let icon = UIImage(named: "Phone")?.withTint(textColor)?.resizedImage(to: CGSize(width: iconSize, height: iconSize)) + let imageView = UIImageView(image: icon) + imageView.contentMode = .center + let iconImageViewSize = CallMessageView.iconImageViewSize + imageView.set(.width, to: iconImageViewSize) + imageView.set(.height, to: iconImageViewSize) + // Body label + let titleLabel = UILabel() + titleLabel.lineBreakMode = .byTruncatingTail + titleLabel.text = message.body + titleLabel.textColor = textColor + titleLabel.font = .systemFont(ofSize: Values.mediumFontSize) + // Stack view + let stackView = UIStackView(arrangedSubviews: [ imageView, titleLabel ]) + stackView.axis = .horizontal + stackView.alignment = .center + stackView.isLayoutMarginsRelativeArrangement = true + stackView.layoutMargins = UIEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 12) + addSubview(stackView) + stackView.pin(to: self, withInset: Values.smallSpacing) + } +} diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 5d66f6401..db758e437 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -275,7 +275,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate { timerViewOutgoingMessageConstraint.isActive = (direction == .outgoing) timerViewIncomingMessageConstraint.isActive = (direction == .incoming) // Swipe to reply - if (message.isDeleted) { + if (message.isDeleted || message.isCallMessage) { removeGestureRecognizer(panGestureRecognizer) } else { addGestureRecognizer(panGestureRecognizer) @@ -398,6 +398,10 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate { let deletedMessageView = DeletedMessageView(viewItem: viewItem, textColor: bodyLabelTextColor) snContentView.addSubview(deletedMessageView) deletedMessageView.pin(to: snContentView) + case .callMessage: + let callMessageView = CallMessageView(viewItem: viewItem, textColor: bodyLabelTextColor) + snContentView.addSubview(callMessageView) + callMessageView.pin(to: snContentView) default: return } } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 0b9e70b2a..fb15fc6c9 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -19,7 +19,6 @@ extension AppDelegate { conversationVC.inputAccessoryView?.alpha = 0 presentingVC.present(callVC, animated: true, completion: nil) } else { - } } } diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index c9200293f..6fb19d3a3 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -572,3 +572,5 @@ "OPEN_SETTINGS_BUTTON" = "Settings"; "voice_call" = "Voice Call"; "video_call" = "Video Call"; +"call_outgoing" = "Outgoing Call"; +"call_incoming" = "Incoming Call"; diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 4e956b725..27996b1b4 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -119,6 +119,8 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { let message = CallMessage() message.kind = .offer message.sdps = [ sdp.sdp ] + let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) + tsMessage.save(with: transaction) MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 { seal.fulfill(()) }.catch2 { error in diff --git a/SessionMessagingKit/Messages/Signal/TSIncomingMessage+Conversion.swift b/SessionMessagingKit/Messages/Signal/TSIncomingMessage+Conversion.swift index 6849fd50c..b963f68f0 100644 --- a/SessionMessagingKit/Messages/Signal/TSIncomingMessage+Conversion.swift +++ b/SessionMessagingKit/Messages/Signal/TSIncomingMessage+Conversion.swift @@ -1,5 +1,26 @@ public extension TSIncomingMessage { + + static func from(_ callMessage: CallMessage, associatedWith thread: TSThread) -> TSIncomingMessage { + let sender = callMessage.sender! + let result = TSIncomingMessage( + timestamp: callMessage.sentTimestamp!, + in: thread, + authorId: sender, + sourceDeviceId: 1, + messageBody: NSLocalizedString("call_incoming", comment: ""), + attachmentIds: [], + expiresInSeconds: 0, + quotedMessage: nil, + linkPreview: nil, + wasReceivedByUD: true, + openGroupInvitationName: nil, + openGroupInvitationURL: nil, + serverHash: callMessage.serverHash + ) + result.isCallMessage = true + return result + } static func from(_ visibleMessage: VisibleMessage, quotedMessage: TSQuotedMessage?, linkPreview: OWSLinkPreview?, associatedWith thread: TSThread) -> TSIncomingMessage { let sender = visibleMessage.sender! diff --git a/SessionMessagingKit/Messages/Signal/TSMessage.h b/SessionMessagingKit/Messages/Signal/TSMessage.h index ff581bb16..b6096bd43 100644 --- a/SessionMessagingKit/Messages/Signal/TSMessage.h +++ b/SessionMessagingKit/Messages/Signal/TSMessage.h @@ -40,6 +40,7 @@ extern const NSUInteger kOversizeTextMessageSizeThreshold; @property (nonatomic, readonly, nullable) NSString *openGroupInvitationURL; @property (nonatomic, nullable) NSString *serverHash; @property (nonatomic) BOOL isDeleted; +@property (nonatomic) BOOL isCallMessage; - (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread NS_UNAVAILABLE; diff --git a/SessionMessagingKit/Messages/Signal/TSMessage.m b/SessionMessagingKit/Messages/Signal/TSMessage.m index 59548cae7..39ff5530c 100644 --- a/SessionMessagingKit/Messages/Signal/TSMessage.m +++ b/SessionMessagingKit/Messages/Signal/TSMessage.m @@ -87,6 +87,7 @@ const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024; _openGroupInvitationURL = openGroupInvitationURL; _serverHash = serverHash; _isDeleted = false; + _isCallMessage = false; return self; } diff --git a/SessionMessagingKit/Messages/Signal/TSOutgoingMessage+Conversion.swift b/SessionMessagingKit/Messages/Signal/TSOutgoingMessage+Conversion.swift index 1509af0f3..83ff29abd 100644 --- a/SessionMessagingKit/Messages/Signal/TSOutgoingMessage+Conversion.swift +++ b/SessionMessagingKit/Messages/Signal/TSOutgoingMessage+Conversion.swift @@ -2,6 +2,13 @@ import SessionUtilitiesKit @objc public extension TSOutgoingMessage { + @objc(fromCallOffer:associatedWith:) + static func from(_ callMessage: CallMessage, associatedWith thread: TSThread) -> TSOutgoingMessage { + let outgoingMessage = TSOutgoingMessage(in: thread, messageBody: NSLocalizedString("call_outgoing", comment: ""), attachmentId: nil) + outgoingMessage.isCallMessage = true + return outgoingMessage + } + @objc(from:associatedWith:) static func from(_ visibleMessage: VisibleMessage, associatedWith thread: TSThread) -> TSOutgoingMessage { return from(visibleMessage, associatedWith: thread, using: nil) diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index aa5a8de47..764476bd3 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -280,6 +280,13 @@ extension MessageReceiver { // Delegate to the main app, which is expected to show a dialog confirming // that the user wants to pick up the call. When they do, the SDP contained // in the offer message will be passed to WebRTCSession.handleRemoteSDP(_:from:). + let storage = SNMessagingKitConfiguration.shared.storage + let transaction = transaction as! YapDatabaseReadWriteTransaction + if let threadID = storage.getOrCreateThread(for: message.sender!, groupPublicKey: message.groupPublicKey, openGroupID: nil, using: transaction), + let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) { + let tsMessage = TSIncomingMessage.from(message, associatedWith: thread) + tsMessage.save(with: transaction) + } handleOfferCallMessage?(message) case .answer: print("[Calls] Received answer message.") From 219440f44454ba57368648496da6455ca91553fa Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 5 Oct 2021 13:41:39 +1100 Subject: [PATCH 057/368] update call protobuf --- Session/Calls/CallVC.swift | 6 ++-- SessionMessagingKit/Calls/WebRTCSession.swift | 23 +++++++++++++- .../Control Messages/CallMessage.swift | 22 ++++++++++---- .../Protos/Generated/SNProto.swift | 30 +++++++++++++++---- .../Protos/Generated/SessionProtos.pb.swift | 26 ++++++++++++++-- .../Protos/SessionProtos.proto | 3 ++ .../MessageReceiver+Handling.swift | 11 ++++--- 7 files changed, 101 insertions(+), 20 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index ed837b7bd..d54af1878 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -165,7 +165,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { init(for sessionID: String, mode: Mode) { self.sessionID = sessionID self.mode = mode - self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID) + self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID, with: UUID().uuidString) super.init(nibName: nil, bundle: nil) self.webRTCSession.delegate = self } @@ -187,7 +187,9 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { if case .offer = mode { callInfoLabel.text = "Ringing..." Storage.write { transaction in - self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() + self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction).done { + self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() + } } answerButton.isHidden = true } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 27996b1b4..b801dd825 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -11,6 +11,7 @@ public protocol WebRTCSessionDelegate : AnyObject { public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public weak var delegate: WebRTCSessionDelegate? private let contactSessionID: String + private let uuid: String private var queuedICECandidates: [RTCIceCandidate] = [] private var iceCandidateSendTimer: Timer? @@ -88,8 +89,9 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { // MARK: Initialization public static var current: WebRTCSession? - public init(for contactSessionID: String) { + public init(for contactSessionID: String, with uuid: String) { self.contactSessionID = contactSessionID + self.uuid = uuid super.init() let mediaStreamTrackIDS = ["ARDAMS"] createDataChannel() @@ -100,6 +102,24 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } // MARK: Signaling + public func sendPreOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { + print("[Calls] Sending pre-offer message.") + guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } + let (promise, seal) = Promise.pending() + DispatchQueue.main.async { + let message = CallMessage() + message.uuid = self.uuid + message.kind = .preOffer + MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 { + print("[Calls] Pre-offer message has been sent.") + seal.fulfill(()) + }.catch2 { error in + seal.reject(error) + } + } + return promise + } + public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { print("[Calls] Sending offer message.") guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } @@ -117,6 +137,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } DispatchQueue.main.async { let message = CallMessage() + message.uuid = self.uuid message.kind = .offer message.sdps = [ sdp.sdp ] let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) diff --git a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift index 19455a24f..cd3609991 100644 --- a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift @@ -3,6 +3,7 @@ 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 uuid: String? public var kind: Kind? /// See https://developer.mozilla.org/en-US/docs/Glossary/SDP for more information. public var sdps: [String]? @@ -13,6 +14,7 @@ public final class CallMessage : ControlMessage { // MARK: Kind public enum Kind : Codable, CustomStringConvertible { + case preOffer case offer case answer case provisionalAnswer @@ -21,6 +23,7 @@ public final class CallMessage : ControlMessage { public var description: String { switch self { + case .preOffer: return "preOffer" case .offer: return "offer" case .answer: return "answer" case .provisionalAnswer: return "provisionalAnswer" @@ -33,8 +36,9 @@ public final class CallMessage : ControlMessage { // MARK: Initialization public override init() { super.init() } - internal init(kind: Kind, sdps: [String]) { + internal init(uuid: String, kind: Kind, sdps: [String]) { super.init() + self.uuid = uuid self.kind = kind self.sdps = sdps } @@ -42,7 +46,7 @@ public final class CallMessage : ControlMessage { // MARK: Validation public override var isValid: Bool { guard super.isValid else { return false } - return kind != nil + return kind != nil && uuid != nil } // MARK: Coding @@ -50,6 +54,7 @@ public final class CallMessage : ControlMessage { super.init(coder: coder) guard let rawKind = coder.decodeObject(forKey: "kind") as! String? else { return nil } switch rawKind { + case "preOffer": kind = .preOffer case "offer": kind = .offer case "answer": kind = .answer case "provisionalAnswer": kind = .provisionalAnswer @@ -61,11 +66,13 @@ public final class CallMessage : ControlMessage { default: preconditionFailure() } if let sdps = coder.decodeObject(forKey: "sdps") as! [String]? { self.sdps = sdps } + if let uuid = coder.decodeObject(forKey: "uuid") as! String? { self.uuid = uuid } } public override func encode(with coder: NSCoder) { super.encode(with: coder) switch kind { + case .preOffer: coder.encode("preOffer", forKey: "kind") case .offer: coder.encode("offer", forKey: "kind") case .answer: coder.encode("answer", forKey: "kind") case .provisionalAnswer: coder.encode("provisionalAnswer", forKey: "kind") @@ -77,6 +84,7 @@ public final class CallMessage : ControlMessage { default: preconditionFailure() } coder.encode(sdps, forKey: "sdps") + coder.encode(uuid, forKey: "uuid") } // MARK: Proto Conversion @@ -84,6 +92,7 @@ public final class CallMessage : ControlMessage { guard let callMessageProto = proto.callMessage else { return nil } let kind: Kind switch callMessageProto.type { + case .preOffer: kind = .preOffer case .offer: kind = .offer case .answer: kind = .answer case .provisionalAnswer: kind = .provisionalAnswer @@ -94,23 +103,25 @@ public final class CallMessage : ControlMessage { case .endCall: kind = .endCall } let sdps = callMessageProto.sdps - return CallMessage(kind: kind, sdps: sdps) + let uuid = callMessageProto.uuid + return CallMessage(uuid: uuid, kind: kind, sdps: sdps) } public override func toProto(using transaction: YapDatabaseReadWriteTransaction) -> SNProtoContent? { - guard let kind = kind else { + guard let kind = kind, let uuid = uuid else { SNLog("Couldn't construct call message proto from: \(self).") return nil } let type: SNProtoCallMessage.SNProtoCallMessageType switch kind { + case .preOffer: type = .preOffer case .offer: type = .offer case .answer: type = .answer case .provisionalAnswer: type = .provisionalAnswer case .iceCandidates(_, _): type = .iceCandidates case .endCall: type = .endCall } - let callMessageProto = SNProtoCallMessage.builder(type: type) + let callMessageProto = SNProtoCallMessage.builder(type: type, uuid: uuid) if let sdps = sdps, !sdps.isEmpty { callMessageProto.setSdps(sdps) } @@ -132,6 +143,7 @@ public final class CallMessage : ControlMessage { public override var description: String { """ CallMessage( + uuid: \(uuid ?? "null"), kind: \(kind?.description ?? "null"), sdps: \(sdps?.description ?? "null") ) diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index dae62350a..e4263501a 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -658,6 +658,7 @@ extension SNProtoContent.SNProtoContentBuilder { case provisionalAnswer = 3 case iceCandidates = 4 case endCall = 5 + case preOffer = 6 } private class func SNProtoCallMessageTypeWrap(_ value: SessionProtos_CallMessage.TypeEnum) -> SNProtoCallMessageType { @@ -667,6 +668,7 @@ extension SNProtoContent.SNProtoContentBuilder { case .provisionalAnswer: return .provisionalAnswer case .iceCandidates: return .iceCandidates case .endCall: return .endCall + case .preOffer: return .preOffer } } @@ -677,18 +679,19 @@ extension SNProtoContent.SNProtoContentBuilder { case .provisionalAnswer: return .provisionalAnswer case .iceCandidates: return .iceCandidates case .endCall: return .endCall + case .preOffer: return .preOffer } } // MARK: - SNProtoCallMessageBuilder - @objc public class func builder(type: SNProtoCallMessageType) -> SNProtoCallMessageBuilder { - return SNProtoCallMessageBuilder(type: type) + @objc public class func builder(type: SNProtoCallMessageType, uuid: String) -> SNProtoCallMessageBuilder { + return SNProtoCallMessageBuilder(type: type, uuid: uuid) } // asBuilder() constructs a builder that reflects the proto's contents. @objc public func asBuilder() -> SNProtoCallMessageBuilder { - let builder = SNProtoCallMessageBuilder(type: type) + let builder = SNProtoCallMessageBuilder(type: type, uuid: uuid) builder.setSdps(sdps) builder.setSdpMlineIndexes(sdpMlineIndexes) builder.setSdpMids(sdpMids) @@ -701,10 +704,11 @@ extension SNProtoContent.SNProtoContentBuilder { @objc fileprivate override init() {} - @objc fileprivate init(type: SNProtoCallMessageType) { + @objc fileprivate init(type: SNProtoCallMessageType, uuid: String) { super.init() setType(type) + setUuid(uuid) } @objc public func setType(_ valueParam: SNProtoCallMessageType) { @@ -741,6 +745,10 @@ extension SNProtoContent.SNProtoContentBuilder { proto.sdpMids = wrappedItems } + @objc public func setUuid(_ valueParam: String) { + proto.uuid = valueParam + } + @objc public func build() throws -> SNProtoCallMessage { return try SNProtoCallMessage.parseProto(proto) } @@ -754,6 +762,8 @@ extension SNProtoContent.SNProtoContentBuilder { @objc public let type: SNProtoCallMessageType + @objc public let uuid: String + @objc public var sdps: [String] { return proto.sdps } @@ -767,9 +777,11 @@ extension SNProtoContent.SNProtoContentBuilder { } private init(proto: SessionProtos_CallMessage, - type: SNProtoCallMessageType) { + type: SNProtoCallMessageType, + uuid: String) { self.proto = proto self.type = type + self.uuid = uuid } @objc @@ -788,12 +800,18 @@ extension SNProtoContent.SNProtoContentBuilder { } let type = SNProtoCallMessageTypeWrap(proto.type) + guard proto.hasUuid else { + throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: uuid") + } + let uuid = proto.uuid + // MARK: - Begin Validation Logic for SNProtoCallMessage - // MARK: - End Validation Logic for SNProtoCallMessage - let result = SNProtoCallMessage(proto: proto, - type: type) + type: type, + uuid: uuid) return result } diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index 7b4894f60..b3b69166d 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -311,7 +311,7 @@ struct SessionProtos_CallMessage { /// @required var type: SessionProtos_CallMessage.TypeEnum { - get {return _type ?? .offer} + get {return _type ?? .preOffer} set {_type = newValue} } /// Returns true if `type` has been explicitly set. @@ -325,10 +325,21 @@ struct SessionProtos_CallMessage { var sdpMids: [String] = [] + /// @required + var uuid: String { + get {return _uuid ?? String()} + set {_uuid = newValue} + } + /// Returns true if `uuid` has been explicitly set. + var hasUuid: Bool {return self._uuid != nil} + /// Clears the value of `uuid`. Subsequent reads from it will return its default value. + mutating func clearUuid() {self._uuid = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() enum TypeEnum: SwiftProtobuf.Enum { typealias RawValue = Int + case preOffer // = 6 case offer // = 1 case answer // = 2 case provisionalAnswer // = 3 @@ -336,7 +347,7 @@ struct SessionProtos_CallMessage { case endCall // = 5 init() { - self = .offer + self = .preOffer } init?(rawValue: Int) { @@ -346,6 +357,7 @@ struct SessionProtos_CallMessage { case 3: self = .provisionalAnswer case 4: self = .iceCandidates case 5: self = .endCall + case 6: self = .preOffer default: return nil } } @@ -357,6 +369,7 @@ struct SessionProtos_CallMessage { case .provisionalAnswer: return 3 case .iceCandidates: return 4 case .endCall: return 5 + case .preOffer: return 6 } } @@ -365,6 +378,7 @@ struct SessionProtos_CallMessage { init() {} fileprivate var _type: SessionProtos_CallMessage.TypeEnum? = nil + fileprivate var _uuid: String? = nil } #if swift(>=4.2) @@ -1798,10 +1812,12 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa 2: .same(proto: "sdps"), 3: .same(proto: "sdpMLineIndexes"), 4: .same(proto: "sdpMids"), + 5: .same(proto: "uuid"), ] public var isInitialized: Bool { if self._type == nil {return false} + if self._uuid == nil {return false} return true } @@ -1815,6 +1831,7 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa case 2: try { try decoder.decodeRepeatedStringField(value: &self.sdps) }() case 3: try { try decoder.decodeRepeatedUInt32Field(value: &self.sdpMlineIndexes) }() case 4: try { try decoder.decodeRepeatedStringField(value: &self.sdpMids) }() + case 5: try { try decoder.decodeSingularStringField(value: &self._uuid) }() default: break } } @@ -1833,6 +1850,9 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa if !self.sdpMids.isEmpty { try visitor.visitRepeatedStringField(value: self.sdpMids, fieldNumber: 4) } + if let v = self._uuid { + try visitor.visitSingularStringField(value: v, fieldNumber: 5) + } try unknownFields.traverse(visitor: &visitor) } @@ -1841,6 +1861,7 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa if lhs.sdps != rhs.sdps {return false} if lhs.sdpMlineIndexes != rhs.sdpMlineIndexes {return false} if lhs.sdpMids != rhs.sdpMids {return false} + if lhs._uuid != rhs._uuid {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1853,6 +1874,7 @@ extension SessionProtos_CallMessage.TypeEnum: SwiftProtobuf._ProtoNameProviding 3: .same(proto: "PROVISIONAL_ANSWER"), 4: .same(proto: "ICE_CANDIDATES"), 5: .same(proto: "END_CALL"), + 6: .same(proto: "PRE_OFFER"), ] } diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index 4193d202c..052ae7e5f 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -54,6 +54,7 @@ message Content { message CallMessage { enum Type { + PRE_OFFER = 6; OFFER = 1; ANSWER = 2; PROVISIONAL_ANSWER = 3; @@ -68,6 +69,8 @@ message CallMessage { repeated string sdps = 2; repeated uint32 sdpMLineIndexes = 3; repeated string sdpMids = 4; + // @required + required string uuid = 5; } message KeyPair { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 94196ac00..bf5263d50 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -269,17 +269,17 @@ extension MessageReceiver { if let current = WebRTCSession.current { result = current } else { - WebRTCSession.current = WebRTCSession(for: message.sender!) + WebRTCSession.current = WebRTCSession(for: message.sender!, with: message.uuid!) result = WebRTCSession.current! } return result } switch message.kind! { + case .preOffer: + print("[Calls] Received pre-offer message.") + // TODO: Notify incoming call case .offer: print("[Calls] Received offer message.") - // Delegate to the main app, which is expected to show a dialog confirming - // that the user wants to pick up the call. When they do, the SDP contained - // in the offer message will be passed to WebRTCSession.handleRemoteSDP(_:from:). let storage = SNMessagingKitConfiguration.shared.storage let transaction = transaction as! YapDatabaseReadWriteTransaction if let threadID = storage.getOrCreateThread(for: message.sender!, groupPublicKey: message.groupPublicKey, openGroupID: nil, using: transaction), @@ -287,6 +287,9 @@ extension MessageReceiver { let tsMessage = TSIncomingMessage.from(message, associatedWith: thread) tsMessage.save(with: transaction) } + // Delegate to the main app, which is expected to show a dialog confirming + // that the user wants to pick up the call. When they do, the SDP contained + // in the offer message will be passed to WebRTCSession.handleRemoteSDP(_:from:). handleOfferCallMessage?(message) case .answer: print("[Calls] Received answer message.") From 0684e5250ddeb5db0b5a80af212ab30a0a78643f Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 5 Oct 2021 16:39:52 +1100 Subject: [PATCH 058/368] use data channel to communicate video enabling status --- Session/Calls/CallVC.swift | 8 ++++++-- .../Calls/WebRTCSession+DataChannel.swift | 8 +++++--- SessionMessagingKit/Calls/WebRTCSession.swift | 9 ++++++--- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index d54af1878..f3d0f3923 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -189,7 +189,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { Storage.write { transaction in self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction).done { self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() - } + }.retainUntilComplete() } answerButton.isHidden = true } @@ -274,7 +274,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } // MARK: Delegate - func webRTCDidConnected() { + func webRTCIsConnected() { DispatchQueue.main.async { self.callInfoLabel.text = "Connected" UIView.animate(withDuration: 0.5, delay: 1, options: [], animations: { @@ -286,6 +286,10 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } } + func isRemoteVideoDidChange(isEnabled: Bool) { + remoteVideoView.isHidden = !isEnabled + } + // MARK: Interaction func handleAnswerMessage(_ message: CallMessage) { callInfoLabel.text = "Connecting..." diff --git a/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift b/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift index 41a1e6aa5..2f22843ce 100644 --- a/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift +++ b/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift @@ -14,7 +14,7 @@ extension WebRTCSession: RTCDataChannelDelegate { } public func sendJSON(_ json: JSON) { - if let dataChannel = self.dataChannel, let jsonAsData = try? JSONSerialization.data(withJSONObject: json, options: [ .fragmentsAllowed ]) { + if let dataChannel = dataChannel, let jsonAsData = try? JSONSerialization.data(withJSONObject: json, options: [ .fragmentsAllowed ]) { let dataBuffer = RTCDataBuffer(data: jsonAsData, isBinary: false) dataChannel.sendData(dataBuffer) } @@ -27,8 +27,10 @@ extension WebRTCSession: RTCDataChannelDelegate { public func dataChannel(_ dataChannel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) { print("[Calls] Data channel did receive data: \(buffer)") - if let json = try? JSONSerialization.jsonObject(with: buffer.data, options: [ .fragmentsAllowed ]) { - + if let json = try? JSONSerialization.jsonObject(with: buffer.data, options: [ .fragmentsAllowed ]) as? JSON { + if let isRemoteVideoEnabled = json["video"] as? Bool { + delegate?.isRemoteVideoDidChange(isEnabled: isRemoteVideoEnabled) + } } } } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index b801dd825..e0e00ab30 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -4,7 +4,8 @@ import WebRTC public protocol WebRTCSessionDelegate : AnyObject { var videoCapturer: RTCVideoCapturer { get } - func webRTCDidConnected() + func webRTCIsConnected() + func isRemoteVideoDidChange(isEnabled: Bool) } /// See https://webrtc.org/getting-started/overview for more information. @@ -244,7 +245,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCIceConnectionState) { print("[Calls] ICE connection state changed to: \(state).") if state == .connected { - delegate?.webRTCDidConnected() + delegate?.webRTCIsConnected() } } @@ -290,9 +291,11 @@ extension WebRTCSession { public func turnOffVideo() { localVideoTrack.isEnabled = false + sendJSON(["video": false]) } - public func turnOnVideo() { + public func turnOnVideo() { localVideoTrack.isEnabled = true + sendJSON(["video": true]) } } From 98268ebf73d82ea030beb57d6e3328ac5b329223 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 6 Oct 2021 16:34:24 +1100 Subject: [PATCH 059/368] hide outgoing call message sending status --- .../Calls/Views & Modals/IncomingCallBanner.swift | 15 +++++++++++++++ .../Message Cells/VisibleMessageCell.swift | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 Session/Calls/Views & Modals/IncomingCallBanner.swift diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift new file mode 100644 index 000000000..cc71c7b5e --- /dev/null +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -0,0 +1,15 @@ +// Copyright © 2021 Rangeproof Pty Ltd. All rights reserved. + +import UIKit + +class IncomingCallBanner: UIView { + + /* + // Only override draw() if you perform custom drawing. + // An empty implementation adversely affects performance during animation. + override func draw(_ rect: CGRect) { + // Drawing code + } + */ + +} diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index db758e437..29f35bcfa 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -257,7 +257,7 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate { messageStatusImageView.image = image messageStatusImageView.backgroundColor = backgroundColor if let message = message as? TSOutgoingMessage { - messageStatusImageView.isHidden = (message.messageState == .sent && message.thread.lastInteraction != message) + messageStatusImageView.isHidden = (message.isCallMessage || message.messageState == .sent && message.thread.lastInteraction != message) } else { messageStatusImageView.isHidden = true } From fbe5b12c9d008da4b74f358fb273309e9b1f4bee Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 6 Oct 2021 17:00:12 +1100 Subject: [PATCH 060/368] WIP: incoming call banner --- Session.xcodeproj/project.pbxproj | 12 ++ Session/Calls/CallVC.swift | 10 +- .../Views & Modals/IncomingCallBanner.swift | 146 ++++++++++++++++-- .../ConversationVC+Interaction.swift | 15 +- Session/Conversations/ConversationVC.swift | 1 - Session/Meta/AppDelegate.swift | 13 +- SessionMessagingKit/Calls/WebRTCSession.swift | 8 +- .../MessageReceiver+Handling.swift | 5 +- 8 files changed, 179 insertions(+), 31 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 19035d934..b4e6571f4 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -140,6 +140,7 @@ 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; }; 7B7CB189270430D20079FF93 /* CallMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB188270430D20079FF93 /* CallMessageView.swift */; }; 7B7CB18B270591630079FF93 /* ShareLogsModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18A270591630079FF93 /* ShareLogsModal.swift */; }; + 7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */; }; 7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; }; 7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; }; @@ -1116,6 +1117,7 @@ 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = ""; }; 7B7CB188270430D20079FF93 /* CallMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageView.swift; sourceTree = ""; }; 7B7CB18A270591630079FF93 /* ShareLogsModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareLogsModal.swift; sourceTree = ""; }; + 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallBanner.swift; sourceTree = ""; }; 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; 7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -2040,6 +2042,14 @@ path = Utilities; sourceTree = ""; }; + 7B7CB18C270D06350079FF93 /* Views & Modals */ = { + isa = PBXGroup; + children = ( + 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */, + ); + path = "Views & Modals"; + sourceTree = ""; + }; 7BC01A3C241F40AB00BC7C55 /* SessionNotificationServiceExtension */ = { isa = PBXGroup; children = ( @@ -2334,6 +2344,7 @@ B877E24126CA12910007970A /* CallVC.swift */, B877E24526CA13BA0007970A /* CallVC+Camera.swift */, B8B558F026C4BB0600693325 /* CameraManager.swift */, + 7B7CB18C270D06350079FF93 /* Views & Modals */, ); path = Calls; sourceTree = ""; @@ -4928,6 +4939,7 @@ C31D1DE9252172D4005D4DA8 /* ContactUtilities.swift in Sources */, 4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */, 340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */, + 7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */, B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */, B85357BF23A1AE0800AAF6CD /* SeedReminderView.swift in Sources */, B821494F25D4E163009C0F2A /* BodyTextView.swift in Sources */, diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index f3d0f3923..be1cb7252 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -6,8 +6,10 @@ import UIKit final class CallVC : UIViewController, WebRTCSessionDelegate { let sessionID: String + let uuid: String let mode: Mode let webRTCSession: WebRTCSession + var shouldAnswer = false var isMuted = false var isVideoEnabled = false var conversationVC: ConversationVC? = nil @@ -162,10 +164,11 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } // MARK: Lifecycle - init(for sessionID: String, mode: Mode) { + init(for sessionID: String, uuid: String, mode: Mode) { self.sessionID = sessionID + self.uuid = uuid self.mode = mode - self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID, with: UUID().uuidString) + self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID, with: uuid) super.init(nibName: nil, bundle: nil) self.webRTCSession.delegate = self } @@ -193,6 +196,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } answerButton.isHidden = true } + if shouldAnswer { answerCall() } } func setUpViewHierarchy() { @@ -299,8 +303,6 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { print("[Calls] Ending call.") callInfoLabel.isHidden = false callInfoLabel.text = "Call Ended" - WebRTCSession.current?.dropConnection() - WebRTCSession.current = nil UIView.animate(withDuration: 0.25) { self.remoteVideoView.alpha = 0 } diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index cc71c7b5e..ca63e881d 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -1,15 +1,141 @@ -// Copyright © 2021 Rangeproof Pty Ltd. All rights reserved. - import UIKit +import WebRTC +import SessionMessagingKit -class IncomingCallBanner: UIView { - - /* - // Only override draw() if you perform custom drawing. - // An empty implementation adversely affects performance during animation. - override func draw(_ rect: CGRect) { - // Drawing code +final class IncomingCallBanner: UIView { + let sessionID: String + let uuid: String + let sdp: RTCSessionDescription + + // MARK: UI Components + private lazy var profilePictureView: ProfilePictureView = { + let result = ProfilePictureView() + let size = CGFloat(60) + result.size = size + result.set(.width, to: size) + result.set(.height, to: size) + return result + }() + + private lazy var displayNameLabel: UILabel = { + let result = UILabel() + result.textColor = UIColor.white + result.font = .boldSystemFont(ofSize: Values.largeFontSize) + result.lineBreakMode = .byTruncatingTail + result.textAlignment = .center + return result + }() + + private lazy var answerButton: UIButton = { + let result = UIButton(type: .custom) + let image = UIImage(named: "AnswerCall")!.withTint(.white)?.resizedImage(to: CGSize(width: 24.8, height: 24.8)) + result.setImage(image, for: UIControl.State.normal) + result.set(.width, to: 48) + result.set(.height, to: 48) + result.backgroundColor = Colors.accent + result.layer.cornerRadius = 24 + result.addTarget(self, action: #selector(answerCall), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var hangUpButton: UIButton = { + let result = UIButton(type: .custom) + let image = UIImage(named: "EndCall")!.withTint(.white)?.resizedImage(to: CGSize(width: 29.6, height: 11.2)) + result.setImage(image, for: UIControl.State.normal) + result.set(.width, to: 48) + result.set(.height, to: 48) + result.backgroundColor = Colors.destructive + result.layer.cornerRadius = 24 + result.addTarget(self, action: #selector(endCall), for: UIControl.Event.touchUpInside) + return result + }() + + // MARK: Initialization + public static var current: IncomingCallBanner? + + init(for sessionID: String, uuid: String, sdp: RTCSessionDescription) { + self.uuid = uuid + self.sessionID = sessionID + self.sdp = sdp + super.init(frame: CGRect.zero) + setUpViewHierarchy() + if let incomingCallBanner = IncomingCallBanner.current { + incomingCallBanner.dismiss() + } + IncomingCallBanner.current = self + } + + override init(frame: CGRect) { + preconditionFailure("Use init(message:) instead.") + } + + required init?(coder: NSCoder) { + preconditionFailure("Use init(coder:) instead.") + } + + private func setUpViewHierarchy() { + self.backgroundColor = UIColor(hex: 0x000000).withAlphaComponent(0.9) + self.layer.cornerRadius = Values.veryLargeSpacing + self.layer.masksToBounds = true + self.set(.height, to: 100) + profilePictureView.publicKey = self.sessionID + profilePictureView.update() + displayNameLabel.text = Storage.shared.getContact(with: sessionID)?.name + let stackView = UIStackView(arrangedSubviews: [profilePictureView, displayNameLabel, UIView.hStretchingSpacer(), hangUpButton, answerButton]) + stackView.axis = .horizontal + stackView.alignment = .center + stackView.spacing = Values.largeSpacing + self.addSubview(stackView) + stackView.center(.vertical, in: self) + stackView.autoPinWidthToSuperview(withMargin: Values.mediumSpacing) + } + + @objc private func answerCall() { + showCallVC(answer: true) + } + + @objc private func endCall() { + Storage.write { transaction in + WebRTCSession.current?.endCall(with: self.sessionID, using: transaction) + } + dismiss() + } + + public func showCallVC(answer: Bool) { + dismiss() + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + let callVC = CallVC(for: sessionID, uuid: uuid, mode: .answer(sdp: sdp)) + callVC.shouldAnswer = answer + callVC.modalPresentationStyle = .overFullScreen + callVC.modalTransitionStyle = .crossDissolve + if let conversationVC = presentingVC as? ConversationVC { + callVC.conversationVC = conversationVC + conversationVC.inputAccessoryView?.isHidden = true + conversationVC.inputAccessoryView?.alpha = 0 + } + presentingVC.present(callVC, animated: true, completion: nil) + } + + public func show() { + self.alpha = 0.0 + let window = CurrentAppContext().mainWindow! + window.addSubview(self) + let topMargin = UIApplication.shared.keyWindow!.safeAreaInsets.top - Values.smallSpacing + self.autoPinWidthToSuperview(withMargin: Values.smallSpacing) + self.autoPinEdge(toSuperviewEdge: .top, withInset: topMargin) + UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: { + self.alpha = 1.0 + }, completion: nil) + AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) + } + + public func dismiss() { + UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: { + self.alpha = 0.0 + }, completion: { _ in + IncomingCallBanner.current = nil + self.removeFromSuperview() + }) } - */ } diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 3ebabf9c8..faad7c3be 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -29,7 +29,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc // MARK: Call @objc func startCall(_ sender: Any) { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } - let callVC = CallVC(for: contactSessionID, mode: .offer) + let callVC = CallVC(for: contactSessionID, uuid: UUID().uuidString, mode: .offer) callVC.conversationVC = self callVC.modalPresentationStyle = .overFullScreen callVC.modalTransitionStyle = .crossDissolve @@ -39,15 +39,10 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc } internal func showCallVCIfNeeded() { - guard hasIncomingCall, let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } - hasIncomingCall = false - let callVC = CallVC(for: contactSessionID, mode: .offer) // TODO: change to answer - callVC.conversationVC = self - callVC.modalPresentationStyle = .overFullScreen - callVC.modalTransitionStyle = .crossDissolve - self.inputAccessoryView?.isHidden = true - self.inputAccessoryView?.alpha = 0 - present(callVC, animated: true, completion: nil) + guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID(), + let incomingCallBanner = IncomingCallBanner.current, incomingCallBanner.sessionID == contactSessionID + else { return } + incomingCallBanner.showCallVC(answer: false) } // MARK: Blocking diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 9181aeb90..fcaf3f5c6 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -10,7 +10,6 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat let focusedMessageID: String? // This isn't actually used ATM var unreadViewItems: [ConversationViewItem] = [] var scrollButtonConstraint: NSLayoutConstraint? - var hasIncomingCall = false // Search var isShowingSearchUI = false var lastSearchedText: String? diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index fb15fc6c9..764e36fdd 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -1,5 +1,7 @@ import PromiseKit import WebRTC +import SessionUIKit +import UIKit extension AppDelegate { @@ -11,7 +13,7 @@ extension AppDelegate { let sdp = RTCSessionDescription(type: .offer, sdp: message.sdps![0]) guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == message.sender! { - let callVC = CallVC(for: message.sender!, mode: .answer(sdp: sdp)) + let callVC = CallVC(for: message.sender!, uuid: message.uuid!, mode: .answer(sdp: sdp)) callVC.modalPresentationStyle = .overFullScreen callVC.modalTransitionStyle = .crossDissolve callVC.conversationVC = conversationVC @@ -19,19 +21,24 @@ extension AppDelegate { conversationVC.inputAccessoryView?.alpha = 0 presentingVC.present(callVC, animated: true, completion: nil) } else { + let incomingCallBanner = IncomingCallBanner(for: message.sender!, uuid: message.uuid!, sdp: sdp) + incomingCallBanner.show() } } } // Answer messages MessageReceiver.handleAnswerCallMessage = { message in DispatchQueue.main.async { - guard let callVC = CurrentAppContext().frontmostViewController() as? CallVC else { return } - callVC.handleAnswerMessage(message) + if let incomingCallBanner = IncomingCallBanner.current, incomingCallBanner.uuid == message.uuid! { incomingCallBanner.dismiss() } + if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleAnswerMessage(message) } + WebRTCSession.current?.dropConnection() + WebRTCSession.current = nil } } // End call messages MessageReceiver.handleEndCallMessage = { message in DispatchQueue.main.async { + if let currentBanner = IncomingCallBanner.current { currentBanner.dismiss() } guard let callVC = CurrentAppContext().frontmostViewController() as? CallVC else { return } callVC.handleEndCallMessage(message) } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index e0e00ab30..d01e8a47c 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -11,8 +11,8 @@ public protocol WebRTCSessionDelegate : AnyObject { /// See https://webrtc.org/getting-started/overview for more information. public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public weak var delegate: WebRTCSessionDelegate? + public let uuid: String private let contactSessionID: String - private let uuid: String private var queuedICECandidates: [RTCIceCandidate] = [] private var iceCandidateSendTimer: Timer? @@ -138,6 +138,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } DispatchQueue.main.async { let message = CallMessage() + message.sentTimestamp = NSDate.millisecondTimestamp() message.uuid = self.uuid message.kind = .offer message.sdps = [ sdp.sdp ] @@ -171,6 +172,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } DispatchQueue.main.async { let message = CallMessage() + message.uuid = self.uuid message.kind = .answer message.sdps = [ sdp.sdp ] MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 { @@ -203,6 +205,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { let sdps = candidates.map { $0.sdp } let sdpMLineIndexes = candidates.map { UInt32($0.sdpMLineIndex) } let sdpMids = candidates.map { $0.sdpMid! } + message.uuid = self.uuid message.kind = .iceCandidates(sdpMLineIndexes: sdpMLineIndexes, sdpMids: sdpMids) message.sdps = sdps self.queuedICECandidates.removeAll() @@ -213,6 +216,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public func endCall(with sessionID: String, using transaction: YapDatabaseReadWriteTransaction) { guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return } let message = CallMessage() + message.uuid = self.uuid message.kind = .endCall print("[Calls] Sending end call message.") MessageSender.sendNonDurably(message, in: thread, using: transaction).retainUntilComplete() @@ -294,7 +298,7 @@ extension WebRTCSession { sendJSON(["video": false]) } - public func turnOnVideo() { + public func turnOnVideo() { localVideoTrack.isEnabled = true sendJSON(["video": true]) } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index bf5263d50..6dc8ff062 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -277,7 +277,10 @@ extension MessageReceiver { switch message.kind! { case .preOffer: print("[Calls] Received pre-offer message.") - // TODO: Notify incoming call + let currentSession = getWebRTCSession() + if currentSession.uuid != message.uuid! { + // TODO: Call in progress, put the new call on hold/reject + } case .offer: print("[Calls] Received offer message.") let storage = SNMessagingKitConfiguration.shared.storage From 0f957efcd82494c45c486a8ccead468c37f101f9 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 7 Oct 2021 16:53:38 +1100 Subject: [PATCH 061/368] minor fix --- Session/Meta/AppDelegate.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 764e36fdd..3d1b56ba5 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -29,18 +29,17 @@ extension AppDelegate { // Answer messages MessageReceiver.handleAnswerCallMessage = { message in DispatchQueue.main.async { - if let incomingCallBanner = IncomingCallBanner.current, incomingCallBanner.uuid == message.uuid! { incomingCallBanner.dismiss() } - if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleAnswerMessage(message) } - WebRTCSession.current?.dropConnection() - WebRTCSession.current = nil + guard let callVC = CurrentAppContext().frontmostViewController() as? CallVC else { return } + callVC.handleAnswerMessage(message) } } // End call messages MessageReceiver.handleEndCallMessage = { message in DispatchQueue.main.async { if let currentBanner = IncomingCallBanner.current { currentBanner.dismiss() } - guard let callVC = CurrentAppContext().frontmostViewController() as? CallVC else { return } - callVC.handleEndCallMessage(message) + if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleEndCallMessage(message) } + WebRTCSession.current?.dropConnection() + WebRTCSession.current = nil } } } From 2cc638cc3e69b5947963b52f9be52f0112ce4dfa Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 7 Oct 2021 16:53:57 +1100 Subject: [PATCH 062/368] add gesture to incoming call banner --- .../Views & Modals/IncomingCallBanner.swift | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index ca63e881d..dbc037139 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -2,7 +2,9 @@ import UIKit import WebRTC import SessionMessagingKit -final class IncomingCallBanner: UIView { +final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { + private static let swipeToOperateThreshold: CGFloat = 60 + private var previousY: CGFloat = 0 let sessionID: String let uuid: String let sdp: RTCSessionDescription @@ -50,6 +52,12 @@ final class IncomingCallBanner: UIView { return result }() + private lazy var panGestureRecognizer: UIPanGestureRecognizer = { + let result = UIPanGestureRecognizer(target: self, action: #selector(handlePan)) + result.delegate = self + return result + }() + // MARK: Initialization public static var current: IncomingCallBanner? @@ -59,6 +67,7 @@ final class IncomingCallBanner: UIView { self.sdp = sdp super.init(frame: CGRect.zero) setUpViewHierarchy() + setUpGestureRecognizers() if let incomingCallBanner = IncomingCallBanner.current { incomingCallBanner.dismiss() } @@ -90,6 +99,47 @@ final class IncomingCallBanner: UIView { stackView.autoPinWidthToSuperview(withMargin: Values.mediumSpacing) } + private func setUpGestureRecognizers() { + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) + tapGestureRecognizer.numberOfTapsRequired = 1 + addGestureRecognizer(tapGestureRecognizer) + addGestureRecognizer(panGestureRecognizer) + } + + // MARK: Interaction + override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer == panGestureRecognizer { + let v = panGestureRecognizer.velocity(in: self) + return abs(v.y) > abs(v.x) // It has to be more vertical than horizontal + } else { + return true + } + } + + @objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { + showCallVC(answer: false) + } + + @objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) { + let translationY = gestureRecognizer.translation(in: self).y + switch gestureRecognizer.state { + case .changed: + self.transform = CGAffineTransform(translationX: 0, y: min(translationY, IncomingCallBanner.swipeToOperateThreshold)) + if abs(translationY) > IncomingCallBanner.swipeToOperateThreshold && abs(previousY) < IncomingCallBanner.swipeToOperateThreshold { + UIImpactFeedbackGenerator(style: .heavy).impactOccurred() // Let the user know when they've hit the swipe to reply threshold + } + previousY = translationY + case .ended, .cancelled: + if abs(translationY) > IncomingCallBanner.swipeToOperateThreshold { + if translationY > 0 { showCallVC(answer: false) } + else { endCall() } // TODO: Or just put the call on hold? + } else { + self.transform = .identity + } + default: break + } + } + @objc private func answerCall() { showCallVC(answer: true) } From a1f8e16eb3e1e948d3b0d0c9c294e9fcb4f42672 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 11 Oct 2021 13:14:39 +1100 Subject: [PATCH 063/368] WIP: add mini call floating view --- Session.xcodeproj/project.pbxproj | 4 + Session/Calls/CallVC.swift | 11 +- .../Calls/Views & Modals/MiniCallView.swift | 138 ++++++++++++++++++ Session/Meta/AppDelegate.swift | 1 + SessionMessagingKit/Calls/WebRTCSession.swift | 4 +- 5 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 Session/Calls/Views & Modals/MiniCallView.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index b4e6571f4..b314e561d 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -141,6 +141,7 @@ 7B7CB189270430D20079FF93 /* CallMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB188270430D20079FF93 /* CallMessageView.swift */; }; 7B7CB18B270591630079FF93 /* ShareLogsModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18A270591630079FF93 /* ShareLogsModal.swift */; }; 7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */; }; + 7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */; }; 7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; }; 7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; }; @@ -1118,6 +1119,7 @@ 7B7CB188270430D20079FF93 /* CallMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageView.swift; sourceTree = ""; }; 7B7CB18A270591630079FF93 /* ShareLogsModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareLogsModal.swift; sourceTree = ""; }; 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallBanner.swift; sourceTree = ""; }; + 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniCallView.swift; sourceTree = ""; }; 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; 7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -2046,6 +2048,7 @@ isa = PBXGroup; children = ( 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */, + 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */, ); path = "Views & Modals"; sourceTree = ""; @@ -4902,6 +4905,7 @@ B85357C323A1BD1200AAF6CD /* SeedVC.swift in Sources */, 45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */, B8D84ECF25E3108A005A043E /* ExpandingAttachmentsButton.swift in Sources */, + 7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */, B875885A264503A6000E60D0 /* JoinOpenGroupModal.swift in Sources */, B8CCF6432397711F0091D419 /* SettingsVC.swift in Sources */, C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */, diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index be1cb7252..569b9cc1e 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -191,7 +191,9 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { callInfoLabel.text = "Ringing..." Storage.write { transaction in self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction).done { - self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() + self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).done { + self.minimizeButton.isHidden = false + }.retainUntilComplete() }.retainUntilComplete() } answerButton.isHidden = true @@ -203,7 +205,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { // Background let background = getBackgroudView() view.addSubview(background) - background.autoPinEdgesToSuperviewEdges() + background.pin(to: view) // Call info label view.addSubview(callInfoLabel) callInfoLabel.translatesAutoresizingMaskIntoConstraints = false @@ -332,7 +334,10 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } @objc private func minimize() { - + let miniCallView = MiniCallView(from: self) + miniCallView.show() + self.conversationVC?.showInputAccessoryView() + presentingViewController?.dismiss(animated: true, completion: nil) } @objc private func operateCamera() { diff --git a/Session/Calls/Views & Modals/MiniCallView.swift b/Session/Calls/Views & Modals/MiniCallView.swift new file mode 100644 index 000000000..41b261c42 --- /dev/null +++ b/Session/Calls/Views & Modals/MiniCallView.swift @@ -0,0 +1,138 @@ +import UIKit +import WebRTC + +final class MiniCallView: UIView { + var callVC: CallVC + + private lazy var remoteVideoView: RTCMTLVideoView = { + let result = RTCMTLVideoView() + result.contentMode = .scaleAspectFill + return result + }() + + // MARK: Initialization + public static var current: MiniCallView? + + init(from callVC: CallVC) { + self.callVC = callVC + super.init(frame: CGRect.zero) + self.backgroundColor = .black + setUpViewHierarchy() + setUpGestureRecognizers() + MiniCallView.current = self + } + + override init(frame: CGRect) { + preconditionFailure("Use init(message:) instead.") + } + + required init?(coder: NSCoder) { + preconditionFailure("Use init(coder:) instead.") + } + + private func setUpViewHierarchy() { + self.set(.width, to: 80) + self.set(.height, to: 173) + // Background + let background = getBackgroudView() + self.addSubview(background) + background.pin(to: self) + // Remote video view + callVC.webRTCSession.attachRemoteRenderer(remoteVideoView) + self.addSubview(remoteVideoView) + remoteVideoView.translatesAutoresizingMaskIntoConstraints = false + remoteVideoView.pin(to: self) + } + + private func getBackgroudView() -> UIView { + let background = UIView() + let imageView = UIImageView() + imageView.layer.cornerRadius = 32 + imageView.layer.masksToBounds = true + imageView.contentMode = .scaleAspectFill + if let profilePicture = OWSProfileManager.shared().profileAvatar(forRecipientId: callVC.sessionID) { + imageView.image = profilePicture + } else { + let displayName = Storage.shared.getContact(with: callVC.sessionID)?.name ?? callVC.sessionID + imageView.image = Identicon.generatePlaceholderIcon(seed: callVC.sessionID, text: displayName, size: 64) + } + background.addSubview(imageView) + imageView.set(.width, to: 64) + imageView.set(.height, to: 64) + imageView.center(in: background) + let blurView = UIView() + blurView.alpha = 0.5 + blurView.backgroundColor = .black + background.addSubview(blurView) + blurView.autoPinEdgesToSuperviewEdges() + return background + } + + private func setUpGestureRecognizers() { + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) + tapGestureRecognizer.numberOfTapsRequired = 1 + addGestureRecognizer(tapGestureRecognizer) + let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan)) + addGestureRecognizer(panGestureRecognizer) + } + + // MARK: Interaction + @objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { + dismiss() + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + presentingVC.present(callVC, animated: true, completion: nil) + } + + @objc private func handlePan(_ gesture: UIPanGestureRecognizer) { + let location = gesture.location(in: self.superview!) + if let draggedView = gesture.view { + draggedView.center = location + if gesture.state == .ended { + let sideMargin = 40 + Values.verySmallSpacing + if draggedView.frame.midX >= self.superview!.layer.frame.width / 2 { + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { + draggedView.center.x = self.superview!.layer.frame.width - sideMargin + }, completion: nil) + }else{ + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { + draggedView.center.x = sideMargin + }, completion: nil) + } + let topMargin = UIApplication.shared.keyWindow!.safeAreaInsets.top + Values.veryLargeSpacing + if draggedView.frame.minY <= topMargin { + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { + draggedView.center.y = topMargin + draggedView.frame.size.height / 2 + }, completion: nil) + } + let bottomMargin = UIApplication.shared.keyWindow!.safeAreaInsets.bottom + if draggedView.frame.maxY >= self.superview!.layer.frame.height { + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { + draggedView.center.y = self.layer.frame.height - draggedView.frame.size.height / 2 - bottomMargin + }, completion: nil) + } + } + } + } + + public func show() { + self.alpha = 0.0 + let window = CurrentAppContext().mainWindow! + window.addSubview(self) + self.autoPinEdge(toSuperviewEdge: .right, withInset: Values.smallSpacing) + let topMargin = UIApplication.shared.keyWindow!.safeAreaInsets.top + Values.veryLargeSpacing + self.autoPinEdge(toSuperviewEdge: .top, withInset: topMargin) + UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: { + self.alpha = 1.0 + }, completion: nil) + } + + public func dismiss() { + UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: { + self.alpha = 0.0 + }, completion: { _ in + MiniCallView.current = nil + self.removeFromSuperview() + }) + } + +} diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 3d1b56ba5..b744061c5 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -38,6 +38,7 @@ extension AppDelegate { DispatchQueue.main.async { if let currentBanner = IncomingCallBanner.current { currentBanner.dismiss() } if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleEndCallMessage(message) } + if let miniCallView = MiniCallView.current { miniCallView.dismiss() } WebRTCSession.current?.dropConnection() WebRTCSession.current = nil } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index d01e8a47c..a9a4b00a9 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -95,7 +95,6 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { self.uuid = uuid super.init() let mediaStreamTrackIDS = ["ARDAMS"] - createDataChannel() peerConnection.add(audioTrack, streamIds: mediaStreamTrackIDS) peerConnection.add(localVideoTrack, streamIds: mediaStreamTrackIDS) // Configure audio session @@ -105,6 +104,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { // MARK: Signaling public func sendPreOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { print("[Calls] Sending pre-offer message.") + createDataChannel() guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } let (promise, seal) = Promise.pending() DispatchQueue.main.async { @@ -267,6 +267,8 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { print("[Calls] Data channel opened.") + self.dataChannel = dataChannel + self.dataChannel?.delegate = self } } From 57a04f4ff5c5e11c60e03d4adb5b08130376bef2 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 11 Oct 2021 15:23:19 +1100 Subject: [PATCH 064/368] show incoming call notification --- .../NotificationServiceExtension.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index d8bd9cd22..078db2e3f 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -87,6 +87,13 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension case .new(_, let name, _, _, _, _): snippet = "\(senderDisplayName) added you to \(name)" default: return self.completeSilenty() } + case let callMessage as CallMessage: + MessageReceiver.handleCallMessage(callMessage, using: transaction) + notificationContent.userInfo = userInfo + notificationContent.badge = 1 + notificationContent.title = "Session" + notificationContent.body = "\(senderDisplayName) is calling..." + return self.handleSuccess(for: notificationContent) default: return self.completeSilenty() } if (senderPublicKey == userPublicKey) { From 42676188c754cffb5e116ccb06e53dba8843e536 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 12 Oct 2021 09:05:37 +1100 Subject: [PATCH 065/368] add continuous vibration --- Session/Calls/Views & Modals/IncomingCallBanner.swift | 7 ++++++- SessionMessagingKit/Calls/WebRTCSession.swift | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index dbc037139..63efce9c3 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -9,6 +9,8 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { let uuid: String let sdp: RTCSessionDescription + private var vibrationTimer: Timer? + // MARK: UI Components private lazy var profilePictureView: ProfilePictureView = { let result = ProfilePictureView() @@ -176,10 +178,13 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: { self.alpha = 1.0 }, completion: nil) - AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) + vibrationTimer = WeakTimer.scheduledTimer(timeInterval: 0.5, target: self, userInfo: nil, repeats: true) { _ in + AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) + } } public func dismiss() { + vibrationTimer?.invalidate() UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: { self.alpha = 0.0 }, completion: { _ in diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index a9a4b00a9..f55c2e345 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -243,7 +243,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) { - // Do nothing + print("[Calls] Peer connection should negotiate.") } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCIceConnectionState) { From 9b9a5d7a39bde257abfca280fb217b5281f922f9 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 12 Oct 2021 16:42:53 +1100 Subject: [PATCH 066/368] improve vibration for incoming calls --- Session.xcodeproj/project.pbxproj | 4 ++++ .../Views & Modals/IncomingCallBanner.swift | 6 ++---- SessionUtilitiesKit/General/Vibration.swift | 19 +++++++++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 SessionUtilitiesKit/General/Vibration.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index b314e561d..6c14cd82b 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -142,6 +142,7 @@ 7B7CB18B270591630079FF93 /* ShareLogsModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18A270591630079FF93 /* ShareLogsModal.swift */; }; 7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */; }; 7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */; }; + 7B7CB192271508AD0079FF93 /* Vibration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB191271508AD0079FF93 /* Vibration.swift */; }; 7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; }; 7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; }; @@ -1120,6 +1121,7 @@ 7B7CB18A270591630079FF93 /* ShareLogsModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareLogsModal.swift; sourceTree = ""; }; 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallBanner.swift; sourceTree = ""; }; 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniCallView.swift; sourceTree = ""; }; + 7B7CB191271508AD0079FF93 /* Vibration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vibration.swift; sourceTree = ""; }; 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; 7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -2320,6 +2322,7 @@ C38EF23D255B6D66007E1867 /* UIView+OWS.h */, C38EF23E255B6D66007E1867 /* UIView+OWS.m */, C38EF2EF255B6DBB007E1867 /* Weak.swift */, + 7B7CB191271508AD0079FF93 /* Vibration.swift */, ); path = General; sourceTree = ""; @@ -4601,6 +4604,7 @@ C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */, C352A36D2557858E00338F3E /* NSTimer+Proxying.m in Sources */, C32C5A2D256DB849003C73A2 /* LKGroupUtilities.m in Sources */, + 7B7CB192271508AD0079FF93 /* Vibration.swift in Sources */, C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */, B8856E09256F1676001CE70E /* UIDevice+featureSupport.swift in Sources */, B8856DEF256F161F001CE70E /* NSString+SSK.m in Sources */, diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index 63efce9c3..d592ff05e 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -178,13 +178,11 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: { self.alpha = 1.0 }, completion: nil) - vibrationTimer = WeakTimer.scheduledTimer(timeInterval: 0.5, target: self, userInfo: nil, repeats: true) { _ in - AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) - } + Vibration.shared.startVibration() } public func dismiss() { - vibrationTimer?.invalidate() + Vibration.shared.stopVibrationIfPossible() UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: { self.alpha = 0.0 }, completion: { _ in diff --git a/SessionUtilitiesKit/General/Vibration.swift b/SessionUtilitiesKit/General/Vibration.swift new file mode 100644 index 000000000..479e1ba3f --- /dev/null +++ b/SessionUtilitiesKit/General/Vibration.swift @@ -0,0 +1,19 @@ +import AudioToolbox + +public final class Vibration { + + public static let shared = Vibration() + + private var vibrationTimer: Timer? + + public func startVibration() { + vibrationTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in + AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) + } + } + + public func stopVibrationIfPossible() { + vibrationTimer?.invalidate() + vibrationTimer = nil + } +} From c1e5511ed4fcff1d84bc0eec50e2259c739f399d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 12 Oct 2021 16:43:30 +1100 Subject: [PATCH 067/368] WIP: background vibrate & refresh tasks --- Session/Meta/AppDelegate.m | 4 ++ Session/Meta/AppDelegate.swift | 51 ++++++++++++++++++- Session/Meta/Session-Info.plist | 10 +++- .../NotificationServiceExtension.swift | 21 +++++++- 4 files changed, 81 insertions(+), 5 deletions(-) diff --git a/Session/Meta/AppDelegate.m b/Session/Meta/AppDelegate.m index 8fb513275..0e4ccd8fd 100644 --- a/Session/Meta/AppDelegate.m +++ b/Session/Meta/AppDelegate.m @@ -224,6 +224,10 @@ static NSTimeInterval launchStartedAt; OWSLogInfo(@"application: didFinishLaunchingWithOptions completed."); [self setUpCallHandling]; + if (@available(iOS 13.0, *)) { + [self registerBackgroundTasks]; + } + return YES; } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index b744061c5..c6958b7cc 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -2,11 +2,13 @@ import PromiseKit import WebRTC import SessionUIKit import UIKit +import BackgroundTasks +import SessionUtilitiesKit extension AppDelegate { - @objc - func setUpCallHandling() { + // MARK: Call handling + @objc func setUpCallHandling() { // Offer messages MessageReceiver.handleOfferCallMessage = { message in DispatchQueue.main.async { @@ -45,6 +47,7 @@ extension AppDelegate { } } + // MARK: Configuration message @objc(syncConfigurationIfNeeded) func syncConfigurationIfNeeded() { guard Storage.shared.getUser()?.name != nil else { return } @@ -75,6 +78,7 @@ extension AppDelegate { return promise } + // MARK: Closed group poller @objc func startClosedGroupPoller() { guard OWSIdentityManager.shared().identityKeyPair() != nil else { return } ClosedGroupPoller.shared.start() @@ -84,6 +88,7 @@ extension AppDelegate { ClosedGroupPoller.shared.stop() } + // MARK: Theme @objc func getAppModeOrSystemDefault() -> AppMode { let userDefaults = UserDefaults.standard if userDefaults.dictionaryRepresentation().keys.contains("appMode") { @@ -98,4 +103,46 @@ extension AppDelegate { } } + // MARK: Background tasks + @available(iOS 13.0, *) + @objc func registerBackgroundTasks() { + BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.loki-project.loki-messenger.refresh", using: nil) { task in + self.handleAppRefresh(task: task as! BGAppRefreshTask) + } + + BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.loki-project.loki-messenger.vibrate", using: nil) { task in + Vibration.shared.startVibration() + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 60, execute: { + Vibration.shared.stopVibrationIfPossible() + task.setTaskCompleted(success: true) + }) + } + } + + @available(iOS 13.0, *) + @objc func cancelAllPendingBGTask() { + BGTaskScheduler.shared.cancelAllTaskRequests() + } + + @available(iOS 13.0, *) + @objc func scheduleAppRefresh() { + let request = BGAppRefreshTaskRequest(identifier: "com.loki-project.loki-messenger.refresh") + // Fetch no earlier than 15 minutes from now. + request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) + + do { + try BGTaskScheduler.shared.submit(request) + } catch { + print("Could not schedule app refresh: \(error)") + } + } + + @available(iOS 13.0, *) + private func handleAppRefresh(task: BGAppRefreshTask) { + // Schedule a new refresh task. + scheduleAppRefresh() + + + } + } diff --git a/Session/Meta/Session-Info.plist b/Session/Meta/Session-Info.plist index 55fb6700d..3771b748c 100644 --- a/Session/Meta/Session-Info.plist +++ b/Session/Meta/Session-Info.plist @@ -2,6 +2,11 @@ + BGTaskSchedulerPermittedIdentifiers + + com.loki-project.loki-messenger.vibrate + com.loki-project.loki-messenger.refresh + BuildDetails CarthageVersion @@ -90,7 +95,7 @@ NSContactsUsageDescription Signal uses your contacts to find users you know. We do not store your contacts on the server. NSFaceIDUsageDescription - Session's Screen Lock feature uses Face ID. + Session's Screen Lock feature uses Face ID. NSMicrophoneUsageDescription Session needs access to your microphone to record media. NSPhotoLibraryAddUsageDescription @@ -119,6 +124,7 @@ UIBackgroundModes fetch + processing remote-notification UILaunchStoryboardName @@ -135,5 +141,7 @@ UIViewControllerBasedStatusBarAppearance + NSHumanReadableCopyright + com.loki-project.loki-messenger diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 078db2e3f..b4eb44d02 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -1,4 +1,5 @@ import UserNotifications +import BackgroundTasks import SessionMessagingKit import SignalUtilitiesKit @@ -93,7 +94,7 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension notificationContent.badge = 1 notificationContent.title = "Session" notificationContent.body = "\(senderDisplayName) is calling..." - return self.handleSuccess(for: notificationContent) + return self.handleSuccessForIncomingCall(for: notificationContent) default: return self.completeSilenty() } if (senderPublicKey == userPublicKey) { @@ -119,7 +120,10 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension } self.handleSuccess(for: notificationContent) } catch { - self.handleFailure(for: notificationContent) + if let error = error as? MessageReceiver.Error, error.isRetryable { + self.handleFailure(for: notificationContent) + } + self.completeSilenty() } } } @@ -209,6 +213,19 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension private func completeSilenty() { contentHandler!(.init()) } + + private func handleSuccessForIncomingCall(for content: UNMutableNotificationContent) { + // TODO: poll for the real offer, play incoming call ring + if #available(iOSApplicationExtension 13.0, *) { + let request = BGAppRefreshTaskRequest(identifier: "com.loki-project.loki-messenger.refresh") + do { + try BGTaskScheduler.shared.submit(request) + } catch { + print("Could not schedule app refresh: \(error)") + } + } + contentHandler!(content) + } private func handleSuccess(for content: UNMutableNotificationContent) { contentHandler!(content) From 7cd7343585753a22bbc64a4c3543fd7eefca052a Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 12 Oct 2021 17:05:13 +1100 Subject: [PATCH 068/368] schedule background refresh tasks --- Session/Meta/AppDelegate.m | 8 ++++++++ Session/Meta/AppDelegate.swift | 15 ++++++++++++--- .../Notifications/PushNotificationAPI.swift | 2 +- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Session/Meta/AppDelegate.m b/Session/Meta/AppDelegate.m index 0e4ccd8fd..a96e311cd 100644 --- a/Session/Meta/AppDelegate.m +++ b/Session/Meta/AppDelegate.m @@ -256,6 +256,10 @@ static NSTimeInterval launchStartedAt; [AppReadiness runNowOrWhenAppDidBecomeReady:^{ [self handleActivation]; }]; + + if (@available(iOS 13, *)) { + [self cancelAllPendingBackgroundTasks]; + } // Clear all notifications whenever we become active. // When opening the app from a notification, @@ -283,6 +287,10 @@ static NSTimeInterval launchStartedAt; [sharedUserDefaults synchronize]; [DDLog flushLog]; + + if (@available(iOS 13, *)) { + [self scheduleAppRefresh]; + } } #pragma mark - Orientation diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index c6958b7cc..18cc4787b 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -4,6 +4,7 @@ import SessionUIKit import UIKit import BackgroundTasks import SessionUtilitiesKit +import SessionMessagingKit extension AppDelegate { @@ -120,7 +121,7 @@ extension AppDelegate { } @available(iOS 13.0, *) - @objc func cancelAllPendingBGTask() { + @objc func cancelAllPendingBackgroundTasks() { BGTaskScheduler.shared.cancelAllTaskRequests() } @@ -141,8 +142,16 @@ extension AppDelegate { private func handleAppRefresh(task: BGAppRefreshTask) { // Schedule a new refresh task. scheduleAppRefresh() - - + AppReadiness.runNowOrWhenAppDidBecomeReady{ + BackgroundPoller.poll(completionHandler: { result in + if result == .failed { + task.setTaskCompleted(success: false) + + } else { + task.setTaskCompleted(success: true) + } + }) + } } } diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index 8fccb96ec..c44649c60 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -5,7 +5,7 @@ import PromiseKit public final class PushNotificationAPI : NSObject { // MARK: Settings - public static let server = "https://live.apns.getsession.org" + public static let server = "https://dev.apns.getsession.org" public static let serverPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" private static let maxRetryCount: UInt = 4 private static let tokenExpirationInterval: TimeInterval = 12 * 60 * 60 From 63d75040a644b46cff1aad4f7011a48644b2f5cd Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 13 Oct 2021 10:39:42 +1100 Subject: [PATCH 069/368] clean --- Session/Meta/AppDelegate.swift | 1 - .../Sending & Receiving/Notifications/PushNotificationAPI.swift | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 18cc4787b..9b2e274f0 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -146,7 +146,6 @@ extension AppDelegate { BackgroundPoller.poll(completionHandler: { result in if result == .failed { task.setTaskCompleted(success: false) - } else { task.setTaskCompleted(success: true) } diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index c44649c60..8fccb96ec 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -5,7 +5,7 @@ import PromiseKit public final class PushNotificationAPI : NSObject { // MARK: Settings - public static let server = "https://dev.apns.getsession.org" + public static let server = "https://live.apns.getsession.org" public static let serverPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" private static let maxRetryCount: UInt = 4 private static let tokenExpirationInterval: TimeInterval = 12 * 60 * 60 From 8a6be4fc5b9d1df6a7a3dd0b631cf186c7f5451d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 13 Oct 2021 11:47:15 +1100 Subject: [PATCH 070/368] notify pre-offer call message --- SessionMessagingKit/Sending & Receiving/MessageSender.swift | 3 +++ SessionMessagingKit/Threads/TSThread.m | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index d32b07681..69946ffc0 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -219,6 +219,9 @@ public final class MessageSender : NSObject { message.serverHash = hash MessageSender.handleSuccessfulMessageSend(message, to: destination, isSyncMessage: isSyncMessage, using: transaction) var shouldNotify = ((message is VisibleMessage || message is UnsendRequest) && !isSyncMessage) + if let callMessage = message as? CallMessage, case .preOffer = callMessage.kind { + shouldNotify = true + } /* if let closedGroupControlMessage = message as? ClosedGroupControlMessage, case .new = closedGroupControlMessage.kind { shouldNotify = true diff --git a/SessionMessagingKit/Threads/TSThread.m b/SessionMessagingKit/Threads/TSThread.m index 3100e5955..6283a324d 100644 --- a/SessionMessagingKit/Threads/TSThread.m +++ b/SessionMessagingKit/Threads/TSThread.m @@ -364,7 +364,7 @@ BOOL IsNoteToSelfEnabled(void) if (!self.shouldBeVisible) { self.shouldBeVisible = YES; - [super saveWithTransaction:transaction]; + [self saveWithTransaction:transaction]; } else { [self touchWithTransaction:transaction]; } From 80151acad2887462f932dd8da0481c0a4af4924d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 13 Oct 2021 14:42:36 +1100 Subject: [PATCH 071/368] communicate video enabling status using data channel --- Session/Calls/CallVC.swift | 7 ++++++- .../Calls/WebRTCSession+DataChannel.swift | 17 ++++++++--------- SessionMessagingKit/Calls/WebRTCSession.swift | 13 +++++++++---- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 569b9cc1e..cda7cb539 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -37,6 +37,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { private lazy var remoteVideoView: RTCMTLVideoView = { let result = RTCMTLVideoView() + result.alpha = 0 result.contentMode = .scaleAspectFill return result }() @@ -293,7 +294,11 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } func isRemoteVideoDidChange(isEnabled: Bool) { - remoteVideoView.isHidden = !isEnabled + DispatchQueue.main.async { + UIView.animate(withDuration: 0.25) { + self.remoteVideoView.alpha = isEnabled ? 1 : 0 + } + } } // MARK: Interaction diff --git a/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift b/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift index 2f22843ce..fa1dcd735 100644 --- a/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift +++ b/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift @@ -3,18 +3,17 @@ import Foundation extension WebRTCSession: RTCDataChannelDelegate { - internal func createDataChannel() { + internal func createDataChannel() -> RTCDataChannel? { let dataChannelConfiguration = RTCDataChannelConfiguration() - dataChannelConfiguration.isOrdered = true - dataChannelConfiguration.isNegotiated = true - dataChannelConfiguration.maxRetransmits = 30 - dataChannelConfiguration.maxPacketLifeTime = 30000 - dataChannel = peerConnection.dataChannel(forLabel: "DATACHANNEL", configuration: dataChannelConfiguration) - dataChannel?.delegate = self + guard let dataChannel = peerConnection.dataChannel(forLabel: "VIDEOCONTROL", configuration: dataChannelConfiguration) else { + print("[Calls] Couldn't create data channel.") + return nil + } + return dataChannel } public func sendJSON(_ json: JSON) { - if let dataChannel = dataChannel, let jsonAsData = try? JSONSerialization.data(withJSONObject: json, options: [ .fragmentsAllowed ]) { + if let dataChannel = remoteDataChannel, let jsonAsData = try? JSONSerialization.data(withJSONObject: json, options: [ .fragmentsAllowed ]) { let dataBuffer = RTCDataBuffer(data: jsonAsData, isBinary: false) dataChannel.sendData(dataBuffer) } @@ -22,7 +21,7 @@ extension WebRTCSession: RTCDataChannelDelegate { // MARK: Data channel delegate public func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) { - print("[Calls] Data channed did change to \(dataChannel.readyState)") + print("[Calls] Data channel did change to \(dataChannel.readyState)") } public func dataChannel(_ dataChannel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) { diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index f55c2e345..08326337a 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -74,7 +74,8 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { }() // Data Channel - internal var dataChannel: RTCDataChannel? + internal var localDataChannel: RTCDataChannel? + internal var remoteDataChannel: RTCDataChannel? // MARK: Error public enum Error : LocalizedError { @@ -99,12 +100,17 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { peerConnection.add(localVideoTrack, streamIds: mediaStreamTrackIDS) // Configure audio session configureAudioSession() + + // Data channel + if let dataChannel = createDataChannel() { + dataChannel.delegate = self + self.localDataChannel = dataChannel + } } // MARK: Signaling public func sendPreOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { print("[Calls] Sending pre-offer message.") - createDataChannel() guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } let (promise, seal) = Promise.pending() DispatchQueue.main.async { @@ -267,8 +273,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { print("[Calls] Data channel opened.") - self.dataChannel = dataChannel - self.dataChannel?.delegate = self + self.remoteDataChannel = dataChannel } } From 45e209f8311554b83ad6f47017e283493ab85314 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 13 Oct 2021 15:15:04 +1100 Subject: [PATCH 072/368] minor UI fix on incoming call banner --- Session/Calls/Views & Modals/IncomingCallBanner.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index d592ff05e..8ec49aadd 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -9,8 +9,6 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { let uuid: String let sdp: RTCSessionDescription - private var vibrationTimer: Timer? - // MARK: UI Components private lazy var profilePictureView: ProfilePictureView = { let result = ProfilePictureView() @@ -26,7 +24,6 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { result.textColor = UIColor.white result.font = .boldSystemFont(ofSize: Values.largeFontSize) result.lineBreakMode = .byTruncatingTail - result.textAlignment = .center return result }() @@ -92,7 +89,7 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { profilePictureView.publicKey = self.sessionID profilePictureView.update() displayNameLabel.text = Storage.shared.getContact(with: sessionID)?.name - let stackView = UIStackView(arrangedSubviews: [profilePictureView, displayNameLabel, UIView.hStretchingSpacer(), hangUpButton, answerButton]) + let stackView = UIStackView(arrangedSubviews: [profilePictureView, displayNameLabel, hangUpButton, answerButton]) stackView.axis = .horizontal stackView.alignment = .center stackView.spacing = Values.largeSpacing From a8b8207154ec8c329b039c17927f79d17e117114 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 14 Oct 2021 10:30:20 +1100 Subject: [PATCH 073/368] clean --- Session/Meta/AppDelegate.m | 12 -------- Session/Meta/AppDelegate.swift | 53 --------------------------------- Session/Meta/Session-Info.plist | 12 +++----- 3 files changed, 4 insertions(+), 73 deletions(-) diff --git a/Session/Meta/AppDelegate.m b/Session/Meta/AppDelegate.m index a96e311cd..4f2854842 100644 --- a/Session/Meta/AppDelegate.m +++ b/Session/Meta/AppDelegate.m @@ -224,11 +224,7 @@ static NSTimeInterval launchStartedAt; OWSLogInfo(@"application: didFinishLaunchingWithOptions completed."); [self setUpCallHandling]; - if (@available(iOS 13.0, *)) { - [self registerBackgroundTasks]; - } - return YES; } @@ -256,10 +252,6 @@ static NSTimeInterval launchStartedAt; [AppReadiness runNowOrWhenAppDidBecomeReady:^{ [self handleActivation]; }]; - - if (@available(iOS 13, *)) { - [self cancelAllPendingBackgroundTasks]; - } // Clear all notifications whenever we become active. // When opening the app from a notification, @@ -287,10 +279,6 @@ static NSTimeInterval launchStartedAt; [sharedUserDefaults synchronize]; [DDLog flushLog]; - - if (@available(iOS 13, *)) { - [self scheduleAppRefresh]; - } } #pragma mark - Orientation diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 9b2e274f0..ef474dd76 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -2,9 +2,6 @@ import PromiseKit import WebRTC import SessionUIKit import UIKit -import BackgroundTasks -import SessionUtilitiesKit -import SessionMessagingKit extension AppDelegate { @@ -103,54 +100,4 @@ extension AppDelegate { } } } - - // MARK: Background tasks - @available(iOS 13.0, *) - @objc func registerBackgroundTasks() { - BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.loki-project.loki-messenger.refresh", using: nil) { task in - self.handleAppRefresh(task: task as! BGAppRefreshTask) - } - - BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.loki-project.loki-messenger.vibrate", using: nil) { task in - Vibration.shared.startVibration() - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 60, execute: { - Vibration.shared.stopVibrationIfPossible() - task.setTaskCompleted(success: true) - }) - } - } - - @available(iOS 13.0, *) - @objc func cancelAllPendingBackgroundTasks() { - BGTaskScheduler.shared.cancelAllTaskRequests() - } - - @available(iOS 13.0, *) - @objc func scheduleAppRefresh() { - let request = BGAppRefreshTaskRequest(identifier: "com.loki-project.loki-messenger.refresh") - // Fetch no earlier than 15 minutes from now. - request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) - - do { - try BGTaskScheduler.shared.submit(request) - } catch { - print("Could not schedule app refresh: \(error)") - } - } - - @available(iOS 13.0, *) - private func handleAppRefresh(task: BGAppRefreshTask) { - // Schedule a new refresh task. - scheduleAppRefresh() - AppReadiness.runNowOrWhenAppDidBecomeReady{ - BackgroundPoller.poll(completionHandler: { result in - if result == .failed { - task.setTaskCompleted(success: false) - } else { - task.setTaskCompleted(success: true) - } - }) - } - } - } diff --git a/Session/Meta/Session-Info.plist b/Session/Meta/Session-Info.plist index 3771b748c..b28a0d205 100644 --- a/Session/Meta/Session-Info.plist +++ b/Session/Meta/Session-Info.plist @@ -2,11 +2,6 @@ - BGTaskSchedulerPermittedIdentifiers - - com.loki-project.loki-messenger.vibrate - com.loki-project.loki-messenger.refresh - BuildDetails CarthageVersion @@ -96,6 +91,8 @@ Signal uses your contacts to find users you know. We do not store your contacts on the server. NSFaceIDUsageDescription Session's Screen Lock feature uses Face ID. + NSHumanReadableCopyright + com.loki-project.loki-messenger NSMicrophoneUsageDescription Session needs access to your microphone to record media. NSPhotoLibraryAddUsageDescription @@ -123,9 +120,10 @@ UIBackgroundModes + audio fetch - processing remote-notification + voip UILaunchStoryboardName Launch Screen @@ -141,7 +139,5 @@ UIViewControllerBasedStatusBarAppearance - NSHumanReadableCopyright - com.loki-project.loki-messenger From 3de81ef2a0d1dca6cb2f08e7b38d80d4742ece00 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 14 Oct 2021 16:01:50 +1100 Subject: [PATCH 074/368] imporvements --- Session/Calls/CallVC.swift | 14 ++++++++------ .../NotificationServiceExtension.swift | 8 -------- SessionUtilitiesKit/General/Vibration.swift | 2 +- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index cda7cb539..482344033 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -12,6 +12,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { var shouldAnswer = false var isMuted = false var isVideoEnabled = false + var shouldRestartCamera = true var conversationVC: ConversationVC? = nil lazy var cameraManager: CameraManager = { @@ -181,7 +182,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { view.backgroundColor = .black WebRTCSession.current = webRTCSession setUpViewHierarchy() - cameraManager.prepare() + if shouldRestartCamera { cameraManager.prepare() } touch(videoCapturer) var contact: Contact? Storage.read { transaction in @@ -192,9 +193,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { callInfoLabel.text = "Ringing..." Storage.write { transaction in self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction).done { - self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).done { - self.minimizeButton.isHidden = false - }.retainUntilComplete() + self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() }.retainUntilComplete() } answerButton.isHidden = true @@ -272,18 +271,20 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - if (isVideoEnabled) { cameraManager.start() } + if (isVideoEnabled && shouldRestartCamera) { cameraManager.start() } + shouldRestartCamera = true } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - if (isVideoEnabled) { cameraManager.stop() } + if (isVideoEnabled && shouldRestartCamera) { cameraManager.stop() } } // MARK: Delegate func webRTCIsConnected() { DispatchQueue.main.async { self.callInfoLabel.text = "Connected" + self.minimizeButton.isHidden = false UIView.animate(withDuration: 0.5, delay: 1, options: [], animations: { self.callInfoLabel.alpha = 0 }, completion: { _ in @@ -339,6 +340,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { } @objc private func minimize() { + self.shouldRestartCamera = false let miniCallView = MiniCallView(from: self) miniCallView.show() self.conversationVC?.showInputAccessoryView() diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index b4eb44d02..930b7b68f 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -216,14 +216,6 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension private func handleSuccessForIncomingCall(for content: UNMutableNotificationContent) { // TODO: poll for the real offer, play incoming call ring - if #available(iOSApplicationExtension 13.0, *) { - let request = BGAppRefreshTaskRequest(identifier: "com.loki-project.loki-messenger.refresh") - do { - try BGTaskScheduler.shared.submit(request) - } catch { - print("Could not schedule app refresh: \(error)") - } - } contentHandler!(content) } diff --git a/SessionUtilitiesKit/General/Vibration.swift b/SessionUtilitiesKit/General/Vibration.swift index 479e1ba3f..4e9aa2e6f 100644 --- a/SessionUtilitiesKit/General/Vibration.swift +++ b/SessionUtilitiesKit/General/Vibration.swift @@ -7,7 +7,7 @@ public final class Vibration { private var vibrationTimer: Timer? public func startVibration() { - vibrationTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in + vibrationTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { _ in AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) } } From 78497d7eece5404f15116905bd67310a4cbb7959 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 14 Oct 2021 16:03:11 +1100 Subject: [PATCH 075/368] update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ Session/Meta/Session-Info.plist | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6c14cd82b..3d853274e 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5123,7 +5123,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 295; + CURRENT_PROJECT_VERSION = 302; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5196,7 +5196,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 295; + CURRENT_PROJECT_VERSION = 302; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5262,7 +5262,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 295; + CURRENT_PROJECT_VERSION = 302; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5336,7 +5336,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 295; + CURRENT_PROJECT_VERSION = 302; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6272,7 +6272,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 295; + CURRENT_PROJECT_VERSION = 302; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6344,7 +6344,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 295; + CURRENT_PROJECT_VERSION = 302; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Meta/Session-Info.plist b/Session/Meta/Session-Info.plist index b28a0d205..533d2efe6 100644 --- a/Session/Meta/Session-Info.plist +++ b/Session/Meta/Session-Info.plist @@ -90,7 +90,7 @@ NSContactsUsageDescription Signal uses your contacts to find users you know. We do not store your contacts on the server. NSFaceIDUsageDescription - Session's Screen Lock feature uses Face ID. + Session's Screen Lock feature uses Face ID. NSHumanReadableCopyright com.loki-project.loki-messenger NSMicrophoneUsageDescription @@ -123,7 +123,6 @@ audio fetch remote-notification - voip UILaunchStoryboardName Launch Screen From 1231b9c20a30e4192778c840f813df46e03ee9e9 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 21 Oct 2021 16:28:48 +1100 Subject: [PATCH 076/368] add preview before staring video --- Session.xcodeproj/project.pbxproj | 12 ++ Session/Calls/CallVC.swift | 25 ++-- Session/Calls/VideoPreviewVC.swift | 123 ++++++++++++++++++ Session/Calls/Views & Modals/RenderView.swift | 36 +++++ .../ConversationVC+Interaction.swift | 11 +- .../Views & Modals/CallModal.swift | 65 +++++++++ .../Session/Check.imageset/Contents.json | 12 ++ .../Session/Check.imageset/check.pdf | Bin 0 -> 4092 bytes .../Translations/en.lproj/Localizable.strings | 2 + 9 files changed, 276 insertions(+), 10 deletions(-) create mode 100644 Session/Calls/VideoPreviewVC.swift create mode 100644 Session/Calls/Views & Modals/RenderView.swift create mode 100644 Session/Conversations/Views & Modals/CallModal.swift create mode 100644 Session/Meta/Images.xcassets/Session/Check.imageset/Contents.json create mode 100644 Session/Meta/Images.xcassets/Session/Check.imageset/check.pdf diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 7299c8883..307b1138d 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -137,6 +137,9 @@ 76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; }; 76EB054018170B33006006FC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C318170B33006006FC /* AppDelegate.m */; }; 7B1581E2271E743B00848B49 /* OWSSounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E1271E743B00848B49 /* OWSSounds.swift */; }; + 7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E3271FC59C00848B49 /* CallModal.swift */; }; + 7B1581E6271FD2A100848B49 /* VideoPreviewVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */; }; + 7B1581E827210ECC00848B49 /* RenderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E727210ECC00848B49 /* RenderView.swift */; }; 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; }; 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; }; 7B7CB189270430D20079FF93 /* CallMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB188270430D20079FF93 /* CallMessageView.swift */; }; @@ -1116,6 +1119,9 @@ 76EB03C218170B33006006FC /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 76EB03C318170B33006006FC /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 7B1581E1271E743B00848B49 /* OWSSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSSounds.swift; sourceTree = ""; }; + 7B1581E3271FC59C00848B49 /* CallModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModal.swift; sourceTree = ""; }; + 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPreviewVC.swift; sourceTree = ""; }; + 7B1581E727210ECC00848B49 /* RenderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderView.swift; sourceTree = ""; }; 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = ""; }; 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = ""; }; @@ -2053,6 +2059,7 @@ children = ( 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */, 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */, + 7B1581E727210ECC00848B49 /* RenderView.swift */, ); path = "Views & Modals"; sourceTree = ""; @@ -2169,6 +2176,7 @@ B8214A2A25D63EB9009C0F2A /* MessagesTableView.swift */, C374EEEA25DA3CA70073A857 /* ConversationTitleView.swift */, B848A4C4269EAAA200617031 /* UserDetailsSheet.swift */, + 7B1581E3271FC59C00848B49 /* CallModal.swift */, ); path = "Views & Modals"; sourceTree = ""; @@ -2350,6 +2358,7 @@ isa = PBXGroup; children = ( B877E24126CA12910007970A /* CallVC.swift */, + 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */, B877E24526CA13BA0007970A /* CallVC+Camera.swift */, B8B558F026C4BB0600693325 /* CameraManager.swift */, 7B7CB18C270D06350079FF93 /* Views & Modals */, @@ -4837,6 +4846,7 @@ 34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */, 451166C01FD86B98000739BA /* AccountManager.swift in Sources */, C374EEF425DB31D40073A857 /* VoiceMessageRecordingView.swift in Sources */, + 7B1581E6271FD2A100848B49 /* VideoPreviewVC.swift in Sources */, B83F2B88240CB75A000A54AB /* UIImage+Scaling.swift in Sources */, 3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */, 340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */, @@ -4917,6 +4927,7 @@ B875885A264503A6000E60D0 /* JoinOpenGroupModal.swift in Sources */, B8CCF6432397711F0091D419 /* SettingsVC.swift in Sources */, C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */, + 7B1581E827210ECC00848B49 /* RenderView.swift in Sources */, 3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */, 45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */, 4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */, @@ -4926,6 +4937,7 @@ 4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */, C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */, 340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */, + 7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */, 34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */, B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */, 34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */, diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 482344033..6b57e9ec1 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -4,7 +4,7 @@ import SessionMessagingKit import SessionUtilitiesKit import UIKit -final class CallVC : UIViewController, WebRTCSessionDelegate { +final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelegate { let sessionID: String let uuid: String let mode: Mode @@ -225,7 +225,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { view.addSubview(fadeView) fadeView.translatesAutoresizingMaskIntoConstraints = false fadeView.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view) - // Close button + // Minimize button view.addSubview(minimizeButton) minimizeButton.translatesAutoresizingMaskIntoConstraints = false minimizeButton.pin(.left, to: .left, of: view) @@ -354,15 +354,22 @@ final class CallVC : UIViewController, WebRTCSessionDelegate { cameraManager.stop() videoButton.alpha = 0.5 switchCameraButton.isEnabled = false + isVideoEnabled = false } else { - webRTCSession.turnOnVideo() - localVideoView.isHidden = false - cameraManager.prepare() - cameraManager.start() - videoButton.alpha = 1.0 - switchCameraButton.isEnabled = true + let previewVC = VideoPreviewVC() + previewVC.delegate = self + present(previewVC, animated: true, completion: nil) } - isVideoEnabled = !isVideoEnabled + } + + func cameraDidConfirmTurningOn() { + webRTCSession.turnOnVideo() + localVideoView.isHidden = false + cameraManager.prepare() + cameraManager.start() + videoButton.alpha = 1.0 + switchCameraButton.isEnabled = true + isVideoEnabled = true } @objc private func switchCamera() { diff --git a/Session/Calls/VideoPreviewVC.swift b/Session/Calls/VideoPreviewVC.swift new file mode 100644 index 000000000..df6e98377 --- /dev/null +++ b/Session/Calls/VideoPreviewVC.swift @@ -0,0 +1,123 @@ +import UIKit +import WebRTC + +public protocol VideoPreviewDelegate : AnyObject { + func cameraDidConfirmTurningOn() +} + +class VideoPreviewVC: UIViewController, CameraManagerDelegate { + weak var delegate: VideoPreviewDelegate? + + lazy var cameraManager: CameraManager = { + let result = CameraManager() + result.delegate = self + return result + }() + + // MARK: UI Components + private lazy var renderView: RenderView = { + let result = RenderView() + return result + }() + + private lazy var fadeView: UIView = { + let result = UIView() + let height: CGFloat = 64 + var frame = UIScreen.main.bounds + frame.size.height = height + let layer = CAGradientLayer() + layer.frame = frame + layer.colors = [ UIColor(hex: 0x000000).withAlphaComponent(0.4).cgColor, UIColor(hex: 0x000000).withAlphaComponent(0).cgColor ] + result.layer.insertSublayer(layer, at: 0) + result.set(.height, to: height) + return result + }() + + private lazy var closeButton: UIButton = { + let result = UIButton(type: .custom) + let image = UIImage(named: "X")!.withTint(.white) + result.setImage(image, for: UIControl.State.normal) + result.set(.width, to: 60) + result.set(.height, to: 60) + result.addTarget(self, action: #selector(cancel), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var confirmButton: UIButton = { + let result = UIButton(type: .custom) + let image = UIImage(named: "Check")!.withTint(.white) + result.setImage(image, for: UIControl.State.normal) + result.set(.width, to: 60) + result.set(.height, to: 60) + result.addTarget(self, action: #selector(confirm), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var titleLabel: UILabel = { + let result = UILabel() + result.text = "Preview" + result.textColor = .white + result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) + result.textAlignment = .center + return result + }() + + // MARK: Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .black + setUpViewHierarchy() + cameraManager.prepare() + } + + func setUpViewHierarchy() { + // Preview video view + view.addSubview(renderView) + renderView.translatesAutoresizingMaskIntoConstraints = false + renderView.pin(to: view) + // Fade view + view.addSubview(fadeView) + fadeView.translatesAutoresizingMaskIntoConstraints = false + fadeView.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view) + // Close button + view.addSubview(closeButton) + closeButton.translatesAutoresizingMaskIntoConstraints = false + closeButton.pin(.left, to: .left, of: view) + closeButton.center(.vertical, in: fadeView) + // Confirm button + view.addSubview(confirmButton) + confirmButton.translatesAutoresizingMaskIntoConstraints = false + confirmButton.pin(.right, to: .right, of: view) + confirmButton.center(.vertical, in: fadeView) + // Title label + view.addSubview(titleLabel) + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.center(.vertical, in: closeButton) + titleLabel.center(.horizontal, in: view) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + cameraManager.start() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + cameraManager.stop() + } + + // MARK: Interaction + @objc func confirm() { + delegate?.cameraDidConfirmTurningOn() + self.dismiss(animated: true, completion: nil) + } + + @objc func cancel() { + self.dismiss(animated: true, completion: nil) + } + + // MARK: CameraManagerDelegate + func handleVideoOutputCaptured(sampleBuffer: CMSampleBuffer) { + renderView.enqueue(sampleBuffer: sampleBuffer) + } +} diff --git a/Session/Calls/Views & Modals/RenderView.swift b/Session/Calls/Views & Modals/RenderView.swift new file mode 100644 index 000000000..da2bd4c56 --- /dev/null +++ b/Session/Calls/Views & Modals/RenderView.swift @@ -0,0 +1,36 @@ +// Copyright © 2021 Rangeproof Pty Ltd. All rights reserved. + +import UIKit +import CoreMedia + +class RenderView: UIView { + + private lazy var displayLayer: AVSampleBufferDisplayLayer = { + let result = AVSampleBufferDisplayLayer() + result.videoGravity = .resizeAspectFill + return result + }() + + init() { + super.init(frame: CGRect.zero) + self.layer.addSublayer(displayLayer) + } + + override init(frame: CGRect) { + preconditionFailure("Use init(message:) instead.") + } + + required init?(coder: NSCoder) { + preconditionFailure("Use init(coder:) instead.") + } + + override func layoutSubviews() { + super.layoutSubviews() + displayLayer.frame = self.bounds + } + + public func enqueue(sampleBuffer: CMSampleBuffer) { + displayLayer.enqueue(sampleBuffer) + } + +} diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index faad7c3be..8cfee86bb 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -27,7 +27,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc } // MARK: Call - @objc func startCall(_ sender: Any) { + @objc func startCall(_ sender: Any?) { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } let callVC = CallVC(for: contactSessionID, uuid: UUID().uuidString, mode: .offer) callVC.conversationVC = self @@ -38,6 +38,15 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc present(callVC, animated: true, completion: nil) } + internal func showCallModal() { + let callModal = CallModal() { [weak self] in + self?.startCall(nil) + } + callModal.modalPresentationStyle = .overFullScreen + callModal.modalTransitionStyle = .crossDissolve + present(callModal, animated: true, completion: nil) + } + internal func showCallVCIfNeeded() { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID(), let incomingCallBanner = IncomingCallBanner.current, incomingCallBanner.sessionID == contactSessionID diff --git a/Session/Conversations/Views & Modals/CallModal.swift b/Session/Conversations/Views & Modals/CallModal.swift new file mode 100644 index 000000000..0620646e7 --- /dev/null +++ b/Session/Conversations/Views & Modals/CallModal.swift @@ -0,0 +1,65 @@ + +final class CallModal : Modal { + private let onCallEnabled: () -> Void + + // MARK: Lifecycle + init(onCallEnabled: @escaping () -> Void) { + self.onCallEnabled = onCallEnabled + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + preconditionFailure("Use init(onCallEnabled:) instead.") + } + + override init(nibName: String?, bundle: Bundle?) { + preconditionFailure("Use init(onCallEnabled:) instead.") + } + + override func populateContentView() { + // Title + let titleLabel = UILabel() + titleLabel.textColor = Colors.text + titleLabel.font = .boldSystemFont(ofSize: Values.largeFontSize) + titleLabel.text = NSLocalizedString("modal_call_title", comment: "") + titleLabel.textAlignment = .center + // Message + let messageLabel = UILabel() + messageLabel.textColor = Colors.text + messageLabel.font = .systemFont(ofSize: Values.smallFontSize) + let message = NSLocalizedString("modal_call_explanation", comment: "") + messageLabel.text = message + messageLabel.numberOfLines = 0 + messageLabel.lineBreakMode = .byWordWrapping + messageLabel.textAlignment = .center + // Enable button + let enableButton = UIButton() + enableButton.set(.height, to: Values.mediumButtonHeight) + enableButton.layer.cornerRadius = Modal.buttonCornerRadius + enableButton.backgroundColor = Colors.buttonBackground + enableButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize) + enableButton.setTitleColor(Colors.text, for: UIControl.State.normal) + enableButton.setTitle(NSLocalizedString("modal_link_previews_button_title", comment: ""), for: UIControl.State.normal) + enableButton.addTarget(self, action: #selector(enable), for: UIControl.Event.touchUpInside) + // Button stack view + let buttonStackView = UIStackView(arrangedSubviews: [ cancelButton, enableButton ]) + buttonStackView.axis = .horizontal + buttonStackView.spacing = Values.mediumSpacing + buttonStackView.distribution = .fillEqually + // Main stack view + let mainStackView = UIStackView(arrangedSubviews: [ titleLabel, messageLabel, buttonStackView ]) + mainStackView.axis = .vertical + mainStackView.spacing = Values.largeSpacing + contentView.addSubview(mainStackView) + mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing) + mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing) + contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.largeSpacing) + contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.largeSpacing) + } + + // MARK: Interaction + @objc private func enable() { + presentingViewController?.dismiss(animated: true, completion: nil) + onCallEnabled() + } +} diff --git a/Session/Meta/Images.xcassets/Session/Check.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/Check.imageset/Contents.json new file mode 100644 index 000000000..37accc538 --- /dev/null +++ b/Session/Meta/Images.xcassets/Session/Check.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "check.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Session/Meta/Images.xcassets/Session/Check.imageset/check.pdf b/Session/Meta/Images.xcassets/Session/Check.imageset/check.pdf new file mode 100644 index 0000000000000000000000000000000000000000..86c5f39ce77bc5ceddc8c0ae7f6f94aadec44420 GIT binary patch literal 4092 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 literal 0 HcmV?d00001 diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 6861f3c03..6316ecd13 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -538,6 +538,8 @@ "modal_link_previews_title" = "Enable Link Previews?"; "modal_link_previews_explanation" = "Enabling link previews will show previews for URLs you send and receive. This can be useful, but Session will need to contact linked websites to generate previews. You can always disable link previews in Session's settings."; "modal_link_previews_button_title" = "Enable"; +"modal_call_title" = "Voice and video calls"; +"modal_call_explanation" = "The current implementation of voice/video calls will expose your IP address to the Oxen Foundation servers and the calling/called user."; "modal_share_logs_title" = "Share Logs"; "modal_share_logs_explanation" = "Would you like to export your application logs to be able to share for troubleshooting?"; "vc_share_title" = "Share to Session"; From fb156b7d52acee13148360490c34a37df86580dd Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 22 Oct 2021 09:20:39 +1100 Subject: [PATCH 077/368] minor fix --- .../Session/Check.imageset/check.pdf | Bin 4092 -> 4093 bytes .../Translations/en.lproj/Localizable.strings | 2 ++ 2 files changed, 2 insertions(+) diff --git a/Session/Meta/Images.xcassets/Session/Check.imageset/check.pdf b/Session/Meta/Images.xcassets/Session/Check.imageset/check.pdf index 86c5f39ce77bc5ceddc8c0ae7f6f94aadec44420..d8caf3f5ff4bbdd89c2e3df01a6af6f779580b93 100644 GIT binary patch delta 726 zcmew(|5tv3L%oH9ogG(kNl|KIE>{I(>SW(88AG1d@1Qo_}py~|D02kE7ksk$@A3CPT~41j$KzjO>$z6>E4=g ztgA?9@SXXqqVU%?98L%yOJ&5IF*QW z<*2X82ruW7nVTJuH9_8}ex;Cc=4*x#dD{wj9jJK5OafzT}VPZVzS@s z5D#0&O+s4hx2SVXx}v%+Mn%YD^D(z8PKv5C9d0za$t~$I5-H|>GdpPqhjIO#8z+phijbp+!R|E3WYkf$sSgi*Z*jLcv0W?kI8@B z{CeUa-~Mhb|GYL~#gDt^3!PPsS}&L8o__wIPVWPAVu*igx zaPZQOS6r!iDbTnEvhtI%xHelenenh28kiaznV3wj;FE!~XY*N#8YvinfI^-E7nosS zY+`0U`6Zt^qtRqReqmb-c>{AJ0}MT;CKi}tmgbWa`E8ud43pAQ%`MD~jnj+`4U9~U mk_-|J4Gb-llZ=gwEt1XcxNHb1nf!oXgu{|cRn^tsjSBz*;2ysK delta 740 zcmew>|3`j;L%q3zogG(kNl|KIE>{I(?qttA8ABe|@1I^(b|(mbHs!Bza4ersh@qU(YC=P{q7N|D24i`iz=sX3VMC?*2lFP z>OveZmUK8x6+e_Ik}UsFFjZ;E@m1sIO_dA};_NRVo$&R99Lb1{R)Wue%&E8nx+1K)1^@Y}XE~D@XKKI`( zoON(-+LZZJs@#nEANXI%@Z0$S`NB-Di?ZAP2lM$tXg=9 zdF@BRv>UsF@(UY|-ul3o)$(x_gV&0@ttE4MA9cLUop##oNa>D8VG=hlt+MT7tdETl z37*U>tJ;$#yo#|i{mz^v3UB0YxT;S(Ci!hq;I8&thkbk-0up%td~W>n{Op`#&wmvE zaSiBM|MBt=Jr`GlL zfBeUv6232iD>W|#8r48neo_{f;bvPVQyz8$BV$toV~fd^d@^wM96n1?Lj?m6P{>o@ z0y7MZP0Y+Dzv5F5#3EyiA!cB1Xo+EhsfjtJn5Eg|Bz_y`v{YkbL!&f9L(9Z8Q$rJD m)1*WbOOq4}i(~_!`^*gOxNHb1nf#Dngu{YMRn^tsjSB$4EGrQJ diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 6316ecd13..59db62789 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -575,6 +575,8 @@ "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; +"call_outgoing" = "Outgoing Call"; +"call_incoming" = "Incoming Call"; "voice_call" = "Voice Call"; "video_call" = "Video Call"; "APN_Message" = "You've got a new message"; From bcf0ecfb6925e0506cb6e07d49a2f53af0867df1 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 25 Oct 2021 11:49:54 +1100 Subject: [PATCH 078/368] show modal of ip exposure for calls --- Session/Calls/CallVC.swift | 29 ++++++++++++++----- .../Views & Modals/IncomingCallBanner.swift | 17 ++++++++++- .../ConversationVC+Interaction.swift | 22 +++++++++----- .../General/SNUserDefaults.swift | 1 + 4 files changed, 53 insertions(+), 16 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 6b57e9ec1..3146d52b8 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -320,14 +320,29 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega } } + internal func showCallModal() { + let callModal = CallModal() { [weak self] in + self?.answerCall() + } + callModal.modalPresentationStyle = .overFullScreen + callModal.modalTransitionStyle = .crossDissolve + present(callModal, animated: true, completion: nil) + } + @objc private func answerCall() { - if case let .answer(sdp) = mode { - callInfoLabel.text = "Connecting..." - webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally - self.answerButton.alpha = 0 - UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { - self.answerButton.isHidden = true - }, completion: nil) + let userDefaults = UserDefaults.standard + if userDefaults[.hasSeenCallIPExposureWarning] { + if case let .answer(sdp) = mode { + callInfoLabel.text = "Connecting..." + webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally + self.answerButton.alpha = 0 + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { + self.answerButton.isHidden = true + }, completion: nil) + } + } else { + userDefaults[.hasSeenCallIPExposureWarning] = true + showCallModal() } } diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index 8ec49aadd..6db4a50b3 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -140,7 +140,22 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { } @objc private func answerCall() { - showCallVC(answer: true) + let userDefaults = UserDefaults.standard + if userDefaults[.hasSeenCallIPExposureWarning] { + showCallVC(answer: true) + } else { + showCallModal() + } + } + + internal func showCallModal() { + let callModal = CallModal() { [weak self] in + self?.showCallVC(answer: true) + } + callModal.modalPresentationStyle = .overFullScreen + callModal.modalTransitionStyle = .crossDissolve + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + presentingVC.present(callModal, animated: true, completion: nil) } @objc private func endCall() { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index a35a20db6..d4071501c 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -28,14 +28,20 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc // MARK: Call @objc func startCall(_ sender: Any?) { - guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } - let callVC = CallVC(for: contactSessionID, uuid: UUID().uuidString, mode: .offer) - callVC.conversationVC = self - callVC.modalPresentationStyle = .overFullScreen - callVC.modalTransitionStyle = .crossDissolve - self.inputAccessoryView?.isHidden = true - self.inputAccessoryView?.alpha = 0 - present(callVC, animated: true, completion: nil) + let userDefaults = UserDefaults.standard + if userDefaults[.hasSeenCallIPExposureWarning] { + guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } + let callVC = CallVC(for: contactSessionID, uuid: UUID().uuidString, mode: .offer) + callVC.conversationVC = self + callVC.modalPresentationStyle = .overFullScreen + callVC.modalTransitionStyle = .crossDissolve + self.inputAccessoryView?.isHidden = true + self.inputAccessoryView?.alpha = 0 + present(callVC, animated: true, completion: nil) + } else { + userDefaults[.hasSeenCallIPExposureWarning] = true + showCallModal() + } } internal func showCallModal() { diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index 8a0fad35b..99065af49 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -6,6 +6,7 @@ public enum SNUserDefaults { case hasSyncedInitialConfiguration = "hasSyncedConfiguration" case hasViewedSeed case hasSeenLinkPreviewSuggestion + case hasSeenCallIPExposureWarning case isUsingFullAPNs } From 18a97681673edb5c5497d031c9f5703c1eae903f Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 25 Oct 2021 13:51:41 +1100 Subject: [PATCH 079/368] filter other call messages --- .../Sending & Receiving/MessageReceiver+Handling.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 6dc8ff062..e70c9c70e 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -277,12 +277,15 @@ extension MessageReceiver { switch message.kind! { case .preOffer: print("[Calls] Received pre-offer message.") - let currentSession = getWebRTCSession() - if currentSession.uuid != message.uuid! { + if getWebRTCSession().uuid != message.uuid! { // TODO: Call in progress, put the new call on hold/reject } case .offer: print("[Calls] Received offer message.") + if getWebRTCSession().uuid != message.uuid! { + // TODO: Call in progress, put the new call on hold/reject + return + } let storage = SNMessagingKitConfiguration.shared.storage let transaction = transaction as! YapDatabaseReadWriteTransaction if let threadID = storage.getOrCreateThread(for: message.sender!, groupPublicKey: message.groupPublicKey, openGroupID: nil, using: transaction), @@ -296,11 +299,13 @@ extension MessageReceiver { handleOfferCallMessage?(message) case .answer: print("[Calls] Received answer message.") + guard getWebRTCSession().uuid == message.uuid! else { return } let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) getWebRTCSession().handleRemoteSDP(sdp, from: message.sender!) handleAnswerCallMessage?(message) case .provisionalAnswer: break // TODO: Implement case let .iceCandidates(sdpMLineIndexes, sdpMids): + guard getWebRTCSession().uuid == message.uuid! else { return } var candidates: [RTCIceCandidate] = [] let sdps = message.sdps! for i in 0.. Date: Mon, 25 Oct 2021 14:17:08 +1100 Subject: [PATCH 080/368] improve ip exposure modal content --- Session/Meta/Translations/en.lproj/Localizable.strings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 59db62789..2f50889d2 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -538,8 +538,8 @@ "modal_link_previews_title" = "Enable Link Previews?"; "modal_link_previews_explanation" = "Enabling link previews will show previews for URLs you send and receive. This can be useful, but Session will need to contact linked websites to generate previews. You can always disable link previews in Session's settings."; "modal_link_previews_button_title" = "Enable"; -"modal_call_title" = "Voice and video calls"; -"modal_call_explanation" = "The current implementation of voice/video calls will expose your IP address to the Oxen Foundation servers and the calling/called user."; +"modal_call_title" = "Voice / video calls"; +"modal_call_explanation" = "The current implementation of voice / video calls will expose your IP address to the Oxen Foundation servers and the calling / called user."; "modal_share_logs_title" = "Share Logs"; "modal_share_logs_explanation" = "Would you like to export your application logs to be able to share for troubleshooting?"; "vc_share_title" = "Share to Session"; From dea57081c716a7bb2e57fd6cbe654b542d21c4e5 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 26 Oct 2021 15:48:31 +1100 Subject: [PATCH 081/368] WIP: callkit & pushkit --- Session.xcodeproj/project.pbxproj | 12 +++++++++++ Session/Calls/CallVC.swift | 9 ++++++++ Session/Meta/AppDelegate+VoIP.swift | 21 +++++++++++++++++++ Session/Meta/AppDelegate.m | 2 ++ SessionMessagingKit/Calls/WebRTCSession.swift | 5 +++++ 5 files changed, 49 insertions(+) create mode 100644 Session/Meta/AppDelegate+VoIP.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 307b1138d..ebacdb3d8 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -149,6 +149,7 @@ 7B7CB192271508AD0079FF93 /* Vibration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB191271508AD0079FF93 /* Vibration.swift */; }; 7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; }; 7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 7BC707EA27267973002817AD /* AppDelegate+VoIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707E927267973002817AD /* AppDelegate+VoIP.swift */; }; 7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; }; 7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */; }; 7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; @@ -1133,6 +1134,7 @@ 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; 7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7BC707E927267973002817AD /* AppDelegate+VoIP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+VoIP.swift"; sourceTree = ""; }; 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+DataChannel.swift"; sourceTree = ""; }; 7BDCFC0424206E7300641C39 /* SessionNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SessionNotificationServiceExtension.entitlements; sourceTree = ""; }; 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtensionContext.swift; sourceTree = ""; }; @@ -2074,6 +2076,13 @@ path = SessionNotificationServiceExtension; sourceTree = ""; }; + 7BC707EB272788CC002817AD /* CallKit */ = { + isa = PBXGroup; + children = ( + ); + path = CallKit; + sourceTree = ""; + }; 9404664EC513585B05DF1350 /* Pods */ = { isa = PBXGroup; children = ( @@ -2361,6 +2370,7 @@ 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */, B877E24526CA13BA0007970A /* CallVC+Camera.swift */, B8B558F026C4BB0600693325 /* CameraManager.swift */, + 7BC707EB272788CC002817AD /* CallKit */, 7B7CB18C270D06350079FF93 /* Views & Modals */, ); path = Calls; @@ -3475,6 +3485,7 @@ 76EB03C218170B33006006FC /* AppDelegate.h */, 76EB03C318170B33006006FC /* AppDelegate.m */, C3AAFFF125AE99710089E6DD /* AppDelegate.swift */, + 7BC707E927267973002817AD /* AppDelegate+VoIP.swift */, 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */, B81D260326158DF5004D1FE1 /* Certificates */, B8FF8E6025C10D8B004D1F22 /* Countries */, @@ -4937,6 +4948,7 @@ 4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */, C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */, 340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */, + 7BC707EA27267973002817AD /* AppDelegate+VoIP.swift in Sources */, 7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */, 34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */, B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */, diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 3146d52b8..457a5e699 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -302,6 +302,15 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega } } + func dataChannelDidOpen() { + // Send initial video status + if (isVideoEnabled) { + webRTCSession.turnOnVideo() + } else { + webRTCSession.turnOffVideo() + } + } + // MARK: Interaction func handleAnswerMessage(_ message: CallMessage) { callInfoLabel.text = "Connecting..." diff --git a/Session/Meta/AppDelegate+VoIP.swift b/Session/Meta/AppDelegate+VoIP.swift new file mode 100644 index 000000000..beb7e9757 --- /dev/null +++ b/Session/Meta/AppDelegate+VoIP.swift @@ -0,0 +1,21 @@ +import PushKit +import SessionUtilitiesKit + +extension AppDelegate: PKPushRegistryDelegate { + + @objc public func registerVoIP() { + let pushRegistry = PKPushRegistry(queue: .main) + pushRegistry.delegate = self + pushRegistry.desiredPushTypes = [.voIP] + } + + public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { + let device = NSData(data: pushCredentials.token) + let deviceId = device.description.replacingOccurrences(of:"<", with:"").replacingOccurrences(of:">", with:"").replacingOccurrences(of:" ", with:"") + SNLog(deviceId) + } + + public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { + SNLog("Incoming VoIP with payload \(payload)") + } +} diff --git a/Session/Meta/AppDelegate.m b/Session/Meta/AppDelegate.m index dfab8a3b3..9a0c79914 100644 --- a/Session/Meta/AppDelegate.m +++ b/Session/Meta/AppDelegate.m @@ -149,6 +149,8 @@ static NSTimeInterval launchStartedAt; launchStartedAt = CACurrentMediaTime(); [LKAppModeManager configureWithDelegate:self]; + + [self registerVoIP]; // OWSLinkPreview is now in SessionMessagingKit, so to still be able to deserialize them we // need to tell NSKeyedUnarchiver about the changes. diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 08326337a..951dc1c35 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -6,6 +6,7 @@ public protocol WebRTCSessionDelegate : AnyObject { func webRTCIsConnected() func isRemoteVideoDidChange(isEnabled: Bool) + func dataChannelDidOpen() } /// See https://webrtc.org/getting-started/overview for more information. @@ -250,6 +251,9 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) { print("[Calls] Peer connection should negotiate.") + Storage.write { transaction in + self.sendOffer(to: self.contactSessionID, using: transaction).retainUntilComplete() + } } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCIceConnectionState) { @@ -274,6 +278,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { print("[Calls] Data channel opened.") self.remoteDataChannel = dataChannel + delegate?.dataChannelDidOpen() } } From e2de82a11d076ff32a130a716082e6a4e2bf79b4 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 26 Oct 2021 16:36:04 +1100 Subject: [PATCH 082/368] WIP: webRTC + callkit --- Session.xcodeproj/project.pbxproj | 12 ++++-------- .../Calls/WebRTCSession+CallKit.swift | 11 +++++++++++ 2 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 SessionMessagingKit/Calls/WebRTCSession+CallKit.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index ebacdb3d8..6aa52e1ec 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -150,6 +150,7 @@ 7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; }; 7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7BC707EA27267973002817AD /* AppDelegate+VoIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707E927267973002817AD /* AppDelegate+VoIP.swift */; }; + 7BC707EF2727C3C6002817AD /* WebRTCSession+CallKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707EE2727C3C6002817AD /* WebRTCSession+CallKit.swift */; }; 7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; }; 7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */; }; 7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; @@ -1135,6 +1136,7 @@ 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; 7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7BC707E927267973002817AD /* AppDelegate+VoIP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+VoIP.swift"; sourceTree = ""; }; + 7BC707EE2727C3C6002817AD /* WebRTCSession+CallKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+CallKit.swift"; sourceTree = ""; }; 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+DataChannel.swift"; sourceTree = ""; }; 7BDCFC0424206E7300641C39 /* SessionNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SessionNotificationServiceExtension.entitlements; sourceTree = ""; }; 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtensionContext.swift; sourceTree = ""; }; @@ -2076,13 +2078,6 @@ path = SessionNotificationServiceExtension; sourceTree = ""; }; - 7BC707EB272788CC002817AD /* CallKit */ = { - isa = PBXGroup; - children = ( - ); - path = CallKit; - sourceTree = ""; - }; 9404664EC513585B05DF1350 /* Pods */ = { isa = PBXGroup; children = ( @@ -2370,7 +2365,6 @@ 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */, B877E24526CA13BA0007970A /* CallVC+Camera.swift */, B8B558F026C4BB0600693325 /* CameraManager.swift */, - 7BC707EB272788CC002817AD /* CallKit */, 7B7CB18C270D06350079FF93 /* Views & Modals */, ); path = Calls; @@ -2410,6 +2404,7 @@ B8B558FE26C4E05E00693325 /* WebRTCSession+MessageHandling.swift */, B8BF43B926CC95FB007828D1 /* WebRTC+Utilities.swift */, 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */, + 7BC707EE2727C3C6002817AD /* WebRTCSession+CallKit.swift */, ); path = Calls; sourceTree = ""; @@ -4816,6 +4811,7 @@ C32C5AF8256DC051003C73A2 /* OWSDisappearingMessagesConfiguration.m in Sources */, C32C5EBA256DE130003C73A2 /* OWSQuotedReplyModel.m in Sources */, C32C5B62256DC333003C73A2 /* OWSDisappearingConfigurationUpdateInfoMessage.m in Sources */, + 7BC707EF2727C3C6002817AD /* WebRTCSession+CallKit.swift in Sources */, C352A2F525574B4700338F3E /* Job.swift in Sources */, C32C5C01256DC9A0003C73A2 /* OWSIdentityManager.m in Sources */, C32C59C4256DB41F003C73A2 /* TSContactThread.m in Sources */, diff --git a/SessionMessagingKit/Calls/WebRTCSession+CallKit.swift b/SessionMessagingKit/Calls/WebRTCSession+CallKit.swift new file mode 100644 index 000000000..e1bada4e8 --- /dev/null +++ b/SessionMessagingKit/Calls/WebRTCSession+CallKit.swift @@ -0,0 +1,11 @@ +import CallKit + +extension WebRTCSession: CXProviderDelegate { + public func providerDidReset(_ provider: CXProvider) { + + } + + public func reportIncomingCall() { + + } +} From 0ef7bdc9ce3947708b2bd86b1f0983737fa08d9c Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 28 Oct 2021 17:02:41 +1100 Subject: [PATCH 083/368] refactor to plug in callkit --- Session.xcodeproj/project.pbxproj | 20 ++- .../Calls/Call Management/SessionCall.swift | 132 ++++++++++++++++++ .../Call Management/SessionCallManager.swift | 96 +++++++++++++ Session/Calls/CallVC.swift | 63 +++------ .../Views & Modals/IncomingCallBanner.swift | 21 ++- .../Calls/Views & Modals/MiniCallView.swift | 7 +- .../ConversationVC+Interaction.swift | 7 +- Session/Meta/AppDelegate.swift | 5 +- Session/Meta/AppEnvironment.swift | 4 + .../Calls/WebRTCSession+CallKit.swift | 11 -- 10 files changed, 282 insertions(+), 84 deletions(-) create mode 100644 Session/Calls/Call Management/SessionCall.swift create mode 100644 Session/Calls/Call Management/SessionCallManager.swift delete mode 100644 SessionMessagingKit/Calls/WebRTCSession+CallKit.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6aa52e1ec..db90d6424 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -147,10 +147,11 @@ 7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */; }; 7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */; }; 7B7CB192271508AD0079FF93 /* Vibration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB191271508AD0079FF93 /* Vibration.swift */; }; + 7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA68908272A27BE00EFC32F /* SessionCall.swift */; }; 7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; }; 7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7BC707EA27267973002817AD /* AppDelegate+VoIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707E927267973002817AD /* AppDelegate+VoIP.swift */; }; - 7BC707EF2727C3C6002817AD /* WebRTCSession+CallKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707EE2727C3C6002817AD /* WebRTCSession+CallKit.swift */; }; + 7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707F127290ACB002817AD /* SessionCallManager.swift */; }; 7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; }; 7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */; }; 7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; @@ -1132,11 +1133,12 @@ 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallBanner.swift; sourceTree = ""; }; 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniCallView.swift; sourceTree = ""; }; 7B7CB191271508AD0079FF93 /* Vibration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vibration.swift; sourceTree = ""; }; + 7BA68908272A27BE00EFC32F /* SessionCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCall.swift; sourceTree = ""; }; 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; 7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7BC707E927267973002817AD /* AppDelegate+VoIP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+VoIP.swift"; sourceTree = ""; }; - 7BC707EE2727C3C6002817AD /* WebRTCSession+CallKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+CallKit.swift"; sourceTree = ""; }; + 7BC707F127290ACB002817AD /* SessionCallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCallManager.swift; sourceTree = ""; }; 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+DataChannel.swift"; sourceTree = ""; }; 7BDCFC0424206E7300641C39 /* SessionNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SessionNotificationServiceExtension.entitlements; sourceTree = ""; }; 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtensionContext.swift; sourceTree = ""; }; @@ -2068,6 +2070,15 @@ path = "Views & Modals"; sourceTree = ""; }; + 7BA68907272A279900EFC32F /* Call Management */ = { + isa = PBXGroup; + children = ( + 7BC707F127290ACB002817AD /* SessionCallManager.swift */, + 7BA68908272A27BE00EFC32F /* SessionCall.swift */, + ); + path = "Call Management"; + sourceTree = ""; + }; 7BC01A3C241F40AB00BC7C55 /* SessionNotificationServiceExtension */ = { isa = PBXGroup; children = ( @@ -2365,6 +2376,7 @@ 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */, B877E24526CA13BA0007970A /* CallVC+Camera.swift */, B8B558F026C4BB0600693325 /* CameraManager.swift */, + 7BA68907272A279900EFC32F /* Call Management */, 7B7CB18C270D06350079FF93 /* Views & Modals */, ); path = Calls; @@ -2404,7 +2416,6 @@ B8B558FE26C4E05E00693325 /* WebRTCSession+MessageHandling.swift */, B8BF43B926CC95FB007828D1 /* WebRTC+Utilities.swift */, 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */, - 7BC707EE2727C3C6002817AD /* WebRTCSession+CallKit.swift */, ); path = Calls; sourceTree = ""; @@ -4811,7 +4822,6 @@ C32C5AF8256DC051003C73A2 /* OWSDisappearingMessagesConfiguration.m in Sources */, C32C5EBA256DE130003C73A2 /* OWSQuotedReplyModel.m in Sources */, C32C5B62256DC333003C73A2 /* OWSDisappearingConfigurationUpdateInfoMessage.m in Sources */, - 7BC707EF2727C3C6002817AD /* WebRTCSession+CallKit.swift in Sources */, C352A2F525574B4700338F3E /* Job.swift in Sources */, C32C5C01256DC9A0003C73A2 /* OWSIdentityManager.m in Sources */, C32C59C4256DB41F003C73A2 /* TSContactThread.m in Sources */, @@ -4874,6 +4884,7 @@ C3548F0624456447009433A8 /* PNModeVC.swift in Sources */, B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */, D221A09A169C9E5E00537ABF /* main.m in Sources */, + 7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */, 3496957221A301A100DCFE74 /* OWSBackup.m in Sources */, B835247925C38D880089A44F /* MessageCell.swift in Sources */, B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */, @@ -4935,6 +4946,7 @@ B8CCF6432397711F0091D419 /* SettingsVC.swift in Sources */, C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */, 7B1581E827210ECC00848B49 /* RenderView.swift in Sources */, + 7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */, 3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */, 45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */, 4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */, diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift new file mode 100644 index 000000000..4d00962f9 --- /dev/null +++ b/Session/Calls/Call Management/SessionCall.swift @@ -0,0 +1,132 @@ +import Foundation +import WebRTC +import SessionMessagingKit + +public final class SessionCall: NSObject { + // MARK: Metadata Properties + let uuid: UUID + let sessionID: String + let mode: Mode + let webRTCSession: WebRTCSession + var contactName: String { + let contact = Storage.shared.getContact(with: self.sessionID) + return contact?.displayName(for: Contact.Context.regular) ?? self.sessionID + } + var profilePicture: UIImage { + if let result = OWSProfileManager.shared().profileAvatar(forRecipientId: sessionID) { + return result + } else { + return Identicon.generatePlaceholderIcon(seed: sessionID, text: contactName, size: 300) + } + } + + // MARK: Mode + enum Mode { + case offer + case answer(sdp: RTCSessionDescription) + } + + // MARK: Call State Properties + var connectingDate: Date? { + didSet { + stateDidChange?() + hasStartedConnectingDidChange?() + } + } + + var connectedDate: Date? { + didSet { + stateDidChange?() + hasConnectedDidChange?() + } + } + + var endDate: Date? { + didSet { + stateDidChange?() + hasEndedDidChange?() + } + } + + // Not yet implemented + var isOnHold = false { + didSet { + stateDidChange?() + } + } + + // MARK: State Change Callbacks + var stateDidChange: (() -> Void)? + var hasStartedConnectingDidChange: (() -> Void)? + var hasConnectedDidChange: (() -> Void)? + var hasEndedDidChange: (() -> Void)? + + // MARK: Derived Properties + var hasStartedConnecting: Bool { + get { return connectingDate != nil } + set { connectingDate = newValue ? Date() : nil } + } + + var hasConnected: Bool { + get { return connectedDate != nil } + set { connectedDate = newValue ? Date() : nil } + } + + var hasEnded: Bool { + get { return endDate != nil } + set { endDate = newValue ? Date() : nil } + } + + var duration: TimeInterval { + guard let connectedDate = connectedDate else { + return 0 + } + + return Date().timeIntervalSince(connectedDate) + } + + // MARK: Initialization + init(for sessionID: String, uuid: String, mode: Mode) { + self.sessionID = sessionID + self.uuid = UUID(uuidString: uuid)! + self.mode = mode + self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID, with: uuid) + super.init() + reportIncomingCallIfNeeded() + } + + func reportIncomingCallIfNeeded() { + guard case .offer = mode else { return } + AppEnvironment.shared.callManager.reportIncomingCall(self, callerName: contactName) { error in + + } + } + + // MARK: Actions + func startSessionCall(completion: (() -> Void)?) { + guard case .offer = mode else { return } + Storage.write { transaction in + self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction).done { + self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).done { + self.hasStartedConnecting = true + }.retainUntilComplete() + }.retainUntilComplete() + } + completion?() + } + + func answerSessionCall(completion: (() -> Void)?) { + guard case let .answer(sdp) = mode else { return } + hasStartedConnecting = true + webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally + completion?() + } + + func endSessionCall() { + guard !hasEnded else { return } + Storage.write { transaction in + self.webRTCSession.endCall(with: self.sessionID, using: transaction) + } + hasEnded = true + } +} diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift new file mode 100644 index 000000000..149451fd6 --- /dev/null +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -0,0 +1,96 @@ +import CallKit +import SessionMessagingKit + +public final class SessionCallManager: NSObject, CXProviderDelegate { + private let provider: CXProvider + var currentCall: SessionCall? + + private static var _sharedProvider: CXProvider? + class func sharedProvider(useSystemCallLog: Bool) -> CXProvider { + let configuration = buildProviderConfiguration(useSystemCallLog: useSystemCallLog) + + if let sharedProvider = self._sharedProvider { + sharedProvider.configuration = configuration + return sharedProvider + } else { + SwiftSingletons.register(self) + let provider = CXProvider(configuration: configuration) + _sharedProvider = provider + return provider + } + } + + class func buildProviderConfiguration(useSystemCallLog: Bool) -> CXProviderConfiguration { + let localizedName = NSLocalizedString("APPLICATION_NAME", comment: "Name of application") + let providerConfiguration = CXProviderConfiguration(localizedName: localizedName) + providerConfiguration.supportsVideo = true + providerConfiguration.maximumCallsPerCallGroup = 1 + providerConfiguration.supportedHandleTypes = [.generic] + let iconMaskImage = #imageLiteral(resourceName: "SessionGreen32") + providerConfiguration.iconTemplateImageData = iconMaskImage.pngData() + providerConfiguration.includesCallsInRecents = useSystemCallLog + + return providerConfiguration + } + + init(useSystemCallLog: Bool = false) { + AssertIsOnMainThread() + self.provider = type(of: self).sharedProvider(useSystemCallLog: useSystemCallLog) + + super.init() + + // We cannot assert singleton here, because this class gets rebuilt when the user changes relevant call settings + self.provider.setDelegate(self, queue: nil) + } + + public func providerDidReset(_ provider: CXProvider) { + AssertIsOnMainThread() + + } + + public func reportOutgoingCall(_ call: SessionCall, completion: @escaping (Error?) -> Void) { + AssertIsOnMainThread() + self.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate) + self.currentCall = call + call.hasConnectedDidChange = { + self.provider.reportOutgoingCall(with: call.uuid, connectedAt: call.connectedDate) + } + } + + public func reportIncomingCall(_ call: SessionCall, callerName: String, completion: @escaping (Error?) -> Void) { + AssertIsOnMainThread() + + // Construct a CXCallUpdate describing the incoming call, including the caller. + let update = CXCallUpdate() + update.localizedCallerName = callerName + update.remoteHandle = CXHandle(type: .generic, value: call.uuid.uuidString) + update.hasVideo = true + + disableUnsupportedFeatures(callUpdate: update) + + // Report the incoming call to the system + self.provider.reportNewIncomingCall(with: call.uuid, update: update) { error in + guard error == nil else { + completion(error) + Logger.error("failed to report new incoming call, error: \(error!)") + return + } + self.currentCall = call + completion(nil) + } + } + + // MARK: Util + private func disableUnsupportedFeatures(callUpdate: CXCallUpdate) { + // Call Holding is failing to restart audio when "swapping" calls on the CallKit screen + // until user returns to in-app call screen. + callUpdate.supportsHolding = false + + // Not yet supported + callUpdate.supportsGrouping = false + callUpdate.supportsUngrouping = false + + // Is there any reason to support this? + callUpdate.supportsDTMF = false + } +} diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 457a5e699..f2ebb0517 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -5,15 +5,13 @@ import SessionUtilitiesKit import UIKit final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelegate { - let sessionID: String - let uuid: String - let mode: Mode - let webRTCSession: WebRTCSession + let call: SessionCall + var webRTCSession: WebRTCSession { return call.webRTCSession } var shouldAnswer = false var isMuted = false var isVideoEnabled = false var shouldRestartCamera = true - var conversationVC: ConversationVC? = nil + weak var conversationVC: ConversationVC? = nil lazy var cameraManager: CameraManager = { let result = CameraManager() @@ -159,20 +157,15 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega return result }() - // MARK: Mode - enum Mode { - case offer - case answer(sdp: RTCSessionDescription) - } - // MARK: Lifecycle - init(for sessionID: String, uuid: String, mode: Mode) { - self.sessionID = sessionID - self.uuid = uuid - self.mode = mode - self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID, with: uuid) + init(for call: SessionCall) { + self.call = call super.init(nibName: nil, bundle: nil) - self.webRTCSession.delegate = self + self.call.webRTCSession.delegate = self + self.call.hasEndedDidChange = { + self.conversationVC?.showInputAccessoryView() + self.presentingViewController?.dismiss(animated: true, completion: nil) + } } required init(coder: NSCoder) { preconditionFailure("Use init(for:) instead.") } @@ -184,19 +177,10 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega setUpViewHierarchy() if shouldRestartCamera { cameraManager.prepare() } touch(videoCapturer) - var contact: Contact? - Storage.read { transaction in - contact = Storage.shared.getContact(with: self.sessionID) - } - titleLabel.text = contact?.displayName(for: Contact.Context.regular) ?? sessionID - if case .offer = mode { - callInfoLabel.text = "Ringing..." - Storage.write { transaction in - self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction).done { - self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).retainUntilComplete() - }.retainUntilComplete() - } - answerButton.isHidden = true + titleLabel.text = self.call.contactName + self.call.startSessionCall{ + self.callInfoLabel.text = "Ringing..." + self.answerButton.isHidden = true } if shouldAnswer { answerCall() } } @@ -251,12 +235,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega imageView.layer.cornerRadius = 150 imageView.layer.masksToBounds = true imageView.contentMode = .scaleAspectFill - if let profilePicture = OWSProfileManager.shared().profileAvatar(forRecipientId: sessionID) { - imageView.image = profilePicture - } else { - let displayName = Storage.shared.getContact(with: sessionID)?.name ?? sessionID - imageView.image = Identicon.generatePlaceholderIcon(seed: sessionID, text: displayName, size: 300) - } + imageView.image = self.call.profilePicture background.addSubview(imageView) imageView.set(.width, to: 300) imageView.set(.height, to: 300) @@ -284,6 +263,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega func webRTCIsConnected() { DispatchQueue.main.async { self.callInfoLabel.text = "Connected" + self.call.hasConnected = true self.minimizeButton.isHidden = false UIView.animate(withDuration: 0.5, delay: 1, options: [], animations: { self.callInfoLabel.alpha = 0 @@ -341,9 +321,8 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega @objc private func answerCall() { let userDefaults = UserDefaults.standard if userDefaults[.hasSeenCallIPExposureWarning] { - if case let .answer(sdp) = mode { - callInfoLabel.text = "Connecting..." - webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally + self.call.answerSessionCall{ + self.callInfoLabel.text = "Connecting..." self.answerButton.alpha = 0 UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { self.answerButton.isHidden = true @@ -356,11 +335,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega } @objc private func endCall() { - Storage.write { transaction in - WebRTCSession.current?.endCall(with: self.sessionID, using: transaction) - } - self.conversationVC?.showInputAccessoryView() - presentingViewController?.dismiss(animated: true, completion: nil) + self.call.endSessionCall() } @objc private func minimize() { diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index 6db4a50b3..9eff176c3 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -5,9 +5,7 @@ import SessionMessagingKit final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { private static let swipeToOperateThreshold: CGFloat = 60 private var previousY: CGFloat = 0 - let sessionID: String - let uuid: String - let sdp: RTCSessionDescription + let call: SessionCall // MARK: UI Components private lazy var profilePictureView: ProfilePictureView = { @@ -60,10 +58,8 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { // MARK: Initialization public static var current: IncomingCallBanner? - init(for sessionID: String, uuid: String, sdp: RTCSessionDescription) { - self.uuid = uuid - self.sessionID = sessionID - self.sdp = sdp + init(for call: SessionCall) { + self.call = call super.init(frame: CGRect.zero) setUpViewHierarchy() setUpGestureRecognizers() @@ -86,9 +82,9 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { self.layer.cornerRadius = Values.veryLargeSpacing self.layer.masksToBounds = true self.set(.height, to: 100) - profilePictureView.publicKey = self.sessionID + profilePictureView.publicKey = call.sessionID profilePictureView.update() - displayNameLabel.text = Storage.shared.getContact(with: sessionID)?.name + displayNameLabel.text = call.contactName let stackView = UIStackView(arrangedSubviews: [profilePictureView, displayNameLabel, hangUpButton, answerButton]) stackView.axis = .horizontal stackView.alignment = .center @@ -159,16 +155,15 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { } @objc private func endCall() { - Storage.write { transaction in - WebRTCSession.current?.endCall(with: self.sessionID, using: transaction) + self.call.endSessionCall{ + self.dismiss() } - dismiss() } public func showCallVC(answer: Bool) { dismiss() guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully - let callVC = CallVC(for: sessionID, uuid: uuid, mode: .answer(sdp: sdp)) + let callVC = CallVC(for: self.call) callVC.shouldAnswer = answer callVC.modalPresentationStyle = .overFullScreen callVC.modalTransitionStyle = .crossDissolve diff --git a/Session/Calls/Views & Modals/MiniCallView.swift b/Session/Calls/Views & Modals/MiniCallView.swift index 41b261c42..c161264e4 100644 --- a/Session/Calls/Views & Modals/MiniCallView.swift +++ b/Session/Calls/Views & Modals/MiniCallView.swift @@ -50,12 +50,7 @@ final class MiniCallView: UIView { imageView.layer.cornerRadius = 32 imageView.layer.masksToBounds = true imageView.contentMode = .scaleAspectFill - if let profilePicture = OWSProfileManager.shared().profileAvatar(forRecipientId: callVC.sessionID) { - imageView.image = profilePicture - } else { - let displayName = Storage.shared.getContact(with: callVC.sessionID)?.name ?? callVC.sessionID - imageView.image = Identicon.generatePlaceholderIcon(seed: callVC.sessionID, text: displayName, size: 64) - } + imageView.image = callVC.call.profilePicture background.addSubview(imageView) imageView.set(.width, to: 64) imageView.set(.height, to: 64) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index d4071501c..3b12eca66 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -31,7 +31,8 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc let userDefaults = UserDefaults.standard if userDefaults[.hasSeenCallIPExposureWarning] { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } - let callVC = CallVC(for: contactSessionID, uuid: UUID().uuidString, mode: .offer) + let call = SessionCall(for: contactSessionID, uuid: UUID().uuidString, mode: .offer) + let callVC = CallVC(for: call) callVC.conversationVC = self callVC.modalPresentationStyle = .overFullScreen callVC.modalTransitionStyle = .crossDissolve @@ -54,9 +55,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc } internal func showCallVCIfNeeded() { - guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID(), - let incomingCallBanner = IncomingCallBanner.current, incomingCallBanner.sessionID == contactSessionID - else { return } + guard let incomingCallBanner = IncomingCallBanner.current else { return } incomingCallBanner.showCallVC(answer: false) } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index ef474dd76..8d4bc29de 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -11,9 +11,10 @@ extension AppDelegate { MessageReceiver.handleOfferCallMessage = { message in DispatchQueue.main.async { let sdp = RTCSessionDescription(type: .offer, sdp: message.sdps![0]) + let call = SessionCall(for: message.sender!, uuid: message.uuid!, mode: .answer(sdp: sdp)) guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == message.sender! { - let callVC = CallVC(for: message.sender!, uuid: message.uuid!, mode: .answer(sdp: sdp)) + let callVC = CallVC(for: call) callVC.modalPresentationStyle = .overFullScreen callVC.modalTransitionStyle = .crossDissolve callVC.conversationVC = conversationVC @@ -21,7 +22,7 @@ extension AppDelegate { conversationVC.inputAccessoryView?.alpha = 0 presentingVC.present(callVC, animated: true, completion: nil) } else { - let incomingCallBanner = IncomingCallBanner(for: message.sender!, uuid: message.uuid!, sdp: sdp) + let incomingCallBanner = IncomingCallBanner(for: call) incomingCallBanner.show() } } diff --git a/Session/Meta/AppEnvironment.swift b/Session/Meta/AppEnvironment.swift index 805df0d81..38f06324f 100644 --- a/Session/Meta/AppEnvironment.swift +++ b/Session/Meta/AppEnvironment.swift @@ -27,6 +27,9 @@ import SignalUtilitiesKit @objc public var accountManager: AccountManager + + @objc + public var callManager: SessionCallManager @objc public var notificationPresenter: NotificationPresenter @@ -54,6 +57,7 @@ import SignalUtilitiesKit private override init() { self.accountManager = AccountManager() + self.callManager = SessionCallManager() self.notificationPresenter = NotificationPresenter() self.pushRegistrationManager = PushRegistrationManager() self.backup = OWSBackup() diff --git a/SessionMessagingKit/Calls/WebRTCSession+CallKit.swift b/SessionMessagingKit/Calls/WebRTCSession+CallKit.swift deleted file mode 100644 index e1bada4e8..000000000 --- a/SessionMessagingKit/Calls/WebRTCSession+CallKit.swift +++ /dev/null @@ -1,11 +0,0 @@ -import CallKit - -extension WebRTCSession: CXProviderDelegate { - public func providerDidReset(_ provider: CXProvider) { - - } - - public func reportIncomingCall() { - - } -} From 4db87992b223fd224b00069a6982caf3858b1b89 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 29 Oct 2021 15:22:23 +1100 Subject: [PATCH 084/368] refactor for voip push notification --- Session.xcodeproj/project.pbxproj | 4 - .../Calls/Call Management/SessionCall.swift | 6 +- .../Call Management/SessionCallManager.swift | 12 ++- Session/Calls/CallVC.swift | 2 + .../Views & Modals/IncomingCallBanner.swift | 7 +- .../ConversationVC+Interaction.swift | 2 - Session/Meta/AppDelegate+VoIP.swift | 21 ----- Session/Meta/AppDelegate.m | 2 - Session/Meta/AppDelegate.swift | 2 - .../PushRegistrationManager.swift | 84 +++++++++++++++++-- 10 files changed, 93 insertions(+), 49 deletions(-) delete mode 100644 Session/Meta/AppDelegate+VoIP.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index db90d6424..cd78b38cf 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -150,7 +150,6 @@ 7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA68908272A27BE00EFC32F /* SessionCall.swift */; }; 7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; }; 7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 7BC707EA27267973002817AD /* AppDelegate+VoIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707E927267973002817AD /* AppDelegate+VoIP.swift */; }; 7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707F127290ACB002817AD /* SessionCallManager.swift */; }; 7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; }; 7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */; }; @@ -1137,7 +1136,6 @@ 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; 7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 7BC707E927267973002817AD /* AppDelegate+VoIP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+VoIP.swift"; sourceTree = ""; }; 7BC707F127290ACB002817AD /* SessionCallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCallManager.swift; sourceTree = ""; }; 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+DataChannel.swift"; sourceTree = ""; }; 7BDCFC0424206E7300641C39 /* SessionNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SessionNotificationServiceExtension.entitlements; sourceTree = ""; }; @@ -3491,7 +3489,6 @@ 76EB03C218170B33006006FC /* AppDelegate.h */, 76EB03C318170B33006006FC /* AppDelegate.m */, C3AAFFF125AE99710089E6DD /* AppDelegate.swift */, - 7BC707E927267973002817AD /* AppDelegate+VoIP.swift */, 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */, B81D260326158DF5004D1FE1 /* Certificates */, B8FF8E6025C10D8B004D1F22 /* Countries */, @@ -4956,7 +4953,6 @@ 4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */, C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */, 340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */, - 7BC707EA27267973002817AD /* AppDelegate+VoIP.swift in Sources */, 7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */, 34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */, B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */, diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 4d00962f9..9292a5667 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -98,13 +98,16 @@ public final class SessionCall: NSObject { func reportIncomingCallIfNeeded() { guard case .offer = mode else { return } AppEnvironment.shared.callManager.reportIncomingCall(self, callerName: contactName) { error in - + if let error = error { + SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") + } } } // MARK: Actions func startSessionCall(completion: (() -> Void)?) { guard case .offer = mode else { return } + AppEnvironment.shared.callManager.reportOutgoingCall(self) Storage.write { transaction in self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction).done { self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).done { @@ -128,5 +131,6 @@ public final class SessionCall: NSObject { self.webRTCSession.endCall(with: self.sessionID, using: transaction) } hasEnded = true + AppEnvironment.shared.callManager.reportCurrentCallEnded() } } diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index 149451fd6..b309ce49a 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -45,13 +45,15 @@ public final class SessionCallManager: NSObject, CXProviderDelegate { public func providerDidReset(_ provider: CXProvider) { AssertIsOnMainThread() - + currentCall?.endSessionCall() } - public func reportOutgoingCall(_ call: SessionCall, completion: @escaping (Error?) -> Void) { + public func reportOutgoingCall(_ call: SessionCall) { AssertIsOnMainThread() - self.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate) self.currentCall = call + call.hasStartedConnectingDidChange = { + self.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate) + } call.hasConnectedDidChange = { self.provider.reportOutgoingCall(with: call.uuid, connectedAt: call.connectedDate) } @@ -80,6 +82,10 @@ public final class SessionCallManager: NSObject, CXProviderDelegate { } } + public func reportCurrentCallEnded() { + self.currentCall = nil + } + // MARK: Util private func disableUnsupportedFeatures(callUpdate: CXCallUpdate) { // Call Holding is failing to restart audio when "swapping" calls on the CallKit screen diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index f2ebb0517..cf816f940 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -166,6 +166,8 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega self.conversationVC?.showInputAccessoryView() self.presentingViewController?.dismiss(animated: true, completion: nil) } + self.modalPresentationStyle = .overFullScreen + self.modalTransitionStyle = .crossDissolve } required init(coder: NSCoder) { preconditionFailure("Use init(for:) instead.") } diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index 9eff176c3..29f1a0221 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -155,9 +155,8 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { } @objc private func endCall() { - self.call.endSessionCall{ - self.dismiss() - } + self.call.endSessionCall() + self.dismiss() } public func showCallVC(answer: Bool) { @@ -165,8 +164,6 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully let callVC = CallVC(for: self.call) callVC.shouldAnswer = answer - callVC.modalPresentationStyle = .overFullScreen - callVC.modalTransitionStyle = .crossDissolve if let conversationVC = presentingVC as? ConversationVC { callVC.conversationVC = conversationVC conversationVC.inputAccessoryView?.isHidden = true diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 3b12eca66..efc0f6ced 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -34,8 +34,6 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc let call = SessionCall(for: contactSessionID, uuid: UUID().uuidString, mode: .offer) let callVC = CallVC(for: call) callVC.conversationVC = self - callVC.modalPresentationStyle = .overFullScreen - callVC.modalTransitionStyle = .crossDissolve self.inputAccessoryView?.isHidden = true self.inputAccessoryView?.alpha = 0 present(callVC, animated: true, completion: nil) diff --git a/Session/Meta/AppDelegate+VoIP.swift b/Session/Meta/AppDelegate+VoIP.swift deleted file mode 100644 index beb7e9757..000000000 --- a/Session/Meta/AppDelegate+VoIP.swift +++ /dev/null @@ -1,21 +0,0 @@ -import PushKit -import SessionUtilitiesKit - -extension AppDelegate: PKPushRegistryDelegate { - - @objc public func registerVoIP() { - let pushRegistry = PKPushRegistry(queue: .main) - pushRegistry.delegate = self - pushRegistry.desiredPushTypes = [.voIP] - } - - public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { - let device = NSData(data: pushCredentials.token) - let deviceId = device.description.replacingOccurrences(of:"<", with:"").replacingOccurrences(of:">", with:"").replacingOccurrences(of:" ", with:"") - SNLog(deviceId) - } - - public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { - SNLog("Incoming VoIP with payload \(payload)") - } -} diff --git a/Session/Meta/AppDelegate.m b/Session/Meta/AppDelegate.m index 9a0c79914..dfab8a3b3 100644 --- a/Session/Meta/AppDelegate.m +++ b/Session/Meta/AppDelegate.m @@ -149,8 +149,6 @@ static NSTimeInterval launchStartedAt; launchStartedAt = CACurrentMediaTime(); [LKAppModeManager configureWithDelegate:self]; - - [self registerVoIP]; // OWSLinkPreview is now in SessionMessagingKit, so to still be able to deserialize them we // need to tell NSKeyedUnarchiver about the changes. diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 8d4bc29de..70c0976b5 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -15,8 +15,6 @@ extension AppDelegate { guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == message.sender! { let callVC = CallVC(for: call) - callVC.modalPresentationStyle = .overFullScreen - callVC.modalTransitionStyle = .crossDissolve callVC.conversationVC = conversationVC conversationVC.inputAccessoryView?.isHidden = true conversationVC.inputAccessoryView?.alpha = 0 diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index fc8b2f600..a2041f069 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -17,7 +17,7 @@ public enum PushRegistrationError: Error { /** * Singleton used to integrate with push notification services - registration and routing received remote notifications. */ -@objc public class PushRegistrationManager: NSObject { +@objc public class PushRegistrationManager: NSObject, PKPushRegistryDelegate { // MARK: - Dependencies @@ -44,21 +44,25 @@ public enum PushRegistrationError: Error { private var vanillaTokenResolver: Resolver? private var voipRegistry: PKPushRegistry? - private var voipTokenPromise: Promise? - private var voipTokenResolver: Resolver? + private var voipTokenPromise: Promise? + private var voipTokenResolver: Resolver? // MARK: Public interface public func requestPushTokens() -> Promise<(pushToken: String, voipToken: String)> { - return firstly { - self.registerUserNotificationSettings() - }.then { () -> Promise<(pushToken: String, voipToken: String)> in + Logger.info("") + + return firstly { () -> Promise in + return self.registerUserNotificationSettings() + }.then { (_) -> Promise<(pushToken: String, voipToken: String)> in guard !Platform.isSimulator else { throw PushRegistrationError.pushNotSupported(description: "Push not supported on simulators") } - - return self.registerForVanillaPushToken().map { vanillaPushToken -> (pushToken: String, voipToken: String) in - return (pushToken: vanillaPushToken, voipToken: "") + + return self.registerForVanillaPushToken().then { vanillaPushToken -> Promise<(pushToken: String, voipToken: String)> in + self.registerForVoipPushToken().map { voipPushToken in + (pushToken: vanillaPushToken, voipToken: voipPushToken ?? "") + } } } } @@ -168,6 +172,68 @@ public enum PushRegistrationError: Error { self.vanillaTokenPromise = nil } } + + private func createVoipRegistryIfNecessary() { + AssertIsOnMainThread() + + guard voipRegistry == nil else { return } + let voipRegistry = PKPushRegistry(queue: nil) + self.voipRegistry = voipRegistry + voipRegistry.desiredPushTypes = [.voIP] + voipRegistry.delegate = self + } + + private func registerForVoipPushToken() -> Promise { + AssertIsOnMainThread() + + guard self.voipTokenPromise == nil else { + let promise = self.voipTokenPromise! + return promise.map { $0?.hexEncodedString } + } + + // No pending voip token yet. Create a new promise + let (promise, resolver) = Promise.pending() + self.voipTokenPromise = promise + self.voipTokenResolver = resolver + + // We don't create the voip registry in init, because it immediately requests the voip token, + // potentially before we're ready to handle it. + createVoipRegistryIfNecessary() + + guard let voipRegistry = self.voipRegistry else { + owsFailDebug("failed to initialize voipRegistry") + resolver.reject(PushRegistrationError.assertionError(description: "failed to initialize voipRegistry")) + return promise.map { _ in + // coerce expected type of returned promise - we don't really care about the value, + // since this promise has been rejected. In practice this shouldn't happen + String() + } + } + + // If we've already completed registering for a voip token, resolve it immediately, + // rather than waiting for the delegate method to be called. + if let voipTokenData = voipRegistry.pushToken(for: .voIP) { + Logger.info("using pre-registered voIP token") + resolver.fulfill(voipTokenData) + } + + return promise.map { (voipTokenData: Data?) -> String? in + Logger.info("successfully registered for voip push notifications") + return voipTokenData?.hexEncodedString + }.ensure { + self.voipTokenPromise = nil + } + } + + // MARK: PKPushRegistryDelegate + public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { + Logger.info("") + owsAssertDebug(type == .voIP) + owsAssertDebug(pushCredentials.type == .voIP) + guard let voipTokenResolver = voipTokenResolver else { return } + + voipTokenResolver.fulfill(pushCredentials.token) + } } // We transmit pushToken data as hex encoded string to the server From e530b50938bad4ea522720cffb5fe7b33ca1787b Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 29 Oct 2021 16:32:50 +1100 Subject: [PATCH 085/368] WIP: notification for voip call --- Session/Calls/Call Management/SessionCall.swift | 2 +- Session/Meta/Session-Info.plist | 1 + .../Notifications/PushRegistrationManager.swift | 6 ++++++ .../NotificationServiceExtension.swift | 16 +++++++++++++--- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 9292a5667..984ac793c 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -96,7 +96,7 @@ public final class SessionCall: NSObject { } func reportIncomingCallIfNeeded() { - guard case .offer = mode else { return } + guard case .answer(_) = mode else { return } AppEnvironment.shared.callManager.reportIncomingCall(self, callerName: contactName) { error in if let error = error { SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") diff --git a/Session/Meta/Session-Info.plist b/Session/Meta/Session-Info.plist index 533d2efe6..05f2de759 100644 --- a/Session/Meta/Session-Info.plist +++ b/Session/Meta/Session-Info.plist @@ -123,6 +123,7 @@ audio fetch remote-notification + voip UILaunchStoryboardName Launch Screen diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index a2041f069..b2d560b95 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -234,6 +234,12 @@ public enum PushRegistrationError: Error { voipTokenResolver.fulfill(pushCredentials.token) } + + public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) { + owsAssertDebug(CurrentAppContext().isMainApp) + owsAssertDebug(type == .voIP) + Vibration.shared.startVibration() + } } // We transmit pushToken data as hex encoded string to the server diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 3ca94db99..fc0ea2967 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -2,6 +2,7 @@ import UserNotifications import BackgroundTasks import SessionMessagingKit import SignalUtilitiesKit +import CallKit public final class NotificationServiceExtension : UNNotificationServiceExtension { private var didPerformSetup = false @@ -95,7 +96,7 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension notificationContent.badge = 1 notificationContent.title = "Session" notificationContent.body = "\(senderDisplayName) is calling..." - return self.handleSuccessForIncomingCall(for: notificationContent) + return self.handleSuccessForIncomingCall(for: notificationContent, callID: callMessage.uuid!) default: return self.completeSilenty() } if (senderPublicKey == userPublicKey) { @@ -215,9 +216,18 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension contentHandler!(.init()) } - private func handleSuccessForIncomingCall(for content: UNMutableNotificationContent) { + private func handleSuccessForIncomingCall(for content: UNMutableNotificationContent, callID: String) { // TODO: poll for the real offer, play incoming call ring - contentHandler!(content) + if #available(iOSApplicationExtension 14.5, *) { + CXProvider.reportNewIncomingVoIPPushPayload(["uuid": callID]) { error in + if let error = error { + owsFailDebug("Failed to notify main app of call message: \(error)") + } else { + Logger.info("Successfully notified main app of call message.") + } + self.contentHandler!(content) + } + } } private func handleSuccess(for content: UNMutableNotificationContent) { From 6f78d6dfbe50fa893104fb1b5efd3f8aee28c083 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 3 Nov 2021 15:31:50 +1100 Subject: [PATCH 086/368] refactoring for CallKit --- .../Calls/Call Management/SessionCall.swift | 91 ++++++++++++++-- Session/Calls/CallVC+Camera.swift | 2 +- Session/Calls/CallVC.swift | 100 +++++++----------- .../Calls/Views & Modals/MiniCallView.swift | 2 +- Session/Meta/AppDelegate.swift | 43 +++++--- .../PushRegistrationManager.swift | 9 +- SessionMessagingKit/Calls/WebRTCSession.swift | 6 +- .../MessageReceiver+Handling.swift | 2 + .../Sending & Receiving/MessageReceiver.swift | 1 + .../Notifications/PushNotificationAPI.swift | 2 +- .../NotificationServiceExtension.swift | 21 ++-- 11 files changed, 178 insertions(+), 101 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 984ac793c..d2320a43b 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -2,12 +2,14 @@ import Foundation import WebRTC import SessionMessagingKit -public final class SessionCall: NSObject { +public final class SessionCall: NSObject, WebRTCSessionDelegate { // MARK: Metadata Properties let uuid: UUID let sessionID: String let mode: Mode let webRTCSession: WebRTCSession + var remoteSDP: RTCSessionDescription? = nil + var isWaitingForRemoteSDP = false var contactName: String { let contact = Storage.shared.getContact(with: self.sessionID) return contact?.displayName(for: Contact.Context.regular) ?? self.sessionID @@ -20,10 +22,40 @@ public final class SessionCall: NSObject { } } + // MARK: Control + lazy public var videoCapturer: RTCVideoCapturer = { + return RTCCameraVideoCapturer(delegate: webRTCSession.localVideoSource) + }() + + var isRemoteVideoEnabled = false { + didSet { + remoteVideoStateDidChange?(isRemoteVideoEnabled) + } + } + + var isMuted = false { + willSet { + if newValue { + webRTCSession.mute() + } else { + webRTCSession.unmute() + } + } + } + var isVideoEnabled = false { + willSet { + if newValue { + webRTCSession.turnOnVideo() + } else { + webRTCSession.turnOffVideo() + } + } + } + // MARK: Mode enum Mode { case offer - case answer(sdp: RTCSessionDescription) + case answer } // MARK: Call State Properties @@ -60,6 +92,7 @@ public final class SessionCall: NSObject { var hasStartedConnectingDidChange: (() -> Void)? var hasConnectedDidChange: (() -> Void)? var hasEndedDidChange: (() -> Void)? + var remoteVideoStateDidChange: ((Bool) -> Void)? // MARK: Derived Properties var hasStartedConnecting: Bool { @@ -92,15 +125,22 @@ public final class SessionCall: NSObject { self.mode = mode self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID, with: uuid) super.init() - reportIncomingCallIfNeeded() + self.webRTCSession.delegate = self } - func reportIncomingCallIfNeeded() { - guard case .answer(_) = mode else { return } + func reportIncomingCallIfNeeded(completion: @escaping (Error?) -> Void) { + guard case .answer = mode else { return } AppEnvironment.shared.callManager.reportIncomingCall(self, callerName: contactName) { error in - if let error = error { - SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") - } + completion(error) + } + } + + func didReceiveRemoteSDP(sdp: RTCSessionDescription) { + guard remoteSDP == nil else { return } + remoteSDP = sdp + if isWaitingForRemoteSDP { + webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally + isWaitingForRemoteSDP = false } } @@ -119,9 +159,13 @@ public final class SessionCall: NSObject { } func answerSessionCall(completion: (() -> Void)?) { - guard case let .answer(sdp) = mode else { return } + guard case .answer = mode else { return } hasStartedConnecting = true - webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally + if let sdp = remoteSDP { + webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally + } else { + isWaitingForRemoteSDP = true + } completion?() } @@ -133,4 +177,31 @@ public final class SessionCall: NSObject { hasEnded = true AppEnvironment.shared.callManager.reportCurrentCallEnded() } + + // MARK: Renderer + func attachRemoteVideoRenderer(_ renderer: RTCVideoRenderer) { + webRTCSession.attachRemoteRenderer(renderer) + } + + func attachLocalVideoRenderer(_ renderer: RTCVideoRenderer) { + webRTCSession.attachLocalRenderer(renderer) + } + + // MARK: Delegate + public func webRTCIsConnected() { + self.hasConnected = true + } + + public func isRemoteVideoDidChange(isEnabled: Bool) { + isRemoteVideoEnabled = isEnabled + } + + public func dataChannelDidOpen() { + // Send initial video status + if (isVideoEnabled) { + webRTCSession.turnOnVideo() + } else { + webRTCSession.turnOffVideo() + } + } } diff --git a/Session/Calls/CallVC+Camera.swift b/Session/Calls/CallVC+Camera.swift index 17dc4f196..ef440e2e4 100644 --- a/Session/Calls/CallVC+Camera.swift +++ b/Session/Calls/CallVC+Camera.swift @@ -9,6 +9,6 @@ extension CallVC : CameraManagerDelegate { let timestampNs = Int64(timestamp * 1000000000) let frame = RTCVideoFrame(buffer: rtcPixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: timestampNs) frame.timeStamp = Int32(timestamp) - webRTCSession.handleLocalFrameCaptured(frame) + call.webRTCSession.handleLocalFrameCaptured(frame) } } diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index cf816f940..2d9a17afc 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -4,12 +4,9 @@ import SessionMessagingKit import SessionUtilitiesKit import UIKit -final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelegate { +final class CallVC : UIViewController, VideoPreviewDelegate { let call: SessionCall - var webRTCSession: WebRTCSession { return call.webRTCSession } var shouldAnswer = false - var isMuted = false - var isVideoEnabled = false var shouldRestartCamera = true weak var conversationVC: ConversationVC? = nil @@ -19,14 +16,10 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega return result }() - lazy var videoCapturer: RTCVideoCapturer = { - return RTCCameraVideoCapturer(delegate: webRTCSession.localVideoSource) - }() - // MARK: UI Components private lazy var localVideoView: RTCMTLVideoView = { let result = RTCMTLVideoView() - result.isHidden = !isVideoEnabled + result.isHidden = !call.isVideoEnabled result.contentMode = .scaleAspectFill result.set(.width, to: 80) result.set(.height, to: 173) @@ -98,7 +91,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega private lazy var switchCameraButton: UIButton = { let result = UIButton(type: .custom) - result.isEnabled = isVideoEnabled + result.isEnabled = call.isVideoEnabled let image = UIImage(named: "SwitchCamera")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) @@ -161,13 +154,35 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega init(for call: SessionCall) { self.call = call super.init(nibName: nil, bundle: nil) - self.call.webRTCSession.delegate = self + setupStateChangeCallbacks() + self.modalPresentationStyle = .overFullScreen + self.modalTransitionStyle = .crossDissolve + } + + func setupStateChangeCallbacks() { + self.call.remoteVideoStateDidChange = { isEnabled in + DispatchQueue.main.async { + UIView.animate(withDuration: 0.25) { + self.remoteVideoView.alpha = isEnabled ? 1 : 0 + } + } + } + self.call.hasConnectedDidChange = { + DispatchQueue.main.async { + self.callInfoLabel.text = "Connected" + self.minimizeButton.isHidden = false + UIView.animate(withDuration: 0.5, delay: 1, options: [], animations: { + self.callInfoLabel.alpha = 0 + }, completion: { _ in + self.callInfoLabel.isHidden = true + self.callInfoLabel.alpha = 1 + }) + } + } self.call.hasEndedDidChange = { self.conversationVC?.showInputAccessoryView() self.presentingViewController?.dismiss(animated: true, completion: nil) } - self.modalPresentationStyle = .overFullScreen - self.modalTransitionStyle = .crossDissolve } required init(coder: NSCoder) { preconditionFailure("Use init(for:) instead.") } @@ -175,10 +190,9 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .black - WebRTCSession.current = webRTCSession setUpViewHierarchy() if shouldRestartCamera { cameraManager.prepare() } - touch(videoCapturer) + touch(call.videoCapturer) titleLabel.text = self.call.contactName self.call.startSessionCall{ self.callInfoLabel.text = "Ringing..." @@ -197,12 +211,12 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega callInfoLabel.translatesAutoresizingMaskIntoConstraints = false callInfoLabel.center(in: view) // Remote video view - webRTCSession.attachRemoteRenderer(remoteVideoView) + call.attachRemoteVideoRenderer(remoteVideoView) view.addSubview(remoteVideoView) remoteVideoView.translatesAutoresizingMaskIntoConstraints = false remoteVideoView.pin(to: view) // Local video view - webRTCSession.attachLocalRenderer(localVideoView) + call.attachLocalVideoRenderer(localVideoView) view.addSubview(localVideoView) localVideoView.pin(.right, to: .right, of: view, withInset: -Values.smallSpacing) let topMargin = UIApplication.shared.keyWindow!.safeAreaInsets.top + Values.veryLargeSpacing @@ -252,45 +266,13 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - if (isVideoEnabled && shouldRestartCamera) { cameraManager.start() } + if (call.isVideoEnabled && shouldRestartCamera) { cameraManager.start() } shouldRestartCamera = true } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - if (isVideoEnabled && shouldRestartCamera) { cameraManager.stop() } - } - - // MARK: Delegate - func webRTCIsConnected() { - DispatchQueue.main.async { - self.callInfoLabel.text = "Connected" - self.call.hasConnected = true - self.minimizeButton.isHidden = false - UIView.animate(withDuration: 0.5, delay: 1, options: [], animations: { - self.callInfoLabel.alpha = 0 - }, completion: { _ in - self.callInfoLabel.isHidden = true - self.callInfoLabel.alpha = 1 - }) - } - } - - func isRemoteVideoDidChange(isEnabled: Bool) { - DispatchQueue.main.async { - UIView.animate(withDuration: 0.25) { - self.remoteVideoView.alpha = isEnabled ? 1 : 0 - } - } - } - - func dataChannelDidOpen() { - // Send initial video status - if (isVideoEnabled) { - webRTCSession.turnOnVideo() - } else { - webRTCSession.turnOffVideo() - } + if (call.isVideoEnabled && shouldRestartCamera) { cameraManager.stop() } } // MARK: Interaction @@ -349,13 +331,12 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega } @objc private func operateCamera() { - if (isVideoEnabled) { - webRTCSession.turnOffVideo() + if (call.isVideoEnabled) { localVideoView.isHidden = true cameraManager.stop() videoButton.alpha = 0.5 switchCameraButton.isEnabled = false - isVideoEnabled = false + call.isVideoEnabled = false } else { let previewVC = VideoPreviewVC() previewVC.delegate = self @@ -364,13 +345,12 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega } func cameraDidConfirmTurningOn() { - webRTCSession.turnOnVideo() localVideoView.isHidden = false cameraManager.prepare() cameraManager.start() videoButton.alpha = 1.0 switchCameraButton.isEnabled = true - isVideoEnabled = true + call.isVideoEnabled = true } @objc private func switchCamera() { @@ -378,14 +358,12 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega } @objc private func switchAudio() { - if isMuted { + if call.isMuted { switchAudioButton.backgroundColor = UIColor(hex: 0x1F1F1F) - isMuted = false - webRTCSession.unmute() + call.isMuted = false } else { switchAudioButton.backgroundColor = Colors.destructive - isMuted = true - webRTCSession.mute() + call.isMuted = true } } diff --git a/Session/Calls/Views & Modals/MiniCallView.swift b/Session/Calls/Views & Modals/MiniCallView.swift index c161264e4..4e991f996 100644 --- a/Session/Calls/Views & Modals/MiniCallView.swift +++ b/Session/Calls/Views & Modals/MiniCallView.swift @@ -38,7 +38,7 @@ final class MiniCallView: UIView { self.addSubview(background) background.pin(to: self) // Remote video view - callVC.webRTCSession.attachRemoteRenderer(remoteVideoView) + callVC.call.attachRemoteVideoRenderer(remoteVideoView) self.addSubview(remoteVideoView) remoteVideoView.translatesAutoresizingMaskIntoConstraints = false remoteVideoView.pin(to: self) diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 70c0976b5..2ca189647 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -6,25 +6,40 @@ import UIKit extension AppDelegate { // MARK: Call handling - @objc func setUpCallHandling() { - // Offer messages - MessageReceiver.handleOfferCallMessage = { message in - DispatchQueue.main.async { - let sdp = RTCSessionDescription(type: .offer, sdp: message.sdps![0]) - let call = SessionCall(for: message.sender!, uuid: message.uuid!, mode: .answer(sdp: sdp)) - guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully - if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == message.sender! { - let callVC = CallVC(for: call) - callVC.conversationVC = conversationVC - conversationVC.inputAccessoryView?.isHidden = true - conversationVC.inputAccessoryView?.alpha = 0 - presentingVC.present(callVC, animated: true, completion: nil) - } else { + func createNewIncomingCall(caller: String, uuid: String) { + let call = SessionCall(for: caller, uuid: uuid, mode: .answer) + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller { + let callVC = CallVC(for: call) + callVC.conversationVC = conversationVC + conversationVC.inputAccessoryView?.isHidden = true + conversationVC.inputAccessoryView?.alpha = 0 + presentingVC.present(callVC, animated: true, completion: nil) + } else { + call.reportIncomingCallIfNeeded{ error in + if let error = error { + SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") let incomingCallBanner = IncomingCallBanner(for: call) incomingCallBanner.show() } } } + } + + @objc func setUpCallHandling() { + // Pre offer messages + MessageReceiver.handlePreOfferCallMessage = { message in + guard CurrentAppContext().isMainApp else { return } + self.createNewIncomingCall(caller: message.sender!, uuid: message.uuid!) + } + // Offer messages + MessageReceiver.handleOfferCallMessage = { message in + DispatchQueue.main.async { + guard let call = AppEnvironment.shared.callManager.currentCall, message.uuid == call.uuid.uuidString else { return } + let sdp = RTCSessionDescription(type: .offer, sdp: message.sdps![0]) + call.didReceiveRemoteSDP(sdp: sdp) + } + } // Answer messages MessageReceiver.handleAnswerCallMessage = { message in DispatchQueue.main.async { diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index b2d560b95..fe47fdf7e 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -238,7 +238,14 @@ public enum PushRegistrationError: Error { public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) { owsAssertDebug(CurrentAppContext().isMainApp) owsAssertDebug(type == .voIP) - Vibration.shared.startVibration() + let payload = payload.dictionaryPayload + if let uuid = payload["uuid"] as? String, let caller = payload["caller"] as? String { + let appDelegate = UIApplication.shared.delegate as! AppDelegate + appDelegate.createNewIncomingCall(caller: caller, uuid: uuid) + appDelegate.startPollerIfNeeded() + appDelegate.startClosedGroupPoller() + appDelegate.startOpenGroupPollersIfNeeded() + } } } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 951dc1c35..f2d5cde4c 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -251,9 +251,9 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) { print("[Calls] Peer connection should negotiate.") - Storage.write { transaction in - self.sendOffer(to: self.contactSessionID, using: transaction).retainUntilComplete() - } +// Storage.write { transaction in +// self.sendOffer(to: self.contactSessionID, using: transaction).retainUntilComplete() +// } } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCIceConnectionState) { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index e70c9c70e..cede76d96 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -279,7 +279,9 @@ extension MessageReceiver { print("[Calls] Received pre-offer message.") if getWebRTCSession().uuid != message.uuid! { // TODO: Call in progress, put the new call on hold/reject + return } + handlePreOfferCallMessage?(message) case .offer: print("[Calls] Received offer message.") if getWebRTCSession().uuid != message.uuid! { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index b2cc26bc4..4d17a232f 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -2,6 +2,7 @@ import SessionUtilitiesKit public enum MessageReceiver { private static var lastEncryptionKeyPairRequest: [String:Date] = [:] + public static var handlePreOfferCallMessage: ((CallMessage) -> Void)? public static var handleOfferCallMessage: ((CallMessage) -> Void)? public static var handleAnswerCallMessage: ((CallMessage) -> Void)? public static var handleEndCallMessage: ((CallMessage) -> Void)? diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index 8fccb96ec..c44649c60 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -5,7 +5,7 @@ import PromiseKit public final class PushNotificationAPI : NSObject { // MARK: Settings - public static let server = "https://live.apns.getsession.org" + public static let server = "https://dev.apns.getsession.org" public static let serverPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" private static let maxRetryCount: UInt = 4 private static let tokenExpirationInterval: TimeInterval = 12 * 60 * 60 diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index fc0ea2967..736bd7c82 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -96,7 +96,7 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension notificationContent.badge = 1 notificationContent.title = "Session" notificationContent.body = "\(senderDisplayName) is calling..." - return self.handleSuccessForIncomingCall(for: notificationContent, callID: callMessage.uuid!) + return self.handleSuccessForIncomingCall(for: notificationContent, callMessage: callMessage) default: return self.completeSilenty() } if (senderPublicKey == userPublicKey) { @@ -216,18 +216,21 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension contentHandler!(.init()) } - private func handleSuccessForIncomingCall(for content: UNMutableNotificationContent, callID: String) { - // TODO: poll for the real offer, play incoming call ring + private func handleSuccessForIncomingCall(for content: UNMutableNotificationContent, callMessage: CallMessage) { if #available(iOSApplicationExtension 14.5, *) { - CXProvider.reportNewIncomingVoIPPushPayload(["uuid": callID]) { error in - if let error = error { - owsFailDebug("Failed to notify main app of call message: \(error)") - } else { - Logger.info("Successfully notified main app of call message.") + if let uuid = callMessage.uuid, let caller = callMessage.sender { + let payload = ["uuid": uuid, "caller": caller] + return CXProvider.reportNewIncomingVoIPPushPayload(payload) { error in + if let error = error { + owsFailDebug("Failed to notify main app of call message: \(error)") + } else { + Logger.info("Successfully notified main app of call message.") + } + self.completeSilenty() } - self.contentHandler!(content) } } + self.contentHandler!(content) } private func handleSuccess(for content: UNMutableNotificationContent) { From bef20e2f9adbdd854dc9c1673630f183893b5d5d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 8 Nov 2021 09:12:18 +1100 Subject: [PATCH 087/368] refactor for CallKit --- Session.xcodeproj/project.pbxproj | 10 +++- .../Calls/Call Management/SessionCall.swift | 22 +++++---- .../SessionCallManager+CXCallController.swift | 47 +++++++++++++++++++ .../SessionCallManager+CXProvider.swift | 44 +++++++++++++++++ .../Call Management/SessionCallManager.swift | 41 ++++++++++++---- Session/Calls/CallVC.swift | 4 +- .../Views & Modals/IncomingCallBanner.swift | 5 +- Session/Meta/AppDelegate.swift | 33 +++++++------ .../Calls/WebRTCSession+MessageHandling.swift | 2 +- SessionMessagingKit/Calls/WebRTCSession.swift | 2 - 10 files changed, 170 insertions(+), 40 deletions(-) create mode 100644 Session/Calls/Call Management/SessionCallManager+CXCallController.swift create mode 100644 Session/Calls/Call Management/SessionCallManager+CXProvider.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index cd78b38cf..9164645b4 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -148,6 +148,8 @@ 7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */; }; 7B7CB192271508AD0079FF93 /* Vibration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB191271508AD0079FF93 /* Vibration.swift */; }; 7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA68908272A27BE00EFC32F /* SessionCall.swift */; }; + 7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */; }; + 7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */; }; 7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; }; 7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707F127290ACB002817AD /* SessionCallManager.swift */; }; @@ -1133,6 +1135,8 @@ 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniCallView.swift; sourceTree = ""; }; 7B7CB191271508AD0079FF93 /* Vibration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vibration.swift; sourceTree = ""; }; 7BA68908272A27BE00EFC32F /* SessionCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCall.swift; sourceTree = ""; }; + 7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+CXCallController.swift"; sourceTree = ""; }; + 7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+CXProvider.swift"; sourceTree = ""; }; 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; 7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -2071,8 +2075,10 @@ 7BA68907272A279900EFC32F /* Call Management */ = { isa = PBXGroup; children = ( - 7BC707F127290ACB002817AD /* SessionCallManager.swift */, 7BA68908272A27BE00EFC32F /* SessionCall.swift */, + 7BC707F127290ACB002817AD /* SessionCallManager.swift */, + 7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */, + 7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */, ); path = "Call Management"; sourceTree = ""; @@ -4894,6 +4900,7 @@ B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */, 346129991FD1E4DA00532771 /* SignalApp.m in Sources */, 3496957121A301A100DCFE74 /* OWSBackupImportJob.m in Sources */, + 7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */, 34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */, C331FFFE2558FF3B00070591 /* ConversationCell.swift in Sources */, B8F5F72325F1B4CA003BF8D4 /* DownloadAttachmentModal.swift in Sources */, @@ -4908,6 +4915,7 @@ B8AF4BB426A5204600583500 /* SendSeedModal.swift in Sources */, B821494625D4D6FF009C0F2A /* URLModal.swift in Sources */, B877E24226CA12910007970A /* CallVC.swift in Sources */, + 7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */, C374EEEB25DA3CA70073A857 /* ConversationTitleView.swift in Sources */, B88FA7F2260C3EB10049422F /* OpenGroupSuggestionGrid.swift in Sources */, 4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */, diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index d2320a43b..a1c2fc9a8 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -1,6 +1,7 @@ import Foundation import WebRTC import SessionMessagingKit +import PromiseKit public final class SessionCall: NSObject, WebRTCSessionDelegate { // MARK: Metadata Properties @@ -147,14 +148,18 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { // MARK: Actions func startSessionCall(completion: (() -> Void)?) { guard case .offer = mode else { return } - AppEnvironment.shared.callManager.reportOutgoingCall(self) - Storage.write { transaction in - self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction).done { - self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).done { - self.hasStartedConnecting = true - }.retainUntilComplete() - }.retainUntilComplete() - } + var promise: Promise! + Storage.write(with: { transaction in + promise = self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction) + }, completion: { [weak self] in + let _ = promise.done { + Storage.shared.write { transaction in + self?.webRTCSession.sendOffer(to: self!.sessionID, using: transaction as! YapDatabaseReadWriteTransaction).done { + self?.hasStartedConnecting = true + }.retainUntilComplete() + } + } + }) completion?() } @@ -175,7 +180,6 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { self.webRTCSession.endCall(with: self.sessionID, using: transaction) } hasEnded = true - AppEnvironment.shared.callManager.reportCurrentCallEnded() } // MARK: Renderer diff --git a/Session/Calls/Call Management/SessionCallManager+CXCallController.swift b/Session/Calls/Call Management/SessionCallManager+CXCallController.swift new file mode 100644 index 000000000..c543f5026 --- /dev/null +++ b/Session/Calls/Call Management/SessionCallManager+CXCallController.swift @@ -0,0 +1,47 @@ +import CallKit +import SessionUtilitiesKit + +extension SessionCallManager { + public func startCall(_ call: SessionCall, completion: (() -> Void)?) { + guard case .offer = call.mode else { return } + let handle = CXHandle(type: .generic, value: call.sessionID) + let startCallAction = CXStartCallAction(call: call.uuid, handle: handle) + + startCallAction.isVideo = false + + let transaction = CXTransaction() + transaction.addAction(startCallAction) + + reportOutgoingCall(call) + requestTransaction(transaction) + completion?() + } + + public func endCall(_ call: SessionCall, completion: (() -> Void)?) { + let endCallAction = CXEndCallAction(call: call.uuid) + let transaction = CXTransaction() + transaction.addAction(endCallAction) + + requestTransaction(transaction) + completion?() + } + + // Not currently in use + public func setOnHoldStatus(for call: SessionCall) { + let setHeldCallAction = CXSetHeldCallAction(call: call.uuid, onHold: true) + let transaction = CXTransaction() + transaction.addAction(setHeldCallAction) + + requestTransaction(transaction) + } + + private func requestTransaction(_ transaction: CXTransaction) { + callController.request(transaction) { error in + if let error = error { + SNLog("Error requesting transaction: \(error)") + } else { + SNLog("Requested transaction successfully") + } + } + } +} diff --git a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift new file mode 100644 index 000000000..3b0cfa9b7 --- /dev/null +++ b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift @@ -0,0 +1,44 @@ +import CallKit + +extension SessionCallManager: CXProviderDelegate { + public func providerDidReset(_ provider: CXProvider) { + AssertIsOnMainThread() + currentCall?.endSessionCall() + } + + public func provider(_ provider: CXProvider, perform action: CXStartCallAction) { + AssertIsOnMainThread() + guard let call = self.currentCall else { return action.fail() } + call.startSessionCall(completion: nil) + action.fulfill() + } + + public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { + AssertIsOnMainThread() + guard let _ = self.currentCall else { return action.fail() } + let userDefaults = UserDefaults.standard + if userDefaults[.hasSeenCallIPExposureWarning] { + showCallVC() + } else { + showCallModal() + } + action.fulfill() + } + + public func provider(_ provider: CXProvider, perform action: CXEndCallAction) { + AssertIsOnMainThread() + guard let call = self.currentCall else { return action.fail() } + call.endSessionCall() + reportCurrentCallEnded(reason: nil) + action.fulfill() + } + + public func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) { + // TODO: set on hold + } + + public func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) { + // TODO: handle timeout + } +} + diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index b309ce49a..914bf5255 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -1,8 +1,9 @@ import CallKit import SessionMessagingKit -public final class SessionCallManager: NSObject, CXProviderDelegate { - private let provider: CXProvider +public final class SessionCallManager: NSObject { + let provider: CXProvider + let callController = CXCallController() var currentCall: SessionCall? private static var _sharedProvider: CXProvider? @@ -43,11 +44,6 @@ public final class SessionCallManager: NSObject, CXProviderDelegate { self.provider.setDelegate(self, queue: nil) } - public func providerDidReset(_ provider: CXProvider) { - AssertIsOnMainThread() - currentCall?.endSessionCall() - } - public func reportOutgoingCall(_ call: SessionCall) { AssertIsOnMainThread() self.currentCall = call @@ -82,8 +78,14 @@ public final class SessionCallManager: NSObject, CXProviderDelegate { } } - public func reportCurrentCallEnded() { + public func reportCurrentCallEnded(reason: CXCallEndedReason?) { + guard let call = currentCall else { return } + if let reason = reason { + self.provider.reportCall(with: call.uuid, endedAt: nil, reason: reason) + } + self.currentCall?.webRTCSession.dropConnection() self.currentCall = nil + WebRTCSession.current = nil } // MARK: Util @@ -99,4 +101,27 @@ public final class SessionCallManager: NSObject, CXProviderDelegate { // Is there any reason to support this? callUpdate.supportsDTMF = false } + + internal func showCallModal() { + let callModal = CallModal() { [weak self] in + self?.showCallVC() + } + callModal.modalPresentationStyle = .overFullScreen + callModal.modalTransitionStyle = .crossDissolve + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + presentingVC.present(callModal, animated: true, completion: nil) + } + + internal func showCallVC() { + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + let callVC = CallVC(for: self.currentCall!) + callVC.shouldAnswer = true + if let conversationVC = presentingVC as? ConversationVC { + callVC.conversationVC = conversationVC + conversationVC.inputAccessoryView?.isHidden = true + conversationVC.inputAccessoryView?.alpha = 0 + } + presentingVC.present(callVC, animated: true, completion: nil) + } } + diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 2d9a17afc..e724cd338 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -194,7 +194,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { if shouldRestartCamera { cameraManager.prepare() } touch(call.videoCapturer) titleLabel.text = self.call.contactName - self.call.startSessionCall{ + AppEnvironment.shared.callManager.startCall(call) { self.callInfoLabel.text = "Ringing..." self.answerButton.isHidden = true } @@ -319,7 +319,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { } @objc private func endCall() { - self.call.endSessionCall() + AppEnvironment.shared.callManager.endCall(call, completion: nil) } @objc private func minimize() { diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index 29f1a0221..9ebdee16d 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -155,8 +155,9 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { } @objc private func endCall() { - self.call.endSessionCall() - self.dismiss() + AppEnvironment.shared.callManager.endCall(call) { + self.dismiss() + } } public func showCallVC(answer: Bool) { diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 2ca189647..be98926c7 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -8,20 +8,24 @@ extension AppDelegate { // MARK: Call handling func createNewIncomingCall(caller: String, uuid: String) { let call = SessionCall(for: caller, uuid: uuid, mode: .answer) - guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully - if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller { - let callVC = CallVC(for: call) - callVC.conversationVC = conversationVC - conversationVC.inputAccessoryView?.isHidden = true - conversationVC.inputAccessoryView?.alpha = 0 - presentingVC.present(callVC, animated: true, completion: nil) - } else { - call.reportIncomingCallIfNeeded{ error in - if let error = error { - SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") - let incomingCallBanner = IncomingCallBanner(for: call) - incomingCallBanner.show() + if CurrentAppContext().isMainAppAndActive { + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller { + DispatchQueue.main.async { + let callVC = CallVC(for: call) + callVC.conversationVC = conversationVC + conversationVC.inputAccessoryView?.isHidden = true + conversationVC.inputAccessoryView?.alpha = 0 + presentingVC.present(callVC, animated: true, completion: nil) } + return + } + } + call.reportIncomingCallIfNeeded{ error in + if let error = error { + SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") + let incomingCallBanner = IncomingCallBanner(for: call) + incomingCallBanner.show() } } } @@ -53,8 +57,7 @@ extension AppDelegate { if let currentBanner = IncomingCallBanner.current { currentBanner.dismiss() } if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleEndCallMessage(message) } if let miniCallView = MiniCallView.current { miniCallView.dismiss() } - WebRTCSession.current?.dropConnection() - WebRTCSession.current = nil + AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: .remoteEnded) } } } diff --git a/SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift b/SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift index ec860bb65..7ad8413d4 100644 --- a/SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift +++ b/SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift @@ -11,7 +11,7 @@ extension WebRTCSession { print("[Calls] Received remote SDP: \(sdp.sdp).") peerConnection.setRemoteDescription(sdp, completionHandler: { [weak self] error in if let error = error { - SNLog("Couldn't set SDP due to error: \(error).") + SNLog("[Calls] Couldn't set SDP due to error: \(error).") } else { guard let self = self, sdp.type == .offer, self.peerConnection.localDescription == nil else { return } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index f2d5cde4c..95d617a67 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -227,8 +227,6 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { message.kind = .endCall print("[Calls] Sending end call message.") MessageSender.sendNonDurably(message, in: thread, using: transaction).retainUntilComplete() - dropConnection() - WebRTCSession.current = nil } public func dropConnection() { From 888df37581c16d1f1ba4b140b0adc6f69615b46d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 8 Nov 2021 15:09:45 +1100 Subject: [PATCH 088/368] fix call connection --- .../Calls/Call Management/SessionCall.swift | 1 + .../Call Management/SessionCallManager.swift | 2 +- Session/Meta/AppDelegate.swift | 24 +++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index a1c2fc9a8..a2d0104ab 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -125,6 +125,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { self.uuid = UUID(uuidString: uuid)! self.mode = mode self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID, with: uuid) + WebRTCSession.current = self.webRTCSession super.init() self.webRTCSession.delegate = self } diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index 914bf5255..72978f143 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -62,7 +62,7 @@ public final class SessionCallManager: NSObject { let update = CXCallUpdate() update.localizedCallerName = callerName update.remoteHandle = CXHandle(type: .generic, value: call.uuid.uuidString) - update.hasVideo = true + update.hasVideo = false disableUnsupportedFeatures(callUpdate: update) diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index be98926c7..d6c994d10 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -7,25 +7,25 @@ extension AppDelegate { // MARK: Call handling func createNewIncomingCall(caller: String, uuid: String) { - let call = SessionCall(for: caller, uuid: uuid, mode: .answer) - if CurrentAppContext().isMainAppAndActive { - guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully - if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller { - DispatchQueue.main.async { + DispatchQueue.main.async { + let call = SessionCall(for: caller, uuid: uuid, mode: .answer) + if CurrentAppContext().isMainAppAndActive { + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller { let callVC = CallVC(for: call) callVC.conversationVC = conversationVC conversationVC.inputAccessoryView?.isHidden = true conversationVC.inputAccessoryView?.alpha = 0 presentingVC.present(callVC, animated: true, completion: nil) + return } - return } - } - call.reportIncomingCallIfNeeded{ error in - if let error = error { - SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") - let incomingCallBanner = IncomingCallBanner(for: call) - incomingCallBanner.show() + call.reportIncomingCallIfNeeded{ error in + if let error = error { + SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") + let incomingCallBanner = IncomingCallBanner(for: call) + incomingCallBanner.show() + } } } } From 86aced218ab089182b14ab52daa84991e4ef27f9 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 8 Nov 2021 17:02:47 +1100 Subject: [PATCH 089/368] minor fix for CallKit PN --- Session/Notifications/PushRegistrationManager.swift | 12 +++++++----- .../NotificationServiceExtension.swift | 8 +++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index fe47fdf7e..0fe999ffb 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -236,15 +236,17 @@ public enum PushRegistrationError: Error { } public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) { + SNLog("[Calls] Receive new voip notification.") owsAssertDebug(CurrentAppContext().isMainApp) owsAssertDebug(type == .voIP) let payload = payload.dictionaryPayload if let uuid = payload["uuid"] as? String, let caller = payload["caller"] as? String { - let appDelegate = UIApplication.shared.delegate as! AppDelegate - appDelegate.createNewIncomingCall(caller: caller, uuid: uuid) - appDelegate.startPollerIfNeeded() - appDelegate.startClosedGroupPoller() - appDelegate.startOpenGroupPollersIfNeeded() + let call = SessionCall(for: caller, uuid: uuid, mode: .answer) + call.reportIncomingCallIfNeeded { error in + if let error = error { + SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") + } + } } } } diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 736bd7c82..a861eacce 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -220,17 +220,19 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension if #available(iOSApplicationExtension 14.5, *) { if let uuid = callMessage.uuid, let caller = callMessage.sender { let payload = ["uuid": uuid, "caller": caller] - return CXProvider.reportNewIncomingVoIPPushPayload(payload) { error in + CXProvider.reportNewIncomingVoIPPushPayload(payload) { error in if let error = error { + self.contentHandler!(content) owsFailDebug("Failed to notify main app of call message: \(error)") } else { + self.completeSilenty() Logger.info("Successfully notified main app of call message.") } - self.completeSilenty() } } + } else { + self.contentHandler!(content) } - self.contentHandler!(content) } private func handleSuccess(for content: UNMutableNotificationContent) { From f019fe77331964ccd0881cbae3361daeab8cee61 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 9 Nov 2021 11:53:38 +1100 Subject: [PATCH 090/368] use CallKit for all cases --- .../Calls/Call Management/SessionCall.swift | 11 +++-- .../SessionCallManager+CXCallController.swift | 21 +++++--- .../SessionCallManager+CXProvider.swift | 16 ++++--- .../Call Management/SessionCallManager.swift | 29 +++++++---- Session/Calls/CallVC.swift | 48 ++++++++++++++----- .../Views & Modals/IncomingCallBanner.swift | 6 ++- .../ConversationVC+Interaction.swift | 1 + Session/Meta/AppDelegate.swift | 1 - 8 files changed, 94 insertions(+), 39 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index a2d0104ab..e2e8b4a20 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -128,6 +128,11 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { WebRTCSession.current = self.webRTCSession super.init() self.webRTCSession.delegate = self + if AppEnvironment.shared.callManager.currentCall == nil { + AppEnvironment.shared.callManager.currentCall = self + } else { + SNLog("[Calls] A call is ongoing.") + } } func reportIncomingCallIfNeeded(completion: @escaping (Error?) -> Void) { @@ -147,7 +152,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { } // MARK: Actions - func startSessionCall(completion: (() -> Void)?) { + func startSessionCall() { guard case .offer = mode else { return } var promise: Promise! Storage.write(with: { transaction in @@ -161,10 +166,9 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { } } }) - completion?() } - func answerSessionCall(completion: (() -> Void)?) { + func answerSessionCall() { guard case .answer = mode else { return } hasStartedConnecting = true if let sdp = remoteSDP { @@ -172,7 +176,6 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { } else { isWaitingForRemoteSDP = true } - completion?() } func endSessionCall() { diff --git a/Session/Calls/Call Management/SessionCallManager+CXCallController.swift b/Session/Calls/Call Management/SessionCallManager+CXCallController.swift index c543f5026..62607e08b 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXCallController.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXCallController.swift @@ -2,7 +2,7 @@ import CallKit import SessionUtilitiesKit extension SessionCallManager { - public func startCall(_ call: SessionCall, completion: (() -> Void)?) { + public func startCall(_ call: SessionCall, completion: ((Error?) -> Void)?) { guard case .offer = call.mode else { return } let handle = CXHandle(type: .generic, value: call.sessionID) let startCallAction = CXStartCallAction(call: call.uuid, handle: handle) @@ -13,17 +13,23 @@ extension SessionCallManager { transaction.addAction(startCallAction) reportOutgoingCall(call) - requestTransaction(transaction) - completion?() + requestTransaction(transaction, completion: completion) } - public func endCall(_ call: SessionCall, completion: (() -> Void)?) { + public func answerCall(_ call: SessionCall, completion: ((Error?) -> Void)?) { + let answerCallAction = CXAnswerCallAction(call: call.uuid) + let transaction = CXTransaction() + transaction.addAction(answerCallAction) + + requestTransaction(transaction, completion: completion) + } + + public func endCall(_ call: SessionCall, completion: ((Error?) -> Void)?) { let endCallAction = CXEndCallAction(call: call.uuid) let transaction = CXTransaction() transaction.addAction(endCallAction) - requestTransaction(transaction) - completion?() + requestTransaction(transaction, completion: completion) } // Not currently in use @@ -35,13 +41,14 @@ extension SessionCallManager { requestTransaction(transaction) } - private func requestTransaction(_ transaction: CXTransaction) { + private func requestTransaction(_ transaction: CXTransaction, completion: ((Error?) -> Void)? = nil) { callController.request(transaction) { error in if let error = error { SNLog("Error requesting transaction: \(error)") } else { SNLog("Requested transaction successfully") } + completion?(error) } } } diff --git a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift index 3b0cfa9b7..23d69e6bc 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift @@ -9,18 +9,22 @@ extension SessionCallManager: CXProviderDelegate { public func provider(_ provider: CXProvider, perform action: CXStartCallAction) { AssertIsOnMainThread() guard let call = self.currentCall else { return action.fail() } - call.startSessionCall(completion: nil) + call.startSessionCall() action.fulfill() } public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { AssertIsOnMainThread() - guard let _ = self.currentCall else { return action.fail() } - let userDefaults = UserDefaults.standard - if userDefaults[.hasSeenCallIPExposureWarning] { - showCallVC() + guard let call = self.currentCall else { return action.fail() } + if let _ = CurrentAppContext().frontmostViewController() as? CallVC { + call.answerSessionCall() } else { - showCallModal() + let userDefaults = UserDefaults.standard + if userDefaults[.hasSeenCallIPExposureWarning] { + showCallVC() + } else { + showCallModal() + } } action.fulfill() } diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index 72978f143..7e4c2ffc0 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -4,7 +4,19 @@ import SessionMessagingKit public final class SessionCallManager: NSObject { let provider: CXProvider let callController = CXCallController() - var currentCall: SessionCall? + var currentCall: SessionCall? = nil { + willSet { + if (newValue != nil) { + DispatchQueue.main.async { + UIApplication.shared.isIdleTimerDisabled = true + } + } else { + DispatchQueue.main.async { + UIApplication.shared.isIdleTimerDisabled = false + } + } + } + } private static var _sharedProvider: CXProvider? class func sharedProvider(useSystemCallLog: Bool) -> CXProvider { @@ -46,12 +58,13 @@ public final class SessionCallManager: NSObject { public func reportOutgoingCall(_ call: SessionCall) { AssertIsOnMainThread() - self.currentCall = call - call.hasStartedConnectingDidChange = { - self.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate) - } - call.hasConnectedDidChange = { - self.provider.reportOutgoingCall(with: call.uuid, connectedAt: call.connectedDate) + call.stateDidChange = { + if call.hasStartedConnecting { + self.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate) + } + if call.hasConnected { + self.provider.reportOutgoingCall(with: call.uuid, connectedAt: call.connectedDate) + } } } @@ -69,11 +82,11 @@ public final class SessionCallManager: NSObject { // Report the incoming call to the system self.provider.reportNewIncomingCall(with: call.uuid, update: update) { error in guard error == nil else { + self.currentCall = nil completion(error) Logger.error("failed to report new incoming call, error: \(error!)") return } - self.currentCall = call completion(nil) } } diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index e724cd338..ba35b2379 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -167,6 +167,15 @@ final class CallVC : UIViewController, VideoPreviewDelegate { } } } + self.call.hasStartedConnectingDidChange = { + DispatchQueue.main.async { + self.callInfoLabel.text = "Connecting..." + self.answerButton.alpha = 0 + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { + self.answerButton.isHidden = true + }, completion: nil) + } + } self.call.hasConnectedDidChange = { DispatchQueue.main.async { self.callInfoLabel.text = "Connected" @@ -180,8 +189,10 @@ final class CallVC : UIViewController, VideoPreviewDelegate { } } self.call.hasEndedDidChange = { - self.conversationVC?.showInputAccessoryView() - self.presentingViewController?.dismiss(animated: true, completion: nil) + DispatchQueue.main.async { + self.conversationVC?.showInputAccessoryView() + self.presentingViewController?.dismiss(animated: true, completion: nil) + } } } @@ -194,9 +205,16 @@ final class CallVC : UIViewController, VideoPreviewDelegate { if shouldRestartCamera { cameraManager.prepare() } touch(call.videoCapturer) titleLabel.text = self.call.contactName - AppEnvironment.shared.callManager.startCall(call) { - self.callInfoLabel.text = "Ringing..." - self.answerButton.isHidden = true + AppEnvironment.shared.callManager.startCall(call) { error in + DispatchQueue.main.async { + if let _ = error { + self.callInfoLabel.text = "Can't start a call." + self.endCall() + } else { + self.callInfoLabel.text = "Ringing..." + self.answerButton.isHidden = true + } + } } if shouldAnswer { answerCall() } } @@ -305,12 +323,13 @@ final class CallVC : UIViewController, VideoPreviewDelegate { @objc private func answerCall() { let userDefaults = UserDefaults.standard if userDefaults[.hasSeenCallIPExposureWarning] { - self.call.answerSessionCall{ - self.callInfoLabel.text = "Connecting..." - self.answerButton.alpha = 0 - UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { - self.answerButton.isHidden = true - }, completion: nil) + AppEnvironment.shared.callManager.answerCall(call) { error in + DispatchQueue.main.async { + if let _ = error { + self.callInfoLabel.text = "Can't answer the call." + self.endCall() + } + } } } else { userDefaults[.hasSeenCallIPExposureWarning] = true @@ -319,7 +338,12 @@ final class CallVC : UIViewController, VideoPreviewDelegate { } @objc private func endCall() { - AppEnvironment.shared.callManager.endCall(call, completion: nil) + AppEnvironment.shared.callManager.endCall(call) { error in + if let _ = error { + self.call.endSessionCall() + AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: nil) + } + } } @objc private func minimize() { diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index 9ebdee16d..569ffdb1f 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -155,7 +155,11 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { } @objc private func endCall() { - AppEnvironment.shared.callManager.endCall(call) { + AppEnvironment.shared.callManager.endCall(call) { error in + if let _ = error { + self.call.endSessionCall() + AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: nil) + } self.dismiss() } } diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index efc0f6ced..7be62ee46 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -31,6 +31,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc let userDefaults = UserDefaults.standard if userDefaults[.hasSeenCallIPExposureWarning] { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } + guard AppEnvironment.shared.callManager.currentCall == nil else { return } let call = SessionCall(for: contactSessionID, uuid: UUID().uuidString, mode: .offer) let callVC = CallVC(for: call) callVC.conversationVC = self diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index d6c994d10..afe933801 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -17,7 +17,6 @@ extension AppDelegate { conversationVC.inputAccessoryView?.isHidden = true conversationVC.inputAccessoryView?.alpha = 0 presentingVC.present(callVC, animated: true, completion: nil) - return } } call.reportIncomingCallIfNeeded{ error in From ff79c58f44783ba7d549c6c1832a6d361bb2700d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 9 Nov 2021 16:05:23 +1100 Subject: [PATCH 091/368] update call message after a call ended --- .../Calls/Call Management/SessionCall.swift | 55 ++++++++++++++++++- .../Call Management/SessionCallManager.swift | 3 + .../Calls/Views & Modals/MiniCallView.swift | 1 + .../ConversationVC+Interaction.swift | 2 +- Session/Meta/AppDelegate.swift | 48 ++++++++-------- .../Translations/en.lproj/Localizable.strings | 3 + .../Calls/WebRTCSession+UI.swift | 4 ++ SessionMessagingKit/Calls/WebRTCSession.swift | 6 +- .../Messages/Signal/TSMessage.h | 2 + .../Messages/Signal/TSMessage.m | 9 +++ .../MessageReceiver+Handling.swift | 17 +++--- 11 files changed, 110 insertions(+), 40 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index e2e8b4a20..50a2a414d 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -9,7 +9,9 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { let sessionID: String let mode: Mode let webRTCSession: WebRTCSession + let isOutgoing: Bool var remoteSDP: RTCSessionDescription? = nil + var callMessageTimestamp: UInt64? var isWaitingForRemoteSDP = false var contactName: String { let contact = Storage.shared.getContact(with: self.sessionID) @@ -59,6 +61,12 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { case answer } + // MARK: End call mode + enum EndCallMode { + case local + case remote + } + // MARK: Call State Properties var connectingDate: Date? { didSet { @@ -115,16 +123,20 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { guard let connectedDate = connectedDate else { return 0 } + if let endDate = endDate { + return endDate.timeIntervalSince(connectedDate) + } return Date().timeIntervalSince(connectedDate) } // MARK: Initialization - init(for sessionID: String, uuid: String, mode: Mode) { + init(for sessionID: String, uuid: String, mode: Mode, outgoing: Bool = false) { self.sessionID = sessionID self.uuid = UUID(uuidString: uuid)! self.mode = mode self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID, with: uuid) + self.isOutgoing = outgoing WebRTCSession.current = self.webRTCSession super.init() self.webRTCSession.delegate = self @@ -160,8 +172,9 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { }, completion: { [weak self] in let _ = promise.done { Storage.shared.write { transaction in - self?.webRTCSession.sendOffer(to: self!.sessionID, using: transaction as! YapDatabaseReadWriteTransaction).done { + self?.webRTCSession.sendOffer(to: self!.sessionID, using: transaction as! YapDatabaseReadWriteTransaction).done { timestamp in self?.hasStartedConnecting = true + self?.callMessageTimestamp = timestamp }.retainUntilComplete() } } @@ -186,11 +199,49 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { hasEnded = true } + // MARK: Update call message + func updateCallMessage(mode: EndCallMode) { + guard let callMessageTimestamp = callMessageTimestamp else { return } + Storage.write { transaction in + let tsMessage: TSMessage? + if self.isOutgoing { + tsMessage = TSOutgoingMessage.find(withTimestamp: callMessageTimestamp) + } else { + tsMessage = TSIncomingMessage.find(withAuthorId: self.sessionID, timestamp: callMessageTimestamp, transaction: transaction) + } + if let messageToUpdate = tsMessage { + var shouldMarkAsRead = false + let newMessageBody: String + if self.duration > 0 { + let durationString = NSString.formatDurationSeconds(UInt32(self.duration), useShortFormat: true) + newMessageBody = "\(self.isOutgoing ? NSLocalizedString("call_outgoing", comment: "") : NSLocalizedString("call_incoming", comment: "")): \(durationString)" + shouldMarkAsRead = true + } else { + switch mode { + case .local: + newMessageBody = self.isOutgoing ? NSLocalizedString("call_cancelled", comment: "") : NSLocalizedString("call_rejected", comment: "") + shouldMarkAsRead = true + case .remote: + newMessageBody = self.isOutgoing ? NSLocalizedString("call_rejected", comment: "") : NSLocalizedString("call_missing", comment: "") + } + } + messageToUpdate.updateCall(withNewBody: newMessageBody, transaction: transaction) + if let incomingMessage = tsMessage as? TSIncomingMessage, shouldMarkAsRead { + incomingMessage.markAsReadNow(withSendReadReceipt: false, transaction: transaction) + } + } + } + } + // MARK: Renderer func attachRemoteVideoRenderer(_ renderer: RTCVideoRenderer) { webRTCSession.attachRemoteRenderer(renderer) } + func removeRemoteVideoRenderer(_ renderer: RTCVideoRenderer) { + webRTCSession.removeRemoteRenderer(renderer) + } + func attachLocalVideoRenderer(_ renderer: RTCVideoRenderer) { webRTCSession.attachLocalRenderer(renderer) } diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index 7e4c2ffc0..8397dbe77 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -95,6 +95,9 @@ public final class SessionCallManager: NSObject { guard let call = currentCall else { return } if let reason = reason { self.provider.reportCall(with: call.uuid, endedAt: nil, reason: reason) + call.updateCallMessage(mode: .remote) + } else { + call.updateCallMessage(mode: .local) } self.currentCall?.webRTCSession.dropConnection() self.currentCall = nil diff --git a/Session/Calls/Views & Modals/MiniCallView.swift b/Session/Calls/Views & Modals/MiniCallView.swift index 4e991f996..f7d5869ce 100644 --- a/Session/Calls/Views & Modals/MiniCallView.swift +++ b/Session/Calls/Views & Modals/MiniCallView.swift @@ -125,6 +125,7 @@ final class MiniCallView: UIView { UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: { self.alpha = 0.0 }, completion: { _ in + self.callVC.call.removeRemoteVideoRenderer(self.remoteVideoView) MiniCallView.current = nil self.removeFromSuperview() }) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 7be62ee46..7bf6de1c4 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -32,7 +32,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc if userDefaults[.hasSeenCallIPExposureWarning] { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } guard AppEnvironment.shared.callManager.currentCall == nil else { return } - let call = SessionCall(for: contactSessionID, uuid: UUID().uuidString, mode: .offer) + let call = SessionCall(for: contactSessionID, uuid: UUID().uuidString, mode: .offer, outgoing: true) let callVC = CallVC(for: call) callVC.conversationVC = self self.inputAccessoryView?.isHidden = true diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index afe933801..20a92a884 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -6,34 +6,34 @@ import UIKit extension AppDelegate { // MARK: Call handling - func createNewIncomingCall(caller: String, uuid: String) { - DispatchQueue.main.async { - let call = SessionCall(for: caller, uuid: uuid, mode: .answer) - if CurrentAppContext().isMainAppAndActive { - guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully - if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller { - let callVC = CallVC(for: call) - callVC.conversationVC = conversationVC - conversationVC.inputAccessoryView?.isHidden = true - conversationVC.inputAccessoryView?.alpha = 0 - presentingVC.present(callVC, animated: true, completion: nil) - } - } - call.reportIncomingCallIfNeeded{ error in - if let error = error { - SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") - let incomingCallBanner = IncomingCallBanner(for: call) - incomingCallBanner.show() - } - } - } - } - @objc func setUpCallHandling() { // Pre offer messages MessageReceiver.handlePreOfferCallMessage = { message in guard CurrentAppContext().isMainApp else { return } - self.createNewIncomingCall(caller: message.sender!, uuid: message.uuid!) + DispatchQueue.main.async { + if let caller = message.sender, let uuid = message.uuid { + let call = SessionCall(for: caller, uuid: uuid, mode: .answer) + call.callMessageTimestamp = message.sentTimestamp + if CurrentAppContext().isMainAppAndActive { + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller { + let callVC = CallVC(for: call) + callVC.conversationVC = conversationVC + conversationVC.inputAccessoryView?.isHidden = true + conversationVC.inputAccessoryView?.alpha = 0 + presentingVC.present(callVC, animated: true, completion: nil) + } + } + call.reportIncomingCallIfNeeded{ error in + if let error = error { + SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") + let incomingCallBanner = IncomingCallBanner(for: call) + incomingCallBanner.show() + } + } + } + + } } // Offer messages MessageReceiver.handleOfferCallMessage = { message in diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 2f50889d2..31673a38b 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -577,6 +577,9 @@ "OPEN_SETTINGS_BUTTON" = "Settings"; "call_outgoing" = "Outgoing Call"; "call_incoming" = "Incoming Call"; +"call_missing" = "Missing Call"; +"call_rejected" = "Rejected Call"; +"call_cancelled" = "Cancelled Call"; "voice_call" = "Voice Call"; "video_call" = "Video Call"; "APN_Message" = "You've got a new message"; diff --git a/SessionMessagingKit/Calls/WebRTCSession+UI.swift b/SessionMessagingKit/Calls/WebRTCSession+UI.swift index 7ed0a54e8..305d1bfb7 100644 --- a/SessionMessagingKit/Calls/WebRTCSession+UI.swift +++ b/SessionMessagingKit/Calls/WebRTCSession+UI.swift @@ -10,6 +10,10 @@ extension WebRTCSession { remoteVideoTrack?.add(renderer) } + public func removeRemoteRenderer(_ renderer: RTCVideoRenderer) { + remoteVideoTrack?.remove(renderer) + } + public func handleLocalFrameCaptured(_ videoFrame: RTCVideoFrame) { guard let videoCapturer = delegate?.videoCapturer else { return } localVideoSource.capturer(videoCapturer, didCapture: videoFrame) diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 95d617a67..79da8db65 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -128,10 +128,10 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { return promise } - public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { + public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { print("[Calls] Sending offer message.") guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } - let (promise, seal) = Promise.pending() + let (promise, seal) = Promise.pending() peerConnection.offer(for: mediaConstraints) { [weak self] sdp, error in if let error = error { seal.reject(error) @@ -152,7 +152,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) tsMessage.save(with: transaction) MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 { - seal.fulfill(()) + seal.fulfill(tsMessage.timestamp) }.catch2 { error in seal.reject(error) } diff --git a/SessionMessagingKit/Messages/Signal/TSMessage.h b/SessionMessagingKit/Messages/Signal/TSMessage.h index b6096bd43..8739181c0 100644 --- a/SessionMessagingKit/Messages/Signal/TSMessage.h +++ b/SessionMessagingKit/Messages/Signal/TSMessage.h @@ -86,6 +86,8 @@ extern const NSUInteger kOversizeTextMessageSizeThreshold; - (void)updateForDeletionWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; +- (void)updateCallMessageWithNewBody:(NSString *)newBody transaction:(YapDatabaseReadWriteTransaction *)transaction; + @end NS_ASSUME_NONNULL_END diff --git a/SessionMessagingKit/Messages/Signal/TSMessage.m b/SessionMessagingKit/Messages/Signal/TSMessage.m index 39ff5530c..0584aaf74 100644 --- a/SessionMessagingKit/Messages/Signal/TSMessage.m +++ b/SessionMessagingKit/Messages/Signal/TSMessage.m @@ -442,6 +442,15 @@ const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024; }]; } +- (void)updateCallMessageWithNewBody:(NSString *)newBody transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + if (!_isCallMessage) { return; } + [self applyChangeToSelfAndLatestCopy:transaction + changeBlock:^(TSMessage *message) { + [message setBody:newBody]; + }]; +} + @end NS_ASSUME_NONNULL_END diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index cede76d96..b33962655 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -281,13 +281,6 @@ extension MessageReceiver { // TODO: Call in progress, put the new call on hold/reject return } - handlePreOfferCallMessage?(message) - case .offer: - print("[Calls] Received offer message.") - if getWebRTCSession().uuid != message.uuid! { - // TODO: Call in progress, put the new call on hold/reject - return - } let storage = SNMessagingKitConfiguration.shared.storage let transaction = transaction as! YapDatabaseReadWriteTransaction if let threadID = storage.getOrCreateThread(for: message.sender!, groupPublicKey: message.groupPublicKey, openGroupID: nil, using: transaction), @@ -295,9 +288,13 @@ extension MessageReceiver { let tsMessage = TSIncomingMessage.from(message, associatedWith: thread) tsMessage.save(with: transaction) } - // Delegate to the main app, which is expected to show a dialog confirming - // that the user wants to pick up the call. When they do, the SDP contained - // in the offer message will be passed to WebRTCSession.handleRemoteSDP(_:from:). + handlePreOfferCallMessage?(message) + case .offer: + print("[Calls] Received offer message.") + if getWebRTCSession().uuid != message.uuid! { + // TODO: Call in progress, put the new call on hold/reject + return + } handleOfferCallMessage?(message) case .answer: print("[Calls] Received answer message.") From a1aa45ae10b28a70bad6f831a67a2b8eec09fedc Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 9 Nov 2021 16:07:34 +1100 Subject: [PATCH 092/368] trace call timestamp from NSE --- Session/Notifications/PushRegistrationManager.swift | 3 ++- .../NotificationServiceExtension.swift | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index 0fe999ffb..3d30cf816 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -240,8 +240,9 @@ public enum PushRegistrationError: Error { owsAssertDebug(CurrentAppContext().isMainApp) owsAssertDebug(type == .voIP) let payload = payload.dictionaryPayload - if let uuid = payload["uuid"] as? String, let caller = payload["caller"] as? String { + if let uuid = payload["uuid"] as? String, let caller = payload["caller"] as? String, let timestamp = payload["timestamp"] as? UInt64 { let call = SessionCall(for: caller, uuid: uuid, mode: .answer) + call.callMessageTimestamp = timestamp call.reportIncomingCallIfNeeded { error in if let error = error { SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index a861eacce..dce15950f 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -218,8 +218,8 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension private func handleSuccessForIncomingCall(for content: UNMutableNotificationContent, callMessage: CallMessage) { if #available(iOSApplicationExtension 14.5, *) { - if let uuid = callMessage.uuid, let caller = callMessage.sender { - let payload = ["uuid": uuid, "caller": caller] + if let uuid = callMessage.uuid, let caller = callMessage.sender, let timestamp = callMessage.sentTimestamp { + let payload = ["uuid": uuid, "caller": caller, "timestamp": timestamp] CXProvider.reportNewIncomingVoIPPushPayload(payload) { error in if let error = error { self.contentHandler!(content) From 446ef838f0f8d843e19675c09170d02b5d00a811 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 9 Nov 2021 16:12:08 +1100 Subject: [PATCH 093/368] minor fix --- .../NotificationServiceExtension.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index dce15950f..8a7f643c1 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -219,7 +219,7 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension private func handleSuccessForIncomingCall(for content: UNMutableNotificationContent, callMessage: CallMessage) { if #available(iOSApplicationExtension 14.5, *) { if let uuid = callMessage.uuid, let caller = callMessage.sender, let timestamp = callMessage.sentTimestamp { - let payload = ["uuid": uuid, "caller": caller, "timestamp": timestamp] + let payload: JSON = ["uuid": uuid, "caller": caller, "timestamp": timestamp] CXProvider.reportNewIncomingVoIPPushPayload(payload) { error in if let error = error { self.contentHandler!(content) From 29421680306fe09be9da9dbc01ca03a95cb18adf Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 10 Nov 2021 11:13:37 +1100 Subject: [PATCH 094/368] fix call kit not working when the screen is locked --- .../Calls/Call Management/SessionCall.swift | 6 +++- .../SessionCallManager+CXCallController.swift | 1 + .../SessionCallManager+CXProvider.swift | 28 +++++++++++++------ Session/Calls/CallVC.swift | 5 ++-- Session/Meta/AppDelegate.m | 1 + Session/Meta/AppDelegate.swift | 14 ++++++++++ .../PushRegistrationManager.swift | 4 +++ 7 files changed, 48 insertions(+), 11 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 50a2a414d..fef6abe60 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -2,6 +2,7 @@ import Foundation import WebRTC import SessionMessagingKit import PromiseKit +import CallKit public final class SessionCall: NSObject, WebRTCSessionDelegate { // MARK: Metadata Properties @@ -13,6 +14,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { var remoteSDP: RTCSessionDescription? = nil var callMessageTimestamp: UInt64? var isWaitingForRemoteSDP = false + var answerCallAction: CXAnswerCallAction? = nil var contactName: String { let contact = Storage.shared.getContact(with: self.sessionID) return contact?.displayName(for: Contact.Context.regular) ?? self.sessionID @@ -181,8 +183,9 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { }) } - func answerSessionCall() { + func answerSessionCall(action: CXAnswerCallAction) { guard case .answer = mode else { return } + answerCallAction = action hasStartedConnecting = true if let sdp = remoteSDP { webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally @@ -249,6 +252,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { // MARK: Delegate public func webRTCIsConnected() { self.hasConnected = true + self.answerCallAction?.fulfill() } public func isRemoteVideoDidChange(isEnabled: Bool) { diff --git a/Session/Calls/Call Management/SessionCallManager+CXCallController.swift b/Session/Calls/Call Management/SessionCallManager+CXCallController.swift index 62607e08b..94a3e6b04 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXCallController.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXCallController.swift @@ -4,6 +4,7 @@ import SessionUtilitiesKit extension SessionCallManager { public func startCall(_ call: SessionCall, completion: ((Error?) -> Void)?) { guard case .offer = call.mode else { return } + guard !call.hasConnected else { return } let handle = CXHandle(type: .generic, value: call.sessionID) let startCallAction = CXStartCallAction(call: call.uuid, handle: handle) diff --git a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift index 23d69e6bc..3a9344ed0 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift @@ -15,18 +15,22 @@ extension SessionCallManager: CXProviderDelegate { public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { AssertIsOnMainThread() + print("[CallKit] Perform CXAnswerCallAction") guard let call = self.currentCall else { return action.fail() } - if let _ = CurrentAppContext().frontmostViewController() as? CallVC { - call.answerSessionCall() - } else { - let userDefaults = UserDefaults.standard - if userDefaults[.hasSeenCallIPExposureWarning] { - showCallVC() + if CurrentAppContext().isMainAppAndActive { + if let _ = CurrentAppContext().frontmostViewController() as? CallVC { + call.answerSessionCall(action: action) } else { - showCallModal() + let userDefaults = UserDefaults.standard + if userDefaults[.hasSeenCallIPExposureWarning] { + showCallVC() + } else { + showCallModal() + } } + } else { + call.answerSessionCall(action: action) } - action.fulfill() } public func provider(_ provider: CXProvider, perform action: CXEndCallAction) { @@ -37,6 +41,14 @@ extension SessionCallManager: CXProviderDelegate { action.fulfill() } + public func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) { + print("[CallKit] Perform CXSetMutedCallAction, isMuted: \(action.isMuted)") + AssertIsOnMainThread() + guard let call = self.currentCall else { return action.fail() } + call.isMuted = action.isMuted + action.fulfill() + } + public func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) { // TODO: set on hold } diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index ba35b2379..826e37afa 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -49,7 +49,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { private lazy var minimizeButton: UIButton = { let result = UIButton(type: .custom) - result.isHidden = true + result.isHidden = !call.hasConnected let image = UIImage(named: "Minimize")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) @@ -60,6 +60,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { private lazy var answerButton: UIButton = { let result = UIButton(type: .custom) + result.isHidden = call.hasConnected let image = UIImage(named: "AnswerCall")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) @@ -108,7 +109,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) result.set(.height, to: 60) - result.backgroundColor = UIColor(hex: 0x1F1F1F) + result.backgroundColor = call.isMuted ? Colors.destructive : UIColor(hex: 0x1F1F1F) result.layer.cornerRadius = 30 result.addTarget(self, action: #selector(switchAudio), for: UIControl.Event.touchUpInside) return result diff --git a/Session/Meta/AppDelegate.m b/Session/Meta/AppDelegate.m index dfab8a3b3..1da921767 100644 --- a/Session/Meta/AppDelegate.m +++ b/Session/Meta/AppDelegate.m @@ -407,6 +407,7 @@ static NSTimeInterval launchStartedAt; if (CurrentAppContext().isMainApp) { [SNJobQueue.shared resumePendingJobs]; [self syncConfigurationIfNeeded]; + [self handleAppActivatedWithOngoingCallIfNeeded]; } }); } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 20a92a884..2669cd533 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -6,6 +6,20 @@ import UIKit extension AppDelegate { // MARK: Call handling + @objc func handleAppActivatedWithOngoingCallIfNeeded() { + guard let call = AppEnvironment.shared.callManager.currentCall else { return } + if let callVC = CurrentAppContext().frontmostViewController() as? CallVC, callVC.call == call { return } + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + let callVC = CallVC(for: call) + if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == call.sessionID { + callVC.conversationVC = conversationVC + conversationVC.inputAccessoryView?.isHidden = true + conversationVC.inputAccessoryView?.alpha = 0 + } + presentingVC.present(callVC, animated: true, completion: nil) + + } + @objc func setUpCallHandling() { // Pre offer messages MessageReceiver.handlePreOfferCallMessage = { message in diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index 3d30cf816..b2299e23c 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -243,6 +243,10 @@ public enum PushRegistrationError: Error { if let uuid = payload["uuid"] as? String, let caller = payload["caller"] as? String, let timestamp = payload["timestamp"] as? UInt64 { let call = SessionCall(for: caller, uuid: uuid, mode: .answer) call.callMessageTimestamp = timestamp + let appDelegate = UIApplication.shared.delegate as! AppDelegate + appDelegate.startPollerIfNeeded() + appDelegate.startClosedGroupPoller() + appDelegate.startOpenGroupPollersIfNeeded() call.reportIncomingCallIfNeeded { error in if let error = error { SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") From 9d42c73de14ae99c517a3912c51897a5d7665c21 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 10 Nov 2021 11:17:35 +1100 Subject: [PATCH 095/368] disable add call --- Session/Calls/Call Management/SessionCallManager.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index 8397dbe77..c675f7df7 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -37,6 +37,7 @@ public final class SessionCallManager: NSObject { let localizedName = NSLocalizedString("APPLICATION_NAME", comment: "Name of application") let providerConfiguration = CXProviderConfiguration(localizedName: localizedName) providerConfiguration.supportsVideo = true + providerConfiguration.maximumCallGroups = 1 providerConfiguration.maximumCallsPerCallGroup = 1 providerConfiguration.supportedHandleTypes = [.generic] let iconMaskImage = #imageLiteral(resourceName: "SessionGreen32") @@ -76,6 +77,9 @@ public final class SessionCallManager: NSObject { update.localizedCallerName = callerName update.remoteHandle = CXHandle(type: .generic, value: call.uuid.uuidString) update.hasVideo = false + update.supportsGrouping = false + update.supportsUngrouping = false + update.supportsHolding = false disableUnsupportedFeatures(callUpdate: update) From 248a02e1e913dd081a656b208488fbdfc086ec8c Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 10 Nov 2021 14:31:02 +1100 Subject: [PATCH 096/368] minor fix --- .../Calls/Call Management/SessionCall.swift | 28 +++++++++++++------ .../SessionCallManager+CXProvider.swift | 6 ++-- Session/Calls/CallVC.swift | 6 ++-- Session/Meta/AppDelegate.swift | 2 +- .../Calls/WebRTCSession+DataChannel.swift | 3 ++ SessionMessagingKit/Calls/WebRTCSession.swift | 24 ++++++++++------ .../MessageReceiver+Handling.swift | 8 +++--- 7 files changed, 51 insertions(+), 26 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index fef6abe60..ce24cc842 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -168,24 +168,21 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { // MARK: Actions func startSessionCall() { guard case .offer = mode else { return } - var promise: Promise! + var promise: Promise! Storage.write(with: { transaction in promise = self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction) }, completion: { [weak self] in - let _ = promise.done { + let _ = promise.done { timestamp in + self?.callMessageTimestamp = timestamp Storage.shared.write { transaction in - self?.webRTCSession.sendOffer(to: self!.sessionID, using: transaction as! YapDatabaseReadWriteTransaction).done { timestamp in - self?.hasStartedConnecting = true - self?.callMessageTimestamp = timestamp - }.retainUntilComplete() + self?.webRTCSession.sendOffer(to: self!.sessionID, using: transaction as! YapDatabaseReadWriteTransaction).retainUntilComplete() } } }) } - func answerSessionCall(action: CXAnswerCallAction) { + func answerSessionCall() { guard case .answer = mode else { return } - answerCallAction = action hasStartedConnecting = true if let sdp = remoteSDP { webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally @@ -194,8 +191,14 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { } } + func answerSessionCallInBackground(action: CXAnswerCallAction) { + answerCallAction = action + self.answerSessionCall() + } + func endSessionCall() { guard !hasEnded else { return } + webRTCSession.hangUp() Storage.write { transaction in self.webRTCSession.endCall(with: self.sessionID, using: transaction) } @@ -259,6 +262,15 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { isRemoteVideoEnabled = isEnabled } + public func didReceiveHangUpSignal() { + DispatchQueue.main.async { + if let currentBanner = IncomingCallBanner.current { currentBanner.dismiss() } + if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleEndCallMessage() } + if let miniCallView = MiniCallView.current { miniCallView.dismiss() } + AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: .remoteEnded) + } + } + public func dataChannelDidOpen() { // Send initial video status if (isVideoEnabled) { diff --git a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift index 3a9344ed0..ab8954e08 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift @@ -19,7 +19,7 @@ extension SessionCallManager: CXProviderDelegate { guard let call = self.currentCall else { return action.fail() } if CurrentAppContext().isMainAppAndActive { if let _ = CurrentAppContext().frontmostViewController() as? CallVC { - call.answerSessionCall(action: action) + call.answerSessionCall() } else { let userDefaults = UserDefaults.standard if userDefaults[.hasSeenCallIPExposureWarning] { @@ -28,12 +28,14 @@ extension SessionCallManager: CXProviderDelegate { showCallModal() } } + action.fulfill() } else { - call.answerSessionCall(action: action) + call.answerSessionCallInBackground(action: action) } } public func provider(_ provider: CXProvider, perform action: CXEndCallAction) { + print("[CallKit] Perform CXEndCallAction") AssertIsOnMainThread() guard let call = self.currentCall else { return action.fail() } call.endSessionCall() diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 826e37afa..27f665cec 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -60,7 +60,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { private lazy var answerButton: UIButton = { let result = UIButton(type: .custom) - result.isHidden = call.hasConnected + result.isHidden = call.hasStartedConnecting let image = UIImage(named: "AnswerCall")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) @@ -145,9 +145,11 @@ final class CallVC : UIViewController, VideoPreviewDelegate { private lazy var callInfoLabel: UILabel = { let result = UILabel() + result.isHidden = call.hasConnected result.textColor = .white result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) result.textAlignment = .center + if call.hasStartedConnecting { result.text = "Connecting..." } return result }() @@ -299,7 +301,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { callInfoLabel.text = "Connecting..." } - func handleEndCallMessage(_ message: CallMessage) { + func handleEndCallMessage() { print("[Calls] Ending call.") callInfoLabel.isHidden = false callInfoLabel.text = "Call Ended" diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 2669cd533..6a09ed0aa 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -68,7 +68,7 @@ extension AppDelegate { MessageReceiver.handleEndCallMessage = { message in DispatchQueue.main.async { if let currentBanner = IncomingCallBanner.current { currentBanner.dismiss() } - if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleEndCallMessage(message) } + if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleEndCallMessage() } if let miniCallView = MiniCallView.current { miniCallView.dismiss() } AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: .remoteEnded) } diff --git a/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift b/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift index fa1dcd735..1587e081a 100644 --- a/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift +++ b/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift @@ -30,6 +30,9 @@ extension WebRTCSession: RTCDataChannelDelegate { if let isRemoteVideoEnabled = json["video"] as? Bool { delegate?.isRemoteVideoDidChange(isEnabled: isRemoteVideoEnabled) } + if let _ = json["hangup"] { + delegate?.didReceiveHangUpSignal() + } } } } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 79da8db65..a3dbeab6d 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -7,6 +7,7 @@ public protocol WebRTCSessionDelegate : AnyObject { func webRTCIsConnected() func isRemoteVideoDidChange(isEnabled: Bool) func dataChannelDidOpen() + func didReceiveHangUpSignal() } /// See https://webrtc.org/getting-started/overview for more information. @@ -110,17 +111,19 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } // MARK: Signaling - public func sendPreOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { + public func sendPreOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { print("[Calls] Sending pre-offer message.") guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } - let (promise, seal) = Promise.pending() + let (promise, seal) = Promise.pending() DispatchQueue.main.async { let message = CallMessage() message.uuid = self.uuid message.kind = .preOffer + let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) + tsMessage.save(with: transaction) MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 { print("[Calls] Pre-offer message has been sent.") - seal.fulfill(()) + seal.fulfill((tsMessage.timestamp)) }.catch2 { error in seal.reject(error) } @@ -128,10 +131,10 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { return promise } - public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { + public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { print("[Calls] Sending offer message.") guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } - let (promise, seal) = Promise.pending() + let (promise, seal) = Promise.pending() peerConnection.offer(for: mediaConstraints) { [weak self] sdp, error in if let error = error { seal.reject(error) @@ -149,10 +152,8 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { message.uuid = self.uuid message.kind = .offer message.sdps = [ sdp.sdp ] - let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) - tsMessage.save(with: transaction) MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 { - seal.fulfill(tsMessage.timestamp) + seal.fulfill(()) }.catch2 { error in seal.reject(error) } @@ -240,7 +241,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { print("[Calls] Peer connection did add stream.") - configureAudioSession() +// configureAudioSession() } public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) { @@ -258,6 +259,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { print("[Calls] ICE connection state changed to: \(state).") if state == .connected { delegate?.webRTCIsConnected() +// configureAudioSession() } } @@ -312,4 +314,8 @@ extension WebRTCSession { localVideoTrack.isEnabled = true sendJSON(["video": true]) } + + public func hangUp() { + sendJSON(["hangup": true]) + } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index b33962655..f3a7850a7 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -291,20 +291,20 @@ extension MessageReceiver { handlePreOfferCallMessage?(message) case .offer: print("[Calls] Received offer message.") - if getWebRTCSession().uuid != message.uuid! { + if WebRTCSession.current?.uuid != message.uuid! { // TODO: Call in progress, put the new call on hold/reject return } handleOfferCallMessage?(message) case .answer: print("[Calls] Received answer message.") - guard getWebRTCSession().uuid == message.uuid! else { return } + guard WebRTCSession.current?.uuid == message.uuid! else { return } let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) getWebRTCSession().handleRemoteSDP(sdp, from: message.sender!) handleAnswerCallMessage?(message) case .provisionalAnswer: break // TODO: Implement case let .iceCandidates(sdpMLineIndexes, sdpMids): - guard getWebRTCSession().uuid == message.uuid! else { return } + guard WebRTCSession.current?.uuid == message.uuid! else { return } var candidates: [RTCIceCandidate] = [] let sdps = message.sdps! for i in 0.. Date: Wed, 10 Nov 2021 15:30:52 +1100 Subject: [PATCH 097/368] fix call kit UI audio nor working --- .../SessionCallManager+CXProvider.swift | 14 ++++++++++++++ SessionMessagingKit/Calls/WebRTCSession.swift | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift index ab8954e08..84b0d2bb8 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift @@ -58,5 +58,19 @@ extension SessionCallManager: CXProviderDelegate { public func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) { // TODO: handle timeout } + + public func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { + print("[CallKit] Audio session did activate.") + AssertIsOnMainThread() + guard let call = self.currentCall else { return } + call.webRTCSession.audioSessionDidActivate(audioSession) + } + + public func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { + print("[CallKit] Audio session did deactivate.") + AssertIsOnMainThread() + guard let call = self.currentCall else { return } + call.webRTCSession.audioSessionDidDeactivate(audioSession) + } } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index a3dbeab6d..05d17859d 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -94,6 +94,8 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public static var current: WebRTCSession? public init(for contactSessionID: String, with uuid: String) { + RTCAudioSession.sharedInstance().useManualAudio = true + RTCAudioSession.sharedInstance().isAudioEnabled = false self.contactSessionID = contactSessionID self.uuid = uuid super.init() @@ -259,7 +261,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { print("[Calls] ICE connection state changed to: \(state).") if state == .connected { delegate?.webRTCIsConnected() -// configureAudioSession() + configureAudioSession() } } @@ -297,6 +299,17 @@ extension WebRTCSession { audioSession.unlockForConfiguration() } + public func audioSessionDidActivate(_ audioSession: AVAudioSession) { + RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession) + RTCAudioSession.sharedInstance().isAudioEnabled = true + configureAudioSession() + } + + public func audioSessionDidDeactivate(_ audioSession: AVAudioSession) { + RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession) + RTCAudioSession.sharedInstance().isAudioEnabled = false + } + public func mute() { audioTrack.isEnabled = false } From 2bbba2c6b76ddcc46e71c135678c5ffe9130daa4 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 10 Nov 2021 15:38:57 +1100 Subject: [PATCH 098/368] clean --- .../Sending & Receiving/Notifications/PushNotificationAPI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index c44649c60..8fccb96ec 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -5,7 +5,7 @@ import PromiseKit public final class PushNotificationAPI : NSObject { // MARK: Settings - public static let server = "https://dev.apns.getsession.org" + public static let server = "https://live.apns.getsession.org" public static let serverPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" private static let maxRetryCount: UInt = 4 private static let tokenExpirationInterval: TimeInterval = 12 * 60 * 60 From e7a6ddb4f1daf22fea1c5693d02b84946660c83e Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 10 Nov 2021 15:39:39 +1100 Subject: [PATCH 099/368] clean --- SessionMessagingKit/Calls/WebRTCSession.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 05d17859d..93e4e15e9 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -243,7 +243,6 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { print("[Calls] Peer connection did add stream.") -// configureAudioSession() } public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) { @@ -252,9 +251,6 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) { print("[Calls] Peer connection should negotiate.") -// Storage.write { transaction in -// self.sendOffer(to: self.contactSessionID, using: transaction).retainUntilComplete() -// } } public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCIceConnectionState) { From da14539639e6e065e63eb53056791db9959e5d11 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 11 Nov 2021 11:09:52 +1100 Subject: [PATCH 100/368] handle busy --- .../Call Management/SessionCallManager.swift | 13 ++++++++++ Session/Meta/AppDelegate.swift | 7 +++++- .../MessageReceiver+Handling.swift | 24 ++++--------------- .../Sending & Receiving/MessageReceiver.swift | 2 +- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index c675f7df7..4120311f8 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -57,6 +57,7 @@ public final class SessionCallManager: NSObject { self.provider.setDelegate(self, queue: nil) } + // MARK: Report calls public func reportOutgoingCall(_ call: SessionCall) { AssertIsOnMainThread() call.stateDidChange = { @@ -122,6 +123,18 @@ public final class SessionCallManager: NSObject { callUpdate.supportsDTMF = false } + public func handleIncomingCallOfferInBusyState(offerMessage: CallMessage, using transaction: YapDatabaseReadWriteTransaction) { + guard let caller = offerMessage.sender, let thread = TSContactThread.fetch(for: caller, using: transaction) else { return } + let message = CallMessage() + message.uuid = offerMessage.uuid + message.kind = .endCall + print("[Calls] Sending end call message.") + MessageSender.sendNonDurably(message, in: thread, using: transaction).retainUntilComplete() + if let tsMessage = TSIncomingMessage.find(withAuthorId: caller, timestamp: offerMessage.sentTimestamp!, transaction: transaction) { + tsMessage.updateCall(withNewBody: NSLocalizedString("call_missing", comment: ""), transaction: transaction) + } + } + internal func showCallModal() { let callModal = CallModal() { [weak self] in self?.showCallVC() diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 6a09ed0aa..0033e2cc4 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -22,8 +22,13 @@ extension AppDelegate { @objc func setUpCallHandling() { // Pre offer messages - MessageReceiver.handlePreOfferCallMessage = { message in + MessageReceiver.handlePreOfferCallMessage = { (message, transaction) in guard CurrentAppContext().isMainApp else { return } + let callManager = AppEnvironment.shared.callManager + guard callManager.currentCall == nil else { + callManager.handleIncomingCallOfferInBusyState(offerMessage: message, using: transaction) + return + } DispatchQueue.main.async { if let caller = message.sender, let uuid = message.uuid { let call = SessionCall(for: caller, uuid: uuid, mode: .answer) diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index f3a7850a7..38eef7c00 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -264,23 +264,9 @@ extension MessageReceiver { // MARK: - Call Messages public static func handleCallMessage(_ message: CallMessage, using transaction: Any) { - func getWebRTCSession() -> WebRTCSession { - let result: WebRTCSession - if let current = WebRTCSession.current { - result = current - } else { - WebRTCSession.current = WebRTCSession(for: message.sender!, with: message.uuid!) - result = WebRTCSession.current! - } - return result - } switch message.kind! { case .preOffer: print("[Calls] Received pre-offer message.") - if getWebRTCSession().uuid != message.uuid! { - // TODO: Call in progress, put the new call on hold/reject - return - } let storage = SNMessagingKitConfiguration.shared.storage let transaction = transaction as! YapDatabaseReadWriteTransaction if let threadID = storage.getOrCreateThread(for: message.sender!, groupPublicKey: message.groupPublicKey, openGroupID: nil, using: transaction), @@ -288,7 +274,7 @@ extension MessageReceiver { let tsMessage = TSIncomingMessage.from(message, associatedWith: thread) tsMessage.save(with: transaction) } - handlePreOfferCallMessage?(message) + handlePreOfferCallMessage?(message, transaction) case .offer: print("[Calls] Received offer message.") if WebRTCSession.current?.uuid != message.uuid! { @@ -298,13 +284,13 @@ extension MessageReceiver { handleOfferCallMessage?(message) case .answer: print("[Calls] Received answer message.") - guard WebRTCSession.current?.uuid == message.uuid! else { return } + guard let currentWebRTCSession = WebRTCSession.current, currentWebRTCSession.uuid == message.uuid! else { return } let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) - getWebRTCSession().handleRemoteSDP(sdp, from: message.sender!) + currentWebRTCSession.handleRemoteSDP(sdp, from: message.sender!) handleAnswerCallMessage?(message) case .provisionalAnswer: break // TODO: Implement case let .iceCandidates(sdpMLineIndexes, sdpMids): - guard WebRTCSession.current?.uuid == message.uuid! else { return } + guard let currentWebRTCSession = WebRTCSession.current, currentWebRTCSession.uuid == message.uuid! else { return } var candidates: [RTCIceCandidate] = [] let sdps = message.sdps! for i in 0.. Void)? + public static var handlePreOfferCallMessage: ((CallMessage, YapDatabaseReadWriteTransaction) -> Void)? public static var handleOfferCallMessage: ((CallMessage) -> Void)? public static var handleAnswerCallMessage: ((CallMessage) -> Void)? public static var handleEndCallMessage: ((CallMessage) -> Void)? From 336c694b522caa8a4ca739d36366be3a4c1589ea Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 11 Nov 2021 12:12:12 +1100 Subject: [PATCH 101/368] refactoring on showing call ip exposure --- .../SessionCallManager+CXProvider.swift | 13 ++++--- .../Call Management/SessionCallManager.swift | 24 +------------ Session/Calls/CallVC.swift | 25 +++---------- .../Views & Modals/IncomingCallBanner.swift | 17 +-------- .../ConversationVC+Interaction.swift | 10 +++--- Session/Conversations/ConversationVC.swift | 6 ++-- .../Views & Modals/CallModal.swift | 5 +++ Session/Meta/AppDelegate.swift | 4 +-- .../Translations/en.lproj/Localizable.strings | 6 ++++ .../PrivacySettingsTableViewController.m | 35 +++++++++++++++++++ .../Database/SSKPreferences.swift | 13 +++++++ .../MessageReceiver+Handling.swift | 12 ++++--- .../NotificationServiceExtension.swift | 3 ++ 13 files changed, 94 insertions(+), 79 deletions(-) diff --git a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift index 84b0d2bb8..ebfae6cd5 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift @@ -21,12 +21,15 @@ extension SessionCallManager: CXProviderDelegate { if let _ = CurrentAppContext().frontmostViewController() as? CallVC { call.answerSessionCall() } else { - let userDefaults = UserDefaults.standard - if userDefaults[.hasSeenCallIPExposureWarning] { - showCallVC() - } else { - showCallModal() + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + let callVC = CallVC(for: self.currentCall!) + callVC.shouldAnswer = true + if let conversationVC = presentingVC as? ConversationVC { + callVC.conversationVC = conversationVC + conversationVC.inputAccessoryView?.isHidden = true + conversationVC.inputAccessoryView?.alpha = 0 } + presentingVC.present(callVC, animated: true, completion: nil) } action.fulfill() } else { diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index 4120311f8..0d561748f 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -123,7 +123,7 @@ public final class SessionCallManager: NSObject { callUpdate.supportsDTMF = false } - public func handleIncomingCallOfferInBusyState(offerMessage: CallMessage, using transaction: YapDatabaseReadWriteTransaction) { + public func handleIncomingCallOfferInBusyOrUnenabledState(offerMessage: CallMessage, using transaction: YapDatabaseReadWriteTransaction) { guard let caller = offerMessage.sender, let thread = TSContactThread.fetch(for: caller, using: transaction) else { return } let message = CallMessage() message.uuid = offerMessage.uuid @@ -134,27 +134,5 @@ public final class SessionCallManager: NSObject { tsMessage.updateCall(withNewBody: NSLocalizedString("call_missing", comment: ""), transaction: transaction) } } - - internal func showCallModal() { - let callModal = CallModal() { [weak self] in - self?.showCallVC() - } - callModal.modalPresentationStyle = .overFullScreen - callModal.modalTransitionStyle = .crossDissolve - guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully - presentingVC.present(callModal, animated: true, completion: nil) - } - - internal func showCallVC() { - guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully - let callVC = CallVC(for: self.currentCall!) - callVC.shouldAnswer = true - if let conversationVC = presentingVC as? ConversationVC { - callVC.conversationVC = conversationVC - conversationVC.inputAccessoryView?.isHidden = true - conversationVC.inputAccessoryView?.alpha = 0 - } - presentingVC.present(callVC, animated: true, completion: nil) - } } diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 27f665cec..af8b80e00 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -314,29 +314,14 @@ final class CallVC : UIViewController, VideoPreviewDelegate { } } - internal func showCallModal() { - let callModal = CallModal() { [weak self] in - self?.answerCall() - } - callModal.modalPresentationStyle = .overFullScreen - callModal.modalTransitionStyle = .crossDissolve - present(callModal, animated: true, completion: nil) - } - @objc private func answerCall() { - let userDefaults = UserDefaults.standard - if userDefaults[.hasSeenCallIPExposureWarning] { - AppEnvironment.shared.callManager.answerCall(call) { error in - DispatchQueue.main.async { - if let _ = error { - self.callInfoLabel.text = "Can't answer the call." - self.endCall() - } + AppEnvironment.shared.callManager.answerCall(call) { error in + DispatchQueue.main.async { + if let _ = error { + self.callInfoLabel.text = "Can't answer the call." + self.endCall() } } - } else { - userDefaults[.hasSeenCallIPExposureWarning] = true - showCallModal() } } diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index 569ffdb1f..2a2cd3cbe 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -136,22 +136,7 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { } @objc private func answerCall() { - let userDefaults = UserDefaults.standard - if userDefaults[.hasSeenCallIPExposureWarning] { - showCallVC(answer: true) - } else { - showCallModal() - } - } - - internal func showCallModal() { - let callModal = CallModal() { [weak self] in - self?.showCallVC(answer: true) - } - callModal.modalPresentationStyle = .overFullScreen - callModal.modalTransitionStyle = .crossDissolve - guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully - presentingVC.present(callModal, animated: true, completion: nil) + showCallVC(answer: true) } @objc private func endCall() { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 7bf6de1c4..0aa3c5985 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -29,7 +29,10 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc // MARK: Call @objc func startCall(_ sender: Any?) { let userDefaults = UserDefaults.standard - if userDefaults[.hasSeenCallIPExposureWarning] { + if !SSKPreferences.areCallsEnabled && !userDefaults[.hasSeenCallIPExposureWarning] { + userDefaults[.hasSeenCallIPExposureWarning] = true + showCallModal() + } else if SSKPreferences.areCallsEnabled { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } guard AppEnvironment.shared.callManager.currentCall == nil else { return } let call = SessionCall(for: contactSessionID, uuid: UUID().uuidString, mode: .offer, outgoing: true) @@ -38,9 +41,6 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc self.inputAccessoryView?.isHidden = true self.inputAccessoryView?.alpha = 0 present(callVC, animated: true, completion: nil) - } else { - userDefaults[.hasSeenCallIPExposureWarning] = true - showCallModal() } } @@ -48,8 +48,6 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc let callModal = CallModal() { [weak self] in self?.startCall(nil) } - callModal.modalPresentationStyle = .overFullScreen - callModal.modalTransitionStyle = .crossDissolve present(callModal, animated: true, completion: nil) } diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index a3eb195c5..641c8a9ea 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -309,8 +309,10 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat settingsButton.accessibilityLabel = "Settings button" settingsButton.isAccessibilityElement = true rightBarButtonItems.append(settingsButton) - let callButton = UIBarButtonItem(image: UIImage(named: "Phone")!, style: .plain, target: self, action: #selector(startCall)) - rightBarButtonItems.append(callButton) + if SSKPreferences.areCallsEnabled || !UserDefaults.standard[.hasSeenCallIPExposureWarning] { + let callButton = UIBarButtonItem(image: UIImage(named: "Phone")!, style: .plain, target: self, action: #selector(startCall)) + rightBarButtonItems.append(callButton) + } } else { let settingsButton = UIBarButtonItem(image: UIImage(named: "Gear"), style: .plain, target: self, action: #selector(openSettings)) settingsButton.accessibilityLabel = "Settings button" diff --git a/Session/Conversations/Views & Modals/CallModal.swift b/Session/Conversations/Views & Modals/CallModal.swift index 0620646e7..d6e512027 100644 --- a/Session/Conversations/Views & Modals/CallModal.swift +++ b/Session/Conversations/Views & Modals/CallModal.swift @@ -1,11 +1,15 @@ +@objc final class CallModal : Modal { private let onCallEnabled: () -> Void // MARK: Lifecycle + @objc init(onCallEnabled: @escaping () -> Void) { self.onCallEnabled = onCallEnabled super.init(nibName: nil, bundle: nil) + self.modalPresentationStyle = .overFullScreen + self.modalTransitionStyle = .crossDissolve } required init?(coder: NSCoder) { @@ -59,6 +63,7 @@ final class CallModal : Modal { // MARK: Interaction @objc private func enable() { + SSKPreferences.areCallsEnabled = true presentingViewController?.dismiss(animated: true, completion: nil) onCallEnabled() } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 0033e2cc4..038a7d977 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -25,8 +25,8 @@ extension AppDelegate { MessageReceiver.handlePreOfferCallMessage = { (message, transaction) in guard CurrentAppContext().isMainApp else { return } let callManager = AppEnvironment.shared.callManager - guard callManager.currentCall == nil else { - callManager.handleIncomingCallOfferInBusyState(offerMessage: message, using: transaction) + guard callManager.currentCall == nil || !SSKPreferences.areCallsEnabled else { + callManager.handleIncomingCallOfferInBusyOrUnenabledState(offerMessage: message, using: transaction) return } DispatchQueue.main.async { diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 31673a38b..4bbda8679 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -350,6 +350,12 @@ "SETTINGS_LINK_PREVIEWS_FOOTER" = "Previews are supported for most urls."; /* Header for setting for enabling & disabling link previews. */ "SETTINGS_LINK_PREVIEWS_HEADER" = "Link Previews"; +/* Setting for enabling & disabling voice & video calls. */ +"SETTINGS_CALLS" = "Voice and video calls"; +/* Footer for setting for enabling & disabling voice & video calls. */ +"SETTINGS_CALLS_FOOTER" = "Allow access to accept voice and video calls from other users."; +/* Header for setting for enabling & disabling voice & video calls. */ +"SETTINGS_CALLS_HEADER" = "Calls"; /* table section header */ "SETTINGS_NOTIFICATION_CONTENT_TITLE" = "Notification Content"; /* Label for the 'read receipts' setting. */ diff --git a/Session/Settings/PrivacySettingsTableViewController.m b/Session/Settings/PrivacySettingsTableViewController.m index 81f2f18a4..9c6217d6b 100644 --- a/Session/Settings/PrivacySettingsTableViewController.m +++ b/Session/Settings/PrivacySettingsTableViewController.m @@ -197,6 +197,25 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s linkPreviewsSection.footerTitle = NSLocalizedString( @"SETTINGS_LINK_PREVIEWS_FOOTER", @"Footer for setting for enabling & disabling link previews."); [contents addSection:linkPreviewsSection]; + + OWSTableSection *callsSection = [OWSTableSection new]; + [callsSection + addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_CALLS", + @"Setting for enabling & disabling voice & video calls.") + accessibilityIdentifier:[NSString stringWithFormat:@"settings.privacy.%@", @"calls"] + isOnBlock:^{ + return [SSKPreferences areCallsEnabled]; + } + isEnabledBlock:^{ + return YES; + } + target:weakSelf + selector:@selector(didToggleCallsEnabled:)]]; + callsSection.headerTitle = NSLocalizedString( + @"SETTINGS_CALLS_HEADER", @"Header for setting for enabling & disabling voice & video calls."); + callsSection.footerTitle = NSLocalizedString( + @"SETTINGS_CALLS_FOOTER", @"Footer for setting for enabling & disabling voice & video calls."); + [contents addSection:callsSection]; self.contents = contents; } @@ -260,6 +279,22 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s SSKPreferences.areLinkPreviewsEnabled = enabled; } +- (void)didToggleCallsEnabled:(UISwitch *)sender +{ + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + BOOL enabled = sender.isOn; + if (enabled && ![userDefaults boolForKey:@"hasSeenCallIPExposureWarning"]) { + [userDefaults setBool:YES forKey:@"hasSeenCallIPExposureWarning"]; + CallModal *modal = [[CallModal alloc] initOnCallEnabled:^{ + OWSLogInfo(@"toggled to: %@", (enabled ? @"ON" : @"OFF")); + }]; + [self presentViewController:modal animated:YES completion:nil]; + } else { + OWSLogInfo(@"toggled to: %@", (enabled ? @"ON" : @"OFF")); + SSKPreferences.areCallsEnabled = enabled; + } +} + - (void)isScreenLockEnabledDidChange:(UISwitch *)sender { BOOL shouldBeEnabled = sender.isOn; diff --git a/SessionMessagingKit/Database/SSKPreferences.swift b/SessionMessagingKit/Database/SSKPreferences.swift index 9d4c34f5d..ea6114f42 100644 --- a/SessionMessagingKit/Database/SSKPreferences.swift +++ b/SessionMessagingKit/Database/SSKPreferences.swift @@ -24,6 +24,19 @@ public class SSKPreferences: NSObject { setBool(newValue, key: areLinkPreviewsEnabledKey) } } + + // MARK: - + private static let areCallsEnabledKey = "areCallsEnabled" + + @objc + public static var areCallsEnabled: Bool { + get { + return getBool(key: areCallsEnabledKey, defaultValue: false) + } + set { + setBool(newValue, key: areCallsEnabledKey) + } + } // MARK: - diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 38eef7c00..0caa30142 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -267,12 +267,14 @@ extension MessageReceiver { switch message.kind! { case .preOffer: print("[Calls] Received pre-offer message.") - let storage = SNMessagingKitConfiguration.shared.storage let transaction = transaction as! YapDatabaseReadWriteTransaction - if let threadID = storage.getOrCreateThread(for: message.sender!, groupPublicKey: message.groupPublicKey, openGroupID: nil, using: transaction), - let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) { - let tsMessage = TSIncomingMessage.from(message, associatedWith: thread) - tsMessage.save(with: transaction) + if SSKPreferences.areCallsEnabled { + let storage = SNMessagingKitConfiguration.shared.storage + if let threadID = storage.getOrCreateThread(for: message.sender!, groupPublicKey: message.groupPublicKey, openGroupID: nil, using: transaction), + let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) { + let tsMessage = TSIncomingMessage.from(message, associatedWith: thread) + tsMessage.save(with: transaction) + } } handlePreOfferCallMessage?(message, transaction) case .offer: diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 8a7f643c1..744193529 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -92,6 +92,9 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension } case let callMessage as CallMessage: MessageReceiver.handleCallMessage(callMessage, using: transaction) + if !SSKPreferences.areCallsEnabled { + return self.completeSilenty() + } notificationContent.userInfo = userInfo notificationContent.badge = 1 notificationContent.title = "Session" From fa79124bb7ba7b94a2158a7e828c1a2af71a00b7 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 11 Nov 2021 16:09:39 +1100 Subject: [PATCH 102/368] minor refactoring & handle network change --- .../Calls/Call Management/SessionCall.swift | 15 ++++---- Session/Calls/CallVC.swift | 7 ++-- Session/Meta/AppDelegate.swift | 6 +++- .../Calls/WebRTCSession+MessageHandling.swift | 3 +- SessionMessagingKit/Calls/WebRTCSession.swift | 35 ++++++++++++------- .../MessageReceiver+Handling.swift | 2 -- 6 files changed, 43 insertions(+), 25 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index ce24cc842..f533291ea 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -13,7 +13,6 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { let isOutgoing: Bool var remoteSDP: RTCSessionDescription? = nil var callMessageTimestamp: UInt64? - var isWaitingForRemoteSDP = false var answerCallAction: CXAnswerCallAction? = nil var contactName: String { let contact = Storage.shared.getContact(with: self.sessionID) @@ -157,11 +156,9 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { } func didReceiveRemoteSDP(sdp: RTCSessionDescription) { - guard remoteSDP == nil else { return } remoteSDP = sdp - if isWaitingForRemoteSDP { + if hasStartedConnecting { webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally - isWaitingForRemoteSDP = false } } @@ -186,8 +183,6 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { hasStartedConnecting = true if let sdp = remoteSDP { webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally - } else { - isWaitingForRemoteSDP = true } } @@ -222,12 +217,18 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { let durationString = NSString.formatDurationSeconds(UInt32(self.duration), useShortFormat: true) newMessageBody = "\(self.isOutgoing ? NSLocalizedString("call_outgoing", comment: "") : NSLocalizedString("call_incoming", comment: "")): \(durationString)" shouldMarkAsRead = true + } else if self.hasStartedConnecting { + newMessageBody = NSLocalizedString("call_cancelled", comment: "") + shouldMarkAsRead = true } else { switch mode { case .local: newMessageBody = self.isOutgoing ? NSLocalizedString("call_cancelled", comment: "") : NSLocalizedString("call_rejected", comment: "") shouldMarkAsRead = true case .remote: + if self.hasStartedConnecting { + + } newMessageBody = self.isOutgoing ? NSLocalizedString("call_rejected", comment: "") : NSLocalizedString("call_missing", comment: "") } } @@ -254,6 +255,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { // MARK: Delegate public func webRTCIsConnected() { + guard !self.hasConnected else { return } self.hasConnected = true self.answerCallAction?.fulfill() } @@ -263,6 +265,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { } public func didReceiveHangUpSignal() { + self.hasEnded = true DispatchQueue.main.async { if let currentBanner = IncomingCallBanner.current { currentBanner.dismiss() } if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleEndCallMessage() } diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index af8b80e00..e9f3defcb 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -193,8 +193,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { } self.call.hasEndedDidChange = { DispatchQueue.main.async { - self.conversationVC?.showInputAccessoryView() - self.presentingViewController?.dismiss(animated: true, completion: nil) + self.handleEndCallMessage() } } } @@ -331,6 +330,10 @@ final class CallVC : UIViewController, VideoPreviewDelegate { self.call.endSessionCall() AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: nil) } + DispatchQueue.main.async { + self.conversationVC?.showInputAccessoryView() + self.presentingViewController?.dismiss(animated: true, completion: nil) + } } } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 038a7d977..f7e64c8dc 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -25,7 +25,7 @@ extension AppDelegate { MessageReceiver.handlePreOfferCallMessage = { (message, transaction) in guard CurrentAppContext().isMainApp else { return } let callManager = AppEnvironment.shared.callManager - guard callManager.currentCall == nil || !SSKPreferences.areCallsEnabled else { + guard callManager.currentCall == nil && SSKPreferences.areCallsEnabled else { callManager.handleIncomingCallOfferInBusyOrUnenabledState(offerMessage: message, using: transaction) return } @@ -65,6 +65,10 @@ extension AppDelegate { // Answer messages MessageReceiver.handleAnswerCallMessage = { message in DispatchQueue.main.async { + guard let call = AppEnvironment.shared.callManager.currentCall, message.uuid == call.uuid.uuidString else { return } + call.hasStartedConnecting = true + let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) + call.didReceiveRemoteSDP(sdp: sdp) guard let callVC = CurrentAppContext().frontmostViewController() as? CallVC else { return } callVC.handleAnswerMessage(message) } diff --git a/SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift b/SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift index 7ad8413d4..0ab2fa2d6 100644 --- a/SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift +++ b/SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift @@ -13,8 +13,7 @@ extension WebRTCSession { if let error = error { SNLog("[Calls] Couldn't set SDP due to error: \(error).") } else { - guard let self = self, - sdp.type == .offer, self.peerConnection.localDescription == nil else { return } + guard let self = self, sdp.type == .offer else { return } Storage.write { transaction in self.sendAnswer(to: sessionID, using: transaction).retainUntilComplete() } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 93e4e15e9..d6e954f14 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -41,15 +41,6 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { return factory.peerConnection(with: configuration, constraints: constraints, delegate: self) }() - internal lazy var mediaConstraints: RTCMediaConstraints = { - let mandatory: [String:String] = [ - kRTCMediaConstraintsOfferToReceiveAudio : kRTCMediaConstraintsValueTrue, - kRTCMediaConstraintsOfferToReceiveVideo : kRTCMediaConstraintsValueTrue, - ] - let optional: [String:String] = [:] - return RTCMediaConstraints(mandatoryConstraints: mandatory, optionalConstraints: optional) - }() - // Audio internal lazy var audioSource: RTCAudioSource = { let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [:]) @@ -110,6 +101,16 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { dataChannel.delegate = self self.localDataChannel = dataChannel } + + // Network reachability + NotificationCenter.default.addObserver(forName: .reachabilityChanged, object: nil, queue: nil) { _ in + print("[Calls] Reachability did change.") + if self.peerConnection.signalingState == .stable { + Storage.write { transaction in + self.sendOffer(to: self.contactSessionID, using: transaction, isRestartingICEConnection: true).retainUntilComplete() + } + } + } } // MARK: Signaling @@ -133,11 +134,11 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { return promise } - public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { + public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction, isRestartingICEConnection: Bool = false) -> Promise { print("[Calls] Sending offer message.") guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } let (promise, seal) = Promise.pending() - peerConnection.offer(for: mediaConstraints) { [weak self] sdp, error in + peerConnection.offer(for: mediaConstraints(isRestartingICEConnection)) { [weak self] sdp, error in if let error = error { seal.reject(error) } else { @@ -169,7 +170,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { print("[Calls] Sending answer message.") guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } let (promise, seal) = Promise.pending() - peerConnection.answer(for: mediaConstraints) { [weak self] sdp, error in + peerConnection.answer(for: mediaConstraints(false)) { [weak self] sdp, error in if let error = error { seal.reject(error) } else { @@ -236,6 +237,16 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { peerConnection.close() } + private func mediaConstraints(_ isRestartingICEConnection: Bool) -> RTCMediaConstraints { + var mandatory: [String:String] = [ + kRTCMediaConstraintsOfferToReceiveAudio : kRTCMediaConstraintsValueTrue, + kRTCMediaConstraintsOfferToReceiveVideo : kRTCMediaConstraintsValueTrue, + ] + if isRestartingICEConnection { mandatory[kRTCMediaConstraintsIceRestart] = kRTCMediaConstraintsValueTrue } + let optional: [String:String] = [:] + return RTCMediaConstraints(mandatoryConstraints: mandatory, optionalConstraints: optional) + } + // MARK: Peer connection delegate public func peerConnection(_ peerConnection: RTCPeerConnection, didChange state: RTCSignalingState) { print("[Calls] Signaling state changed to: \(state).") diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 0caa30142..4db78aa44 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -287,8 +287,6 @@ extension MessageReceiver { case .answer: print("[Calls] Received answer message.") guard let currentWebRTCSession = WebRTCSession.current, currentWebRTCSession.uuid == message.uuid! else { return } - let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) - currentWebRTCSession.handleRemoteSDP(sdp, from: message.sender!) handleAnswerCallMessage?(message) case .provisionalAnswer: break // TODO: Implement case let .iceCandidates(sdpMLineIndexes, sdpMids): From a6e5eb6fd62f5d0401196624a1c70ffd53a8420f Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 11 Nov 2021 16:19:50 +1100 Subject: [PATCH 103/368] update version number --- Session.xcodeproj/project.pbxproj | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 9164645b4..6607cfc28 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5163,7 +5163,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 302; + CURRENT_PROJECT_VERSION = 307; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5188,7 +5188,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.12.0; + MARKETING_VERSION = 1.12.1; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5236,7 +5236,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 302; + CURRENT_PROJECT_VERSION = 307; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5266,7 +5266,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.12.0; + MARKETING_VERSION = 1.12.1; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5302,7 +5302,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 302; + CURRENT_PROJECT_VERSION = 307; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5325,7 +5325,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.12.0; + MARKETING_VERSION = 1.12.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -5376,7 +5376,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 302; + CURRENT_PROJECT_VERSION = 307; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5404,7 +5404,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.12.0; + MARKETING_VERSION = 1.12.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -6312,7 +6312,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 302; + CURRENT_PROJECT_VERSION = 307; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6352,7 +6352,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.12.0; + MARKETING_VERSION = 1.12.1; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -6384,7 +6384,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 302; + CURRENT_PROJECT_VERSION = 307; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6424,7 +6424,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.12.0; + MARKETING_VERSION = 1.12.1; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session; From 9346864843fa8ddb6a5da8afa13cac6336f1f792 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 11 Nov 2021 16:51:54 +1100 Subject: [PATCH 104/368] end call if there is no answer in 60s --- .../Calls/Call Management/SessionCall.swift | 8 +++++--- .../SessionCallManager+CXProvider.swift | 6 +++++- .../Call Management/SessionCallManager.swift | 19 ++++++++++++++++++- Session/Meta/AppDelegate.swift | 1 + .../Translations/en.lproj/Localizable.strings | 1 + 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index f533291ea..8fdc34f97 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -66,6 +66,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { enum EndCallMode { case local case remote + case unanswered } // MARK: Call State Properties @@ -119,6 +120,8 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { get { return endDate != nil } set { endDate = newValue ? Date() : nil } } + + var didTimeout = false var duration: TimeInterval { guard let connectedDate = connectedDate else { @@ -226,10 +229,9 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { newMessageBody = self.isOutgoing ? NSLocalizedString("call_cancelled", comment: "") : NSLocalizedString("call_rejected", comment: "") shouldMarkAsRead = true case .remote: - if self.hasStartedConnecting { - - } newMessageBody = self.isOutgoing ? NSLocalizedString("call_rejected", comment: "") : NSLocalizedString("call_missing", comment: "") + case .unanswered: + newMessageBody = NSLocalizedString("call_timeout", comment: "") } } messageToUpdate.updateCall(withNewBody: newMessageBody, transaction: transaction) diff --git a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift index ebfae6cd5..6ae6d8faf 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift @@ -42,7 +42,11 @@ extension SessionCallManager: CXProviderDelegate { AssertIsOnMainThread() guard let call = self.currentCall else { return action.fail() } call.endSessionCall() - reportCurrentCallEnded(reason: nil) + if call.didTimeout { + reportCurrentCallEnded(reason: .unanswered) + } else { + reportCurrentCallEnded(reason: nil) + } action.fulfill() } diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index 0d561748f..af6a19e3a 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -4,6 +4,7 @@ import SessionMessagingKit public final class SessionCallManager: NSObject { let provider: CXProvider let callController = CXCallController() + var callTimeOutTimer: Timer? = nil var currentCall: SessionCall? = nil { willSet { if (newValue != nil) { @@ -68,6 +69,13 @@ public final class SessionCallManager: NSObject { self.provider.reportOutgoingCall(with: call.uuid, connectedAt: call.connectedDate) } } + callTimeOutTimer = Timer.scheduledTimer(withTimeInterval: 60, repeats: false) { _ in + guard let currentCall = self.currentCall else { return } + currentCall.didTimeout = true + self.endCall(currentCall) { error in + self.callTimeOutTimer = nil + } + } } public func reportIncomingCall(_ call: SessionCall, callerName: String, completion: @escaping (Error?) -> Void) { @@ -100,7 +108,11 @@ public final class SessionCallManager: NSObject { guard let call = currentCall else { return } if let reason = reason { self.provider.reportCall(with: call.uuid, endedAt: nil, reason: reason) - call.updateCallMessage(mode: .remote) + if reason == .unanswered { + call.updateCallMessage(mode: .unanswered) + } else { + call.updateCallMessage(mode: .remote) + } } else { call.updateCallMessage(mode: .local) } @@ -134,5 +146,10 @@ public final class SessionCallManager: NSObject { tsMessage.updateCall(withNewBody: NSLocalizedString("call_missing", comment: ""), transaction: transaction) } } + + public func invalidateTimeoutTimer() { + callTimeOutTimer?.invalidate() + callTimeOutTimer = nil + } } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index f7e64c8dc..972b5e5a9 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -66,6 +66,7 @@ extension AppDelegate { MessageReceiver.handleAnswerCallMessage = { message in DispatchQueue.main.async { guard let call = AppEnvironment.shared.callManager.currentCall, message.uuid == call.uuid.uuidString else { return } + AppEnvironment.shared.callManager.invalidateTimeoutTimer() call.hasStartedConnecting = true let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) call.didReceiveRemoteSDP(sdp: sdp) diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 4bbda8679..efd20b6af 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -586,6 +586,7 @@ "call_missing" = "Missing Call"; "call_rejected" = "Rejected Call"; "call_cancelled" = "Cancelled Call"; +"call_timeout" = "No answer"; "voice_call" = "Voice Call"; "video_call" = "Video Call"; "APN_Message" = "You've got a new message"; From bd938897adc30550743b3a9b2d4426dae5a3a6fd Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 11 Nov 2021 17:00:36 +1100 Subject: [PATCH 105/368] minor update on translation --- Session/Meta/Translations/en.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index efd20b6af..a5b1fc26f 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -586,7 +586,7 @@ "call_missing" = "Missing Call"; "call_rejected" = "Rejected Call"; "call_cancelled" = "Cancelled Call"; -"call_timeout" = "No answer"; +"call_timeout" = "Unanswered Call"; "voice_call" = "Voice Call"; "video_call" = "Video Call"; "APN_Message" = "You've got a new message"; From 02d049961891aea8a94d567252d21a906fcdbdea Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 12 Nov 2021 09:17:06 +1100 Subject: [PATCH 106/368] seperate CallKit uuid from session call id --- Session/Calls/Call Management/SessionCall.swift | 6 ++++-- .../SessionCallManager+CXCallController.swift | 8 ++++---- Session/Calls/Call Management/SessionCallManager.swift | 10 +++++----- Session/Meta/AppDelegate.swift | 4 ++-- SessionMessagingKit/Calls/WebRTCSession.swift | 3 ++- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 8fdc34f97..fd089fe76 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -6,7 +6,8 @@ import CallKit public final class SessionCall: NSObject, WebRTCSessionDelegate { // MARK: Metadata Properties - let uuid: UUID + let uuid: String + let callID: UUID // This is for CallKit let sessionID: String let mode: Mode let webRTCSession: WebRTCSession @@ -137,7 +138,8 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { // MARK: Initialization init(for sessionID: String, uuid: String, mode: Mode, outgoing: Bool = false) { self.sessionID = sessionID - self.uuid = UUID(uuidString: uuid)! + self.uuid = uuid + self.callID = UUID() self.mode = mode self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID, with: uuid) self.isOutgoing = outgoing diff --git a/Session/Calls/Call Management/SessionCallManager+CXCallController.swift b/Session/Calls/Call Management/SessionCallManager+CXCallController.swift index 94a3e6b04..4b83f2295 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXCallController.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXCallController.swift @@ -6,7 +6,7 @@ extension SessionCallManager { guard case .offer = call.mode else { return } guard !call.hasConnected else { return } let handle = CXHandle(type: .generic, value: call.sessionID) - let startCallAction = CXStartCallAction(call: call.uuid, handle: handle) + let startCallAction = CXStartCallAction(call: call.callID, handle: handle) startCallAction.isVideo = false @@ -18,7 +18,7 @@ extension SessionCallManager { } public func answerCall(_ call: SessionCall, completion: ((Error?) -> Void)?) { - let answerCallAction = CXAnswerCallAction(call: call.uuid) + let answerCallAction = CXAnswerCallAction(call: call.callID) let transaction = CXTransaction() transaction.addAction(answerCallAction) @@ -26,7 +26,7 @@ extension SessionCallManager { } public func endCall(_ call: SessionCall, completion: ((Error?) -> Void)?) { - let endCallAction = CXEndCallAction(call: call.uuid) + let endCallAction = CXEndCallAction(call: call.callID) let transaction = CXTransaction() transaction.addAction(endCallAction) @@ -35,7 +35,7 @@ extension SessionCallManager { // Not currently in use public func setOnHoldStatus(for call: SessionCall) { - let setHeldCallAction = CXSetHeldCallAction(call: call.uuid, onHold: true) + let setHeldCallAction = CXSetHeldCallAction(call: call.callID, onHold: true) let transaction = CXTransaction() transaction.addAction(setHeldCallAction) diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index af6a19e3a..75006ab16 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -63,10 +63,10 @@ public final class SessionCallManager: NSObject { AssertIsOnMainThread() call.stateDidChange = { if call.hasStartedConnecting { - self.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate) + self.provider.reportOutgoingCall(with: call.callID, startedConnectingAt: call.connectingDate) } if call.hasConnected { - self.provider.reportOutgoingCall(with: call.uuid, connectedAt: call.connectedDate) + self.provider.reportOutgoingCall(with: call.callID, connectedAt: call.connectedDate) } } callTimeOutTimer = Timer.scheduledTimer(withTimeInterval: 60, repeats: false) { _ in @@ -84,7 +84,7 @@ public final class SessionCallManager: NSObject { // Construct a CXCallUpdate describing the incoming call, including the caller. let update = CXCallUpdate() update.localizedCallerName = callerName - update.remoteHandle = CXHandle(type: .generic, value: call.uuid.uuidString) + update.remoteHandle = CXHandle(type: .generic, value: call.callID.uuidString) update.hasVideo = false update.supportsGrouping = false update.supportsUngrouping = false @@ -93,7 +93,7 @@ public final class SessionCallManager: NSObject { disableUnsupportedFeatures(callUpdate: update) // Report the incoming call to the system - self.provider.reportNewIncomingCall(with: call.uuid, update: update) { error in + self.provider.reportNewIncomingCall(with: call.callID, update: update) { error in guard error == nil else { self.currentCall = nil completion(error) @@ -107,7 +107,7 @@ public final class SessionCallManager: NSObject { public func reportCurrentCallEnded(reason: CXCallEndedReason?) { guard let call = currentCall else { return } if let reason = reason { - self.provider.reportCall(with: call.uuid, endedAt: nil, reason: reason) + self.provider.reportCall(with: call.callID, endedAt: nil, reason: reason) if reason == .unanswered { call.updateCallMessage(mode: .unanswered) } else { diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 972b5e5a9..e7a7dd2f1 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -57,7 +57,7 @@ extension AppDelegate { // Offer messages MessageReceiver.handleOfferCallMessage = { message in DispatchQueue.main.async { - guard let call = AppEnvironment.shared.callManager.currentCall, message.uuid == call.uuid.uuidString else { return } + guard let call = AppEnvironment.shared.callManager.currentCall, message.uuid! == call.uuid else { return } let sdp = RTCSessionDescription(type: .offer, sdp: message.sdps![0]) call.didReceiveRemoteSDP(sdp: sdp) } @@ -65,7 +65,7 @@ extension AppDelegate { // Answer messages MessageReceiver.handleAnswerCallMessage = { message in DispatchQueue.main.async { - guard let call = AppEnvironment.shared.callManager.currentCall, message.uuid == call.uuid.uuidString else { return } + guard let call = AppEnvironment.shared.callManager.currentCall, message.uuid! == call.uuid else { return } AppEnvironment.shared.callManager.invalidateTimeoutTimer() call.hasStartedConnecting = true let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index d6e954f14..fa8b37591 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -35,7 +35,8 @@ public final class WebRTCSession : 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: defaultICEServers) ] + configuration.iceServers = [ RTCIceServer(urlStrings: ["stun:freyr.getsession.org:5349"]), RTCIceServer(urlStrings: ["turn:freyr.getsession.org"], username: "session", credential: "session") ] +// configuration.iceServers = [ RTCIceServer(urlStrings: defaultICEServers) ] configuration.sdpSemantics = .unifiedPlan let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [:]) return factory.peerConnection(with: configuration, constraints: constraints, delegate: self) From 925bc8538c8bdcd0727075296cf266429e0626c5 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 12 Nov 2021 10:31:58 +1100 Subject: [PATCH 107/368] minor refactor on answering call logic --- .../Call Management/SessionCallManager+CXProvider.swift | 5 +++-- Session/Calls/CallVC.swift | 2 -- Session/Calls/Views & Modals/IncomingCallBanner.swift | 5 +++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift index 6ae6d8faf..44dd8025f 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift @@ -23,13 +23,14 @@ extension SessionCallManager: CXProviderDelegate { } else { guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully let callVC = CallVC(for: self.currentCall!) - callVC.shouldAnswer = true if let conversationVC = presentingVC as? ConversationVC { callVC.conversationVC = conversationVC conversationVC.inputAccessoryView?.isHidden = true conversationVC.inputAccessoryView?.alpha = 0 } - presentingVC.present(callVC, animated: true, completion: nil) + presentingVC.present(callVC, animated: true) { + call.answerSessionCall() + } } action.fulfill() } else { diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index e9f3defcb..fa3f7fdc2 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -6,7 +6,6 @@ import UIKit final class CallVC : UIViewController, VideoPreviewDelegate { let call: SessionCall - var shouldAnswer = false var shouldRestartCamera = true weak var conversationVC: ConversationVC? = nil @@ -218,7 +217,6 @@ final class CallVC : UIViewController, VideoPreviewDelegate { } } } - if shouldAnswer { answerCall() } } func setUpViewHierarchy() { diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index 2a2cd3cbe..e5e80d40f 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -153,13 +153,14 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { dismiss() guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully let callVC = CallVC(for: self.call) - callVC.shouldAnswer = answer if let conversationVC = presentingVC as? ConversationVC { callVC.conversationVC = conversationVC conversationVC.inputAccessoryView?.isHidden = true conversationVC.inputAccessoryView?.alpha = 0 } - presentingVC.present(callVC, animated: true, completion: nil) + presentingVC.present(callVC, animated: true) { + if answer { self.call.answerSessionCall() } + } } public func show() { From edffbe7d4c362791d556605b53e3cecc5e3eadab Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 12 Nov 2021 13:32:27 +1100 Subject: [PATCH 108/368] fix data channel and uuid check --- .../ConversationVC+Interaction.swift | 2 +- Session/Meta/AppDelegate.swift | 6 +++++- .../Calls/WebRTCSession+DataChannel.swift | 15 ++++++++++---- SessionMessagingKit/Calls/WebRTCSession.swift | 20 +++++-------------- .../MessageReceiver+Handling.swift | 6 +++++- 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 0aa3c5985..2f3a3cd9c 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -35,7 +35,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc } else if SSKPreferences.areCallsEnabled { guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } guard AppEnvironment.shared.callManager.currentCall == nil else { return } - let call = SessionCall(for: contactSessionID, uuid: UUID().uuidString, mode: .offer, outgoing: true) + let call = SessionCall(for: contactSessionID, uuid: UUID().uuidString.lowercased(), mode: .offer, outgoing: true) let callVC = CallVC(for: call) callVC.conversationVC = self self.inputAccessoryView?.isHidden = true diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index e7a7dd2f1..f0e5c45e8 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -65,7 +65,11 @@ extension AppDelegate { // Answer messages MessageReceiver.handleAnswerCallMessage = { message in DispatchQueue.main.async { - guard let call = AppEnvironment.shared.callManager.currentCall, message.uuid! == call.uuid else { return } + guard let call = AppEnvironment.shared.callManager.currentCall, message.uuid! == call.uuid else { + let call = AppEnvironment.shared.callManager.currentCall + print("[Calls] \(call == nil), \(message.uuid!), \(call?.uuid)") + return + } AppEnvironment.shared.callManager.invalidateTimeoutTimer() call.hasStartedConnecting = true let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) diff --git a/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift b/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift index 1587e081a..acdfe5be0 100644 --- a/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift +++ b/SessionMessagingKit/Calls/WebRTCSession+DataChannel.swift @@ -5,7 +5,10 @@ extension WebRTCSession: RTCDataChannelDelegate { internal func createDataChannel() -> RTCDataChannel? { let dataChannelConfiguration = RTCDataChannelConfiguration() - guard let dataChannel = peerConnection.dataChannel(forLabel: "VIDEOCONTROL", configuration: dataChannelConfiguration) else { + dataChannelConfiguration.isOrdered = true + dataChannelConfiguration.isNegotiated = true + dataChannelConfiguration.channelId = 548 + guard let dataChannel = peerConnection.dataChannel(forLabel: "CONTROL", configuration: dataChannelConfiguration) else { print("[Calls] Couldn't create data channel.") return nil } @@ -13,7 +16,8 @@ extension WebRTCSession: RTCDataChannelDelegate { } public func sendJSON(_ json: JSON) { - if let dataChannel = remoteDataChannel, let jsonAsData = try? JSONSerialization.data(withJSONObject: json, options: [ .fragmentsAllowed ]) { + if let dataChannel = self.dataChannel, let jsonAsData = try? JSONSerialization.data(withJSONObject: json, options: [ .fragmentsAllowed ]) { + print("[Calls] Send json to data channel") let dataBuffer = RTCDataBuffer(data: jsonAsData, isBinary: false) dataChannel.sendData(dataBuffer) } @@ -21,12 +25,15 @@ extension WebRTCSession: RTCDataChannelDelegate { // MARK: Data channel delegate public func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) { - print("[Calls] Data channel did change to \(dataChannel.readyState)") + print("[Calls] Data channel did change to \(dataChannel.readyState.rawValue)") + if dataChannel.readyState == .open { + delegate?.dataChannelDidOpen() + } } public func dataChannel(_ dataChannel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) { - print("[Calls] Data channel did receive data: \(buffer)") if let json = try? JSONSerialization.jsonObject(with: buffer.data, options: [ .fragmentsAllowed ]) as? JSON { + print("[Calls] Data channel did receive data: \(json)") if let isRemoteVideoEnabled = json["video"] as? Bool { delegate?.isRemoteVideoDidChange(isEnabled: isRemoteVideoEnabled) } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index fa8b37591..11fd3ad27 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -18,17 +18,11 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { private var queuedICECandidates: [RTCIceCandidate] = [] private var iceCandidateSendTimer: Timer? - 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() - return RTCPeerConnectionFactory() + let videoEncoderFactory = RTCDefaultVideoEncoderFactory() + let videoDecoderFactory = RTCDefaultVideoDecoderFactory() + return RTCPeerConnectionFactory(encoderFactory: videoEncoderFactory, decoderFactory: videoDecoderFactory) }() /// Represents a WebRTC connection between the user and a remote peer. Provides methods to connect to a @@ -36,7 +30,6 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { internal lazy var peerConnection: RTCPeerConnection = { let configuration = RTCConfiguration() configuration.iceServers = [ RTCIceServer(urlStrings: ["stun:freyr.getsession.org:5349"]), RTCIceServer(urlStrings: ["turn:freyr.getsession.org"], username: "session", credential: "session") ] -// configuration.iceServers = [ RTCIceServer(urlStrings: defaultICEServers) ] configuration.sdpSemantics = .unifiedPlan let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [:]) return factory.peerConnection(with: configuration, constraints: constraints, delegate: self) @@ -68,8 +61,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { }() // Data Channel - internal var localDataChannel: RTCDataChannel? - internal var remoteDataChannel: RTCDataChannel? + internal var dataChannel: RTCDataChannel? // MARK: Error public enum Error : LocalizedError { @@ -100,7 +92,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { // Data channel if let dataChannel = createDataChannel() { dataChannel.delegate = self - self.localDataChannel = dataChannel + self.dataChannel = dataChannel } // Network reachability @@ -287,8 +279,6 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { public func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { print("[Calls] Data channel opened.") - self.remoteDataChannel = dataChannel - delegate?.dataChannelDidOpen() } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 4db78aa44..bd4bb37eb 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -286,7 +286,11 @@ extension MessageReceiver { handleOfferCallMessage?(message) case .answer: print("[Calls] Received answer message.") - guard let currentWebRTCSession = WebRTCSession.current, currentWebRTCSession.uuid == message.uuid! else { return } + guard let currentWebRTCSession = WebRTCSession.current, currentWebRTCSession.uuid == message.uuid! else { + let currentWebRTCSession = WebRTCSession.current + print("[Calls] \(currentWebRTCSession == nil), \(currentWebRTCSession?.uuid) \(message.uuid)") + return + } handleAnswerCallMessage?(message) case .provisionalAnswer: break // TODO: Implement case let .iceCandidates(sdpMLineIndexes, sdpMids): From d8021f6d6bc266f1bb1a56c12ac97a8486b81408 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 12 Nov 2021 13:33:19 +1100 Subject: [PATCH 109/368] clean --- Session/Meta/AppDelegate.swift | 6 +----- .../Sending & Receiving/MessageReceiver+Handling.swift | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index f0e5c45e8..e7a7dd2f1 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -65,11 +65,7 @@ extension AppDelegate { // Answer messages MessageReceiver.handleAnswerCallMessage = { message in DispatchQueue.main.async { - guard let call = AppEnvironment.shared.callManager.currentCall, message.uuid! == call.uuid else { - let call = AppEnvironment.shared.callManager.currentCall - print("[Calls] \(call == nil), \(message.uuid!), \(call?.uuid)") - return - } + guard let call = AppEnvironment.shared.callManager.currentCall, message.uuid! == call.uuid else { return } AppEnvironment.shared.callManager.invalidateTimeoutTimer() call.hasStartedConnecting = true let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index bd4bb37eb..4db78aa44 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -286,11 +286,7 @@ extension MessageReceiver { handleOfferCallMessage?(message) case .answer: print("[Calls] Received answer message.") - guard let currentWebRTCSession = WebRTCSession.current, currentWebRTCSession.uuid == message.uuid! else { - let currentWebRTCSession = WebRTCSession.current - print("[Calls] \(currentWebRTCSession == nil), \(currentWebRTCSession?.uuid) \(message.uuid)") - return - } + guard let currentWebRTCSession = WebRTCSession.current, currentWebRTCSession.uuid == message.uuid! else { return } handleAnswerCallMessage?(message) case .provisionalAnswer: break // TODO: Implement case let .iceCandidates(sdpMLineIndexes, sdpMids): From ee8352362174731d86b298981750dd69fb0ac76a Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 12 Nov 2021 13:43:00 +1100 Subject: [PATCH 110/368] clean --- Session/Calls/Call Management/SessionCallManager.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index 75006ab16..0118933d9 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -86,9 +86,6 @@ public final class SessionCallManager: NSObject { update.localizedCallerName = callerName update.remoteHandle = CXHandle(type: .generic, value: call.callID.uuidString) update.hasVideo = false - update.supportsGrouping = false - update.supportsUngrouping = false - update.supportsHolding = false disableUnsupportedFeatures(callUpdate: update) From 716378ccd34a1fbd89750dcabd721eb4ae49ee0e Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 12 Nov 2021 16:24:47 +1100 Subject: [PATCH 111/368] handle offer message might come in earlier than pre offer message --- .../Calls/Call Management/SessionCall.swift | 1 + Session/Meta/AppDelegate.swift | 18 ++++++++++++------ .../MessageReceiver+Handling.swift | 16 +++------------- .../Sending & Receiving/MessageReceiver.swift | 2 +- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index fd089fe76..a9b80ab88 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -161,6 +161,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { } func didReceiveRemoteSDP(sdp: RTCSessionDescription) { + print("[Calls] Did receive remote sdp.") remoteSDP = sdp if hasStartedConnecting { webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index e7a7dd2f1..588823759 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -22,17 +22,24 @@ extension AppDelegate { @objc func setUpCallHandling() { // Pre offer messages - MessageReceiver.handlePreOfferCallMessage = { (message, transaction) in + MessageReceiver.handleNewCallOfferMessageIfNeeded = { (message, transaction) in guard CurrentAppContext().isMainApp else { return } let callManager = AppEnvironment.shared.callManager + // Ignore pre offer message afte the same call instance has been generated + if let currentCall = callManager.currentCall, currentCall.uuid == message.uuid! { return } guard callManager.currentCall == nil && SSKPreferences.areCallsEnabled else { callManager.handleIncomingCallOfferInBusyOrUnenabledState(offerMessage: message, using: transaction) return } - DispatchQueue.main.async { - if let caller = message.sender, let uuid = message.uuid { - let call = SessionCall(for: caller, uuid: uuid, mode: .answer) - call.callMessageTimestamp = message.sentTimestamp + // Create incoming call message + let thread = TSContactThread.getOrCreateThread(withContactSessionID: message.sender!, transaction: transaction) + let tsMessage = TSIncomingMessage.from(message, associatedWith: thread) + tsMessage.save(with: transaction) + // Handle UI + if let caller = message.sender, let uuid = message.uuid { + let call = SessionCall(for: caller, uuid: uuid, mode: .answer) + call.callMessageTimestamp = message.sentTimestamp + DispatchQueue.main.async { if CurrentAppContext().isMainAppAndActive { guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller { @@ -51,7 +58,6 @@ extension AppDelegate { } } } - } } // Offer messages diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 4db78aa44..5c2e84feb 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -268,21 +268,11 @@ extension MessageReceiver { case .preOffer: print("[Calls] Received pre-offer message.") let transaction = transaction as! YapDatabaseReadWriteTransaction - if SSKPreferences.areCallsEnabled { - let storage = SNMessagingKitConfiguration.shared.storage - if let threadID = storage.getOrCreateThread(for: message.sender!, groupPublicKey: message.groupPublicKey, openGroupID: nil, using: transaction), - let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) { - let tsMessage = TSIncomingMessage.from(message, associatedWith: thread) - tsMessage.save(with: transaction) - } - } - handlePreOfferCallMessage?(message, transaction) + handleNewCallOfferMessageIfNeeded?(message, transaction) case .offer: print("[Calls] Received offer message.") - if WebRTCSession.current?.uuid != message.uuid! { - // TODO: Call in progress, put the new call on hold/reject - return - } + let transaction = transaction as! YapDatabaseReadWriteTransaction + handleNewCallOfferMessageIfNeeded?(message, transaction) handleOfferCallMessage?(message) case .answer: print("[Calls] Received answer message.") diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 31bb772e3..b64ca4452 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -2,7 +2,7 @@ import SessionUtilitiesKit public enum MessageReceiver { private static var lastEncryptionKeyPairRequest: [String:Date] = [:] - public static var handlePreOfferCallMessage: ((CallMessage, YapDatabaseReadWriteTransaction) -> Void)? + public static var handleNewCallOfferMessageIfNeeded: ((CallMessage, YapDatabaseReadWriteTransaction) -> Void)? public static var handleOfferCallMessage: ((CallMessage) -> Void)? public static var handleAnswerCallMessage: ((CallMessage) -> Void)? public static var handleEndCallMessage: ((CallMessage) -> Void)? From 67f979e014bbfce7d25f8a06273688d161931dc5 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 12 Nov 2021 17:01:57 +1100 Subject: [PATCH 112/368] update icons --- Session/Calls/CallVC.swift | 12 +++++++----- .../Session/AudioOff.imageset/Contents.json | 2 +- .../{AudioOff.pdf => audio_off_fill.pdf} | Bin 5362 -> 5063 bytes .../SwitchCamera.imageset/Contents.json | 2 +- ...wtichCamera.pdf => switch_camera_fill.pdf} | Bin 5773 -> 5311 bytes .../Session/VideoCall.imageset/Contents.json | 2 +- .../{video_call.pdf => video_call_fill.pdf} | Bin 5281 -> 5157 bytes 7 files changed, 10 insertions(+), 8 deletions(-) rename Session/Meta/Images.xcassets/Session/AudioOff.imageset/{AudioOff.pdf => audio_off_fill.pdf} (59%) rename Session/Meta/Images.xcassets/Session/SwitchCamera.imageset/{SwtichCamera.pdf => switch_camera_fill.pdf} (55%) rename Session/Meta/Images.xcassets/Session/VideoCall.imageset/{video_call.pdf => video_call_fill.pdf} (74%) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index fa3f7fdc2..aeac31c80 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -116,19 +116,19 @@ final class CallVC : UIViewController, VideoPreviewDelegate { private lazy var videoButton: UIButton = { let result = UIButton(type: .custom) - let image = UIImage(named: "VideoCall")!.withTint(.white) + let image = UIImage(named: "VideoCall")?.withRenderingMode(.alwaysTemplate) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) result.set(.height, to: 60) + result.tintColor = .white result.backgroundColor = UIColor(hex: 0x1F1F1F) result.layer.cornerRadius = 30 - result.alpha = 0.5 result.addTarget(self, action: #selector(operateCamera), for: UIControl.Event.touchUpInside) return result }() private lazy var operationPanel: UIStackView = { - let result = UIStackView(arrangedSubviews: [videoButton, switchAudioButton, switchCameraButton]) + let result = UIStackView(arrangedSubviews: [switchCameraButton, videoButton, switchAudioButton]) result.axis = .horizontal result.spacing = Values.veryLargeSpacing return result @@ -347,7 +347,8 @@ final class CallVC : UIViewController, VideoPreviewDelegate { if (call.isVideoEnabled) { localVideoView.isHidden = true cameraManager.stop() - videoButton.alpha = 0.5 + videoButton.tintColor = .white + videoButton.backgroundColor = UIColor(hex: 0x1F1F1F) switchCameraButton.isEnabled = false call.isVideoEnabled = false } else { @@ -361,7 +362,8 @@ final class CallVC : UIViewController, VideoPreviewDelegate { localVideoView.isHidden = false cameraManager.prepare() cameraManager.start() - videoButton.alpha = 1.0 + videoButton.tintColor = UIColor(hex: 0x1F1F1F) + videoButton.backgroundColor = .white switchCameraButton.isEnabled = true call.isVideoEnabled = true } diff --git a/Session/Meta/Images.xcassets/Session/AudioOff.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/AudioOff.imageset/Contents.json index 710e3736b..f53aef847 100644 --- a/Session/Meta/Images.xcassets/Session/AudioOff.imageset/Contents.json +++ b/Session/Meta/Images.xcassets/Session/AudioOff.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "AudioOff.pdf", + "filename" : "audio_off_fill.pdf", "idiom" : "universal" } ], diff --git a/Session/Meta/Images.xcassets/Session/AudioOff.imageset/AudioOff.pdf b/Session/Meta/Images.xcassets/Session/AudioOff.imageset/audio_off_fill.pdf similarity index 59% rename from Session/Meta/Images.xcassets/Session/AudioOff.imageset/AudioOff.pdf rename to Session/Meta/Images.xcassets/Session/AudioOff.imageset/audio_off_fill.pdf index 93a3af9457845f16bb335274c93957b83fc36e17..4d054f40b101f93471ad135c9cc5aae7ba8ce2e2 100644 GIT binary patch delta 1732 zcmah~XHXM{0)`d^!jd5Y!ichDn3qYy2!R+GRRS{NLuS}RMi@dB8m0+L2x$>T#+k+g z5)cS!5kUk52`wNIB49y8pE4{OMu33Q>wACRo$u~_zrJsd)vCibSHpj*fuT zw3JA40w7a1q00HjzigBVJC21AXtA67k6q)69iy_2$I^1@Wbz;2DqLqp8RoqGpwmn( zf4`~nEmtYS#M6N1rZL9*J$7aPTPF7%Hnz&FH(=ufTc86T?5SqoqAjOkgPY#1ifC-c z!|%*}OD6cG5f1{;lT*~xTEd-!>XtpX`@12rEKEfVCsN;7t)AFjY=X zWm4bf{LVQ(Y5X#QTRpKoU-**)ZQ60v-d8}1u3w<1M_NDNQ!n(+O1HS22(~}5E9jbl z(GYVjgmR`c*iP&+-RXQ=c7(4x2$D-lFdm<`c+%mPQNgKDh`wb@mkB;1KUI7qXFWc_ zjYL_KsD1b_*&aThUFv1^hBf=J6Z;g>z-i zN7eOQFFnJ>$dV%MylNqKmCr_&*z(@Abo#JYX_(D`NtCL)Ge0}-t@{;~C!wZkoenKG zD{8k^HO}LM){BYqT8m3p!L5Eu>NP7SL5&R_s~Kig9G`aY5=0XgHBC(MFzLA9w@owL@ARd8Brlm#{QUN`oKEMVgn z?@j@qP*%!fnZ(@*m*BR$;inB$`aGwFPrqMPmOH2If!E8hb5`dc*P#HneB8I!-#u2tCvvTO%*y2qwtdOG@P$=`44!cwizgGlRtv7H#%iQ^5P}9;{oUl)G}*l0L{x z3e#%s>{Yzw)b@n2^ya+zM;!c_xD(eo&#AAqL9?|BGaeY}&g0^XZVF*=M%<~EfpYqG zx^_j0;=CoxH5da_N>V8)VYLNdZH=tLhVXo+qRJAFpKsh*aj;qHx)>Q?$V5#us7c&2 zK}QYl_4ALu5)WSY25RgOaeaZGkTv_G$Cf7bBR6LV5~M5KUdR3qoe{4q7@0ML)B#zm ze-y~X4ejOy2KJpYjaMOxZ9SSvnT9z?>ty~|Td2oqXnSiH0utDTnoahjo6FPBW15yv z&Z)Z+!S_hIi_$n}#&(=E+Y(HLX}>t4&*=~?P-M(pB=wk7YpcGLAzga$DI=%o(7^QP zP8KoB+&x%ITW0W>X8vJgCFdvn;bL;eV6N;8P?Wg`UW}2$>P>Fyn5DiEA0JBy_E$5! zulnY9EX!l1tmj7gRTK)q zxtaHtN#s|JxkbEvoKXmFvV7~s-ZO%GS<7C9A9Vw7-cNWVEs>70oiu>BP0Zc)i^0M*}w=6*y--~#Jd679^fckdnNUxzU z?u5Zd?*DWoKh${h|7UzmDL z_#~E|eGF1^90sw6^|f@lk}S{BR;X(qD8C#nIejV1?y>lbG}l`p>j20!D>>vD$rmZf z!kA4O4*@V33=T!1;7~L(QB(K7B|{SpL?Pi|=-&V*f&u>~P$Uw8X1>xi^|68fj|}>i z2eYw-ezk#uVZQck(9DZk4k2N11QZ63f<=(gVPqRJ3?2?cV{FkVC>a9}gTlfc0S^DS c0uK7mL6K9^GE*X>07x|47GP?6*3$*>7mubCSO5S3 delta 2071 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 skw_RkoDfbxV-RR-faU*1z$gFH*WrXiV=_^ zMQKt(dnowmAShjt8bDAC0t&cqcYpTm?m2tE?#!K;ulcy0ni*$lrW7Ostq(Od1w}-L z`;vk{(E#5(yI}==Fm?aNomO+ntb%tK3mnEA7!RBRfZv~az3)-F5w(@k=H(z+*0=aG z?j$D8%)ygzJXqnU+c@}jmQAm$H8$<4ExTDc-*)RZA)A4o$#M;;*OgZvU2l0n5A?|Y zN`64dagvz0HuQ#fmw)<|3hU+vCCwot+To*_h3fFQJz4 zt(@(*!?C;0a=O71<^`e^b!TtU-udfgcvIK*D{6nY-waR?W4-O)9REJj>OWFdm z39BYoYA8L7G~(Bjs2ylTh6#hf#tE&^1G>$di_E zhLHa1A5|0hToH^M@!)KaGrz>y=vOad{qW}YD z+z(=Qci4q6m-c9JEA&?3FQRxOV0UcV_0TmoAYo@E-iR?-s;B}{lsdVz+goshTI%1j5|HGbouz7U(#>&y!(ed3gQ5|ug zt7-70t>e@~qK_G2Ed+V1@@Sk{EAsNS#nPzbGx4o9v5PtH+izr_w8W4fdkuH`(AX(d z{WLm8$Q6A2JVw0eb&?Bbo&v&X^=n4ylt?_~a}H3pvx1ap%v(E_Ehctjgkjk9{P?2t ze>BEn-ITB6S-Rt1Wp2td38BOMGs_>;&JJnFLB<|?!x~%;X$uDxueqdBt;ekBp1lgq z5U;66zOijch1mGV>yY4>4`M3ys8HgSSXo0ZR2-|@)!58y({Q=SSn-H zCYNepJ~oWB6&$8Pvm|=^^hU2meNAl11Znun)u$TDJx5-?me^~MyK`Xru)B3q5W^YN zd!1CQ>CgAk@m`Qk*g&HQt^XjhU3Kl(Y9eqXu{*LZEmTR|aPub4%;f_Sr!mF(ML0RD zqG}M&VcT3*3q@U{^t$=8^5g(j3n1)*D%8kT+C94u)Y?%x(-T2!g z*)mcKSRn%T{p1sJar%=Vk1Wbap`W;}<2wT7cy}xq@%2e;*Lx>qC90lIl1I03iNjm! z>6b6|ESi)b&^9q@+-hQ6enE$60hmS`X`;hJJP#LBt4o-I|20EHH@zn{I(_}=k8-w~ z3-5GgNmjj(D8B<=u{((__71#CzGS&Ctt>sXktooQL&mZ+d9a=c2U8TVBEJEmd`^~n z!xK^$+z$$?RcOTvoA*H1f)~ z7oe7MxKq>6^m9GLClA{WPnoYp;F71LMF10qCRkQ0>o(vd(j=BRb zMVm1J-M6nD^;S84PcYYSwlX9cc8<2@J-cUoo}v;);u)eocUOHmJsYap_4>E*pSffF zAI5dGMr6;^8bbi)+B(kedG=HI=$TlS)pMe;Q2esyPgmI~gB~rJ@-UO}Bka_gfMH0t z3AbE5xHzo0QAc z(aYQNaokXBqZQn~_0dXPSh1(YR9^4}#RPch7Q9fCSk#jJc$UG0vzUB=DU8KoIJE#H zxjSzI&Il?|()Lbgy}RhJAl2CQ85+aPvXjq8^$q3t-7cDujubMKSvxXEzlJ?8n@Zt*?j_!T)$Cb&?dVf zzl^1Fyh>WO2EnBzE@S0%d67-W&5!!C8Og3vmuz^4oXmSaRv|YZ`lyfMfV1XRqEf+; zn>iwxF+@Ib?xh`ru#r(KYmqgW7WSwUS%ThSz%t{9$cXmqDL?ikEEQH=9=iGGqIVVa z3i%SMngi1{w|g_+b56merGy`M)OIjs7Ege0i;Mp3D_^P*4;qTVaIkVrHV3jfDIgQ1{*E^ssgrJqwJq2h+r)B7Js#D8%})SveM3_&6d z{+rX+&ry*y_VPuYgZses5J))6+YfOSgZ8J3ct_|jvFfG3d;8np7Y-Ca`3oO#G>P6%Hc{&L);Ab(m(QVel{Vs>vYIV z=k>3I)~Ng5U1Yv`+48sSexl+pHZF+fVK=fgFg7-Fp3KA>$YgH1Ih4185iWLxuZ+=R zvNwMKzlD*7g1%2`UV2G}f{BIcL*`j$1vrTXH%Uz^@seE+6*N;TSIm$%GzB#*2!C4@J#y4J{9>oyWbvc52qjN zfBVM2C+F+^gq6kXMgH{N>?}U;V&U7IFGVTdWj9%St#?Xx)a&eaN@cI;W4v~}&iVkW zP4e*u>E~zE)7tkZDm6RuJT@@U{>BzlEcN5xtmQYvPkVlB?EmxnccO1f=HB~u7an`| zowEEPR4C?px{d^vTxGQbSmUx z(v9!pdlc%AX^DIi{llaxG~wrDbD>gQLqkI&15*=I1Jfu219c$MRM7X$PjN{sNmbBr zu`)6+0;+&1nS4{oQq0mo!2kpl@)Wqh3%fVu+a=T40Eo7-NcAT9~1$Gch%V*fE(?#4_H%%p%3oJkcQ4I3*>;&@|1` w!otiT(aa>#GS$$+GBwqX%Z8v5g;+>VPAn>^C`wJ^GBGzY;8Im}^>^a}0M~khhyVZp delta 1143 zcmZ3gu~2h@U6O^Vf}I^#aY<2XVlGz&qjm^Kh(^eTay_#U54|u0hQp6iSeb2&TYVO^ zC`LG34{*HhaqxtLqGk9&O_n1Jjb3M_pf#bjb3 zz;OAmi2cSj(LC&i&X%rbrf!q@cmtVCjW;LqRxrZF9`KbhnoJJo58$`7Fi_CmAvXw`3*UTFR-^WKdoz+-n+R`ch!i4A1rh%kE}g zFDko!_vW46MF%>po+`E!3I5`Dc@YvI8M0hYcFKnY@$Ofb6i-Th|GMv{p%+)x;qQN+ zegD0;^8LrN>8YRDwmq~GxpK_MmRbEsWZT`&jgKm)-)A@ybm_ul(R#kw8*NWEy>6HI zuIjt`!KceTSCe1fSXSS<^ZFI;yjmUO>p{CRLq1L2-E((l$+@DRlE?byOf@YDHVyjt zWhZx50C$Ga;l#D6D`psOnYLnH!M8ONH*E>q#=do$qw@4~>B(+>+?fI~N1GRaunJZE zZM@{r>3MbrwvYJhTZ7*IIKA3Eum05Zr~E6w_U@9;hzMz)sTH$Y=udC{witescQ;mS zmN(V0nj3SOkw3g^b-s(R@xxuFHtxKi_wE;YU}3lUk?WqA_u4SH>dAuP#@U=cVxPdZIGt=kHrim40C< z^<9%g5~u1%eD~4JH|(E&XJWHe=7D?uhp&V^tZ2Mm(ykU;Ci?gkbC2^q3BFlNGy|U~ z`Ts}>vD4hYx_;sNEpD%$a#XWsym;oh+wj4z&tIn8`xv!Q(mnFP9HC<}^LE#XyS_~q zS<2zHM#Ny@3XMmTUf)|5{Y~oQIs3m-t}bk$e+n+ttM7J^pQ|Ik%B8F76!#V{?LMOx zyHvqi3suSTy8V^$FBGHZ_N Date: Mon, 15 Nov 2021 12:22:31 +1100 Subject: [PATCH 113/368] deal with audio I/O change --- .../Calls/Call Management/SessionCall.swift | 20 ++++++++++++++++++ Session/Calls/CallVC.swift | 16 +++++++++++++- .../Session/Speaker.imageset/Contents.json | 12 +++++++++++ .../Session/Speaker.imageset/speaker.pdf | Bin 0 -> 4975 bytes SessionMessagingKit/Calls/WebRTCSession.swift | 5 ++--- 5 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 Session/Meta/Images.xcassets/Session/Speaker.imageset/Contents.json create mode 100644 Session/Meta/Images.xcassets/Session/Speaker.imageset/speaker.pdf diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index a9b80ab88..5e750c724 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -10,6 +10,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { let callID: UUID // This is for CallKit let sessionID: String let mode: Mode + var audioMode: AudioMode let webRTCSession: WebRTCSession let isOutgoing: Bool var remoteSDP: RTCSessionDescription? = nil @@ -70,6 +71,14 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { case unanswered } + // MARK: Audio I/O mode + enum AudioMode { + case earpiece + case speaker + case headphone + case bluetooth + } + // MARK: Call State Properties var connectingDate: Date? { didSet { @@ -141,6 +150,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { self.uuid = uuid self.callID = UUID() self.mode = mode + self.audioMode = .earpiece self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionID, with: uuid) self.isOutgoing = outgoing WebRTCSession.current = self.webRTCSession @@ -258,6 +268,16 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { webRTCSession.attachLocalRenderer(renderer) } + // MARK: Audio I/O swithcing + func switchAudioMode(mode: AudioMode) { + audioMode = mode + if mode == .speaker { + webRTCSession.configureAudioSession(outputAudioPort: .speaker) + } else { + webRTCSession.configureAudioSession() + } + } + // MARK: Delegate public func webRTCIsConnected() { guard !self.hasConnected else { return } diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index aeac31c80..bb0eae1e4 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -3,6 +3,7 @@ import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit import UIKit +import MediaPlayer final class CallVC : UIViewController, VideoPreviewDelegate { let call: SessionCall @@ -127,8 +128,21 @@ final class CallVC : UIViewController, VideoPreviewDelegate { return result }() + private lazy var volumeView: MPVolumeView = { + let result = MPVolumeView() + let image = UIImage(named: "Speaker")?.withRenderingMode(.alwaysTemplate) + result.showsRouteButton = true + result.setRouteButtonImage(image, for: UIControl.State.normal) + result.set(.width, to: 60) + result.set(.height, to: 60) + result.tintColor = .white + result.backgroundColor = UIColor(hex: 0x1F1F1F) + result.layer.cornerRadius = 30 + return result + }() + private lazy var operationPanel: UIStackView = { - let result = UIStackView(arrangedSubviews: [switchCameraButton, videoButton, switchAudioButton]) + let result = UIStackView(arrangedSubviews: [switchCameraButton, videoButton, switchAudioButton, volumeView]) result.axis = .horizontal result.spacing = Values.veryLargeSpacing return result diff --git a/Session/Meta/Images.xcassets/Session/Speaker.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/Speaker.imageset/Contents.json new file mode 100644 index 000000000..e931b16c1 --- /dev/null +++ b/Session/Meta/Images.xcassets/Session/Speaker.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "speaker.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Session/Meta/Images.xcassets/Session/Speaker.imageset/speaker.pdf b/Session/Meta/Images.xcassets/Session/Speaker.imageset/speaker.pdf new file mode 100644 index 0000000000000000000000000000000000000000..59d053ccacea89308b40e730f2322f509b3d8092 GIT binary patch literal 4975 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 m@eQHkU Date: Mon, 15 Nov 2021 16:02:24 +1100 Subject: [PATCH 114/368] minor fix --- Session/Calls/CallVC.swift | 1 + Session/Calls/Views & Modals/MiniCallView.swift | 1 + SessionMessagingKit/Database/Storage+Messaging.swift | 1 + SessionMessagingKit/Sending & Receiving/MessageSender.swift | 4 ---- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index bb0eae1e4..7a4ba3df7 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -131,6 +131,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { private lazy var volumeView: MPVolumeView = { let result = MPVolumeView() let image = UIImage(named: "Speaker")?.withRenderingMode(.alwaysTemplate) + result.showsVolumeSlider = false result.showsRouteButton = true result.setRouteButtonImage(image, for: UIControl.State.normal) result.set(.width, to: 60) diff --git a/Session/Calls/Views & Modals/MiniCallView.swift b/Session/Calls/Views & Modals/MiniCallView.swift index f7d5869ce..7fe48fb71 100644 --- a/Session/Calls/Views & Modals/MiniCallView.swift +++ b/Session/Calls/Views & Modals/MiniCallView.swift @@ -33,6 +33,7 @@ final class MiniCallView: UIView { private func setUpViewHierarchy() { self.set(.width, to: 80) self.set(.height, to: 173) + self.layer.masksToBounds = true // Background let background = getBackgroudView() self.addSubview(background) diff --git a/SessionMessagingKit/Database/Storage+Messaging.swift b/SessionMessagingKit/Database/Storage+Messaging.swift index e02363de7..0035217f2 100644 --- a/SessionMessagingKit/Database/Storage+Messaging.swift +++ b/SessionMessagingKit/Database/Storage+Messaging.swift @@ -28,6 +28,7 @@ extension Storage { let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return nil } let tsMessage: TSMessage if message.sender == getUserPublicKey() { + if let _ = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) { return nil } let tsOutgoingMessage = TSOutgoingMessage.from(message, associatedWith: thread, using: transaction) var recipients: [String] = [] if let syncTarget = message.syncTarget { diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 69946ffc0..0206929b1 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -116,8 +116,6 @@ public final class MessageSender : NSObject { if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set message.sentTimestamp = NSDate.millisecondTimestamp() } - // Ignore future self-sends - Storage.shared.addReceivedMessageTimestamp(message.sentTimestamp!, using: transaction) message.sender = userPublicKey switch destination { case .contact(let publicKey): message.recipient = publicKey @@ -271,8 +269,6 @@ public final class MessageSender : NSObject { if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set message.sentTimestamp = NSDate.millisecondTimestamp() } - // Ignore future self-sends - Storage.shared.addReceivedMessageTimestamp(message.sentTimestamp!, using: transaction) message.sender = storage.getUserPublicKey() switch destination { case .contact(_): preconditionFailure() From bd0a89184be60671fa07eb3d5af5c30b1d3a45bf Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 15 Nov 2021 16:05:25 +1100 Subject: [PATCH 115/368] update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6607cfc28..ee9f7731f 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5163,7 +5163,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 307; + CURRENT_PROJECT_VERSION = 308; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5236,7 +5236,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 307; + CURRENT_PROJECT_VERSION = 308; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5302,7 +5302,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 307; + CURRENT_PROJECT_VERSION = 308; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5376,7 +5376,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 307; + CURRENT_PROJECT_VERSION = 308; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6312,7 +6312,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 307; + CURRENT_PROJECT_VERSION = 308; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6384,7 +6384,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 307; + CURRENT_PROJECT_VERSION = 308; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", From 57f0595dc3e055a12c4c59dbbc69a1e1f2b4684a Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Wed, 17 Nov 2021 16:24:06 +1100 Subject: [PATCH 116/368] hide call button for note to self --- Session/Conversations/ConversationVC.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 641c8a9ea..5c7a92cc0 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -309,7 +309,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat settingsButton.accessibilityLabel = "Settings button" settingsButton.isAccessibilityElement = true rightBarButtonItems.append(settingsButton) - if SSKPreferences.areCallsEnabled || !UserDefaults.standard[.hasSeenCallIPExposureWarning] { + if !thread.isNoteToSelf() && (SSKPreferences.areCallsEnabled || !UserDefaults.standard[.hasSeenCallIPExposureWarning]) { let callButton = UIBarButtonItem(image: UIImage(named: "Phone")!, style: .plain, target: self, action: #selector(startCall)) rightBarButtonItems.append(callButton) } From 32790a9d997927d249ea0704f7c0c19ae058c551 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Wed, 17 Nov 2021 17:02:13 +1100 Subject: [PATCH 117/368] fix title view position --- Session/Conversations/ConversationVC.swift | 3 ++- .../Conversations/Views & Modals/ConversationTitleView.swift | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 5c7a92cc0..c17e7150b 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -309,7 +309,8 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat settingsButton.accessibilityLabel = "Settings button" settingsButton.isAccessibilityElement = true rightBarButtonItems.append(settingsButton) - if !thread.isNoteToSelf() && (SSKPreferences.areCallsEnabled || !UserDefaults.standard[.hasSeenCallIPExposureWarning]) { + let shouldShowCallButton = !thread.isNoteToSelf() && (SSKPreferences.areCallsEnabled || !UserDefaults.standard[.hasSeenCallIPExposureWarning]) + if shouldShowCallButton { let callButton = UIBarButtonItem(image: UIImage(named: "Phone")!, style: .plain, target: self, action: #selector(startCall)) rightBarButtonItems.append(callButton) } diff --git a/Session/Conversations/Views & Modals/ConversationTitleView.swift b/Session/Conversations/Views & Modals/ConversationTitleView.swift index 0d744f16c..724737704 100644 --- a/Session/Conversations/Views & Modals/ConversationTitleView.swift +++ b/Session/Conversations/Views & Modals/ConversationTitleView.swift @@ -44,7 +44,8 @@ final class ConversationTitleView : UIView { stackView.axis = .vertical stackView.alignment = .center stackView.isLayoutMarginsRelativeArrangement = true - let leftMargin: CGFloat = (thread is TSContactThread) ? 54 : 8 // Contact threads also have the call button to compensate for + let shouldShowCallButton = !thread.isNoteToSelf() && (SSKPreferences.areCallsEnabled || !UserDefaults.standard[.hasSeenCallIPExposureWarning]) + let leftMargin: CGFloat = shouldShowCallButton ? 54 : 8 // Contact threads also have the call button to compensate for stackView.layoutMargins = UIEdgeInsets(top: 0, left: leftMargin, bottom: 0, right: 0) addSubview(stackView) stackView.pin(to: self) From d3a2b456f19a417f06f9a16736dfcea62c6794f1 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 18 Nov 2021 10:39:50 +1100 Subject: [PATCH 118/368] handle permission request for voice and video calls --- Session.xcodeproj/project.pbxproj | 4 + Session/Calls/CallVC.swift | 1 + .../ConversationVC+Interaction.swift | 88 +----------------- Session/Utilities/Permissions.swift | 90 +++++++++++++++++++ 4 files changed, 97 insertions(+), 86 deletions(-) create mode 100644 Session/Utilities/Permissions.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index ee9f7731f..70bc73c20 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -156,6 +156,7 @@ 7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; }; 7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */; }; 7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; + 7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A892745C4F000FB91B9 /* Permissions.swift */; }; 945AA2B82B621254F69FA9E8 /* Pods_SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9117261809D69B3D7C26B8F1 /* Pods_SessionUtilitiesKit.framework */; }; 9EE44C6B4D4A069B86112387 /* Pods_SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9559C3068280BA2383F547F7 /* Pods_SessionSnodeKit.framework */; }; A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; }; @@ -1144,6 +1145,7 @@ 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+DataChannel.swift"; sourceTree = ""; }; 7BDCFC0424206E7300641C39 /* SessionNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SessionNotificationServiceExtension.entitlements; sourceTree = ""; }; 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtensionContext.swift; sourceTree = ""; }; + 7BFD1A892745C4F000FB91B9 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = ""; }; 7DD180F770F8518B4E8796F2 /* Pods-SessionUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUtilitiesKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionUtilitiesKit/Pods-SessionUtilitiesKit.app store release.xcconfig"; sourceTree = ""; }; 8981C8F64D94D3C52EB67A2C /* Pods-SignalTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.test.xcconfig"; sourceTree = ""; }; 8EEE74B0753448C085B48721 /* Pods-SignalMessaging.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.app store release.xcconfig"; sourceTree = ""; }; @@ -2058,6 +2060,7 @@ B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */, C31A6C59247F214E001123EF /* UIView+Glow.swift */, C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */, + 7BFD1A892745C4F000FB91B9 /* Permissions.swift */, ); path = Utilities; sourceTree = ""; @@ -4989,6 +4992,7 @@ 340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */, 7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */, B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */, + 7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */, B85357BF23A1AE0800AAF6CD /* SeedReminderView.swift in Sources */, B821494F25D4E163009C0F2A /* BodyTextView.swift in Sources */, C35E8AAE2485E51D00ACB629 /* IP2Country.swift in Sources */, diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 7a4ba3df7..bd01d0560 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -367,6 +367,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { switchCameraButton.isEnabled = false call.isVideoEnabled = false } else { + guard requestCameraPermissionIfNeeded() else { return } let previewVC = VideoPreviewVC() previewVC.delegate = self present(previewVC, animated: true, completion: nil) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 2f3a3cd9c..d9db9c7fa 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -33,6 +33,8 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc userDefaults[.hasSeenCallIPExposureWarning] = true showCallModal() } else if SSKPreferences.areCallsEnabled { + requestMicrophonePermissionIfNeeded { } + guard AVAudioSession.sharedInstance().recordPermission == .granted else { return } guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return } guard AppEnvironment.shared.callManager.currentCall == nil else { return } let call = SessionCall(for: contactSessionID, uuid: UUID().uuidString.lowercased(), mode: .offer, outgoing: true) @@ -905,92 +907,6 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc } } - // MARK: Requesting Permission - func requestCameraPermissionIfNeeded() -> Bool { - switch AVCaptureDevice.authorizationStatus(for: .video) { - case .authorized: return true - case .denied, .restricted: - let modal = PermissionMissingModal(permission: "camera") { } - modal.modalPresentationStyle = .overFullScreen - modal.modalTransitionStyle = .crossDissolve - present(modal, animated: true, completion: nil) - return false - case .notDetermined: - AVCaptureDevice.requestAccess(for: .video, completionHandler: { _ in }) - return false - default: return false - } - } - - func requestMicrophonePermissionIfNeeded(onNotGranted: @escaping () -> Void) { - switch AVAudioSession.sharedInstance().recordPermission { - case .granted: break - case .denied: - onNotGranted() - let modal = PermissionMissingModal(permission: "microphone") { - onNotGranted() - } - modal.modalPresentationStyle = .overFullScreen - modal.modalTransitionStyle = .crossDissolve - present(modal, animated: true, completion: nil) - case .undetermined: - onNotGranted() - AVAudioSession.sharedInstance().requestRecordPermission { _ in } - default: break - } - } - - func requestLibraryPermissionIfNeeded(onAuthorized: @escaping () -> Void) { - let authorizationStatus: PHAuthorizationStatus - if #available(iOS 14, *) { - authorizationStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite) - if authorizationStatus == .notDetermined { - // When the user chooses to select photos (which is the .limit status), - // the PHPhotoUI will present the picker view on the top of the front view. - // Since we have the ScreenLockUI showing when we request premissions, - // the picker view will be presented on the top of the ScreenLockUI. - // However, the ScreenLockUI will dismiss with the permission request alert view, so - // the picker view then will dismiss, too. The selection process cannot be finished - // this way. So we add a flag (isRequestingPermission) to prevent the ScreenLockUI - // from showing when we request the photo library permission. - Environment.shared.isRequestingPermission = true - let appMode = AppModeManager.shared.currentAppMode - // FIXME: Rather than setting the app mode to light and then to dark again once we're done, - // it'd be better to just customize the appearance of the image picker. There doesn't currently - // appear to be a good way to do so though... - AppModeManager.shared.setCurrentAppMode(to: .light) - PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in - DispatchQueue.main.async { - AppModeManager.shared.setCurrentAppMode(to: appMode) - } - Environment.shared.isRequestingPermission = false - if [ PHAuthorizationStatus.authorized, PHAuthorizationStatus.limited ].contains(status) { - onAuthorized() - } - } - } - } else { - authorizationStatus = PHPhotoLibrary.authorizationStatus() - if authorizationStatus == .notDetermined { - PHPhotoLibrary.requestAuthorization { status in - if status == .authorized { - onAuthorized() - } - } - } - } - switch authorizationStatus { - case .authorized, .limited: - onAuthorized() - case .denied, .restricted: - let modal = PermissionMissingModal(permission: "library") { } - modal.modalPresentationStyle = .overFullScreen - modal.modalTransitionStyle = .crossDissolve - present(modal, animated: true, completion: nil) - default: return - } - } - // MARK: Convenience func showErrorAlert(for attachment: SignalAttachment) { let title = NSLocalizedString("ATTACHMENT_ERROR_ALERT_TITLE", comment: "") diff --git a/Session/Utilities/Permissions.swift b/Session/Utilities/Permissions.swift new file mode 100644 index 000000000..7146580e3 --- /dev/null +++ b/Session/Utilities/Permissions.swift @@ -0,0 +1,90 @@ +import Photos +import PhotosUI + +public func requestCameraPermissionIfNeeded() -> Bool { + switch AVCaptureDevice.authorizationStatus(for: .video) { + case .authorized: return true + case .denied, .restricted: + let modal = PermissionMissingModal(permission: "camera") { } + modal.modalPresentationStyle = .overFullScreen + modal.modalTransitionStyle = .crossDissolve + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } + presentingVC.present(modal, animated: true, completion: nil) + return false + case .notDetermined: + AVCaptureDevice.requestAccess(for: .video, completionHandler: { _ in }) + return false + default: return false + } +} + +public func requestMicrophonePermissionIfNeeded(onNotGranted: @escaping () -> Void) { + switch AVAudioSession.sharedInstance().recordPermission { + case .granted: break + case .denied: + onNotGranted() + let modal = PermissionMissingModal(permission: "microphone") { + onNotGranted() + } + modal.modalPresentationStyle = .overFullScreen + modal.modalTransitionStyle = .crossDissolve + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } + presentingVC.present(modal, animated: true, completion: nil) + case .undetermined: + onNotGranted() + AVAudioSession.sharedInstance().requestRecordPermission { _ in } + default: break + } +} + +public func requestLibraryPermissionIfNeeded(onAuthorized: @escaping () -> Void) { + let authorizationStatus: PHAuthorizationStatus + if #available(iOS 14, *) { + authorizationStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite) + if authorizationStatus == .notDetermined { + // When the user chooses to select photos (which is the .limit status), + // the PHPhotoUI will present the picker view on the top of the front view. + // Since we have the ScreenLockUI showing when we request premissions, + // the picker view will be presented on the top of the ScreenLockUI. + // However, the ScreenLockUI will dismiss with the permission request alert view, so + // the picker view then will dismiss, too. The selection process cannot be finished + // this way. So we add a flag (isRequestingPermission) to prevent the ScreenLockUI + // from showing when we request the photo library permission. + Environment.shared.isRequestingPermission = true + let appMode = AppModeManager.shared.currentAppMode + // FIXME: Rather than setting the app mode to light and then to dark again once we're done, + // it'd be better to just customize the appearance of the image picker. There doesn't currently + // appear to be a good way to do so though... + AppModeManager.shared.setCurrentAppMode(to: .light) + PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in + DispatchQueue.main.async { + AppModeManager.shared.setCurrentAppMode(to: appMode) + } + Environment.shared.isRequestingPermission = false + if [ PHAuthorizationStatus.authorized, PHAuthorizationStatus.limited ].contains(status) { + onAuthorized() + } + } + } + } else { + authorizationStatus = PHPhotoLibrary.authorizationStatus() + if authorizationStatus == .notDetermined { + PHPhotoLibrary.requestAuthorization { status in + if status == .authorized { + onAuthorized() + } + } + } + } + switch authorizationStatus { + case .authorized, .limited: + onAuthorized() + case .denied, .restricted: + let modal = PermissionMissingModal(permission: "library") { } + modal.modalPresentationStyle = .overFullScreen + modal.modalTransitionStyle = .crossDissolve + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } + presentingVC.present(modal, animated: true, completion: nil) + default: return + } +} From 8947f47d779971a848ee64234c5dbe2f45ca85a3 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 18 Nov 2021 13:17:18 +1100 Subject: [PATCH 119/368] fix conversation title view position --- .../Conversations/Views & Modals/ConversationTitleView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Conversations/Views & Modals/ConversationTitleView.swift b/Session/Conversations/Views & Modals/ConversationTitleView.swift index 724737704..b34e31a1c 100644 --- a/Session/Conversations/Views & Modals/ConversationTitleView.swift +++ b/Session/Conversations/Views & Modals/ConversationTitleView.swift @@ -44,7 +44,7 @@ final class ConversationTitleView : UIView { stackView.axis = .vertical stackView.alignment = .center stackView.isLayoutMarginsRelativeArrangement = true - let shouldShowCallButton = !thread.isNoteToSelf() && (SSKPreferences.areCallsEnabled || !UserDefaults.standard[.hasSeenCallIPExposureWarning]) + let shouldShowCallButton = !thread.isNoteToSelf() && !thread.isGroupThread() && (SSKPreferences.areCallsEnabled || !UserDefaults.standard[.hasSeenCallIPExposureWarning]) let leftMargin: CGFloat = shouldShowCallButton ? 54 : 8 // Contact threads also have the call button to compensate for stackView.layoutMargins = UIEdgeInsets(top: 0, left: leftMargin, bottom: 0, right: 0) addSubview(stackView) From 81dd4557a56f91894aca63622ad6761825688e59 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 18 Nov 2021 13:19:47 +1100 Subject: [PATCH 120/368] sync answer and end call message --- .../Call Management/SessionCallManager.swift | 9 ++--- Session/Meta/AppDelegate.swift | 35 +++++++++++++------ .../Control Messages/CallMessage.swift | 1 + .../Sending & Receiving/MessageSender.swift | 23 ++++++++++-- 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index 0118933d9..235992f4f 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -105,10 +105,11 @@ public final class SessionCallManager: NSObject { guard let call = currentCall else { return } if let reason = reason { self.provider.reportCall(with: call.callID, endedAt: nil, reason: reason) - if reason == .unanswered { - call.updateCallMessage(mode: .unanswered) - } else { - call.updateCallMessage(mode: .remote) + switch (reason) { + case .unanswered: call.updateCallMessage(mode: .unanswered) + case .declinedElsewhere: call.updateCallMessage(mode: .local) + case .answeredElsewhere: break + default: call.updateCallMessage(mode: .remote) } } else { call.updateCallMessage(mode: .local) diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 588823759..7aef82e4f 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -17,7 +17,12 @@ extension AppDelegate { conversationVC.inputAccessoryView?.alpha = 0 } presentingVC.present(callVC, animated: true, completion: nil) - + } + + @objc func dismissAllCallUI() { + if let currentBanner = IncomingCallBanner.current { currentBanner.dismiss() } + if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleEndCallMessage() } + if let miniCallView = MiniCallView.current { miniCallView.dismiss() } } @objc func setUpCallHandling() { @@ -72,21 +77,29 @@ extension AppDelegate { MessageReceiver.handleAnswerCallMessage = { message in DispatchQueue.main.async { guard let call = AppEnvironment.shared.callManager.currentCall, message.uuid! == call.uuid else { return } - AppEnvironment.shared.callManager.invalidateTimeoutTimer() - call.hasStartedConnecting = true - let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) - call.didReceiveRemoteSDP(sdp: sdp) - guard let callVC = CurrentAppContext().frontmostViewController() as? CallVC else { return } - callVC.handleAnswerMessage(message) + if message.sender! == getUserHexEncodedPublicKey() { + self.dismissAllCallUI() + AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: .answeredElsewhere) + } else { + AppEnvironment.shared.callManager.invalidateTimeoutTimer() + call.hasStartedConnecting = true + let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0]) + call.didReceiveRemoteSDP(sdp: sdp) + guard let callVC = CurrentAppContext().frontmostViewController() as? CallVC else { return } + callVC.handleAnswerMessage(message) + } } } // End call messages MessageReceiver.handleEndCallMessage = { message in DispatchQueue.main.async { - if let currentBanner = IncomingCallBanner.current { currentBanner.dismiss() } - if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleEndCallMessage() } - if let miniCallView = MiniCallView.current { miniCallView.dismiss() } - AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: .remoteEnded) + guard let call = AppEnvironment.shared.callManager.currentCall, message.uuid! == call.uuid else { return } + self.dismissAllCallUI() + if message.sender! == getUserHexEncodedPublicKey() { + AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: .declinedElsewhere) + } else { + AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: .remoteEnded) + } } } } diff --git a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift index cd3609991..0a364c7e7 100644 --- a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift @@ -9,6 +9,7 @@ public final class CallMessage : ControlMessage { public var sdps: [String]? public override var ttl: UInt64 { 2 * 60 * 1000 } + public override var isSelfSendValid: Bool { true } // NOTE: Multiple ICE candidates may be batched together for performance diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 0206929b1..bbe0a1c56 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -135,8 +135,8 @@ public final class MessageSender : NSObject { // • a sync message // • a closed group control message of type `new` // • an unsend request - let isNewClosedGroupControlMessage = given(message as? ClosedGroupControlMessage) { if case .new = $0.kind { return true } else { return false } } ?? false - guard !isSelfSend || message is ConfigurationMessage || isSyncMessage || isNewClosedGroupControlMessage || message is UnsendRequest else { + // • a call message of type `answer` or `endCall` + guard !isSelfSend || isSyncMessage || shouldSyncMessage(message) else { storage.write(with: { transaction in MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction) seal.fulfill(()) @@ -379,4 +379,23 @@ public final class MessageSender : NSObject { tsMessage.update(sendingError: error, transaction: transaction) MessageInvalidator.invalidate(tsMessage, with: transaction) } + + // MARK: Utils + private static func shouldSyncMessage(_ message: Message) -> Bool { + let isNewClosedGroupControlMessage = given(message as? ClosedGroupControlMessage) { + if case .new = $0.kind { + return true + } else { + return false + } } ?? false + let isCallControlMessage = given(message as? CallMessage) { + if case .answer = $0.kind { + return true + } else if case .endCall = $0.kind { + return true + } else { + return false + } } ?? false + return isNewClosedGroupControlMessage || isCallControlMessage || message is ConfigurationMessage || message is UnsendRequest + } } From bf33030d9a1a33346d922a08a2db2ec239b20c13 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 18 Nov 2021 15:24:53 +1100 Subject: [PATCH 121/368] minor fix for handling calls on linked device --- Session/Meta/AppDelegate.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 7aef82e4f..68d30f129 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -78,6 +78,7 @@ extension AppDelegate { DispatchQueue.main.async { guard let call = AppEnvironment.shared.callManager.currentCall, message.uuid! == call.uuid else { return } if message.sender! == getUserHexEncodedPublicKey() { + guard !call.hasStartedConnecting else { return } self.dismissAllCallUI() AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: .answeredElsewhere) } else { From e9f19b9c620bc009fca3ea878645122e584d1cb7 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Fri, 19 Nov 2021 10:04:00 +1100 Subject: [PATCH 122/368] fix duplicated incoming call --- .../Call Management/SessionCallManager.swift | 2 +- Session/Meta/AppDelegate.swift | 46 ++++++++++--------- .../MessageReceiver+Handling.swift | 2 - 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index 235992f4f..823f23477 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -105,10 +105,10 @@ public final class SessionCallManager: NSObject { guard let call = currentCall else { return } if let reason = reason { self.provider.reportCall(with: call.callID, endedAt: nil, reason: reason) + if reason == .answeredElsewhere { return } switch (reason) { case .unanswered: call.updateCallMessage(mode: .unanswered) case .declinedElsewhere: call.updateCallMessage(mode: .local) - case .answeredElsewhere: break default: call.updateCallMessage(mode: .remote) } } else { diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 68d30f129..0d2bbdeb9 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -19,18 +19,40 @@ extension AppDelegate { presentingVC.present(callVC, animated: true, completion: nil) } - @objc func dismissAllCallUI() { + private func dismissAllCallUI() { if let currentBanner = IncomingCallBanner.current { currentBanner.dismiss() } if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleEndCallMessage() } if let miniCallView = MiniCallView.current { miniCallView.dismiss() } } + private func showCallUIForCall(_ call: SessionCall) { + DispatchQueue.main.async { + if CurrentAppContext().isMainAppAndActive { + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == call.sessionID { + let callVC = CallVC(for: call) + callVC.conversationVC = conversationVC + conversationVC.inputAccessoryView?.isHidden = true + conversationVC.inputAccessoryView?.alpha = 0 + presentingVC.present(callVC, animated: true, completion: nil) + } + } + call.reportIncomingCallIfNeeded{ error in + if let error = error { + SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") + let incomingCallBanner = IncomingCallBanner(for: call) + incomingCallBanner.show() + } + } + } + } + @objc func setUpCallHandling() { // Pre offer messages MessageReceiver.handleNewCallOfferMessageIfNeeded = { (message, transaction) in guard CurrentAppContext().isMainApp else { return } let callManager = AppEnvironment.shared.callManager - // Ignore pre offer message afte the same call instance has been generated + // Ignore pre offer message after the same call instance has been generated if let currentCall = callManager.currentCall, currentCall.uuid == message.uuid! { return } guard callManager.currentCall == nil && SSKPreferences.areCallsEnabled else { callManager.handleIncomingCallOfferInBusyOrUnenabledState(offerMessage: message, using: transaction) @@ -44,25 +66,7 @@ extension AppDelegate { if let caller = message.sender, let uuid = message.uuid { let call = SessionCall(for: caller, uuid: uuid, mode: .answer) call.callMessageTimestamp = message.sentTimestamp - DispatchQueue.main.async { - if CurrentAppContext().isMainAppAndActive { - guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully - if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller { - let callVC = CallVC(for: call) - callVC.conversationVC = conversationVC - conversationVC.inputAccessoryView?.isHidden = true - conversationVC.inputAccessoryView?.alpha = 0 - presentingVC.present(callVC, animated: true, completion: nil) - } - } - call.reportIncomingCallIfNeeded{ error in - if let error = error { - SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") - let incomingCallBanner = IncomingCallBanner(for: call) - incomingCallBanner.show() - } - } - } + self.showCallUIForCall(call) } } // Offer messages diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 5c2e84feb..ade9f21b2 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -271,8 +271,6 @@ extension MessageReceiver { handleNewCallOfferMessageIfNeeded?(message, transaction) case .offer: print("[Calls] Received offer message.") - let transaction = transaction as! YapDatabaseReadWriteTransaction - handleNewCallOfferMessageIfNeeded?(message, transaction) handleOfferCallMessage?(message) case .answer: print("[Calls] Received answer message.") From 74ef42558b2038655273f1e002a24272e2844efc Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Fri, 19 Nov 2021 10:39:10 +1100 Subject: [PATCH 123/368] add turn server info --- Session.xcodeproj/project.pbxproj | 4 +++ .../Calls/TurnServerInfo.swift | 30 +++++++++++++++++++ SessionMessagingKit/Calls/WebRTCSession.swift | 10 +++++++ 3 files changed, 44 insertions(+) create mode 100644 SessionMessagingKit/Calls/TurnServerInfo.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 70bc73c20..92db4ef14 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -157,6 +157,7 @@ 7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */; }; 7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; 7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A892745C4F000FB91B9 /* Permissions.swift */; }; + 7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A8B2747150E00FB91B9 /* TurnServerInfo.swift */; }; 945AA2B82B621254F69FA9E8 /* Pods_SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9117261809D69B3D7C26B8F1 /* Pods_SessionUtilitiesKit.framework */; }; 9EE44C6B4D4A069B86112387 /* Pods_SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9559C3068280BA2383F547F7 /* Pods_SessionSnodeKit.framework */; }; A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; }; @@ -1146,6 +1147,7 @@ 7BDCFC0424206E7300641C39 /* SessionNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SessionNotificationServiceExtension.entitlements; sourceTree = ""; }; 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtensionContext.swift; sourceTree = ""; }; 7BFD1A892745C4F000FB91B9 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = ""; }; + 7BFD1A8B2747150E00FB91B9 /* TurnServerInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TurnServerInfo.swift; sourceTree = ""; }; 7DD180F770F8518B4E8796F2 /* Pods-SessionUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUtilitiesKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionUtilitiesKit/Pods-SessionUtilitiesKit.app store release.xcconfig"; sourceTree = ""; }; 8981C8F64D94D3C52EB67A2C /* Pods-SignalTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.test.xcconfig"; sourceTree = ""; }; 8EEE74B0753448C085B48721 /* Pods-SignalMessaging.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.app store release.xcconfig"; sourceTree = ""; }; @@ -2423,6 +2425,7 @@ B8B558FE26C4E05E00693325 /* WebRTCSession+MessageHandling.swift */, B8BF43B926CC95FB007828D1 /* WebRTC+Utilities.swift */, 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */, + 7BFD1A8B2747150E00FB91B9 /* TurnServerInfo.swift */, ); path = Calls; sourceTree = ""; @@ -4703,6 +4706,7 @@ C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */, C32C5AAD256DBE8F003C73A2 /* TSInfoMessage.m in Sources */, C32C5A13256DB7A5003C73A2 /* PushNotificationAPI.swift in Sources */, + 7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */, C32A026325A801AA000ED5D4 /* NSData+messagePadding.m in Sources */, C352A3932557883D00338F3E /* JobDelegate.swift in Sources */, C32C5B84256DC54F003C73A2 /* SSKEnvironment.m in Sources */, diff --git a/SessionMessagingKit/Calls/TurnServerInfo.swift b/SessionMessagingKit/Calls/TurnServerInfo.swift new file mode 100644 index 000000000..9b1a47032 --- /dev/null +++ b/SessionMessagingKit/Calls/TurnServerInfo.swift @@ -0,0 +1,30 @@ +// Copyright © 2021 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +struct TurnServerInfo { + + let password: String + let username: String + let urls: [String] + + init?(attributes: JSON) { + if let passwordAttribute = (attributes["password"] as? String) { + password = passwordAttribute + } else { + return nil + } + + if let usernameAttribute = attributes["username"] as? String { + username = usernameAttribute + } else { + return nil + } + + if let urlsAttribute = attributes["urls"] as? [String] { + urls = urlsAttribute + } else { + return nil + } + } +} diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 2e5c88ec2..65a6246eb 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -18,6 +18,13 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { private var queuedICECandidates: [RTCIceCandidate] = [] private var iceCandidateSendTimer: Timer? + private lazy var defaultICEServer: TurnServerInfo? = { + let url = Bundle.main.url(forResource: "Session-Turn-Server", withExtension: nil)! + let data = try! Data(contentsOf: url) + let json = try! JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as! JSON + return TurnServerInfo(attributes: json) + }() + internal lazy var factory: RTCPeerConnectionFactory = { RTCInitializeSSL() let videoEncoderFactory = RTCDefaultVideoEncoderFactory() @@ -30,6 +37,9 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { internal lazy var peerConnection: RTCPeerConnection = { let configuration = RTCConfiguration() configuration.iceServers = [ RTCIceServer(urlStrings: ["stun:freyr.getsession.org:5349"]), RTCIceServer(urlStrings: ["turn:freyr.getsession.org"], username: "session", credential: "session") ] + if let defaultICEServer = defaultICEServer { + configuration.iceServers.append(RTCIceServer(urlStrings: defaultICEServer.urls, username: defaultICEServer.username, credential: defaultICEServer.password)) + } configuration.sdpSemantics = .unifiedPlan let constraints = RTCMediaConstraints(mandatoryConstraints: [:], optionalConstraints: [:]) return factory.peerConnection(with: configuration, constraints: constraints, delegate: self) From 77e1f30bc93a6cf0888e29c719093417595e2abb Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Fri, 19 Nov 2021 11:11:48 +1100 Subject: [PATCH 124/368] fix sync message sending bug --- .../Sending & Receiving/MessageSender.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index bbe0a1c56..cdfb80415 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -132,13 +132,13 @@ public final class MessageSender : NSObject { guard message.isValid else { handleFailure(with: Error.invalidMessage, using: transaction); return promise } // Stop here if this is a self-send, unless it's: // • a configuration message - // • a sync message + // • a sync message (visible message or expiration timer update message) // • a closed group control message of type `new` // • an unsend request // • a call message of type `answer` or `endCall` - guard !isSelfSend || isSyncMessage || shouldSyncMessage(message) else { + guard !isSelfSend || shouldSyncMessage(message) else { storage.write(with: { transaction in - MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction) + MessageSender.handleSuccessfulMessageSend(message, to: destination, isSyncMessage: isSyncMessage, using: transaction) seal.fulfill(()) }, completion: { }) return promise @@ -396,6 +396,6 @@ public final class MessageSender : NSObject { } else { return false } } ?? false - return isNewClosedGroupControlMessage || isCallControlMessage || message is ConfigurationMessage || message is UnsendRequest + return isNewClosedGroupControlMessage || isCallControlMessage || message is ConfigurationMessage || message is UnsendRequest || message is VisibleMessage || message is ExpirationTimerUpdate } } From 0c2027d7c89580e42a8d665f91185301645d6dd5 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Fri, 19 Nov 2021 11:22:13 +1100 Subject: [PATCH 125/368] fix call message self send valid --- .../Messages/Control Messages/CallMessage.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift index 0a364c7e7..b9b5c2455 100644 --- a/SessionMessagingKit/Messages/Control Messages/CallMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/CallMessage.swift @@ -9,7 +9,11 @@ public final class CallMessage : ControlMessage { public var sdps: [String]? public override var ttl: UInt64 { 2 * 60 * 1000 } - public override var isSelfSendValid: Bool { true } + public override var isSelfSendValid: Bool { + if case .answer = kind { return true } + if case .endCall = kind { return true } + return false + } // NOTE: Multiple ICE candidates may be batched together for performance From e504e23f926e319418d253bec34bb95e0840ea32 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Fri, 19 Nov 2021 11:50:14 +1100 Subject: [PATCH 126/368] minor fix --- Session/Calls/Call Management/SessionCall.swift | 4 ++++ Session/Calls/Call Management/SessionCallManager.swift | 2 +- .../Sending & Receiving/MessageSender.swift | 8 ++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 5e750c724..63c94149f 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -69,6 +69,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { case local case remote case unanswered + case answeredElsewhere } // MARK: Audio I/O mode @@ -245,6 +246,9 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { newMessageBody = self.isOutgoing ? NSLocalizedString("call_rejected", comment: "") : NSLocalizedString("call_missing", comment: "") case .unanswered: newMessageBody = NSLocalizedString("call_timeout", comment: "") + case .answeredElsewhere: + newMessageBody = messageToUpdate.body! + shouldMarkAsRead = true } } messageToUpdate.updateCall(withNewBody: newMessageBody, transaction: transaction) diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index 823f23477..990833cfb 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -105,8 +105,8 @@ public final class SessionCallManager: NSObject { guard let call = currentCall else { return } if let reason = reason { self.provider.reportCall(with: call.callID, endedAt: nil, reason: reason) - if reason == .answeredElsewhere { return } switch (reason) { + case .answeredElsewhere: call.updateCallMessage(mode: .answeredElsewhere) case .unanswered: call.updateCallMessage(mode: .unanswered) case .declinedElsewhere: call.updateCallMessage(mode: .local) default: call.updateCallMessage(mode: .remote) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index cdfb80415..bbe0a1c56 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -132,13 +132,13 @@ public final class MessageSender : NSObject { guard message.isValid else { handleFailure(with: Error.invalidMessage, using: transaction); return promise } // Stop here if this is a self-send, unless it's: // • a configuration message - // • a sync message (visible message or expiration timer update message) + // • a sync message // • a closed group control message of type `new` // • an unsend request // • a call message of type `answer` or `endCall` - guard !isSelfSend || shouldSyncMessage(message) else { + guard !isSelfSend || isSyncMessage || shouldSyncMessage(message) else { storage.write(with: { transaction in - MessageSender.handleSuccessfulMessageSend(message, to: destination, isSyncMessage: isSyncMessage, using: transaction) + MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction) seal.fulfill(()) }, completion: { }) return promise @@ -396,6 +396,6 @@ public final class MessageSender : NSObject { } else { return false } } ?? false - return isNewClosedGroupControlMessage || isCallControlMessage || message is ConfigurationMessage || message is UnsendRequest || message is VisibleMessage || message is ExpirationTimerUpdate + return isNewClosedGroupControlMessage || isCallControlMessage || message is ConfigurationMessage || message is UnsendRequest } } From af16416898116a70977d280e6e2b71a61869b5fb Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Fri, 19 Nov 2021 16:14:46 +1100 Subject: [PATCH 127/368] update .gitignore --- .gitignore | 1 + Session.xcodeproj/project.pbxproj | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/.gitignore b/.gitignore index 21c390b4b..36979ca27 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ DerivedData *.ipa *.xcuserstate Index/ +Session-Turn-Server # CocoaPods Pods diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 92db4ef14..7ae20282a 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -158,6 +158,7 @@ 7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; 7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A892745C4F000FB91B9 /* Permissions.swift */; }; 7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A8B2747150E00FB91B9 /* TurnServerInfo.swift */; }; + 7BFD1A972747689000FB91B9 /* Session-Turn-Server in Resources */ = {isa = PBXBuildFile; fileRef = 7BFD1A962747689000FB91B9 /* Session-Turn-Server */; }; 945AA2B82B621254F69FA9E8 /* Pods_SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9117261809D69B3D7C26B8F1 /* Pods_SessionUtilitiesKit.framework */; }; 9EE44C6B4D4A069B86112387 /* Pods_SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9559C3068280BA2383F547F7 /* Pods_SessionSnodeKit.framework */; }; A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; }; @@ -1148,6 +1149,7 @@ 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtensionContext.swift; sourceTree = ""; }; 7BFD1A892745C4F000FB91B9 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = ""; }; 7BFD1A8B2747150E00FB91B9 /* TurnServerInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TurnServerInfo.swift; sourceTree = ""; }; + 7BFD1A962747689000FB91B9 /* Session-Turn-Server */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Session-Turn-Server"; sourceTree = ""; }; 7DD180F770F8518B4E8796F2 /* Pods-SessionUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUtilitiesKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionUtilitiesKit/Pods-SessionUtilitiesKit.app store release.xcconfig"; sourceTree = ""; }; 8981C8F64D94D3C52EB67A2C /* Pods-SignalTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.test.xcconfig"; sourceTree = ""; }; 8EEE74B0753448C085B48721 /* Pods-SignalMessaging.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.app store release.xcconfig"; sourceTree = ""; }; @@ -2098,6 +2100,14 @@ path = SessionNotificationServiceExtension; sourceTree = ""; }; + 7BFD1A952747689000FB91B9 /* TurnServers */ = { + isa = PBXGroup; + children = ( + 7BFD1A962747689000FB91B9 /* Session-Turn-Server */, + ); + path = TurnServers; + sourceTree = ""; + }; 9404664EC513585B05DF1350 /* Pods */ = { isa = PBXGroup; children = ( @@ -3502,6 +3512,7 @@ 76EB03C318170B33006006FC /* AppDelegate.m */, C3AAFFF125AE99710089E6DD /* AppDelegate.swift */, 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */, + 7BFD1A952747689000FB91B9 /* TurnServers */, B81D260326158DF5004D1FE1 /* Certificates */, B8FF8E6025C10D8B004D1F22 /* Countries */, 34330A581E7875FB00DF2FB9 /* Fonts */, @@ -4187,6 +4198,7 @@ C3A01E05261D24C400290BEB /* public-loki-foundation.der in Resources */, B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */, 34CF0788203E6B78005C4D61 /* ringback_tone_ansi.caf in Resources */, + 7BFD1A972747689000FB91B9 /* Session-Turn-Server in Resources */, 34C3C78F2040A4F70000134C /* sonarping.mp3 in Resources */, 34661FB820C1C0D60056EDD6 /* message_sent.aiff in Resources */, 45CB2FA81CB7146C00E1B343 /* Launch Screen.storyboard in Resources */, From 75658e1720b015729c5759f838dbd99bb438cb0e Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 22 Nov 2021 09:34:51 +1100 Subject: [PATCH 128/368] update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 7ae20282a..430aaf359 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5183,7 +5183,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 309; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5256,7 +5256,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 309; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5322,7 +5322,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 309; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5396,7 +5396,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 309; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6332,7 +6332,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 309; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6404,7 +6404,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 308; + CURRENT_PROJECT_VERSION = 309; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", From 78971b7d09d9592254da27cee650dd7e220a9281 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 25 Nov 2021 16:50:30 +1100 Subject: [PATCH 129/368] ignore call messages over 60s --- Session.xcodeproj/project.pbxproj | 4 ++++ .../Sending & Receiving/MessageReceiver+Handling.swift | 1 + SessionUtilitiesKit/General/TimestampUtils.swift | 8 ++++++++ 3 files changed, 13 insertions(+) create mode 100644 SessionUtilitiesKit/General/TimestampUtils.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 430aaf359..cd2c46514 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -136,6 +136,7 @@ 768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 768A1A2A17FC9CD300E00ED8 /* libz.dylib */; }; 76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; }; 76EB054018170B33006006FC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C318170B33006006FC /* AppDelegate.m */; }; + 7B0EFDEE274F598600FFAAE7 /* TimestampUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */; }; 7B1581E2271E743B00848B49 /* OWSSounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E1271E743B00848B49 /* OWSSounds.swift */; }; 7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E3271FC59C00848B49 /* CallModal.swift */; }; 7B1581E6271FD2A100848B49 /* VideoPreviewVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */; }; @@ -1125,6 +1126,7 @@ 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; 76EB03C218170B33006006FC /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 76EB03C318170B33006006FC /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampUtils.swift; sourceTree = ""; }; 7B1581E1271E743B00848B49 /* OWSSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSSounds.swift; sourceTree = ""; }; 7B1581E3271FC59C00848B49 /* CallModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModal.swift; sourceTree = ""; }; 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPreviewVC.swift; sourceTree = ""; }; @@ -2367,6 +2369,7 @@ C38EF23E255B6D66007E1867 /* UIView+OWS.m */, C38EF2EF255B6DBB007E1867 /* Weak.swift */, 7B7CB191271508AD0079FF93 /* Vibration.swift */, + 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */, ); path = General; sourceTree = ""; @@ -4677,6 +4680,7 @@ B8AE75A425A6C6A6001A84D2 /* Data+Trimming.swift in Sources */, B8856DE6256F15F2001CE70E /* String+SSK.swift in Sources */, C3471ED42555386B00297E91 /* AESGCM.swift in Sources */, + 7B0EFDEE274F598600FFAAE7 /* TimestampUtils.swift in Sources */, C32C5DDB256DD9FF003C73A2 /* ContentProxy.swift in Sources */, C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */, B8F5F58325EC94A6003BF8D4 /* Collection+Subscripting.swift in Sources */, diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 8796742bc..8c4ecba30 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -268,6 +268,7 @@ extension MessageReceiver { // MARK: - Call Messages public static func handleCallMessage(_ message: CallMessage, using transaction: Any) { + guard let timestamp = message.sentTimestamp, TimestampUtils.isWithinOneMinute(timestamp: timestamp) else { return } switch message.kind! { case .preOffer: print("[Calls] Received pre-offer message.") diff --git a/SessionUtilitiesKit/General/TimestampUtils.swift b/SessionUtilitiesKit/General/TimestampUtils.swift new file mode 100644 index 000000000..3497bdbad --- /dev/null +++ b/SessionUtilitiesKit/General/TimestampUtils.swift @@ -0,0 +1,8 @@ + +public final class TimestampUtils { + + public static func isWithinOneMinute(timestamp: UInt64) -> Bool { + Date().timeIntervalSince(NSDate.ows_date(withMillisecondsSince1970: timestamp)) <= 60 + } + +} From 0a3d84d5c8ea56bc8bedb5f012f28674d98c4c24 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Fri, 26 Nov 2021 16:57:57 +1100 Subject: [PATCH 130/368] WIP: Call message Ui improvements --- Session.xcodeproj/project.pbxproj | 4 + .../Call Management/SessionCallManager.swift | 2 +- .../Message Cells/CallMessageCell.swift | 70 ++++++++++++++++++ .../Message Cells/MessageCell.swift | 6 +- .../Views & Modals/MessagesTableView.swift | 1 + Session/Meta/AppDelegate.swift | 8 +- .../CallIncoming.imageset/CallIncoming.pdf | Bin 0 -> 5103 bytes .../CallIncoming.imageset/Contents.json | 12 +++ .../CallOutgoing.imageset/CallOutgoing.pdf | Bin 0 -> 5106 bytes .../CallOutgoing.imageset/Contents.json | 12 +++ Session/Shared/ConversationCell.swift | 2 +- .../MessageReceiver+Handling.swift | 4 +- SessionMessagingKit/Threads/TSThread.h | 7 ++ SessionMessagingKit/Threads/TSThread.m | 12 +++ SessionUIKit/Style Guide/Colors.swift | 1 + .../Contents.json | 38 ++++++++++ 16 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 Session/Conversations/Message Cells/CallMessageCell.swift create mode 100644 Session/Meta/Images.xcassets/Session/CallIncoming.imageset/CallIncoming.pdf create mode 100644 Session/Meta/Images.xcassets/Session/CallIncoming.imageset/Contents.json create mode 100644 Session/Meta/Images.xcassets/Session/CallOutgoing.imageset/CallOutgoing.pdf create mode 100644 Session/Meta/Images.xcassets/Session/CallOutgoing.imageset/Contents.json create mode 100644 SessionUIKit/Style Guide/Colors.xcassets/session_call_message_background.colorset/Contents.json diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index cd2c46514..97c79506e 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -137,6 +137,7 @@ 76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; }; 76EB054018170B33006006FC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C318170B33006006FC /* AppDelegate.m */; }; 7B0EFDEE274F598600FFAAE7 /* TimestampUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */; }; + 7B0EFDF0275084AA00FFAAE7 /* CallMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */; }; 7B1581E2271E743B00848B49 /* OWSSounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E1271E743B00848B49 /* OWSSounds.swift */; }; 7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E3271FC59C00848B49 /* CallModal.swift */; }; 7B1581E6271FD2A100848B49 /* VideoPreviewVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */; }; @@ -1127,6 +1128,7 @@ 76EB03C218170B33006006FC /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 76EB03C318170B33006006FC /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampUtils.swift; sourceTree = ""; }; + 7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageCell.swift; sourceTree = ""; }; 7B1581E1271E743B00848B49 /* OWSSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSSounds.swift; sourceTree = ""; }; 7B1581E3271FC59C00848B49 /* CallModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModal.swift; sourceTree = ""; }; 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPreviewVC.swift; sourceTree = ""; }; @@ -2245,6 +2247,7 @@ B835247825C38D880089A44F /* MessageCell.swift */, B835249A25C3AB650089A44F /* VisibleMessageCell.swift */, B83524A425C3BA4B0089A44F /* InfoMessageCell.swift */, + 7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */, B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */, B8041A7325C8F758003C2166 /* Content Views */, ); @@ -5003,6 +5006,7 @@ B8269D3D25C7B34D00488AB4 /* InputTextView.swift in Sources */, 76EB054018170B33006006FC /* AppDelegate.m in Sources */, 340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */, + 7B0EFDF0275084AA00FFAAE7 /* CallMessageCell.swift in Sources */, C33100082558FF6D00070591 /* NewConversationButtonSet.swift in Sources */, C3AAFFF225AE99710089E6DD /* AppDelegate.swift in Sources */, B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */, diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index 990833cfb..ed3ccc1be 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -133,7 +133,7 @@ public final class SessionCallManager: NSObject { callUpdate.supportsDTMF = false } - public func handleIncomingCallOfferInBusyOrUnenabledState(offerMessage: CallMessage, using transaction: YapDatabaseReadWriteTransaction) { + public func handleIncomingCallOfferInBusyState(offerMessage: CallMessage, using transaction: YapDatabaseReadWriteTransaction) { guard let caller = offerMessage.sender, let thread = TSContactThread.fetch(for: caller, using: transaction) else { return } let message = CallMessage() message.uuid = offerMessage.uuid diff --git a/Session/Conversations/Message Cells/CallMessageCell.swift b/Session/Conversations/Message Cells/CallMessageCell.swift new file mode 100644 index 000000000..1715210a7 --- /dev/null +++ b/Session/Conversations/Message Cells/CallMessageCell.swift @@ -0,0 +1,70 @@ +import UIKit + +final class CallMessageCell : MessageCell { + private lazy var iconImageViewWidthConstraint = iconImageView.set(.width, to: CallMessageCell.iconSize) + private lazy var iconImageViewHeightConstraint = iconImageView.set(.height, to: CallMessageCell.iconSize) + + // MARK: UI Components + private lazy var iconImageView = UIImageView() + + private lazy var label: UILabel = { + let result = UILabel() + result.numberOfLines = 0 + result.lineBreakMode = .byWordWrapping + result.font = .boldSystemFont(ofSize: Values.smallFontSize) + result.textColor = Colors.text + result.textAlignment = .center + return result + }() + + private lazy var container: UIView = { + let result = UIView() + result.set(.height, to: 50) + result.layer.cornerRadius = 18 + result.backgroundColor = Colors.callMessageBackground + result.addSubview(label) + label.autoCenterInSuperview() + result.addSubview(iconImageView) + iconImageView.autoVCenterInSuperview() + iconImageView.pin(.left, to: .left, of: result, withInset: CallMessageCell.inset) + return result + }() + + // MARK: Settings + private static let iconSize: CGFloat = 16 + private static let inset = Values.mediumSpacing + private static let margin = UIScreen.main.bounds.width * 0.1 + + override class var identifier: String { "CallMessageCell" } + + // MARK: Lifecycle + override func setUpViewHierarchy() { + super.setUpViewHierarchy() + iconImageViewWidthConstraint.isActive = true + iconImageViewHeightConstraint.isActive = true + addSubview(container) + container.pin(.left, to: .left, of: self, withInset: CallMessageCell.margin) + container.pin(.top, to: .top, of: self, withInset: CallMessageCell.inset) + container.pin(.right, to: .right, of: self, withInset: -CallMessageCell.margin) + container.pin(.bottom, to: .bottom, of: self, withInset: -CallMessageCell.inset) + } + + // MARK: Updating + override func update() { + guard let message = viewItem?.interaction as? TSMessage, message.isCallMessage else { return } + let icon: UIImage? + switch message.interactionType() { + case .outgoingMessage: icon = UIImage(named: "CallOutgoing") + case .incomingMessage: icon = UIImage(named: "CallIncoming") + default: icon = nil + } + if let icon = icon { + iconImageView.image = icon.withTint(Colors.text) + } + iconImageViewWidthConstraint.constant = (icon != nil) ? CallMessageCell.iconSize : 0 + iconImageViewHeightConstraint.constant = (icon != nil) ? CallMessageCell.iconSize : 0 + Storage.read { transaction in + self.label.text = message.previewText(with: transaction) + } + } +} diff --git a/Session/Conversations/Message Cells/MessageCell.swift b/Session/Conversations/Message Cells/MessageCell.swift index a2d846692..56d93b2b2 100644 --- a/Session/Conversations/Message Cells/MessageCell.swift +++ b/Session/Conversations/Message Cells/MessageCell.swift @@ -46,7 +46,11 @@ class MessageCell : UITableViewCell { static func getCellType(for viewItem: ConversationViewItem) -> MessageCell.Type { switch viewItem.interaction { case is TSIncomingMessage: fallthrough - case is TSOutgoingMessage: return VisibleMessageCell.self + case is TSOutgoingMessage: + if let message = viewItem.interaction as? TSMessage, message.isCallMessage { + return CallMessageCell.self + } + return VisibleMessageCell.self case is TSInfoMessage: return InfoMessageCell.self case is TypingIndicatorInteraction: return TypingIndicatorCell.self default: preconditionFailure() diff --git a/Session/Conversations/Views & Modals/MessagesTableView.swift b/Session/Conversations/Views & Modals/MessagesTableView.swift index d5648f2bb..0f19cff94 100644 --- a/Session/Conversations/Views & Modals/MessagesTableView.swift +++ b/Session/Conversations/Views & Modals/MessagesTableView.swift @@ -31,6 +31,7 @@ final class MessagesTableView : UITableView { register(VisibleMessageCell.self, forCellReuseIdentifier: VisibleMessageCell.identifier) register(InfoMessageCell.self, forCellReuseIdentifier: InfoMessageCell.identifier) register(TypingIndicatorCell.self, forCellReuseIdentifier: TypingIndicatorCell.identifier) + register(CallMessageCell.self, forCellReuseIdentifier: CallMessageCell.identifier) separatorStyle = .none backgroundColor = .clear showsVerticalScrollIndicator = false diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 0d2bbdeb9..7ca15247e 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -51,11 +51,15 @@ extension AppDelegate { // Pre offer messages MessageReceiver.handleNewCallOfferMessageIfNeeded = { (message, transaction) in guard CurrentAppContext().isMainApp else { return } + guard SSKPreferences.areCallsEnabled else { + // TODO: Show tips and insert a missing call message + return + } let callManager = AppEnvironment.shared.callManager // Ignore pre offer message after the same call instance has been generated if let currentCall = callManager.currentCall, currentCall.uuid == message.uuid! { return } - guard callManager.currentCall == nil && SSKPreferences.areCallsEnabled else { - callManager.handleIncomingCallOfferInBusyOrUnenabledState(offerMessage: message, using: transaction) + guard callManager.currentCall == nil else { + callManager.handleIncomingCallOfferInBusyState(offerMessage: message, using: transaction) return } // Create incoming call message diff --git a/Session/Meta/Images.xcassets/Session/CallIncoming.imageset/CallIncoming.pdf b/Session/Meta/Images.xcassets/Session/CallIncoming.imageset/CallIncoming.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fbc5a4a875971060d79f642f11a200ff3d3d7e9d GIT binary patch literal 5103 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 N$x`s~sq1P`{0~2=8sY!| literal 0 HcmV?d00001 diff --git a/Session/Meta/Images.xcassets/Session/CallIncoming.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/CallIncoming.imageset/Contents.json new file mode 100644 index 000000000..083076594 --- /dev/null +++ b/Session/Meta/Images.xcassets/Session/CallIncoming.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "CallIncoming.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Session/Meta/Images.xcassets/Session/CallOutgoing.imageset/CallOutgoing.pdf b/Session/Meta/Images.xcassets/Session/CallOutgoing.imageset/CallOutgoing.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f6550ced9dff0058648e4f21d80f9ad7818beac8 GIT binary patch literal 5106 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 QJvb5x19NjLXefgJ2O=Q_G5`Po literal 0 HcmV?d00001 diff --git a/Session/Meta/Images.xcassets/Session/CallOutgoing.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/CallOutgoing.imageset/Contents.json new file mode 100644 index 000000000..d6bb5d534 --- /dev/null +++ b/Session/Meta/Images.xcassets/Session/CallOutgoing.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "CallOutgoing.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Session/Shared/ConversationCell.swift b/Session/Shared/ConversationCell.swift index 2eaf22d3a..d0e98b494 100644 --- a/Session/Shared/ConversationCell.swift +++ b/Session/Shared/ConversationCell.swift @@ -228,7 +228,7 @@ final class ConversationCell : UITableViewCell { } statusIndicatorView.backgroundColor = nil let lastMessage = threadViewModel.lastMessageForInbox - if let lastMessage = lastMessage as? TSOutgoingMessage { + if let lastMessage = lastMessage as? TSOutgoingMessage, !lastMessage.isCallMessage { let image: UIImage let status = MessageRecipientStatusUtils.recipientStatus(outgoingMessage: lastMessage) switch status { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 8c4ecba30..184111889 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -269,10 +269,12 @@ extension MessageReceiver { public static func handleCallMessage(_ message: CallMessage, using transaction: Any) { guard let timestamp = message.sentTimestamp, TimestampUtils.isWithinOneMinute(timestamp: timestamp) else { return } + let transaction = transaction as! YapDatabaseReadWriteTransaction + // Ignore call messages from threads without outgoing messages + guard let sender = message.sender, let thread = TSContactThread.fetch(for: sender, using: transaction), thread.hasOutgoingInteraction(with: transaction) else { return } switch message.kind! { case .preOffer: print("[Calls] Received pre-offer message.") - let transaction = transaction as! YapDatabaseReadWriteTransaction handleNewCallOfferMessageIfNeeded?(message, transaction) case .offer: print("[Calls] Received offer message.") diff --git a/SessionMessagingKit/Threads/TSThread.h b/SessionMessagingKit/Threads/TSThread.h index c2449397a..71818c3d0 100644 --- a/SessionMessagingKit/Threads/TSThread.h +++ b/SessionMessagingKit/Threads/TSThread.h @@ -38,6 +38,13 @@ BOOL IsNoteToSelfEnabled(void); */ - (NSString *)name; +/** + * Returns if there is any outgoing interations in this thread. + * + * @return YES if there are outgoing interations, NO otherwise. + */ +- (BOOL)hasOutgoingInteractionWithTransaction:(YapDatabaseReadTransaction *)transaction; + /** * @returns recipientId for each recipient in the thread */ diff --git a/SessionMessagingKit/Threads/TSThread.m b/SessionMessagingKit/Threads/TSThread.m index 6283a324d..bf4bad523 100644 --- a/SessionMessagingKit/Threads/TSThread.m +++ b/SessionMessagingKit/Threads/TSThread.m @@ -153,6 +153,18 @@ BOOL IsNoteToSelfEnabled(void) return @[]; } +- (BOOL)hasOutgoingInteractionWithTransaction:(YapDatabaseReadTransaction *)transaction +{ + __block BOOL hasOutgoingInteraction = NO; + [self enumerateInteractionsWithTransaction:transaction usingBlock:^(TSInteraction *interaction, BOOL *stop) { + if ([interaction interactionType] == OWSInteractionType_OutgoingMessage) { + hasOutgoingInteraction = YES; + *stop = YES; + } + }]; + return hasOutgoingInteraction; +} + #pragma mark Interactions /** diff --git a/SessionUIKit/Style Guide/Colors.swift b/SessionUIKit/Style Guide/Colors.swift index 06f4e3095..e6e092829 100644 --- a/SessionUIKit/Style Guide/Colors.swift +++ b/SessionUIKit/Style Guide/Colors.swift @@ -41,4 +41,5 @@ public final class Colors : NSObject { @objc public static var pnOptionBackground: UIColor { UIColor(named: "session_pn_option_background")! } @objc public static var pnOptionBorder: UIColor { UIColor(named: "session_pn_option_border")! } @objc public static var pathsBuilding: UIColor { UIColor(named: "session_paths_building")! } + @objc public static var callMessageBackground: UIColor { UIColor(named: "session_call_message_background")! } } diff --git a/SessionUIKit/Style Guide/Colors.xcassets/session_call_message_background.colorset/Contents.json b/SessionUIKit/Style Guide/Colors.xcassets/session_call_message_background.colorset/Contents.json new file mode 100644 index 000000000..843f0f6c1 --- /dev/null +++ b/SessionUIKit/Style Guide/Colors.xcassets/session_call_message_background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF5", + "green" : "0xF5", + "red" : "0xF5" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x21", + "green" : "0x21", + "red" : "0x21" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} From 9e6c81d28ba8388404268dca22f8201dda8901c7 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 29 Nov 2021 12:10:33 +1100 Subject: [PATCH 131/368] improve call message UI --- Session.xcodeproj/project.pbxproj | 4 ++ .../Calls/Call Management/SessionCall.swift | 45 +++++++----------- .../Call Management/SessionCallManager.swift | 6 +-- .../Message Cells/CallMessageCell.swift | 13 +++-- .../Message Cells/MessageCell.swift | 8 ++-- Session/Meta/AppDelegate.swift | 7 +-- .../CallIncoming.imageset/CallIncoming.pdf | Bin 5103 -> 5425 bytes .../CallMissed.imageset/CallMissed.pdf | Bin 0 -> 5337 bytes .../Session/CallMissed.imageset/Contents.json | 12 +++++ .../CallOutgoing.imageset/CallOutgoing.pdf | Bin 5106 -> 5418 bytes .../Translations/en.lproj/Localizable.strings | 6 +-- .../PushRegistrationManager.swift | 7 ++- SessionMessagingKit/Calls/WebRTCSession.swift | 12 +++-- .../Messages/Signal/TSInfoMessage+Calls.swift | 40 ++++++++++++++++ .../Messages/Signal/TSInfoMessage.h | 13 ++++- .../Messages/Signal/TSInfoMessage.m | 1 + 16 files changed, 117 insertions(+), 57 deletions(-) create mode 100644 Session/Meta/Images.xcassets/Session/CallMissed.imageset/CallMissed.pdf create mode 100644 Session/Meta/Images.xcassets/Session/CallMissed.imageset/Contents.json create mode 100644 SessionMessagingKit/Messages/Signal/TSInfoMessage+Calls.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 97c79506e..569aadfef 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -138,6 +138,7 @@ 76EB054018170B33006006FC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C318170B33006006FC /* AppDelegate.m */; }; 7B0EFDEE274F598600FFAAE7 /* TimestampUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */; }; 7B0EFDF0275084AA00FFAAE7 /* CallMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */; }; + 7B0EFDF2275449AA00FFAAE7 /* TSInfoMessage+Calls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDF1275449AA00FFAAE7 /* TSInfoMessage+Calls.swift */; }; 7B1581E2271E743B00848B49 /* OWSSounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E1271E743B00848B49 /* OWSSounds.swift */; }; 7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E3271FC59C00848B49 /* CallModal.swift */; }; 7B1581E6271FD2A100848B49 /* VideoPreviewVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */; }; @@ -1129,6 +1130,7 @@ 76EB03C318170B33006006FC /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampUtils.swift; sourceTree = ""; }; 7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageCell.swift; sourceTree = ""; }; + 7B0EFDF1275449AA00FFAAE7 /* TSInfoMessage+Calls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSInfoMessage+Calls.swift"; sourceTree = ""; }; 7B1581E1271E743B00848B49 /* OWSSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSSounds.swift; sourceTree = ""; }; 7B1581E3271FC59C00848B49 /* CallModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModal.swift; sourceTree = ""; }; 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPreviewVC.swift; sourceTree = ""; }; @@ -2633,6 +2635,7 @@ C33FDB56255A580D00E217F9 /* TSOutgoingMessage.m */, B84072952565E9F50037CB17 /* TSOutgoingMessage+Conversion.swift */, 34B6A904218B4C90007C4606 /* TypingIndicatorInteraction.swift */, + 7B0EFDF1275449AA00FFAAE7 /* TSInfoMessage+Calls.swift */, ); path = Signal; sourceTree = ""; @@ -4847,6 +4850,7 @@ C32C5C88256DD0D2003C73A2 /* Storage+Messaging.swift in Sources */, C32C59C7256DB41F003C73A2 /* TSThread.m in Sources */, C300A5B22554AF9800555489 /* VisibleMessage+Profile.swift in Sources */, + 7B0EFDF2275449AA00FFAAE7 /* TSInfoMessage+Calls.swift in Sources */, C32C5A75256DBBCF003C73A2 /* TSAttachmentPointer+Conversion.swift in Sources */, C32C5AF8256DC051003C73A2 /* OWSDisappearingMessagesConfiguration.m in Sources */, C32C5EBA256DE130003C73A2 /* OWSQuotedReplyModel.m in Sources */, diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 63c94149f..15bc4b4c9 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -14,7 +14,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { let webRTCSession: WebRTCSession let isOutgoing: Bool var remoteSDP: RTCSessionDescription? = nil - var callMessageTimestamp: UInt64? + var callMessageID: String? var answerCallAction: CXAnswerCallAction? = nil var contactName: String { let contact = Storage.shared.getContact(with: self.sessionID) @@ -182,12 +182,12 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { // MARK: Actions func startSessionCall() { guard case .offer = mode else { return } - var promise: Promise! + var promise: Promise! Storage.write(with: { transaction in promise = self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction) }, completion: { [weak self] in - let _ = promise.done { timestamp in - self?.callMessageTimestamp = timestamp + let _ = promise.done { messageID in + self?.callMessageID = messageID Storage.shared.write { transaction in self?.webRTCSession.sendOffer(to: self!.sessionID, using: transaction as! YapDatabaseReadWriteTransaction).retainUntilComplete() } @@ -219,41 +219,28 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { // MARK: Update call message func updateCallMessage(mode: EndCallMode) { - guard let callMessageTimestamp = callMessageTimestamp else { return } + guard let callMessageID = callMessageID else { return } Storage.write { transaction in - let tsMessage: TSMessage? - if self.isOutgoing { - tsMessage = TSOutgoingMessage.find(withTimestamp: callMessageTimestamp) - } else { - tsMessage = TSIncomingMessage.find(withAuthorId: self.sessionID, timestamp: callMessageTimestamp, transaction: transaction) - } - if let messageToUpdate = tsMessage { + let infoMessage = TSInfoMessage.fetch(uniqueId: callMessageID, transaction: transaction) + if let messageToUpdate = infoMessage { var shouldMarkAsRead = false - let newMessageBody: String if self.duration > 0 { - let durationString = NSString.formatDurationSeconds(UInt32(self.duration), useShortFormat: true) - newMessageBody = "\(self.isOutgoing ? NSLocalizedString("call_outgoing", comment: "") : NSLocalizedString("call_incoming", comment: "")): \(durationString)" shouldMarkAsRead = true } else if self.hasStartedConnecting { - newMessageBody = NSLocalizedString("call_cancelled", comment: "") shouldMarkAsRead = true } else { switch mode { - case .local: - newMessageBody = self.isOutgoing ? NSLocalizedString("call_cancelled", comment: "") : NSLocalizedString("call_rejected", comment: "") - shouldMarkAsRead = true - case .remote: - newMessageBody = self.isOutgoing ? NSLocalizedString("call_rejected", comment: "") : NSLocalizedString("call_missing", comment: "") - case .unanswered: - newMessageBody = NSLocalizedString("call_timeout", comment: "") - case .answeredElsewhere: - newMessageBody = messageToUpdate.body! - shouldMarkAsRead = true + case .local: shouldMarkAsRead = true + case .remote: break + case .unanswered: break + case .answeredElsewhere: shouldMarkAsRead = true + } + if messageToUpdate.callState == .incoming { + messageToUpdate.updateCallInfoMessage(.missed, using: transaction) } } - messageToUpdate.updateCall(withNewBody: newMessageBody, transaction: transaction) - if let incomingMessage = tsMessage as? TSIncomingMessage, shouldMarkAsRead { - incomingMessage.markAsReadNow(withSendReadReceipt: false, transaction: transaction) + if shouldMarkAsRead { + messageToUpdate.markAsRead(atTimestamp: NSDate.ows_millisecondTimeStamp(), sendReadReceipt: false, transaction: transaction) } } } diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index ed3ccc1be..a229f92cb 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -140,9 +140,9 @@ public final class SessionCallManager: NSObject { message.kind = .endCall print("[Calls] Sending end call message.") MessageSender.sendNonDurably(message, in: thread, using: transaction).retainUntilComplete() - if let tsMessage = TSIncomingMessage.find(withAuthorId: caller, timestamp: offerMessage.sentTimestamp!, transaction: transaction) { - tsMessage.updateCall(withNewBody: NSLocalizedString("call_missing", comment: ""), transaction: transaction) - } + let infoMessage = TSInfoMessage.from(offerMessage, associatedWith: thread) + infoMessage.save(with: transaction) + infoMessage.updateCallInfoMessage(.missed, using: transaction) } public func invalidateTimeoutTimer() { diff --git a/Session/Conversations/Message Cells/CallMessageCell.swift b/Session/Conversations/Message Cells/CallMessageCell.swift index 1715210a7..1c7454967 100644 --- a/Session/Conversations/Message Cells/CallMessageCell.swift +++ b/Session/Conversations/Message Cells/CallMessageCell.swift @@ -51,11 +51,12 @@ final class CallMessageCell : MessageCell { // MARK: Updating override func update() { - guard let message = viewItem?.interaction as? TSMessage, message.isCallMessage else { return } + guard let message = viewItem?.interaction as? TSInfoMessage, message.messageType == .call else { return } let icon: UIImage? - switch message.interactionType() { - case .outgoingMessage: icon = UIImage(named: "CallOutgoing") - case .incomingMessage: icon = UIImage(named: "CallIncoming") + switch message.callState { + case .outgoing: icon = UIImage(named: "CallOutgoing")?.withTint(Colors.text) + case .incoming: icon = UIImage(named: "CallIncoming")?.withTint(Colors.text) + case .missed: icon = UIImage(named: "CallMissed")?.withTint(Colors.destructive) default: icon = nil } if let icon = icon { @@ -63,8 +64,6 @@ final class CallMessageCell : MessageCell { } iconImageViewWidthConstraint.constant = (icon != nil) ? CallMessageCell.iconSize : 0 iconImageViewHeightConstraint.constant = (icon != nil) ? CallMessageCell.iconSize : 0 - Storage.read { transaction in - self.label.text = message.previewText(with: transaction) - } + self.label.text = message.customMessage } } diff --git a/Session/Conversations/Message Cells/MessageCell.swift b/Session/Conversations/Message Cells/MessageCell.swift index 56d93b2b2..e98f0281c 100644 --- a/Session/Conversations/Message Cells/MessageCell.swift +++ b/Session/Conversations/Message Cells/MessageCell.swift @@ -46,12 +46,12 @@ class MessageCell : UITableViewCell { static func getCellType(for viewItem: ConversationViewItem) -> MessageCell.Type { switch viewItem.interaction { case is TSIncomingMessage: fallthrough - case is TSOutgoingMessage: - if let message = viewItem.interaction as? TSMessage, message.isCallMessage { + case is TSOutgoingMessage: return VisibleMessageCell.self + case is TSInfoMessage: + if let message = viewItem.interaction as? TSInfoMessage, message.messageType == .call { return CallMessageCell.self } - return VisibleMessageCell.self - case is TSInfoMessage: return InfoMessageCell.self + return InfoMessageCell.self case is TypingIndicatorInteraction: return TypingIndicatorCell.self default: preconditionFailure() } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 7ca15247e..f90ec017c 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -2,6 +2,7 @@ import PromiseKit import WebRTC import SessionUIKit import UIKit +import SessionMessagingKit extension AppDelegate { @@ -64,12 +65,12 @@ extension AppDelegate { } // Create incoming call message let thread = TSContactThread.getOrCreateThread(withContactSessionID: message.sender!, transaction: transaction) - let tsMessage = TSIncomingMessage.from(message, associatedWith: thread) - tsMessage.save(with: transaction) + let infoMessage = TSInfoMessage.from(message, associatedWith: thread) + infoMessage.save(with: transaction) // Handle UI if let caller = message.sender, let uuid = message.uuid { let call = SessionCall(for: caller, uuid: uuid, mode: .answer) - call.callMessageTimestamp = message.sentTimestamp + call.callMessageID = infoMessage.uniqueId self.showCallUIForCall(call) } } diff --git a/Session/Meta/Images.xcassets/Session/CallIncoming.imageset/CallIncoming.pdf b/Session/Meta/Images.xcassets/Session/CallIncoming.imageset/CallIncoming.pdf index fbc5a4a875971060d79f642f11a200ff3d3d7e9d..93e911237cdeabd01b7c703d8b78c100b9d3fa15 100644 GIT binary patch delta 2136 zcmb7FX;>5I76uiN0fYn)N^Z-@!z$9uOeV<$5e!QVh_wiaf~b=al0Y_-Ac*2v+Y2aK z#URCMp@QI61xrg2i%X$$1wjP0P@tu#AQVOFQf1LQ3EJZI-XC{Oa0IcLP< z%>2Cp1O$Qp{(wfS7U6Or&G_AA=YVaHP1oek>cKDX$Dew<@<(f%2iCy$K(oP@AIDtT zYi7P_L*GBUyywL{JMf9u>9(jpxCO+1tbW_)&0kf^7eCBAPNDzHPNAm2_Te+3#jLNo#7q#LdJ`q+ile)X`i}mIwSG8K{#3^ZR1M;!j z=;jvp`Ct5o$5HzW8y3q?W>9AHx5-()DBxv{u;I$5hL^L6@Dozi#db-@X;;)V`kggAfJOMrk3WM|@T*VhjNq&GV(h)sUE0V9&Lsq@gGGX#L?nbep;UqO61f(y+ z)25CL2qH21{CHBC2mxTkz?{FSZBV?fhMo*|S_w=BiI7fTaf6Mrrnq56tXLXu8)3Jo zT<&X6^N&8fZ?;K<3%_*2WrzEOtmELb)t;#Qu%szU;#Hk%V!?HASKM%46k}Xn*mB128tj_cHK1al*zn^TPHNt|#?()rzDh1m5?{U8wVGDdGVxQKD&OR0MW1Wc3O=bN7C46In^Y*F?v4A+65NV`$Q7 z;WeT7P{XdyV;$M=M0gE3uBPyP^A0wG<94zTr>c1t$Lxu@v+_Q^xCbu0SM<%HSnTYz zlv`_J+Nr-jv!FvEb42xkYBF}tojTgflyAAAMXLtfT8r`Yom(T^{q8k*bq88KT^tA3 z9v!@6+1%=xQM@{xH*$}d=hMoLEgtzM{I=auGi{OSJ&RLqNUGV2>qZX1kt@S~``g7% zWo(q6iap(N=X#TK2QbjzZesP*VM>T3GwYF4Z@p$Xxa@H&nz|+2A~x>_@6=+8wJ+lP zpUb{aU${~7?juzJeZj$ivMsqeJ+h|ZbC0t>XqSh%u5(@2sC!yqO%#;J(sI31OM42= z`b`2p_K&L45I&)_BTH6>WsF8BYn^tCO`O?RQ*n)`3f;Z2 z@Xx5cuTi(yAb5GkliU+w^sUC9nOH9PFO$QVp?g6|G~+-J%0?kwGW8#S$)8f4X27WK zIF;rI!5kFSPjv`XfPg`O7%+_Kx~Z-Ngs|VfgfPZ`0t|2icA66e#on@Gu*qd$u%7l9 zhx3+>jgY@SL~h>cQK6__`KFG;A@|LjIut{u1*lcyi?om@xctXQE`lH+S? zErlu)l^{$a^jj-57>on`V2qg{f`xF5K!{+dAQ4Z%1spbm$z)0|I=h@ Yi)pmDTAQX8B?2f1!vHt8kmWq!-)?#+X#fBK delta 1965 zcmah~dpOjI8g|*jFj#U+__-}f=3?#|w?Qjfjmx&=d!P6Fp7)>khMCwT-G+<8p#f`a zNMsZn3=D%DlMKsoskXo>@Lt_JE3)0;l0!RDq&7-c4+ln@_XDGAq$c*pD?8cX@Pd`K z^nI7kZ#3Q?+ZcV!?(W~OXFXK>G2kOZuv8xryl~;h3(>WMeMLu6K9LKVi|TRLBhsdb zVZ)2jh5yLEBOWQdvv)A8jKb9;GDE+gC~nvifDwzH3^pAXz(#!IzPl*)#ykN9i%&-q zVxHe&gg0KB8pbc}5=NIM&kz>6o{dfEf(KbSkAnv1cz2^2PaMs~S0WpA}t|Fhic)9R^fxbFV z^|fk-Y4sd)9VFYOE9rCFb{8bCiMHeg*DbxNW7gI^v#1tp7*Lwb)jY1YaMBePp!Ul> z^-zvkj{d|brFGk}9ejEDw#fx4GLaTIa<{>XgKuR5JV{#MPbZ2wspXAx0de@-uy9j`u+OL{QvMa`BrBrD}!wU(j8 zSa!NlYW7Mey2x78VRm+BpPK!#v-NjRcVAa%*3`IpjYlbF+}8JyHZ`S&Ptvb3WK(-x zi=@`+zz=g=$b6nDw79B5!uu4uKim-VR?3r1GB$YMRgi%yjVG4NsbA7kOk&RH6SkF4 zJ2{>?jXQoi>M|0VmH23?66z9ieN~_1VGH_P>w963IuYY*us$CBNto(4UUlcpi84W( zti;P0vnTQw0}s8f63TeSy?oRmf3W2_BW6kD+i(jq!CTN)rO~=OZ(S@+@(Y` z@aE$rhDVTaTi4YP8Nb{k21OpTd-VAghR5xjNsXzEWSosZv}fe|62@>`$LwMAv&}y- zw0*p|7rXfhXH1PnFd`Ii{pDmk{Oz+2ie7xVevLI?UbXTlT+1a9nkpcNysY4i6Znd` zmDv_bq^l1?GL@}BgS;ur)8=VUt$M5Rnrf(_!wnC}2HQLiOynPE(%SOS+FZ3}y0Ce8 zX+vSWVRcnsF$q7}ay_xiHDN}+hUaOQYbN4a`ANWy{dZ4nKUW*^S&ZAMva1md37*y2 z8+(@f0jD{@nK-{(&(|Egq!Qaw>12OwUB+i?a&WLs(JeZL)A~-cU5l^V{voM_m$1c( zr`ejJ{z2t~z%1>;KylUzEu(UW&A|Z6E_mFecSpL^jGu00qA-88gY%n&u?;zmX}HGC zS8|HbeJ)3c2-rSvA)-VoJI^`Kzc~SC9BI&<)<33__ndatbyY-(0g_*!h zWvCSgLbBObfo@7Wp^TGy&H25iDJPy=>Dwo%JUl6ocU)Cpo=nE{Dn^NAhI(u;RV!vZJoS@6{h#_ne>??^#K| zuJbW#uvbP=ChSe37<97IJ)do4oY3$`M#WLd55Aum^j|-TKR(afKgJc^Utdyf`FwYE zd38B@?WEkh^QWe87iy-GUYR6mILwRFa|9fqQt#vMBDbvhb^YQC8)xPR zz(_0xi|r8}7z6@)_H2%k2mlSh0r$-W_!0sjlg6SRfnWqtl2;&-m`p$cBZLeE0@KENIt z0;D6+I4Y7(#bZDu4vxVhK_ra|r_wNJ1RZ4!`FjfB_pc-lWJi6q35CVrA;!jzZcdOt E0bj4{U8Ml)kcNy-vN_JkL&J!FeYghV8= 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 WII6_eN-P1ObJ#H8>>L=%2Yv^>065wJ delta 1893 zcmai#do?LeZ9cO0MHh%B@+gEt(d)ZQHwlyyx_u^FHVKJ(1R)iVPYnh3p;y$KVDZkyN^p9 zuxU={r8QJ_)ENloz3;6p&2C~t)?3^C$Mm8L{TnmQ0=t@h2F-*0hL~f^XyJF;Clg{- z;-~KR^w7sc-nG{abt@xc3qg)>?)651WH}5pP zfDSw;R~mP_SUg)ZJmA6uJ2Oj{YXx1K~DlKW=JyDiMTghNjZC9b=yNP{!%TA_=bGd|JX>-Z{eC`tX?UUVJnV)iMP3Fh7dv68K=9Dem>>Ui6Cueg(5B#@hY_P3;X`GB z1E^<>@9a|;F6@7!1HE;1B^lKu=s_m9Thfbbyd#iBW@h?`jt5(>8UvML-a0SNtukp6 zVi9Hv!hJ6>^=G}`NYX_uQSYB9tm*Chp@$nE#Zid4y6q0ig5ft*Y>%o8w(wJmPc8XF zeHvF?A{H%Bom%H#mBF28Pu^j#1Z3T@$ht}?rxZ%X>$IIXmT{usZ3?pfR1>E-FA+{H zWNjg*N0kk`@Qzbf0x|*dLicQGPUlFjDRQCAw`az_M7=I~*-Z92Gd2UouWZl}H)&vT zn4T95(W(g7;}8AMM*vO~ZZ~FQYOz`gIGw^*N+v@;SNbU0H;#^02JhGA)O<`mLVZ&&{XDPv8qL}z_lhFxaO93|5qlV> zAgJbs`yb4{XeFVnZ5}paXjduh}9xyv*iR}U?`t`A0&HL^sv!jzqTHEp0aZB7~( zi5R%kp+ArGPZnCpN!4!827%}ujLar4-#E+J1DIzP&RP8B%wI{ z@maZ<4}07EVf-Eh-b@~SJTQ-=)~)P%Ak9ACo!iJFGd~}2+K*%}A*%8#tDCf%7g}Sh zyw~XF=&h zxQ-aL)?t^LhD|!~l?}E+zq_?Mb3^s*ZJzGoa%Xs(M-1>MuKfu6- z&16TohXn-E0aMd2V}2N N!ceHAC+r}wzW~MRHMal& diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 15cf77447..5af272ffc 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -581,9 +581,9 @@ "DISMISS_BUTTON_TEXT" = "Dismiss"; /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; -"call_outgoing" = "Outgoing Call"; -"call_incoming" = "Incoming Call"; -"call_missing" = "Missing Call"; +"call_outgoing" = "You called %@"; +"call_incoming" = "%@ called you"; +"call_missed" = "Missed Call from %@"; "call_rejected" = "Rejected Call"; "call_cancelled" = "Cancelled Call"; "call_timeout" = "Unanswered Call"; diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index b2299e23c..3344d05ae 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -242,7 +242,12 @@ public enum PushRegistrationError: Error { let payload = payload.dictionaryPayload if let uuid = payload["uuid"] as? String, let caller = payload["caller"] as? String, let timestamp = payload["timestamp"] as? UInt64 { let call = SessionCall(for: caller, uuid: uuid, mode: .answer) - call.callMessageTimestamp = timestamp + Storage.write{ transaction in + let thread = TSContactThread.getOrCreateThread(withContactSessionID: caller, transaction: transaction) + let infoMessage = TSInfoMessage.callInfoMessage(from: caller, timestamp: timestamp, in: thread) + infoMessage.save(with: transaction) + call.callMessageID = infoMessage.uniqueId + } let appDelegate = UIApplication.shared.delegate as! AppDelegate appDelegate.startPollerIfNeeded() appDelegate.startClosedGroupPoller() diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 65a6246eb..17fa4ec19 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -117,19 +117,21 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } // MARK: Signaling - public func sendPreOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { + public func sendPreOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { print("[Calls] Sending pre-offer message.") guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } - let (promise, seal) = Promise.pending() + let (promise, seal) = Promise.pending() DispatchQueue.main.async { let message = CallMessage() + message.sender = getUserHexEncodedPublicKey() + message.sentTimestamp = NSDate.millisecondTimestamp() message.uuid = self.uuid message.kind = .preOffer - let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) - tsMessage.save(with: transaction) + let infoMessage = TSInfoMessage.from(message, associatedWith: thread) + infoMessage.save(with: transaction) MessageSender.sendNonDurably(message, in: thread, using: transaction).done2 { print("[Calls] Pre-offer message has been sent.") - seal.fulfill((tsMessage.timestamp)) + seal.fulfill((infoMessage.uniqueId)) }.catch2 { error in seal.reject(error) } diff --git a/SessionMessagingKit/Messages/Signal/TSInfoMessage+Calls.swift b/SessionMessagingKit/Messages/Signal/TSInfoMessage+Calls.swift new file mode 100644 index 000000000..fcf0af1be --- /dev/null +++ b/SessionMessagingKit/Messages/Signal/TSInfoMessage+Calls.swift @@ -0,0 +1,40 @@ + +@objc public extension TSInfoMessage { + @objc(fromCallOffer:associatedWith:) + static func from(_ callMessage: CallMessage, associatedWith thread: TSThread) -> TSInfoMessage { + return callInfoMessage(from: callMessage.sender!, timestamp: callMessage.sentTimestamp!, in: thread) + } + + static func callInfoMessage(from caller: String, timestamp: UInt64, in thread: TSThread) -> TSInfoMessage { + let callState: TSInfoMessageCallState + let messageBody: String + var contactName: String = "" + if let contactThread = thread as? TSContactThread { + let sessionID = contactThread.contactSessionID() + contactName = Storage.shared.getContact(with: sessionID)?.displayName(for: Contact.Context.regular) ?? sessionID + } + if caller == getUserHexEncodedPublicKey() { + callState = .outgoing + messageBody = String(format: NSLocalizedString("call_outgoing", comment: ""), contactName) + } else { + callState = .incoming + messageBody = String(format: NSLocalizedString("call_incoming", comment: ""), contactName) + } + let infoMessage = TSInfoMessage.init(timestamp: timestamp, in: thread, messageType: .call, customMessage: messageBody) + infoMessage.callState = callState + return infoMessage + } + + @objc(updateCallInfoMessageWithNewState:usingTransaction:) + func updateCallInfoMessage(_ newCallState: TSInfoMessageCallState, using transaction: YapDatabaseReadWriteTransaction) { + guard self.messageType == .call else { return } + self.callState = newCallState + var contactName: String = "" + if let contactThread = self.thread as? TSContactThread { + let sessionID = contactThread.contactSessionID() + contactName = Storage.shared.getContact(with: sessionID)?.displayName(for: Contact.Context.regular) ?? sessionID + } + self.customMessage = String(format: NSLocalizedString("call_missed", comment: ""), contactName) + self.save(with: transaction) + } +} diff --git a/SessionMessagingKit/Messages/Signal/TSInfoMessage.h b/SessionMessagingKit/Messages/Signal/TSInfoMessage.h index 745ffd0d6..30f607378 100644 --- a/SessionMessagingKit/Messages/Signal/TSInfoMessage.h +++ b/SessionMessagingKit/Messages/Signal/TSInfoMessage.h @@ -15,12 +15,21 @@ typedef NS_ENUM(NSInteger, TSInfoMessageType) { TSInfoMessageTypeGroupCurrentUserLeft, TSInfoMessageTypeDisappearingMessagesUpdate, TSInfoMessageTypeScreenshotNotification, - TSInfoMessageTypeMediaSavedNotification + TSInfoMessageTypeMediaSavedNotification, + TSInfoMessageTypeCall +}; + +typedef NS_ENUM(NSInteger, TSInfoMessageCallState) { + TSInfoMessageCallStateIncoming, + TSInfoMessageCallStateOutgoing, + TSInfoMessageCallStateMissed, + TSInfoMessageCallStateUnknown }; @property (atomic, readonly) TSInfoMessageType messageType; -@property (atomic, readonly, nullable) NSString *customMessage; +@property (atomic, nullable) NSString *customMessage; @property (atomic, readonly, nullable) NSString *unregisteredRecipientId; +@property (atomic) TSInfoMessageCallState callState; - (instancetype)initMessageWithTimestamp:(uint64_t)timestamp inThread:(nullable TSThread *)thread diff --git a/SessionMessagingKit/Messages/Signal/TSInfoMessage.m b/SessionMessagingKit/Messages/Signal/TSInfoMessage.m index 67bb6b339..833c1cd37 100644 --- a/SessionMessagingKit/Messages/Signal/TSInfoMessage.m +++ b/SessionMessagingKit/Messages/Signal/TSInfoMessage.m @@ -65,6 +65,7 @@ NSUInteger TSInfoMessageSchemaVersion = 1; } _messageType = infoMessage; + _callState = TSInfoMessageCallStateUnknown; _infoMessageSchemaVersion = TSInfoMessageSchemaVersion; if (self.isDynamicInteraction) { From 35f75490a47c9f55e46e10bcd3945b1420fdf538 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 29 Nov 2021 13:35:32 +1100 Subject: [PATCH 132/368] clean --- .../Signal/TSIncomingMessage+Conversion.swift | 21 ------------------- .../Messages/Signal/TSInfoMessage+Calls.swift | 2 +- .../Signal/TSOutgoingMessage+Conversion.swift | 7 ------- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/SessionMessagingKit/Messages/Signal/TSIncomingMessage+Conversion.swift b/SessionMessagingKit/Messages/Signal/TSIncomingMessage+Conversion.swift index b963f68f0..6849fd50c 100644 --- a/SessionMessagingKit/Messages/Signal/TSIncomingMessage+Conversion.swift +++ b/SessionMessagingKit/Messages/Signal/TSIncomingMessage+Conversion.swift @@ -1,26 +1,5 @@ public extension TSIncomingMessage { - - static func from(_ callMessage: CallMessage, associatedWith thread: TSThread) -> TSIncomingMessage { - let sender = callMessage.sender! - let result = TSIncomingMessage( - timestamp: callMessage.sentTimestamp!, - in: thread, - authorId: sender, - sourceDeviceId: 1, - messageBody: NSLocalizedString("call_incoming", comment: ""), - attachmentIds: [], - expiresInSeconds: 0, - quotedMessage: nil, - linkPreview: nil, - wasReceivedByUD: true, - openGroupInvitationName: nil, - openGroupInvitationURL: nil, - serverHash: callMessage.serverHash - ) - result.isCallMessage = true - return result - } static func from(_ visibleMessage: VisibleMessage, quotedMessage: TSQuotedMessage?, linkPreview: OWSLinkPreview?, associatedWith thread: TSThread) -> TSIncomingMessage { let sender = visibleMessage.sender! diff --git a/SessionMessagingKit/Messages/Signal/TSInfoMessage+Calls.swift b/SessionMessagingKit/Messages/Signal/TSInfoMessage+Calls.swift index fcf0af1be..6be8d499c 100644 --- a/SessionMessagingKit/Messages/Signal/TSInfoMessage+Calls.swift +++ b/SessionMessagingKit/Messages/Signal/TSInfoMessage+Calls.swift @@ -30,7 +30,7 @@ guard self.messageType == .call else { return } self.callState = newCallState var contactName: String = "" - if let contactThread = self.thread as? TSContactThread { + if let contactThread = self.thread(with: transaction) as? TSContactThread { let sessionID = contactThread.contactSessionID() contactName = Storage.shared.getContact(with: sessionID)?.displayName(for: Contact.Context.regular) ?? sessionID } diff --git a/SessionMessagingKit/Messages/Signal/TSOutgoingMessage+Conversion.swift b/SessionMessagingKit/Messages/Signal/TSOutgoingMessage+Conversion.swift index 83ff29abd..1509af0f3 100644 --- a/SessionMessagingKit/Messages/Signal/TSOutgoingMessage+Conversion.swift +++ b/SessionMessagingKit/Messages/Signal/TSOutgoingMessage+Conversion.swift @@ -2,13 +2,6 @@ import SessionUtilitiesKit @objc public extension TSOutgoingMessage { - @objc(fromCallOffer:associatedWith:) - static func from(_ callMessage: CallMessage, associatedWith thread: TSThread) -> TSOutgoingMessage { - let outgoingMessage = TSOutgoingMessage(in: thread, messageBody: NSLocalizedString("call_outgoing", comment: ""), attachmentId: nil) - outgoingMessage.isCallMessage = true - return outgoingMessage - } - @objc(from:associatedWith:) static func from(_ visibleMessage: VisibleMessage, associatedWith thread: TSThread) -> TSOutgoingMessage { return from(visibleMessage, associatedWith: thread, using: nil) From 6703d8c7f42a576e2ca5a302d5e78b3a959f725d Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 29 Nov 2021 14:49:53 +1100 Subject: [PATCH 133/368] add timestamp to call message UI --- .../Message Cells/CallMessageCell.swift | 33 ++++++++++++++---- .../CallIncoming.imageset/CallIncoming.pdf | Bin 5425 -> 5125 bytes .../CallMissed.imageset/CallMissed.pdf | Bin 5337 -> 4750 bytes .../CallOutgoing.imageset/CallOutgoing.pdf | Bin 5418 -> 5121 bytes .../Messages/Signal/TSInfoMessage.m | 2 ++ 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Session/Conversations/Message Cells/CallMessageCell.swift b/Session/Conversations/Message Cells/CallMessageCell.swift index 1c7454967..65e1c28dc 100644 --- a/Session/Conversations/Message Cells/CallMessageCell.swift +++ b/Session/Conversations/Message Cells/CallMessageCell.swift @@ -7,6 +7,14 @@ final class CallMessageCell : MessageCell { // MARK: UI Components private lazy var iconImageView = UIImageView() + private lazy var timestampLabel: UILabel = { + let result = UILabel() + result.font = .boldSystemFont(ofSize: Values.verySmallFontSize) + result.textColor = Colors.text + result.textAlignment = .center + return result + }() + private lazy var label: UILabel = { let result = UILabel() result.numberOfLines = 0 @@ -30,6 +38,14 @@ final class CallMessageCell : MessageCell { return result }() + private lazy var stackView: UIStackView = { + let result = UIStackView(arrangedSubviews: [ timestampLabel, container ]) + result.axis = .vertical + result.alignment = .center + result.spacing = Values.smallSpacing + return result + }() + // MARK: Settings private static let iconSize: CGFloat = 16 private static let inset = Values.mediumSpacing @@ -42,11 +58,12 @@ final class CallMessageCell : MessageCell { super.setUpViewHierarchy() iconImageViewWidthConstraint.isActive = true iconImageViewHeightConstraint.isActive = true - addSubview(container) - container.pin(.left, to: .left, of: self, withInset: CallMessageCell.margin) - container.pin(.top, to: .top, of: self, withInset: CallMessageCell.inset) - container.pin(.right, to: .right, of: self, withInset: -CallMessageCell.margin) - container.pin(.bottom, to: .bottom, of: self, withInset: -CallMessageCell.inset) + addSubview(stackView) + container.autoPinWidthToSuperview() + stackView.pin(.left, to: .left, of: self, withInset: CallMessageCell.margin) + stackView.pin(.top, to: .top, of: self, withInset: CallMessageCell.inset) + stackView.pin(.right, to: .right, of: self, withInset: -CallMessageCell.margin) + stackView.pin(.bottom, to: .bottom, of: self, withInset: -CallMessageCell.inset) } // MARK: Updating @@ -60,10 +77,14 @@ final class CallMessageCell : MessageCell { default: icon = nil } if let icon = icon { - iconImageView.image = icon.withTint(Colors.text) + iconImageView.image = icon } iconImageViewWidthConstraint.constant = (icon != nil) ? CallMessageCell.iconSize : 0 iconImageViewHeightConstraint.constant = (icon != nil) ? CallMessageCell.iconSize : 0 self.label.text = message.customMessage + + let date = message.dateForUI() + let description = DateUtil.formatDate(forDisplay: date) + timestampLabel.text = description } } diff --git a/Session/Meta/Images.xcassets/Session/CallIncoming.imageset/CallIncoming.pdf b/Session/Meta/Images.xcassets/Session/CallIncoming.imageset/CallIncoming.pdf index 93e911237cdeabd01b7c703d8b78c100b9d3fa15..1b8e6519fcd7fad60fe12001a9fa06b77c40d2ed 100644 GIT binary patch delta 1959 zcmai#S5(sp7RCupCWJCHQQAL{7%2-$NFfX$0tR6Ols5F9AR0)aEG>o>1PcjB zEGVFKkpUJIk>*mA4#Q9v2LzGOaTvFrCVtCu!_KOM z(;#QJYk%fm@Md?=f(E5~clT%asX2`m3ZcQS`7t}DXsR!^_t;GBWbIG0%RT;sH$KeN z_7N>^#$f7>kAPeM$H#SkDMvy(~Bf2McGwa1E0u}Nd~GY2bVB^B4w4@6`p zBF>N8i>0?vVZ&bh-(t!Nm$cs#-lXQJ9t)R{xq;isF-yn@YI|Psc2t&9=4&FqkY0iF{N;YTDR=X91)zUvzjrDZP(>yzEIfrS}J{|XAxM2 zd-G%9zSrFQY<>?bv&Q4Hj|hqup|`Qa_3OzKoqu_oCU}Q(m3@YkEh$hCAstv}+!>^{ zTPe}1T<2GSK}3|!3WH0+;=C2g-bYHiKUxumGti`-M4F?t&oEUC(43V~Z?zX{sF~o4 zm{kG%k#th*qR?sqhh_%SEuEpJn(!S(ie zhN4Aa{tyqP^Qc(M%Pe}3{3l863nd~+FScBiGOg<_yWTUiQa*FuEluVfXWU_VCTqob z(9&fkPm)fZmm#DSMawDnK|f7A37l5mQ|mQ_Fh(zC$^Qz2BM4716^L~kSr8=eVe~W) zd&yRg6?-H_uFz9kVjG;ee9|`4PQzWgku5Zp&d#>+i+!Chd4%SPpyrC}Lb}C-+v{UR z+F?!Y%+-_5Vb)?*w1z0=1JP>k-#9jXTABir140UA89VUQze{nhlISuNL?~FI&hqBm z(X-nxGfaKHcZ{**-#NeW+pY(@q17V7{?=?dOGys>T`R=xJmE@}p|xeB_CpC_+p~-E z@x_LSSLO$BseJ8+sSaT-P}Wr^q4guDEj2GcFw7vl)lzd53M{nAyQUVnv>1ON{lvr0 z3qC7knW(D#Mf^p;!-10e6{Y$HWla7!9g2xwN_y)YJe;`JO4%uSgC?2^kA%LiE4O79 zuqyifjia3^48fhQ2Eq~dJM&!WgJz!2lG*z#!N?&+8H6R%Y*@z3?!7_N%Hzj*E~Uw* z%ZmGH5O<9WFRhg9_v7ZC|5@)-q6qq@>H^&yGL$JF(*d%oln6OGYCo_V(_^2k6<(zC z7v;nc=*d7)?;QjEt(k-96P@?YiFVdqXR(^a?|3kmt^fj}#aeMOsLI>o7jJdH^|8u` z*%MpQm+lo%5n^TCMFd!O8+v5A+dMzxM z`%YOX4%^4G+rR&ekPt>8X4$M>~!4hKPmm2bnnOj>xOBk)D_G!sfLIJW%O)Gy-; z%1Y8_c8wd(WlEjsU6-{uMYiQ8_js%p(dPVCv)#3E;rl(Yd zP-7BktJU!xfo9INR+Y5Qjr#mDfkmfxMHTr(zNV)`mjn97VMUHld&_JA!BJuLyL;mz zWp=c4Co!Gs#?q29Po++W9^*78c9)#qD0rZVW#A1tG1`}4FSwRkL`Bh0x5vbx#Fy

g8|taVa~5pUnt@?%TPRegxL~Ybr&yOenR;$*HH961M09_Gyj07 z*)gnC;$r}0sF&f_#+)iApums zOJH1880aw=gyR7aoG=bRV|i{6I0(;+gCIc$yb+m;BKn7g<`HF89llO(NF?I0HUCQ^ zl0hPakpKaV{C5F3ELI;2kb!?P6b^|7zFvWle=!V(falfA!dNJr!T&hozF{al7Ws_` z7H{wk!=ds2VPOp4APR{A{=)=^plA|60QBss)G*-i=g;Q#*uc>W`UeHme2{lVY~C@>s;#*PU7 E9j7Q!cmMzZ delta 2134 zcmb7Fc~leU7Kdt}3?L)~q2yEs4y(w^%w$3)h+tSEAl4!v3Zf=qObFRbf*^|H(gKQB zF-T*z@B~5Y29`dGSkxAtR49nx0!3Pi3PN$AE>#xoB0k#I455C+M ze`$}o#rm~#6R2rk@up4h(s7hJ+WXZlD0~r6u_!(m>ma9G2J$*-*f~-P+c9>-IS}lMTK;IR2pH#Gh@<(+Vv`& z^rB9~V~tXI0*f%4&jFRvH5^8 zqEK8TQm3f2N;L>WhM-8#$~Z-eNSTa-Fg&T@r>HgI+7O9A4YF7uGfJTO^Aewutd(eh zL=w)$z@#Y&8PSH!3`s}f3b9rKA`mAZoT-~-Zbc;R`BqvF*@iwC-7&8`2RtGcd3l>U z?=AG7FbQ*F@V=p?KJPtWakzk<*~Y8NWL32{zNixyhZB*N(`dVjK3{Edd|8{dPMntE zJ|G{hi)(51nEm-Lf<#(>QR961@!ZGp{6aUzy`NUp{8A=y?gNWFf6Q{uIcAl*GyR9& z&|O1>zuK{?r>h}vKd-iA?58_t7Iz265X@7F)9>*2V*#_fdMiUkF4>+(t5a&9YKQ2Z z6Ab$@lm zKnTcOB*>V&vLJ}W81mz%$Z!OJ5hHW{WVccAx*7&D(6thn3=$!ozTyT4^_%2|HL+x2 zlwCA^UWMG>o)H*#Xz#m}XxH$vao6o0$^@tM;YPaZow75w^*&t^|;vGdUHeT#JMq#>r@+|Wu|~%8`PH< zY)2LD>v6iD0zb6BaMb}AeaHVI@GEi5)-L<})-;|M?N`5t7S{&_*UtAp5FMnAq%oe1 z6rMib<6to~QkZulwL~iZ;X?OvMs@4>jYL%;%Ngm``9-W1WO8crVJ|L_8$D*re*eUH;?EeLfUfss2l%^NNU3 zPxl;}FqwN`vswhO!AqPxXk z9ZbN^Tu!^bBEEz6^D|2(6gC5I08|qRGj7*2UZ(wz7g4-yz`d-! z)=%dr!gWUmZ(Fsrd1aL>&*VS2OSt&9aS}=%d=+(ze#Bf`YzT}FD1914# zaKOF}u~Rt*4Nu3OZohq{*|`%K=IMp-5leCLy1 zV!84~QvY+=H<@$SN#1{?(lh5A2rA#0pVuR69zOdx_k#|3q}wXDRZY65dK*Gtk-*6J zNiXZspYuIqmSyjMsk&>S1}z>gnf~+XIern&8I)V?YrOgS-sbWE$IihhqR_!4zb2!R)8J;^^7$=qc6DaC5e-%JiumX1!7P#~0pLb@%qSuRE?0fIcq z&wI*<%~0cO00@Lp-FX_r5rVlWXc+Slr~m<@0I^^g)BQ$sBOrwH_D=|7{S{z@8?jTI zASm{h9g9P52cz|r$GF_LbR2}71rV3CoZ=8g4azrlTrRnQ-qfKO!qQ3Y7=&T=EKw@S z$u+r=A{1+sAWQ~tSXdFEU?La*W9(!c7QsRhf}z4Sf@Fb^%VDwEY>e$EWMgn3@Xscb Y`%9x0sI|t@qFf9E+}*>bE#?D%0PF%ME&u=k diff --git a/Session/Meta/Images.xcassets/Session/CallMissed.imageset/CallMissed.pdf b/Session/Meta/Images.xcassets/Session/CallMissed.imageset/CallMissed.pdf index 8c620676499ac2d150507b66c97dece4c12d7944..85818f18f172ba8774c9bfad21354d73e4197b10 100644 GIT binary patch delta 1485 zcmai!YdF&j0LHgNmYKUGGy6MODq|NmD@o)U9l3NAV$&w%a$JTcOFS&MXi;*VLp+>r zBqU3@m5hT}Y$vw}xrUbe>733tAI|&f_q^}>ydU3z18Lgn$Cy|=4lpr+hK7Yu$pO$v zFg43Q5Mc$27lkHquO89LnL+VCMEOK!h#3RHphH(jEFKixSW>b%Ud$`JmvhX;qH~?Z zO)K#3DtBU};`x>7hUYCiOZLe>SL*9Iy~N5t8jZ37IhU?oSM$&J9*sGgFqm@gGc3od z%1?UbfPa)UGcQ{RSM3!|5qo7GQi4;V&5^h2-eia9@q7iH;B)z?=682TsB7TMF!ofX z&gy2a{Mi{T2UbBQ9h_Nu#9>;4o6&b^E@aj=%%&&jejR;Ix)L45x9%1eaY|4{2EH=( z3`4qIRsBkxp>Cm>`R^=o`*_`SwBNuK1!`|r*vM?^uZTV9Yda$3EQ>j2L@lojUO89> z0#BP%$A@?aTE*5LhWANWx4a1-T8g@Md-v|HyaXq5CPeGZiBC(u<MKY))%H7oMwD3 z-eLL|HKv3mE3}*Yd(O(*b`m&#^k??-Zc_2wUY&-#@Q=gp#~AFaSKbopZ|POZ*5`qW zKOqqai%pZF@keV3!r0=D4MbCmOKW^E)T2yVt6N9UEGN@tFKFDl@3C^!NR@m`PwK6^ zLn1=V-21@e=q|>+bkjc&Iwu+LC)89PN-{ltF;DMk-at{`4hq)Q@9A31_U{t=YONlG z{7J0`W0{m|FCpui#~D`DjH|DuIX&%W@U(3B$t8~iK2^UumvTrmRsuo_Ri}Z5;#|P= zUk_bYn8d?KZovdejz$MNk&Egm6gnr57o9w75l$4fH?GK&pVTR*=tb4hRg9^jOreS$ zLlf-dJn-}Am(Q9CeuH}7ScsCGw7}LgQw)}sR(tc72TH)h82t6p0Lyl|6M!H3R{DCZ zqObL)Rg3A@n>=%PM7_FP|8?X0x!mG8*LWzcVSl5Yb+hYf>Gnd#JZl?BT)SH?a$3-@ z6j>~1dcg3kqM8vt=kg08l2O5wejBs z-F9c1*~vc6H&skj&0COer%BeY)R>YMUR^PZErE zEebw)8jbPO*SGoJ%xDE}ren;$@)|Y$xKjRvCkHt%*A_9k8NR(puR(t3p64S)T~`ga z)DLr`?KV~g{-Z$!t9c;u2J;#u*Nb(PWGyZ9LyvK^BQ2s%zOY;4AD5FjDIK4^aaz z1X28;gsL8J1r$tR$G`~UL;y!**TS^HMD_<525iKRRk$dQ$KwpyvkJ%`Ag2filh|lQ z_+BgyM+7kc2nYnA{{qGUi(`8#YCB``n4KBMXorWz6AgE4;4t`|o*|BHtORG00DZ@x zpfKS365t5v+XT{r0Hgo-1mFf36ERq_(FF?407t;n2owVX7LO+rsk93e4A#hqiZ_A& ee+BUPan$6Hu*eW94QgP3B|x>cEq<{CL;nUkN0KT4 delta 2038 zcmb7FdstKF8J7w3@qiE@6$d!JIsz4ilaq6DbAU!5g@J%13O*6#5Dph{OJzrOGHUe0^@ zz3-`D4K6pYHVH8h842l09f5100;dvl5A+Mf?fvm-=9lX{`h%Xw=?xQi*PYQegSW=; z!Qv+E#;P|wk69B+u{&nZ;PQbs?+=dHYjkCg2brZ8e7l(I8Ho!e+RF01MeLiG^TYhF zcnsXwrRct-V8twLZusNLj}EtN(Ihtsq+eFWw^tV>pWac_a&ye*?52TqmDKfq%pPZ1 zd`QXBd+@VdU=V)i7Lr(K?oIoL@b>Q?TKA8-z0+d#V)Dr^LRX?by+On+7Op%V&O z&&!b$3mWgmy|8pR7rDSvqJXq;UBF^ktj#4scsPKiQV!q)Hb{XYBP~@7pHnKAzd%j` ziyMRI%mp0GB4)6hgcdb}ZBa42Ob$C(V#p-l%_Asloq>=#ZfkKpF(VYwCu)U69HrId z;c_x`ts0lBwrcbAMg(J1XX|!F4v9nwV|ZLK^2e;%pIEHXzA$tZI^AEeX0BXYB}ncEe1t%2px0X8v>_4#T`MelkKhZfa)ie9wn~qtT z#$M4WBYNs;=ZlXOM`BICYo715?{H1V!iKcoMHYpjfb~H0{l^=6E(e9wjNme_(Zii< zG!46-riu>)`u%HobNfhbPQYDoIC!WiptRmiRlfa_J$m0`(<^&s19i1M& z-;s1PQeuJ|WzP4-hXmmvgZAcV`J20fKRlzfeM3HG4m|gJqZTs-4Q>c5?VH-Rb7HFe zfyV{L-lT}tFKzNpI^!P+JDWRQ%h}v#+C;Z#w%R7D+>Y`m;u@-#|8L*axIz!G84qjM zY72lY6y@-Pn6B3Z0P*NN;ScO z5RQX3KVbq5%vOVDf;X9i&~eQ1g3IGNcu||OPUXFx83DhS(AQhJ8_Ac;8eBE3fVQ;c zSl>lsF7NzR8Jj7&gk1?bQ4r=58ILC=7Xcg z;jD5g(>ZB!^p<|??DpZ!TPD=Yix;&!2M?uWx6X6jcCZiG|69|={y#ZQv8&^b`}vx< zlWPv_su>UdHmm3Ev6GdS+Z~0E6)%>xbtfbaJ)byW)2~!!`%c%`!czWO>7!Zt=D7C4 z%L(b<3>l2DhSH5sK3LkAe(hS4_sInt?WFA4#jgsR+(Q)g*W11s|0+}+E`F5N^vg84 z^~sh`@3i)Bsh$db81}@TapLsZ@)+C2%ffW-R z0Vw+Vk@rrYA1vH)S@MJT6HCbIJNn{FpDb1teGPx|+0lZ13V*-pw>zqiC|*`S?Y=6! z{?sqv?S-BGjrC?|1WL5?mJRHQ95NGieJff6zn&BgcY8N(s=KJzR0X$&G}Q*8lWQ8g zf=zW@^^Nv5nGd5Y-5>3=vTu~1t7^T)`a&5XIVa`z=XYCcO9i)XFAYP4~zTB5skbr$zN03awP*W+&jPh^P#8lIbD6#%yY*2_v1Z+>OF7jv0ryrmt@rs z@U8L36N4v9CS1?Im{)ahN!Q}ymh+#_pT7H@)9D+wy#F$QsHKLHLadS&SnP0EYPx# diff --git a/Session/Meta/Images.xcassets/Session/CallOutgoing.imageset/CallOutgoing.pdf b/Session/Meta/Images.xcassets/Session/CallOutgoing.imageset/CallOutgoing.pdf index 92206098f17d19aa779106cb1276b136c4272c06..e7c0d3f42ae4aee66a441bb73f0261e3aecda117 100644 GIT binary patch delta 1915 zcmah~c{J3G8aAJ7W6e^QFMhUW$YI8q-;A;^$(QU7Ld=XAjAblCH9n24K{q!V!bE70 zHK7pGs83|e&X7cgRFtB!<#v7Df9^TmbMAZIKc4q_-}9XF{PRB5x=HXXb2=J<0?f>y zG1O=xE*yFR;&J12L>WdRb@PV~H3_a^n=yJEmhT}I07MF$c>H)%WIgv%%j_+0YWrDf zv+655ewT9Rh?5iD`ElITZM`+(M04CGoGnF3_|_Qug==TvvRjDac-7RbDVFn8xk0`L zT^!oeZwn7*z7e}Fwn=@J{9_oFaqc|@KoNic;t|=P^oy!W&_^qCsS@nrW*_ABahoTVEPBZB%|#1>zk8D@Orf#xcY675rN8Fyhy~3(aV=I| zNn`k@OhImTz;1K=96`Zjs3*o1hNlINqfa5KF~wHNIU$`7>z_Y5>Q&)Wd?lt!W6=BN z+FMx1$l+a5?BLM~x3LjsoAc*0^6eZ2pV*fB2ly@ae2bl{4=kf=i@uzg@c4&;O%SnY zvAl`v85>%afTBMeyo z#U18gaOLu7;Z&6?%=uKg;11E{{7$A9t4$@RlBp9Kk;+aM=;W#=TB^IHi=LKzJNdp@ zUxp>rFa+web2D05vH3OJ`k2}G-!-G#6dWJd7R1=_%;+?}^PB4ea$f0cvq!gu`|V*+o@GmO@xgvlvgd@!@=?OE*u)j(i+tb6CrC{h z#j&Mh2-1A$OOb0DY!|Ei%VW=1GL?s|tjs~%nlgb4PSRdE*6;JQNF5hfK@j#N&hXA~ z7wmurU!D7sX-Y|5p zI!3fyQuJkheudf^%|oU5_pLm4GvBO>q=Q^>=rezX&dj6%Vt3|%+SN0sTi4S2g3~lS zdjpQOt5Pq@>Bw+sWNL4ig<|#d;E_ytO2J2`d`_NO_UOiOIOCw&6{ONVod}q=Ie$32 zCnHTYdr^snJOrA-Up~C4lm1Ff0@A2lk=4a=uvRa=o@y$kMkz4FOZ$BOO}?B-|FZ-+ zx#*y9ZsJJR-T*gKvM3)vid}veHI~x)HES!WaCQC%PZvL8JsfH(@vJae+?60m|Rn{wS(^jy2@NXL`k@=Z?-3C(Ueg(@`T4~aoYi3PZLz^4Ra_N z6=d-&lO{)_^eoE(OZViSk9oh*$Gk0UHt(ic&1`%;%&#CbGEH8dUvzp>Wer* zBlQms1u)U%TiPe@1Q>YQ@T20nt)s~huW2RTRW08)1EyazUiauKt(boaS z9B7DQX3DG3jR6B!3WW+_emXR;k04Qiebs%p2KF|94`6~J;?O7p3XdQlkyw%;e%}j$ oghpTp#&{5qLzB#)|NjK=`%kRmqNzVE8)2{*sJ6DP(@6;Q-!unYCIA2c delta 2147 zcmb7FX;>5I7FG*#9aJDJvXnZdY+^AplgS1c3`-OXf)$eKMZ?WxO2&|pF*GU3&=c9>-M7dnuholKWBbzLjO^}cx#WHA?cWI!4aN3 zw$XCjWV|ovmAhw$+;RZG<D)>=$u;(%;soF6P6wVBB-X(#9=m9^B)t4)T(QBbmrR? z=)_QEA|9$cd0{GCcNLwl{k)fk2)q^X37D90^@_v9v6~bLDm9Ek)VC=~A|+sNt}lxz z0CXOGaFtjoOCS_V5QelNQqd}e^7S&YA{qx_cp4JOlq$koiYY+`1El+7ZwX1Trx|O;(t7?91PSWK* zpS=%BH>Kr?-tN{th5V-SS9cyX2Hxg{ko02_dJhDJ91oZ7zFHsghnY@CPs*BpB_5jH zoUk#kKuO1Lp?y}@^UY~bpXUr3wHogUT<$s5ByO9@1Z~#3a=uA_x>MRNy(u_JRb1DB zHCQ~=-M`zqI834f(Pv zsC-1vPi>x;4Ftq4>K+#Ge^jQVoSB-smYXp&VKn;U^x)`k-%ITW&auY4Klcypu+6k< zx$5%O@{Svo{ngv$XGc{j!W`!HYiT4)I=T9~{$x~r*f~kKWUg0f;?%5r^<}5Rsy|Q4 zQMvc>4_Yg7CvPPjU0T(Dpyq~H*g5sPzLQnZdmml#GRS86ie&wxeM3XZpIoRjA8Oz2 zdB<(-$BUlsxpe7b&B5CCBL_rB2d%3TK9kww`Xj>^YrcbTU(UbPlectRyJ-OMjP-QhdBfUMRRJY1tYbWpF&*?WrXF24VJ+chPi0 zlOJN}{-W`RqDL<4?-rAFhL`tUL*;r6X|@K>2EI%3jxRbA7!7`-FBpz^pR{bry*>#^xIyJPbBZd5qceCN5f26|lq2StBUx2! zk+(~fx+J#P^1|~jqdS{3Vw`<<+Hr`KEj|_dSC)v@o1)eCqkk@gD`WZDjt`>GSgMGu z`*2-B8Kb0<{pdx>lfxV86cbj-Y_~(?qRf$6&#qzB$wa+5YXY5(cXM{|6e~Y&XtT}U zsV=Fj4O#HAcfZx6k%YWFVQ5o*)ITH7R<%ccIls$K=IqwpWejBYmP-9n&pvMKpaDsd zN@0Fo{aF30!^bjmHAR%VC6TPeV*r%PfdK~x-#`KI2T#U19RL6T diff --git a/SessionMessagingKit/Messages/Signal/TSInfoMessage.m b/SessionMessagingKit/Messages/Signal/TSInfoMessage.m index 833c1cd37..f5578ea7b 100644 --- a/SessionMessagingKit/Messages/Signal/TSInfoMessage.m +++ b/SessionMessagingKit/Messages/Signal/TSInfoMessage.m @@ -113,6 +113,8 @@ NSUInteger TSInfoMessageSchemaVersion = 1; return NSLocalizedString(@"GROUP_YOU_LEFT", @""); case TSInfoMessageTypeGroupUpdated: return _customMessage != nil ? _customMessage : NSLocalizedString(@"GROUP_UPDATED", @""); + case TSInfoMessageTypeCall: + return _customMessage; default: break; } From 570c3fbe3bf1266342a638b675ad2e50d5933f8b Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 29 Nov 2021 16:32:02 +1100 Subject: [PATCH 134/368] add ringtone and other UI improvement --- Session.xcodeproj/project.pbxproj | 12 ++++-- .../SessionCallManager+CXProvider.swift | 1 + .../Call Management/SessionCallManager.swift | 2 +- Session/Calls/CallVC.swift | 1 + .../Views & Modals/IncomingCallBanner.swift | 6 ++- Session/Meta/AppDelegate.swift | 16 +++++--- Session/Meta/AudioFiles/ringing.mp3 | Bin 0 -> 55057 bytes .../General/CallRingTonePlayer.swift | 37 ++++++++++++++++++ SessionUtilitiesKit/General/Vibration.swift | 19 --------- 9 files changed, 63 insertions(+), 31 deletions(-) create mode 100644 Session/Meta/AudioFiles/ringing.mp3 create mode 100644 SessionUtilitiesKit/General/CallRingTonePlayer.swift delete mode 100644 SessionUtilitiesKit/General/Vibration.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 569aadfef..c423f2c19 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -139,6 +139,7 @@ 7B0EFDEE274F598600FFAAE7 /* TimestampUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */; }; 7B0EFDF0275084AA00FFAAE7 /* CallMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */; }; 7B0EFDF2275449AA00FFAAE7 /* TSInfoMessage+Calls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDF1275449AA00FFAAE7 /* TSInfoMessage+Calls.swift */; }; + 7B0EFDF4275490EA00FFAAE7 /* ringing.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 7B0EFDF3275490EA00FFAAE7 /* ringing.mp3 */; }; 7B1581E2271E743B00848B49 /* OWSSounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E1271E743B00848B49 /* OWSSounds.swift */; }; 7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E3271FC59C00848B49 /* CallModal.swift */; }; 7B1581E6271FD2A100848B49 /* VideoPreviewVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */; }; @@ -149,7 +150,7 @@ 7B7CB18B270591630079FF93 /* ShareLogsModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18A270591630079FF93 /* ShareLogsModal.swift */; }; 7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */; }; 7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */; }; - 7B7CB192271508AD0079FF93 /* Vibration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB191271508AD0079FF93 /* Vibration.swift */; }; + 7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB191271508AD0079FF93 /* CallRingTonePlayer.swift */; }; 7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA68908272A27BE00EFC32F /* SessionCall.swift */; }; 7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */; }; 7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */; }; @@ -1131,6 +1132,7 @@ 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampUtils.swift; sourceTree = ""; }; 7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageCell.swift; sourceTree = ""; }; 7B0EFDF1275449AA00FFAAE7 /* TSInfoMessage+Calls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSInfoMessage+Calls.swift"; sourceTree = ""; }; + 7B0EFDF3275490EA00FFAAE7 /* ringing.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringing.mp3; sourceTree = ""; }; 7B1581E1271E743B00848B49 /* OWSSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSSounds.swift; sourceTree = ""; }; 7B1581E3271FC59C00848B49 /* CallModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModal.swift; sourceTree = ""; }; 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPreviewVC.swift; sourceTree = ""; }; @@ -1142,7 +1144,7 @@ 7B7CB18A270591630079FF93 /* ShareLogsModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareLogsModal.swift; sourceTree = ""; }; 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallBanner.swift; sourceTree = ""; }; 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniCallView.swift; sourceTree = ""; }; - 7B7CB191271508AD0079FF93 /* Vibration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vibration.swift; sourceTree = ""; }; + 7B7CB191271508AD0079FF93 /* CallRingTonePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallRingTonePlayer.swift; sourceTree = ""; }; 7BA68908272A27BE00EFC32F /* SessionCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCall.swift; sourceTree = ""; }; 7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+CXCallController.swift"; sourceTree = ""; }; 7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+CXProvider.swift"; sourceTree = ""; }; @@ -1953,6 +1955,7 @@ 34074F54203D0722004596AE /* Sounds */ = { isa = PBXGroup; children = ( + 7B0EFDF3275490EA00FFAAE7 /* ringing.mp3 */, 45A2F004204473A3002E978A /* NewMessage.aifc */, 34661FB720C1C0D60056EDD6 /* message_sent.aiff */, 34CF0783203E6B77005C4D61 /* busy_tone_ansi.caf */, @@ -2373,7 +2376,7 @@ C38EF23D255B6D66007E1867 /* UIView+OWS.h */, C38EF23E255B6D66007E1867 /* UIView+OWS.m */, C38EF2EF255B6DBB007E1867 /* Weak.swift */, - 7B7CB191271508AD0079FF93 /* Vibration.swift */, + 7B7CB191271508AD0079FF93 /* CallRingTonePlayer.swift */, 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */, ); path = General; @@ -4218,6 +4221,7 @@ 45A2F005204473A3002E978A /* NewMessage.aifc in Resources */, 45B74A882044AAB600CD42F8 /* aurora.aifc in Resources */, 45B74A742044AAB600CD42F8 /* aurora-quiet.aifc in Resources */, + 7B0EFDF4275490EA00FFAAE7 /* ringing.mp3 in Resources */, 45B74A852044AAB600CD42F8 /* bamboo.aifc in Resources */, C3A01E06261D24C400290BEB /* storage-seed-1.der in Resources */, 45B74A782044AAB600CD42F8 /* bamboo-quiet.aifc in Resources */, @@ -4663,7 +4667,7 @@ C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */, C352A36D2557858E00338F3E /* NSTimer+Proxying.m in Sources */, C32C5A2D256DB849003C73A2 /* LKGroupUtilities.m in Sources */, - 7B7CB192271508AD0079FF93 /* Vibration.swift in Sources */, + 7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */, C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */, B8856E09256F1676001CE70E /* UIDevice+featureSupport.swift in Sources */, B8856DEF256F161F001CE70E /* NSString+SSK.m in Sources */, diff --git a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift index 44dd8025f..7b3fe2d3a 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift @@ -72,6 +72,7 @@ extension SessionCallManager: CXProviderDelegate { AssertIsOnMainThread() guard let call = self.currentCall else { return } call.webRTCSession.audioSessionDidActivate(audioSession) + if call.isOutgoing && !call.hasConnected { CallRingTonePlayer.shared.startPlayingRingTone() } } public func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index a229f92cb..ef68ee87f 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -69,7 +69,7 @@ public final class SessionCallManager: NSObject { self.provider.reportOutgoingCall(with: call.callID, connectedAt: call.connectedDate) } } - callTimeOutTimer = Timer.scheduledTimer(withTimeInterval: 60, repeats: false) { _ in + callTimeOutTimer = Timer.scheduledTimer(withTimeInterval: 30, repeats: false) { _ in guard let currentCall = self.currentCall else { return } currentCall.didTimeout = true self.endCall(currentCall) { error in diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index bd01d0560..af43db4f6 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -195,6 +195,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { } self.call.hasConnectedDidChange = { DispatchQueue.main.async { + CallRingTonePlayer.shared.stopPlayingRingTone() self.callInfoLabel.text = "Connected" self.minimizeButton.isHidden = false UIView.animate(withDuration: 0.5, delay: 1, options: [], animations: { diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index e5e80d40f..9ba23cf3f 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -173,11 +173,13 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: { self.alpha = 1.0 }, completion: nil) - Vibration.shared.startVibration() + CallRingTonePlayer.shared.startVibration() + CallRingTonePlayer.shared.startPlayingRingTone() } public func dismiss() { - Vibration.shared.stopVibrationIfPossible() + CallRingTonePlayer.shared.stopVibrationIfPossible() + CallRingTonePlayer.shared.stopPlayingRingTone() UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: { self.alpha = 0.0 }, completion: { _ in diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index f90ec017c..03f15508c 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -48,12 +48,21 @@ extension AppDelegate { } } + private func insertCallInfoMessage(for message: CallMessage, using transaction: YapDatabaseReadWriteTransaction) -> TSInfoMessage { + let thread = TSContactThread.getOrCreateThread(withContactSessionID: message.sender!, transaction: transaction) + let infoMessage = TSInfoMessage.from(message, associatedWith: thread) + infoMessage.save(with: transaction) + return infoMessage + } + @objc func setUpCallHandling() { // Pre offer messages MessageReceiver.handleNewCallOfferMessageIfNeeded = { (message, transaction) in guard CurrentAppContext().isMainApp else { return } guard SSKPreferences.areCallsEnabled else { - // TODO: Show tips and insert a missing call message + let infoMessage = self.insertCallInfoMessage(for: message, using: transaction) + infoMessage.updateCallInfoMessage(.missed, using: transaction) + // TODO: add tips return } let callManager = AppEnvironment.shared.callManager @@ -63,10 +72,7 @@ extension AppDelegate { callManager.handleIncomingCallOfferInBusyState(offerMessage: message, using: transaction) return } - // Create incoming call message - let thread = TSContactThread.getOrCreateThread(withContactSessionID: message.sender!, transaction: transaction) - let infoMessage = TSInfoMessage.from(message, associatedWith: thread) - infoMessage.save(with: transaction) + let infoMessage = self.insertCallInfoMessage(for: message, using: transaction) // Handle UI if let caller = message.sender, let uuid = message.uuid { let call = SessionCall(for: caller, uuid: uuid, mode: .answer) diff --git a/Session/Meta/AudioFiles/ringing.mp3 b/Session/Meta/AudioFiles/ringing.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..547f7133ca264c54265de39cb2b9d982377ac894 GIT binary patch literal 55057 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