Support updated tgcalls

This commit is contained in:
Ali 2022-05-03 17:53:10 +04:00
parent 80938082ef
commit be44990da7
8 changed files with 491 additions and 47 deletions

View File

@ -57,6 +57,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case sendLogs(PresentationTheme)
case sendOneLog(PresentationTheme)
case sendShareLogs
case sendGroupCallLogs
case sendNotificationLogs(PresentationTheme)
case sendCriticalLogs(PresentationTheme)
case accounts(PresentationTheme)
@ -97,7 +98,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
switch self {
case .testStickerImport:
return DebugControllerSection.sticker.rawValue
case .sendLogs, .sendOneLog, .sendShareLogs, .sendNotificationLogs, .sendCriticalLogs:
case .sendLogs, .sendOneLog, .sendShareLogs, .sendGroupCallLogs, .sendNotificationLogs, .sendCriticalLogs:
return DebugControllerSection.logs.rawValue
case .accounts:
return DebugControllerSection.logs.rawValue
@ -126,68 +127,70 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 2
case .sendShareLogs:
return 3
case .sendNotificationLogs:
case .sendGroupCallLogs:
return 4
case .sendCriticalLogs:
case .sendNotificationLogs:
return 5
case .accounts:
case .sendCriticalLogs:
return 6
case .logToFile:
case .accounts:
return 7
case .logToConsole:
case .logToFile:
return 8
case .redactSensitiveData:
case .logToConsole:
return 9
case .enableRaiseToSpeak:
case .redactSensitiveData:
return 10
case .keepChatNavigationStack:
case .enableRaiseToSpeak:
return 11
case .skipReadHistory:
case .keepChatNavigationStack:
return 12
case .crashOnSlowQueries:
case .skipReadHistory:
return 13
case .clearTips:
case .crashOnSlowQueries:
return 14
case .crash:
case .clearTips:
return 15
case .resetData:
case .crash:
return 16
case .resetDatabase:
case .resetData:
return 17
case .resetDatabaseAndCache:
case .resetDatabase:
return 18
case .resetHoles:
case .resetDatabaseAndCache:
return 19
case .reindexUnread:
case .resetHoles:
return 20
case .resetBiometricsData:
case .reindexUnread:
return 21
case .resetWebViewCache:
case .resetBiometricsData:
return 22
case .optimizeDatabase:
case .resetWebViewCache:
return 23
case .photoPreview:
case .optimizeDatabase:
return 24
case .knockoutWallpaper:
case .photoPreview:
return 25
case .experimentalCompatibility:
case .knockoutWallpaper:
return 26
case .enableDebugDataDisplay:
case .experimentalCompatibility:
return 27
case .acceleratedStickers:
case .enableDebugDataDisplay:
return 28
case .experimentalBackground:
case .acceleratedStickers:
return 29
case .snow:
case .experimentalBackground:
return 30
case .playerEmbedding:
case .snow:
return 31
case .playlistPlayback:
case .playerEmbedding:
return 32
case .voiceConference:
case .playlistPlayback:
return 33
case .voiceConference:
return 34
case let .preferredVideoCodec(index, _, _, _):
return 34 + index
return 35 + index
case .disableVideoAspectScaling:
return 100
case .enableVoipTcp:
@ -463,6 +466,90 @@ private enum DebugControllerEntry: ItemListNodeEntry {
arguments.getRootController()?.present(composeController, animated: true, completion: nil)
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
arguments.presentController(actionSheet, nil)
})
})
case .sendGroupCallLogs:
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Group Call Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: {
let _ = (Logger.shared.collectLogs(basePath: arguments.context!.account.basePath + "/group-calls")
|> deliverOnMainQueue).start(next: { logs in
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationData: presentationData)
var items: [ActionSheetButtonItem] = []
if let context = arguments.context, context.sharedContext.applicationBindings.isMainApp {
items.append(ActionSheetButtonItem(title: "Via Telegram", color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled]))
controller.peerSelected = { [weak controller] peer in
let peerId = peer.id
if let strongController = controller {
strongController.dismiss()
let lineFeed = "\n".data(using: .utf8)!
var rawLogData: Data = Data()
for (name, path) in logs {
if !rawLogData.isEmpty {
rawLogData.append(lineFeed)
rawLogData.append(lineFeed)
}
rawLogData.append("------ File: \(name) ------\n".data(using: .utf8)!)
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
rawLogData.append(data)
}
}
let tempSource = TempBox.shared.tempFile(fileName: "Log.txt")
let tempZip = TempBox.shared.tempFile(fileName: "destination.zip")
let _ = try? rawLogData.write(to: URL(fileURLWithPath: tempSource.path))
SSZipArchive.createZipFile(atPath: tempZip.path, withFilesAtPaths: [tempSource.path])
guard let gzippedData = try? Data(contentsOf: URL(fileURLWithPath: tempZip.path)) else {
return
}
TempBox.shared.dispose(tempSource)
TempBox.shared.dispose(tempZip)
let id = Int64.random(in: Int64.min ... Int64.max)
let fileResource = LocalFileMediaResource(fileId: id, size: gzippedData.count, isSecretRelated: false)
context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: gzippedData)
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: gzippedData.count, attributes: [.FileName(fileName: "Log-iOS-Full.txt.zip")])
let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil, correlationId: nil)
let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start()
}
}
arguments.pushController(controller)
}))
}
items.append(ActionSheetButtonItem(title: "Via Email", color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let composeController = MFMailComposeViewController()
composeController.mailComposeDelegate = arguments.mailComposeDelegate
composeController.setSubject("Telegram Logs")
for (name, path) in logs {
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) {
composeController.addAttachmentData(data, mimeType: "application/text", fileName: name)
}
}
arguments.getRootController()?.present(composeController, animated: true, completion: nil)
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
@ -948,6 +1035,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.sendLogs(presentationData.theme))
//entries.append(.sendOneLog(presentationData.theme))
entries.append(.sendShareLogs)
entries.append(.sendGroupCallLogs)
entries.append(.sendNotificationLogs(presentationData.theme))
entries.append(.sendCriticalLogs(presentationData.theme))
if isMainApp {

View File

@ -435,6 +435,49 @@ private extension CurrentImpl {
}
}
public func groupCallLogsPath(account: Account) -> String {
return account.basePath + "/group-calls"
}
private func cleanupGroupCallLogs(account: Account) {
let path = groupCallLogsPath(account: account)
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: path, isDirectory: nil) {
try? fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
}
var oldest: [(URL, Date)] = []
var count = 0
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: path), includingPropertiesForKeys: [.contentModificationDateKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
for url in enumerator {
if let url = url as? URL {
if let date = (try? url.resourceValues(forKeys: Set([.contentModificationDateKey])))?.contentModificationDate {
oldest.append((url, date))
count += 1
}
}
}
}
let callLogsLimit = 20
if count > callLogsLimit {
oldest.sort(by: { $0.1 > $1.1 })
while oldest.count > callLogsLimit {
try? fileManager.removeItem(atPath: oldest[oldest.count - 1].0.path)
oldest.removeLast()
}
}
}
public func allocateCallLogPath(account: Account) -> String {
let path = groupCallLogsPath(account: account)
let _ = try? FileManager.default.createDirectory(at: URL(fileURLWithPath: path), withIntermediateDirectories: true, attributes: nil)
let name = "log-\(Date())".replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: ":", with: "_")
return "\(path)/\(name).log"
}
public final class PresentationGroupCallImpl: PresentationGroupCall {
private enum InternalState {
case requesting
@ -1618,7 +1661,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
strongSelf.requestCall(movingFromBroadcastToRtc: false)
}
}
}, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: self.isVideoEnabled ? .generic : .none, enableNoiseSuppression: false, disableAudioInput: self.isStream, preferX264: self.accountContext.sharedContext.immediateExperimentalUISettings.preferredVideoCodec == "H264"
}, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: self.isVideoEnabled ? .generic : .none, enableNoiseSuppression: false, disableAudioInput: self.isStream, preferX264: self.accountContext.sharedContext.immediateExperimentalUISettings.preferredVideoCodec == "H264", logPath: allocateCallLogPath(account: self.account)
))
}
@ -2927,7 +2970,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.hasScreencast = true
let screencastCallContext = OngoingGroupCallContext(video: self.screencastCapturer, requestMediaChannelDescriptions: { _, _ in EmptyDisposable }, rejoinNeeded: { }, outgoingAudioBitrateKbit: nil, videoContentType: .screencast, enableNoiseSuppression: false, disableAudioInput: true, preferX264: false)
let screencastCallContext = OngoingGroupCallContext(video: self.screencastCapturer, requestMediaChannelDescriptions: { _, _ in EmptyDisposable }, rejoinNeeded: { }, outgoingAudioBitrateKbit: nil, videoContentType: .screencast, enableNoiseSuppression: false, disableAudioInput: true, preferX264: false, logPath: "")
self.screencastCallContext = screencastCallContext
self.screencastJoinDisposable.set((screencastCallContext.joinPayload

View File

@ -157,6 +157,30 @@ public final class Logger {
}
}
public func collectLogs(basePath: String) -> Signal<[(String, String)], NoError> {
return Signal { subscriber in
self.queue.async {
let logsPath: String = basePath
var result: [(Date, String, String)] = []
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: logsPath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) {
for url in files {
if url.lastPathComponent.hasPrefix("log-") {
if let creationDate = (try? url.resourceValues(forKeys: Set([.creationDateKey])))?.creationDate {
result.append((creationDate, url.lastPathComponent, url.path))
}
}
}
}
result.sort(by: { $0.0 < $1.0 })
subscriber.putNext(result.map { ($0.1, $0.2) })
subscriber.putCompletion()
}
return EmptyDisposable
}
}
public func collectShortLogFiles() -> Signal<[(String, String)], NoError> {
return Signal { subscriber in
self.queue.async {

View File

@ -416,7 +416,7 @@ public final class OngoingGroupCallContext {
private let broadcastPartsSource = Atomic<BroadcastPartSource?>(value: nil)
init(queue: Queue, inputDeviceId: String, outputDeviceId: String, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, preferX264: Bool) {
init(queue: Queue, inputDeviceId: String, outputDeviceId: String, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, preferX264: Bool, logPath: String) {
self.queue = queue
var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)?
@ -523,7 +523,8 @@ public final class OngoingGroupCallContext {
videoContentType: _videoContentType,
enableNoiseSuppression: enableNoiseSuppression,
disableAudioInput: disableAudioInput,
preferX264: preferX264
preferX264: preferX264,
logPath: logPath
)
let queue = self.queue
@ -935,10 +936,10 @@ public final class OngoingGroupCallContext {
}
}
public init(inputDeviceId: String = "", outputDeviceId: String = "", video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, preferX264: Bool) {
public init(inputDeviceId: String = "", outputDeviceId: String = "", video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, preferX264: Bool, logPath: String) {
let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: {
return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, video: video, requestMediaChannelDescriptions: requestMediaChannelDescriptions, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: videoContentType, enableNoiseSuppression: enableNoiseSuppression, disableAudioInput: disableAudioInput, preferX264: preferX264)
return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, video: video, requestMediaChannelDescriptions: requestMediaChannelDescriptions, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: videoContentType, enableNoiseSuppression: enableNoiseSuppression, disableAudioInput: disableAudioInput, preferX264: preferX264, logPath: logPath)
})
}

View File

@ -3,6 +3,7 @@ import UIKit
import SwiftSignalKit
import TelegramCore
import TelegramUIPreferences
import Network
import TgVoip
import TgVoipWebrtc
@ -739,6 +740,8 @@ public final class OngoingCallContext {
private let tempLogFile: EngineTempBoxFile
private let tempStatsLogFile: EngineTempBoxFile
private var signalingConnectionManager: QueueLocalObject<CallSignalingConnectionManager>?
public static func versions(includeExperimental: Bool, includeReference: Bool) -> [(version: String, supportsVideo: Bool)] {
if debugUseLegacyVersionForReflectors {
return [(OngoingCallThreadLocalContext.version(), true)]
@ -781,7 +784,7 @@ public final class OngoingCallContext {
var allowP2P = allowP2P
if debugUseLegacyVersionForReflectors {
useModernImplementation = true
version = "4.0.2"
version = "4.0.3"
allowP2P = false
} else {
useModernImplementation = version != OngoingCallThreadLocalContext.version()
@ -830,7 +833,21 @@ public final class OngoingCallContext {
if debugUseLegacyVersionForReflectors {
for connection in filteredConnections {
if connection.username == "reflector" {
filteredConnections.append(OngoingCallConnectionDescriptionWebrtc(reflectorId: 0, hasStun: false, hasTurn: true, hasTcp: true, ip: "91.108.12.1", port: 533, username: "reflector", password: connection.password))
let peerTag = dataWithHexString(connection.password)
if #available(iOS 12.0, *) {
strongSelf.signalingConnectionManager = QueueLocalObject(queue: queue, generate: {
return CallSignalingConnectionManager(queue: queue, peerTag: peerTag, servers: [OngoingCallConnectionDescriptionWebrtc(reflectorId: 0, hasStun: false, hasTurn: true, hasTcp: true, ip: "91.108.12.1", port: 533, username: "reflector", password: connection.password)], dataReceived: { data in
guard let strongSelf = self else {
return
}
strongSelf.withContext { context in
if let context = context as? OngoingCallThreadLocalContextWebrtc {
context.addSignaling(data)
}
}
})
})
}
break
}
@ -838,7 +855,20 @@ public final class OngoingCallContext {
}
let context = OngoingCallThreadLocalContextWebrtc(version: version, queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, connections: filteredConnections, maxLayer: maxLayer, allowP2P: allowP2P, allowTCP: enableTCP, enableStunMarking: enableStunMarking, logPath: tempLogPath, statsLogPath: tempStatsLogPath, sendSignalingData: { [weak callSessionManager] data in
callSessionManager?.sendSignalingData(internalId: internalId, data: data)
queue.async {
guard let strongSelf = self else {
return
}
if let signalingConnectionManager = strongSelf.signalingConnectionManager {
signalingConnectionManager.with { impl in
impl.send(payloadData: data)
}
}
if let callSessionManager = callSessionManager {
callSessionManager.sendSignalingData(internalId: internalId, data: data)
}
}
}, videoCapturer: video?.impl, preferredVideoCodec: preferredVideoCodec, audioInputDeviceId: "")
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
@ -948,6 +978,10 @@ public final class OngoingCallContext {
}
}
})
strongSelf.signalingConnectionManager?.with { impl in
impl.start()
}
}
}))
}
@ -1143,3 +1177,257 @@ public final class OngoingCallContext {
}
}
}
private protocol CallSignalingConnection {
func start()
func stop()
func send(payloadData: Data)
}
@available(iOS 12.0, *)
private final class CallSignalingConnectionImpl: CallSignalingConnection {
private let queue: Queue
private let host: NWEndpoint.Host
private let port: NWEndpoint.Port
private let peerTag: Data
private let dataReceived: (Data) -> Void
private let isClosed: () -> Void
private let connection: NWConnection
private var isConnected: Bool = false
private var pingTimer: SwiftSignalKit.Timer?
private var queuedPayloads: [Data] = []
init(queue: Queue, host: String, port: UInt16, peerTag: Data, dataReceived: @escaping (Data) -> Void, isClosed: @escaping () -> Void) {
self.queue = queue
self.host = NWEndpoint.Host(host)
self.port = NWEndpoint.Port(rawValue: port)!
self.peerTag = peerTag
self.dataReceived = dataReceived
self.isClosed = isClosed
self.connection = NWConnection(host: self.host, port: self.port, using: .tcp)
self.connection.stateUpdateHandler = { [weak self] state in
queue.async {
self?.stateUpdated(state: state)
}
}
}
private func stateUpdated(state: NWConnection.State) {
switch state {
case .ready:
Logger.shared.log("CallSignaling", "Connection state is ready")
var headerData = Data(count: 4)
headerData.withUnsafeMutableBytes { bytes in
bytes.baseAddress!.assumingMemoryBound(to: UInt32.self).pointee = 0xeeeeeeee
}
self.connection.send(content: headerData, completion: .contentProcessed({ error in
if let error = error {
Logger.shared.log("CallSignaling", "Connection send header error: \(error)")
}
}))
self.beginPingTimer()
self.sendPacket(payload: Data())
case let .failed(error):
Logger.shared.log("CallSignaling", "Connection error: \(error)")
self.onIsClosed()
default:
break
}
}
func start() {
self.connection.start(queue: self.queue.queue)
self.receivePacketHeader()
}
private func beginPingTimer() {
self.pingTimer = SwiftSignalKit.Timer(timeout: self.isConnected ? 2.0 : 0.15, repeat: false, completion: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.sendPacket(payload: Data())
strongSelf.beginPingTimer()
}, queue: self.queue)
self.pingTimer?.start()
}
private func receivePacketHeader() {
self.connection.receive(minimumIncompleteLength: 4, maximumLength: 4, completion: { [weak self] data, _, _, error in
guard let strongSelf = self else {
return
}
if let data = data, data.count == 4 {
let payloadSize = data.withUnsafeBytes { bytes -> UInt32 in
return bytes.baseAddress!.assumingMemoryBound(to: UInt32.self).pointee
}
if payloadSize < 2 * 1024 * 1024 {
strongSelf.receivePacketPayload(size: Int(payloadSize))
} else {
Logger.shared.log("CallSignaling", "Connection received invalid packet size: \(payloadSize)")
}
} else {
Logger.shared.log("CallSignaling", "Connection receive packet header error: \(String(describing: error))")
strongSelf.onIsClosed()
}
})
}
private func receivePacketPayload(size: Int) {
self.connection.receive(minimumIncompleteLength: size, maximumLength: size, completion: { [weak self] data, _, _, error in
guard let strongSelf = self else {
return
}
if let data = data, data.count == size {
Logger.shared.log("CallSignaling", "Connection receive packet payload: \(data.count) bytes")
if data.count < 16 + 4 {
Logger.shared.log("CallSignaling", "Connection invalid payload size: \(data.count)")
strongSelf.onIsClosed()
} else {
let readPeerTag = data.subdata(in: 0 ..< 16)
if readPeerTag != strongSelf.peerTag {
Logger.shared.log("CallSignaling", "Peer tag mismatch: \(hexString(readPeerTag))")
strongSelf.onIsClosed()
} else {
let actualPayloadSize = data.withUnsafeBytes { bytes -> UInt32 in
var result: UInt32 = 0
memcpy(&result, bytes.baseAddress!.assumingMemoryBound(to: UInt8.self).advanced(by: 16), 4)
return result
}
if Int(actualPayloadSize) > data.count - 16 - 4 {
Logger.shared.log("CallSignaling", "Connection invalid actual payload size: \(actualPayloadSize)")
strongSelf.onIsClosed()
} else {
if !strongSelf.isConnected {
strongSelf.isConnected = true
for payload in strongSelf.queuedPayloads {
strongSelf.sendPacket(payload: payload)
}
strongSelf.queuedPayloads.removeAll()
}
if actualPayloadSize != 0 {
strongSelf.dataReceived(data.subdata(in: (16 + 4) ..< (16 + 4 + Int(actualPayloadSize))))
} else {
//strongSelf.sendPacket(payload: Data())
}
strongSelf.receivePacketHeader()
}
}
}
} else {
Logger.shared.log("CallSignaling", "Connection receive packet payload error: \(String(describing: error))")
strongSelf.onIsClosed()
}
})
}
func stop() {
self.connection.stateUpdateHandler = nil
self.connection.cancel()
}
private func onIsClosed() {
self.connection.stateUpdateHandler = nil
self.connection.cancel()
self.isClosed()
}
func send(payloadData: Data) {
if self.isConnected {
self.sendPacket(payload: payloadData)
} else {
self.queuedPayloads.append(payloadData)
}
}
private func sendPacket(payload: Data) {
var payloadSize = UInt32(payload.count)
let cleanSize = 16 + 4 + payloadSize
let paddingSize = ((cleanSize + 3) & ~(4 - 1)) - cleanSize
var totalSize = cleanSize + paddingSize
var sendBuffer = Data(count: 4 + Int(totalSize))
sendBuffer.withUnsafeMutableBytes { bytes in
let baseAddress = bytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
memcpy(baseAddress, &totalSize, 4)
self.peerTag.withUnsafeBytes { peerTagBytes -> Void in
memcpy(baseAddress.advanced(by: 4), peerTagBytes.baseAddress!.assumingMemoryBound(to: UInt8.self), 16)
}
memcpy(baseAddress.advanced(by: 4 + 16), &payloadSize, 4)
payload.withUnsafeBytes { payloadBytes -> Void in
memcpy(baseAddress.advanced(by: 4 + 16 + 4), payloadBytes.baseAddress!.assumingMemoryBound(to: UInt8.self), payloadBytes.count)
}
}
Logger.shared.log("CallSignaling", "Send packet payload: \(totalSize) bytes")
self.connection.send(content: sendBuffer, isComplete: true, completion: .contentProcessed({ error in
if let error = error {
Logger.shared.log("CallSignaling", "Connection send payload error: \(error)")
}
}))
}
}
private final class CallSignalingConnectionManager {
private let queue: Queue
private var nextConnectionId: Int = 0
private var connections: [Int: CallSignalingConnection] = [:]
init(queue: Queue, peerTag: Data, servers: [OngoingCallConnectionDescriptionWebrtc], dataReceived: @escaping (Data) -> Void) {
self.queue = queue
for server in servers {
if server.hasTcp {
let id = self.nextConnectionId
self.nextConnectionId += 1
if #available(iOS 12.0, *) {
let connection = CallSignalingConnectionImpl(queue: queue, host: server.ip, port: UInt16(server.port), peerTag: peerTag, dataReceived: { data in
dataReceived(data)
}, isClosed: { [weak self] in
guard let strongSelf = self else {
return
}
let _ = strongSelf
})
connections[id] = connection
}
}
}
}
func start() {
for (_, connection) in self.connections {
connection.start()
}
}
func stop() {
for (_, connection) in self.connections {
connection.stop()
}
}
func send(payloadData: Data) {
for (_, connection) in self.connections {
connection.send(payloadData: payloadData)
}
}
}

View File

@ -353,7 +353,8 @@ typedef NS_ENUM(int32_t, OngoingGroupCallRequestedVideoQuality) {
videoContentType:(OngoingGroupCallVideoContentType)videoContentType
enableNoiseSuppression:(bool)enableNoiseSuppression
disableAudioInput:(bool)disableAudioInput
preferX264:(bool)preferX264;
preferX264:(bool)preferX264
logPath:(NSString * _Nonnull)logPath;
- (void)stop;

View File

@ -1391,7 +1391,8 @@ private:
videoContentType:(OngoingGroupCallVideoContentType)videoContentType
enableNoiseSuppression:(bool)enableNoiseSuppression
disableAudioInput:(bool)disableAudioInput
preferX264:(bool)preferX264 {
preferX264:(bool)preferX264
logPath:(NSString * _Nonnull)logPath {
self = [super init];
if (self != nil) {
_queue = queue;
@ -1429,10 +1430,8 @@ private:
bool disableOutgoingAudioProcessing = false;
tgcalls::GroupConfig config;
config.need_log = false;
#if DEBUG
config.need_log = true;
#endif
config.logPath.data = std::string(logPath.length == 0 ? "" : logPath.UTF8String);
__weak GroupCallThreadLocalContext *weakSelf = self;
_instance.reset(new tgcalls::GroupInstanceCustomImpl((tgcalls::GroupInstanceDescriptor){

@ -1 +1 @@
Subproject commit 644b8e5757cdd286ba665d2d3e5adf57f9b66f4c
Subproject commit 0ecfbd22fdc764d341a50236e6c1546884bafa3c