Batch send ICE candidates

pull/560/head
Niels Andriesse 4 years ago
parent 525eb40d8d
commit 1ad42547b2

@ -37,6 +37,7 @@ final class CallVCV2 : UIViewController, WebRTCWrapperDelegate {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
view.backgroundColor = .black
WebRTCWrapper.current = webRTCWrapper WebRTCWrapper.current = webRTCWrapper
setUpViewHierarchy() setUpViewHierarchy()
cameraManager.prepare() cameraManager.prepare()

@ -7,7 +7,7 @@ extension AppDelegate {
func setUpCallHandling() { func setUpCallHandling() {
MessageReceiver.handleOfferCallMessage = { message in MessageReceiver.handleOfferCallMessage = { message in
DispatchQueue.main.async { 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 guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully
let alert = UIAlertController(title: "Incoming Call", message: nil, preferredStyle: .alert) let alert = UIAlertController(title: "Incoming Call", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Accept", style: .default, handler: { _ in alert.addAction(UIAlertAction(title: "Accept", style: .default, handler: { _ in

@ -2,9 +2,9 @@ import WebRTC
extension WebRTCWrapper { extension WebRTCWrapper {
public func handleICECandidate(_ candidate: RTCIceCandidate) { public func handleICECandidates(_ candidate: [RTCIceCandidate]) {
print("[Calls] Received ICE candidate message.") print("[Calls] Received ICE candidate message.")
peerConnection.add(candidate) candidate.forEach { peerConnection.add($0) }
} }
public func handleRemoteSDP(_ sdp: RTCSessionDescription, from sessionID: String) { public func handleRemoteSDP(_ sdp: RTCSessionDescription, from sessionID: String) {
@ -15,7 +15,6 @@ extension WebRTCWrapper {
} else { } else {
guard let self = self, guard let self = self,
sdp.type == .offer, self.peerConnection.localDescription == nil else { return } sdp.type == .offer, self.peerConnection.localDescription == nil else { return }
// Automatically answer the call
Storage.write { transaction in Storage.write { transaction in
self.sendAnswer(to: sessionID, using: transaction).retainUntilComplete() self.sendAnswer(to: sessionID, using: transaction).retainUntilComplete()
} }

@ -9,6 +9,8 @@ public protocol WebRTCWrapperDelegate : AnyObject {
public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate { public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate {
public weak var delegate: WebRTCWrapperDelegate? public weak var delegate: WebRTCWrapperDelegate?
private let contactSessionID: String private let contactSessionID: String
private var queuedICECandidates: [RTCIceCandidate] = []
private var iceCandidateSendTimer: Timer?
private let defaultICEServers = [ private let defaultICEServers = [
"stun:stun.l.google.com:19302", "stun:stun.l.google.com:19302",
@ -101,7 +103,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate {
audioSession.unlockForConfiguration() audioSession.unlockForConfiguration()
} }
// MARK: Call Management // MARK: Signaling
public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise<Void> { public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise<Void> {
print("[Calls] Initiating call.") print("[Calls] Initiating call.")
guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) } 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 { DispatchQueue.main.async {
let message = CallMessage() let message = CallMessage()
message.kind = .offer message.kind = .offer
message.sdp = sdp.sdp message.sdps = [ sdp.sdp ]
MessageSender.send(message, in: thread, using: transaction) MessageSender.send(message, in: thread, using: transaction)
seal.fulfill(()) seal.fulfill(())
} }
@ -147,7 +149,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate {
DispatchQueue.main.async { DispatchQueue.main.async {
let message = CallMessage() let message = CallMessage()
message.kind = .answer message.kind = .answer
message.sdp = sdp.sdp message.sdps = [ sdp.sdp ]
MessageSender.send(message, in: thread, using: transaction) MessageSender.send(message, in: thread, using: transaction)
seal.fulfill(()) seal.fulfill(())
} }
@ -156,6 +158,29 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate {
return promise 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() { public func dropConnection() {
peerConnection.close() peerConnection.close()
} }
@ -187,13 +212,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate {
public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) { public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {
print("[Calls] ICE candidate generated.") print("[Calls] ICE candidate generated.")
Storage.write { transaction in queueICECandidateForSending(candidate)
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]) { public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {

@ -1,25 +1,27 @@
import WebRTC 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. /// See https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription for more information.
@objc(SNCallMessage) @objc(SNCallMessage)
public final class CallMessage : ControlMessage { public final class CallMessage : ControlMessage {
public var kind: Kind? public var kind: Kind?
/// See https://developer.mozilla.org/en-US/docs/Glossary/SDP for more information. /// See https://developer.mozilla.org/en-US/docs/Glossary/SDP for more information.
public var sdp: String? public var sdps: [String]?
// MARK: Kind // MARK: Kind
public enum Kind : Codable, CustomStringConvertible { public enum Kind : Codable, CustomStringConvertible {
case offer case offer
case answer case answer
case provisionalAnswer case provisionalAnswer
case iceCandidate(sdpMLineIndex: UInt32, sdpMid: String) case iceCandidates(sdpMLineIndexes: [UInt32], sdpMids: [String])
public var description: String { public var description: String {
switch self { switch self {
case .offer: return "offer" case .offer: return "offer"
case .answer: return "answer" case .answer: return "answer"
case .provisionalAnswer: return "provisionalAnswer" case .provisionalAnswer: return "provisionalAnswer"
case .iceCandidate(_, _): return "iceCandidate" case .iceCandidates(_, _): return "iceCandidates"
} }
} }
} }
@ -27,16 +29,17 @@ public final class CallMessage : ControlMessage {
// MARK: Initialization // MARK: Initialization
public override init() { super.init() } public override init() { super.init() }
internal init(kind: Kind, sdp: String) { internal init(kind: Kind, sdps: [String]) {
super.init() super.init()
self.kind = kind self.kind = kind
self.sdp = sdp self.sdps = sdps
} }
// MARK: Validation // MARK: Validation
public override var isValid: Bool { public override var isValid: Bool {
guard super.isValid else { return false } 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 // MARK: Coding
@ -47,13 +50,13 @@ public final class CallMessage : ControlMessage {
case "offer": kind = .offer case "offer": kind = .offer
case "answer": kind = .answer case "answer": kind = .answer
case "provisionalAnswer": kind = .provisionalAnswer case "provisionalAnswer": kind = .provisionalAnswer
case "iceCandidate": case "iceCandidates":
guard let sdpMLineIndex = coder.decodeObject(forKey: "sdpMLineIndex") as? UInt32, guard let sdpMLineIndexes = coder.decodeObject(forKey: "sdpMLineIndexes") as? [UInt32],
let sdpMid = coder.decodeObject(forKey: "sdpMid") as? String else { return nil } let sdpMids = coder.decodeObject(forKey: "sdpMids") as? [String] else { return nil }
kind = .iceCandidate(sdpMLineIndex: sdpMLineIndex, sdpMid: sdpMid) kind = .iceCandidates(sdpMLineIndexes: sdpMLineIndexes, sdpMids: sdpMids)
default: preconditionFailure() 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) { public override func encode(with coder: NSCoder) {
@ -62,13 +65,13 @@ public final class CallMessage : ControlMessage {
case .offer: coder.encode("offer", forKey: "kind") case .offer: coder.encode("offer", forKey: "kind")
case .answer: coder.encode("answer", forKey: "kind") case .answer: coder.encode("answer", forKey: "kind")
case .provisionalAnswer: coder.encode("provisionalAnswer", forKey: "kind") case .provisionalAnswer: coder.encode("provisionalAnswer", forKey: "kind")
case let .iceCandidate(sdpMLineIndex, sdpMid): case let .iceCandidates(sdpMLineIndexes, sdpMids):
coder.encode("iceCandidate", forKey: "kind") coder.encode("iceCandidates", forKey: "kind")
coder.encode(sdpMLineIndex, forKey: "sdpMLineIndex") coder.encode(sdpMLineIndexes, forKey: "sdpMLineIndexes")
coder.encode(sdpMid, forKey: "sdpMid") coder.encode(sdpMids, forKey: "sdpMids")
default: preconditionFailure() default: preconditionFailure()
} }
coder.encode(sdp, forKey: "sdp") coder.encode(sdps, forKey: "sdps")
} }
// MARK: Proto Conversion // MARK: Proto Conversion
@ -79,17 +82,17 @@ public final class CallMessage : ControlMessage {
case .offer: kind = .offer case .offer: kind = .offer
case .answer: kind = .answer case .answer: kind = .answer
case .provisionalAnswer: kind = .provisionalAnswer case .provisionalAnswer: kind = .provisionalAnswer
case .iceCandidate: case .iceCandidates:
let sdpMLineIndex = callMessageProto.sdpMlineIndex let sdpMLineIndexes = callMessageProto.sdpMlineIndexes
guard let sdpMid = callMessageProto.sdpMid else { return nil } let sdpMids = callMessageProto.sdpMids
kind = .iceCandidate(sdpMLineIndex: sdpMLineIndex, sdpMid: sdpMid) kind = .iceCandidates(sdpMLineIndexes: sdpMLineIndexes, sdpMids: sdpMids)
} }
let sdp = callMessageProto.sdp let sdps = callMessageProto.sdps
return CallMessage(kind: kind, sdp: sdp) return CallMessage(kind: kind, sdps: sdps)
} }
public override func toProto(using transaction: YapDatabaseReadWriteTransaction) -> SNProtoContent? { 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).") SNLog("Couldn't construct call message proto from: \(self).")
return nil return nil
} }
@ -101,12 +104,13 @@ public final class CallMessage : ControlMessage {
case .offer: type = .offer case .offer: type = .offer
case .answer: type = .answer case .answer: type = .answer
case .provisionalAnswer: type = .provisionalAnswer case .provisionalAnswer: type = .provisionalAnswer
case .iceCandidate(_, _): type = .iceCandidate case .iceCandidates(_, _): type = .iceCandidates
} }
let callMessageProto = SNProtoCallMessage.builder(type: type, sdp: sdp) let callMessageProto = SNProtoCallMessage.builder(type: type)
if case let .iceCandidate(sdpMLineIndex, sdpMid) = kind { callMessageProto.setSdps(sdps)
callMessageProto.setSdpMlineIndex(sdpMLineIndex) if case let .iceCandidates(sdpMLineIndexes, sdpMids) = kind {
callMessageProto.setSdpMid(sdpMid) callMessageProto.setSdpMlineIndexes(sdpMLineIndexes)
callMessageProto.setSdpMids(sdpMids)
} }
let contentProto = SNProtoContent.builder() let contentProto = SNProtoContent.builder()
do { do {
@ -123,7 +127,7 @@ public final class CallMessage : ControlMessage {
""" """
CallMessage( CallMessage(
kind: \(kind?.description ?? "null"), kind: \(kind?.description ?? "null"),
sdp: \(sdp ?? "null") sdps: \(sdps?.description ?? "null")
) )
""" """
} }

@ -656,7 +656,7 @@ extension SNProtoContent.SNProtoContentBuilder {
case offer = 1 case offer = 1
case answer = 2 case answer = 2
case provisionalAnswer = 3 case provisionalAnswer = 3
case iceCandidate = 4 case iceCandidates = 4
} }
private class func SNProtoCallMessageTypeWrap(_ value: SessionProtos_CallMessage.TypeEnum) -> SNProtoCallMessageType { private class func SNProtoCallMessageTypeWrap(_ value: SessionProtos_CallMessage.TypeEnum) -> SNProtoCallMessageType {
@ -664,7 +664,7 @@ extension SNProtoContent.SNProtoContentBuilder {
case .offer: return .offer case .offer: return .offer
case .answer: return .answer case .answer: return .answer
case .provisionalAnswer: return .provisionalAnswer case .provisionalAnswer: return .provisionalAnswer
case .iceCandidate: return .iceCandidate case .iceCandidates: return .iceCandidates
} }
} }
@ -673,25 +673,22 @@ extension SNProtoContent.SNProtoContentBuilder {
case .offer: return .offer case .offer: return .offer
case .answer: return .answer case .answer: return .answer
case .provisionalAnswer: return .provisionalAnswer case .provisionalAnswer: return .provisionalAnswer
case .iceCandidate: return .iceCandidate case .iceCandidates: return .iceCandidates
} }
} }
// MARK: - SNProtoCallMessageBuilder // MARK: - SNProtoCallMessageBuilder
@objc public class func builder(type: SNProtoCallMessageType, sdp: String) -> SNProtoCallMessageBuilder { @objc public class func builder(type: SNProtoCallMessageType) -> SNProtoCallMessageBuilder {
return SNProtoCallMessageBuilder(type: type, sdp: sdp) return SNProtoCallMessageBuilder(type: type)
} }
// asBuilder() constructs a builder that reflects the proto's contents. // asBuilder() constructs a builder that reflects the proto's contents.
@objc public func asBuilder() -> SNProtoCallMessageBuilder { @objc public func asBuilder() -> SNProtoCallMessageBuilder {
let builder = SNProtoCallMessageBuilder(type: type, sdp: sdp) let builder = SNProtoCallMessageBuilder(type: type)
if hasSdpMlineIndex { builder.setSdps(sdps)
builder.setSdpMlineIndex(sdpMlineIndex) builder.setSdpMlineIndexes(sdpMlineIndexes)
} builder.setSdpMids(sdpMids)
if let _value = sdpMid {
builder.setSdpMid(_value)
}
return builder return builder
} }
@ -701,27 +698,44 @@ extension SNProtoContent.SNProtoContentBuilder {
@objc fileprivate override init() {} @objc fileprivate override init() {}
@objc fileprivate init(type: SNProtoCallMessageType, sdp: String) { @objc fileprivate init(type: SNProtoCallMessageType) {
super.init() super.init()
setType(type) setType(type)
setSdp(sdp)
} }
@objc public func setType(_ valueParam: SNProtoCallMessageType) { @objc public func setType(_ valueParam: SNProtoCallMessageType) {
proto.type = SNProtoCallMessageTypeUnwrap(valueParam) proto.type = SNProtoCallMessageTypeUnwrap(valueParam)
} }
@objc public func setSdp(_ valueParam: String) { @objc public func addSdps(_ valueParam: String) {
proto.sdp = valueParam var items = proto.sdps
items.append(valueParam)
proto.sdps = items
}
@objc public func setSdps(_ wrappedItems: [String]) {
proto.sdps = wrappedItems
}
@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 setSdpMlineIndex(_ valueParam: UInt32) { @objc public func addSdpMids(_ valueParam: String) {
proto.sdpMlineIndex = valueParam var items = proto.sdpMids
items.append(valueParam)
proto.sdpMids = items
} }
@objc public func setSdpMid(_ valueParam: String) { @objc public func setSdpMids(_ wrappedItems: [String]) {
proto.sdpMid = valueParam proto.sdpMids = wrappedItems
} }
@objc public func build() throws -> SNProtoCallMessage { @objc public func build() throws -> SNProtoCallMessage {
@ -737,31 +751,22 @@ extension SNProtoContent.SNProtoContentBuilder {
@objc public let type: SNProtoCallMessageType @objc public let type: SNProtoCallMessageType
@objc public let sdp: String @objc public var sdps: [String] {
return proto.sdps
@objc public var sdpMlineIndex: UInt32 {
return proto.sdpMlineIndex
}
@objc public var hasSdpMlineIndex: Bool {
return proto.hasSdpMlineIndex
} }
@objc public var sdpMid: String? { @objc public var sdpMlineIndexes: [UInt32] {
guard proto.hasSdpMid else { return proto.sdpMlineIndexes
return nil
}
return proto.sdpMid
} }
@objc public var hasSdpMid: Bool {
return proto.hasSdpMid @objc public var sdpMids: [String] {
return proto.sdpMids
} }
private init(proto: SessionProtos_CallMessage, private init(proto: SessionProtos_CallMessage,
type: SNProtoCallMessageType, type: SNProtoCallMessageType) {
sdp: String) {
self.proto = proto self.proto = proto
self.type = type self.type = type
self.sdp = sdp
} }
@objc @objc
@ -780,18 +785,12 @@ extension SNProtoContent.SNProtoContentBuilder {
} }
let type = SNProtoCallMessageTypeWrap(proto.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: - Begin Validation Logic for SNProtoCallMessage -
// MARK: - End Validation Logic for SNProtoCallMessage - // MARK: - End Validation Logic for SNProtoCallMessage -
let result = SNProtoCallMessage(proto: proto, let result = SNProtoCallMessage(proto: proto,
type: type, type: type)
sdp: sdp)
return result return result
} }

@ -319,33 +319,11 @@ struct SessionProtos_CallMessage {
/// Clears the value of `type`. Subsequent reads from it will return its default value. /// Clears the value of `type`. Subsequent reads from it will return its default value.
mutating func clearType() {self._type = nil} mutating func clearType() {self._type = nil}
/// @required var sdps: [String] = []
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 sdpMlineIndex: UInt32 { var sdpMlineIndexes: [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 { var sdpMids: [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() var unknownFields = SwiftProtobuf.UnknownStorage()
@ -354,7 +332,7 @@ struct SessionProtos_CallMessage {
case offer // = 1 case offer // = 1
case answer // = 2 case answer // = 2
case provisionalAnswer // = 3 case provisionalAnswer // = 3
case iceCandidate // = 4 case iceCandidates // = 4
init() { init() {
self = .offer self = .offer
@ -365,7 +343,7 @@ struct SessionProtos_CallMessage {
case 1: self = .offer case 1: self = .offer
case 2: self = .answer case 2: self = .answer
case 3: self = .provisionalAnswer case 3: self = .provisionalAnswer
case 4: self = .iceCandidate case 4: self = .iceCandidates
default: return nil default: return nil
} }
} }
@ -375,7 +353,7 @@ struct SessionProtos_CallMessage {
case .offer: return 1 case .offer: return 1
case .answer: return 2 case .answer: return 2
case .provisionalAnswer: return 3 case .provisionalAnswer: return 3
case .iceCandidate: return 4 case .iceCandidates: return 4
} }
} }
@ -384,9 +362,6 @@ struct SessionProtos_CallMessage {
init() {} init() {}
fileprivate var _type: SessionProtos_CallMessage.TypeEnum? = nil 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) #if swift(>=4.2)
@ -1817,14 +1792,13 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
static let protoMessageName: String = _protobuf_package + ".CallMessage" static let protoMessageName: String = _protobuf_package + ".CallMessage"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "type"), 1: .same(proto: "type"),
2: .same(proto: "sdp"), 2: .same(proto: "sdps"),
3: .same(proto: "sdpMLineIndex"), 3: .same(proto: "sdpMLineIndexes"),
4: .same(proto: "sdpMid"), 4: .same(proto: "sdpMids"),
] ]
public var isInitialized: Bool { public var isInitialized: Bool {
if self._type == nil {return false} if self._type == nil {return false}
if self._sdp == nil {return false}
return true return true
} }
@ -1835,9 +1809,9 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
// enabled. https://github.com/apple/swift-protobuf/issues/1034 // enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber { switch fieldNumber {
case 1: try { try decoder.decodeSingularEnumField(value: &self._type) }() case 1: try { try decoder.decodeSingularEnumField(value: &self._type) }()
case 2: try { try decoder.decodeSingularStringField(value: &self._sdp) }() case 2: try { try decoder.decodeRepeatedStringField(value: &self.sdps) }()
case 3: try { try decoder.decodeSingularUInt32Field(value: &self._sdpMlineIndex) }() case 3: try { try decoder.decodeRepeatedUInt32Field(value: &self.sdpMlineIndexes) }()
case 4: try { try decoder.decodeSingularStringField(value: &self._sdpMid) }() case 4: try { try decoder.decodeRepeatedStringField(value: &self.sdpMids) }()
default: break default: break
} }
} }
@ -1847,23 +1821,23 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
if let v = self._type { if let v = self._type {
try visitor.visitSingularEnumField(value: v, fieldNumber: 1) try visitor.visitSingularEnumField(value: v, fieldNumber: 1)
} }
if let v = self._sdp { if !self.sdps.isEmpty {
try visitor.visitSingularStringField(value: v, fieldNumber: 2) try visitor.visitRepeatedStringField(value: self.sdps, fieldNumber: 2)
} }
if let v = self._sdpMlineIndex { if !self.sdpMlineIndexes.isEmpty {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) try visitor.visitRepeatedUInt32Field(value: self.sdpMlineIndexes, fieldNumber: 3)
} }
if let v = self._sdpMid { if !self.sdpMids.isEmpty {
try visitor.visitSingularStringField(value: v, fieldNumber: 4) try visitor.visitRepeatedStringField(value: self.sdpMids, fieldNumber: 4)
} }
try unknownFields.traverse(visitor: &visitor) try unknownFields.traverse(visitor: &visitor)
} }
static func ==(lhs: SessionProtos_CallMessage, rhs: SessionProtos_CallMessage) -> Bool { static func ==(lhs: SessionProtos_CallMessage, rhs: SessionProtos_CallMessage) -> Bool {
if lhs._type != rhs._type {return false} if lhs._type != rhs._type {return false}
if lhs._sdp != rhs._sdp {return false} if lhs.sdps != rhs.sdps {return false}
if lhs._sdpMlineIndex != rhs._sdpMlineIndex {return false} if lhs.sdpMlineIndexes != rhs.sdpMlineIndexes {return false}
if lhs._sdpMid != rhs._sdpMid {return false} if lhs.sdpMids != rhs.sdpMids {return false}
if lhs.unknownFields != rhs.unknownFields {return false} if lhs.unknownFields != rhs.unknownFields {return false}
return true return true
} }
@ -1874,7 +1848,7 @@ extension SessionProtos_CallMessage.TypeEnum: SwiftProtobuf._ProtoNameProviding
1: .same(proto: "OFFER"), 1: .same(proto: "OFFER"),
2: .same(proto: "ANSWER"), 2: .same(proto: "ANSWER"),
3: .same(proto: "PROVISIONAL_ANSWER"), 3: .same(proto: "PROVISIONAL_ANSWER"),
4: .same(proto: "ICE_CANDIDATE"), 4: .same(proto: "ICE_CANDIDATES"),
] ]
} }

@ -57,15 +57,16 @@ message CallMessage {
OFFER = 1; OFFER = 1;
ANSWER = 2; ANSWER = 2;
PROVISIONAL_ANSWER = 3; PROVISIONAL_ANSWER = 3;
ICE_CANDIDATE = 4; ICE_CANDIDATES = 4;
} }
// Multiple ICE candidates may be batched together for performance
// @required // @required
required Type type = 1; required Type type = 1;
// @required repeated string sdps = 2;
required string sdp = 2; repeated uint32 sdpMLineIndexes = 3;
optional uint32 sdpMLineIndex = 3; repeated string sdpMids = 4;
optional string sdpMid = 4;
} }
message KeyPair { message KeyPair {

@ -269,12 +269,20 @@ extension MessageReceiver {
handleOfferCallMessage?(message) handleOfferCallMessage?(message)
case .answer: case .answer:
print("[Calls] Received answer message.") 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!) webRTCWrapper.handleRemoteSDP(sdp, from: message.sender!)
case .provisionalAnswer: break // TODO: Implement case .provisionalAnswer: break // TODO: Implement
case let .iceCandidate(sdpMLineIndex, sdpMid): case let .iceCandidates(sdpMLineIndexes, sdpMids):
let candidate = RTCIceCandidate(sdp: message.sdp!, sdpMLineIndex: Int32(sdpMLineIndex), sdpMid: sdpMid) var candidates: [RTCIceCandidate] = []
webRTCWrapper.handleICECandidate(candidate) let sdps = message.sdps!
for i in 0..<sdps.count {
let sdp = sdps[i]
let sdpMLineIndex = sdpMLineIndexes[i]
let sdpMid = sdpMids[i]
let candidate = RTCIceCandidate(sdp: sdp, sdpMLineIndex: Int32(sdpMLineIndex), sdpMid: sdpMid)
candidates.append(candidate)
}
webRTCWrapper.handleICECandidates(candidates)
} }
} }

@ -109,7 +109,7 @@ public enum HTTP {
} }
} }
public static func execute(_ verb: Verb, _ url: String, body: Data?, headers: [String:String] = [:], timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise<JSON> { public static func execute(_ verb: Verb, _ url: String, body: Data?, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise<JSON> {
var request = URLRequest(url: URL(string: url)!) var request = URLRequest(url: URL(string: url)!)
request.httpMethod = verb.rawValue request.httpMethod = verb.rawValue
request.httpBody = body request.httpBody = body
@ -117,9 +117,6 @@ public enum HTTP {
request.allHTTPHeaderFields?.removeValue(forKey: "User-Agent") request.allHTTPHeaderFields?.removeValue(forKey: "User-Agent")
request.setValue("WhatsApp", forHTTPHeaderField: "User-Agent") // Set a fake value request.setValue("WhatsApp", forHTTPHeaderField: "User-Agent") // Set a fake value
request.setValue("en-us", forHTTPHeaderField: "Accept-Language") // 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<JSON>.pending() let (promise, seal) = Promise<JSON>.pending()
let urlSession = useSeedNodeURLSession ? seedNodeURLSession : snodeURLSession let urlSession = useSeedNodeURLSession ? seedNodeURLSession : snodeURLSession
let task = urlSession.dataTask(with: request) { data, response, error in let task = urlSession.dataTask(with: request) { data, response, error in

Loading…
Cancel
Save