mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-04 21:41:45 +00:00
Support updated tgcalls
This commit is contained in:
parent
80938082ef
commit
be44990da7
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user