Implement CallManager

pull/560/head
Niels Andriesse 3 years ago
parent 030c869d06
commit 1a12199d0b

@ -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 = "<group>"; };
B8D8F1EF256621180092EF10 /* MessageSender+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "MessageSender+Convenience.swift"; path = "../../SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift"; sourceTree = "<group>"; };
B8DE1FAF26C228780079C9CE /* SignalRingRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SignalRingRTC.framework; path = Dependencies/SignalRingRTC.framework; sourceTree = "<group>"; };
B8DE1FB326C22F2F0079C9CE /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; };
B8DE1FB326C22F2F0079C9CE /* CallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = "<group>"; };
B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessage.swift; sourceTree = "<group>"; };
B8EB20E6263F7E4B00773E52 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+OpenGroupInvitation.swift"; sourceTree = "<group>"; };
@ -2334,17 +2334,10 @@
path = Shared;
sourceTree = "<group>";
};
B8DE1FB126C22AF20079C9CE /* Calls */ = {
isa = PBXGroup;
children = (
);
path = Calls;
sourceTree = "<group>";
};
B8DE1FB226C22F1F0079C9CE /* Calls */ = {
isa = PBXGroup;
children = (
B8DE1FB326C22F2F0079C9CE /* File.swift */,
B8DE1FB326C22F2F0079C9CE /* CallManager.swift */,
);
path = Calls;
sourceTree = "<group>";
@ -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 */,

@ -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<Void> {
guard let thread = TSContactThread.fetch(for: publicKey, using: transaction) else { return Promise(error: Error.noThread) }
let (promise, seal) = Promise<Void>.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<Void> {
guard let thread = TSContactThread.fetch(for: publicKey, using: transaction) else { return Promise(error: Error.noThread) }
let (promise, seal) = Promise<Void>.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.")
}
}

@ -1,16 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import WebRTC
//The RTCSessionDescription interface describes one end of a connectionor potential connectionand 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: "")
}
}

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

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

Loading…
Cancel
Save