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 sendLogs(PresentationTheme)
|
||||||
case sendOneLog(PresentationTheme)
|
case sendOneLog(PresentationTheme)
|
||||||
case sendShareLogs
|
case sendShareLogs
|
||||||
|
case sendGroupCallLogs
|
||||||
case sendNotificationLogs(PresentationTheme)
|
case sendNotificationLogs(PresentationTheme)
|
||||||
case sendCriticalLogs(PresentationTheme)
|
case sendCriticalLogs(PresentationTheme)
|
||||||
case accounts(PresentationTheme)
|
case accounts(PresentationTheme)
|
||||||
@ -97,7 +98,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
switch self {
|
switch self {
|
||||||
case .testStickerImport:
|
case .testStickerImport:
|
||||||
return DebugControllerSection.sticker.rawValue
|
return DebugControllerSection.sticker.rawValue
|
||||||
case .sendLogs, .sendOneLog, .sendShareLogs, .sendNotificationLogs, .sendCriticalLogs:
|
case .sendLogs, .sendOneLog, .sendShareLogs, .sendGroupCallLogs, .sendNotificationLogs, .sendCriticalLogs:
|
||||||
return DebugControllerSection.logs.rawValue
|
return DebugControllerSection.logs.rawValue
|
||||||
case .accounts:
|
case .accounts:
|
||||||
return DebugControllerSection.logs.rawValue
|
return DebugControllerSection.logs.rawValue
|
||||||
@ -126,68 +127,70 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return 2
|
return 2
|
||||||
case .sendShareLogs:
|
case .sendShareLogs:
|
||||||
return 3
|
return 3
|
||||||
case .sendNotificationLogs:
|
case .sendGroupCallLogs:
|
||||||
return 4
|
return 4
|
||||||
case .sendCriticalLogs:
|
case .sendNotificationLogs:
|
||||||
return 5
|
return 5
|
||||||
case .accounts:
|
case .sendCriticalLogs:
|
||||||
return 6
|
return 6
|
||||||
case .logToFile:
|
case .accounts:
|
||||||
return 7
|
return 7
|
||||||
case .logToConsole:
|
case .logToFile:
|
||||||
return 8
|
return 8
|
||||||
case .redactSensitiveData:
|
case .logToConsole:
|
||||||
return 9
|
return 9
|
||||||
case .enableRaiseToSpeak:
|
case .redactSensitiveData:
|
||||||
return 10
|
return 10
|
||||||
case .keepChatNavigationStack:
|
case .enableRaiseToSpeak:
|
||||||
return 11
|
return 11
|
||||||
case .skipReadHistory:
|
case .keepChatNavigationStack:
|
||||||
return 12
|
return 12
|
||||||
case .crashOnSlowQueries:
|
case .skipReadHistory:
|
||||||
return 13
|
return 13
|
||||||
case .clearTips:
|
case .crashOnSlowQueries:
|
||||||
return 14
|
return 14
|
||||||
case .crash:
|
case .clearTips:
|
||||||
return 15
|
return 15
|
||||||
case .resetData:
|
case .crash:
|
||||||
return 16
|
return 16
|
||||||
case .resetDatabase:
|
case .resetData:
|
||||||
return 17
|
return 17
|
||||||
case .resetDatabaseAndCache:
|
case .resetDatabase:
|
||||||
return 18
|
return 18
|
||||||
case .resetHoles:
|
case .resetDatabaseAndCache:
|
||||||
return 19
|
return 19
|
||||||
case .reindexUnread:
|
case .resetHoles:
|
||||||
return 20
|
return 20
|
||||||
case .resetBiometricsData:
|
case .reindexUnread:
|
||||||
return 21
|
return 21
|
||||||
case .resetWebViewCache:
|
case .resetBiometricsData:
|
||||||
return 22
|
return 22
|
||||||
case .optimizeDatabase:
|
case .resetWebViewCache:
|
||||||
return 23
|
return 23
|
||||||
case .photoPreview:
|
case .optimizeDatabase:
|
||||||
return 24
|
return 24
|
||||||
case .knockoutWallpaper:
|
case .photoPreview:
|
||||||
return 25
|
return 25
|
||||||
case .experimentalCompatibility:
|
case .knockoutWallpaper:
|
||||||
return 26
|
return 26
|
||||||
case .enableDebugDataDisplay:
|
case .experimentalCompatibility:
|
||||||
return 27
|
return 27
|
||||||
case .acceleratedStickers:
|
case .enableDebugDataDisplay:
|
||||||
return 28
|
return 28
|
||||||
case .experimentalBackground:
|
case .acceleratedStickers:
|
||||||
return 29
|
return 29
|
||||||
case .snow:
|
case .experimentalBackground:
|
||||||
return 30
|
return 30
|
||||||
case .playerEmbedding:
|
case .snow:
|
||||||
return 31
|
return 31
|
||||||
case .playlistPlayback:
|
case .playerEmbedding:
|
||||||
return 32
|
return 32
|
||||||
case .voiceConference:
|
case .playlistPlayback:
|
||||||
return 33
|
return 33
|
||||||
|
case .voiceConference:
|
||||||
|
return 34
|
||||||
case let .preferredVideoCodec(index, _, _, _):
|
case let .preferredVideoCodec(index, _, _, _):
|
||||||
return 34 + index
|
return 35 + index
|
||||||
case .disableVideoAspectScaling:
|
case .disableVideoAspectScaling:
|
||||||
return 100
|
return 100
|
||||||
case .enableVoipTcp:
|
case .enableVoipTcp:
|
||||||
@ -463,6 +466,90 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
arguments.getRootController()?.present(composeController, animated: true, completion: nil)
|
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: [
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
@ -948,6 +1035,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
|||||||
entries.append(.sendLogs(presentationData.theme))
|
entries.append(.sendLogs(presentationData.theme))
|
||||||
//entries.append(.sendOneLog(presentationData.theme))
|
//entries.append(.sendOneLog(presentationData.theme))
|
||||||
entries.append(.sendShareLogs)
|
entries.append(.sendShareLogs)
|
||||||
|
entries.append(.sendGroupCallLogs)
|
||||||
entries.append(.sendNotificationLogs(presentationData.theme))
|
entries.append(.sendNotificationLogs(presentationData.theme))
|
||||||
entries.append(.sendCriticalLogs(presentationData.theme))
|
entries.append(.sendCriticalLogs(presentationData.theme))
|
||||||
if isMainApp {
|
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 {
|
public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||||
private enum InternalState {
|
private enum InternalState {
|
||||||
case requesting
|
case requesting
|
||||||
@ -1618,7 +1661,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
strongSelf.requestCall(movingFromBroadcastToRtc: false)
|
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
|
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.screencastCallContext = screencastCallContext
|
||||||
|
|
||||||
self.screencastJoinDisposable.set((screencastCallContext.joinPayload
|
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> {
|
public func collectShortLogFiles() -> Signal<[(String, String)], NoError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
self.queue.async {
|
self.queue.async {
|
||||||
|
|||||||
@ -416,7 +416,7 @@ public final class OngoingGroupCallContext {
|
|||||||
|
|
||||||
private let broadcastPartsSource = Atomic<BroadcastPartSource?>(value: nil)
|
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
|
self.queue = queue
|
||||||
|
|
||||||
var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)?
|
var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)?
|
||||||
@ -523,7 +523,8 @@ public final class OngoingGroupCallContext {
|
|||||||
videoContentType: _videoContentType,
|
videoContentType: _videoContentType,
|
||||||
enableNoiseSuppression: enableNoiseSuppression,
|
enableNoiseSuppression: enableNoiseSuppression,
|
||||||
disableAudioInput: disableAudioInput,
|
disableAudioInput: disableAudioInput,
|
||||||
preferX264: preferX264
|
preferX264: preferX264,
|
||||||
|
logPath: logPath
|
||||||
)
|
)
|
||||||
|
|
||||||
let queue = self.queue
|
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
|
let queue = self.queue
|
||||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
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 SwiftSignalKit
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
|
import Network
|
||||||
|
|
||||||
import TgVoip
|
import TgVoip
|
||||||
import TgVoipWebrtc
|
import TgVoipWebrtc
|
||||||
@ -739,6 +740,8 @@ public final class OngoingCallContext {
|
|||||||
private let tempLogFile: EngineTempBoxFile
|
private let tempLogFile: EngineTempBoxFile
|
||||||
private let tempStatsLogFile: EngineTempBoxFile
|
private let tempStatsLogFile: EngineTempBoxFile
|
||||||
|
|
||||||
|
private var signalingConnectionManager: QueueLocalObject<CallSignalingConnectionManager>?
|
||||||
|
|
||||||
public static func versions(includeExperimental: Bool, includeReference: Bool) -> [(version: String, supportsVideo: Bool)] {
|
public static func versions(includeExperimental: Bool, includeReference: Bool) -> [(version: String, supportsVideo: Bool)] {
|
||||||
if debugUseLegacyVersionForReflectors {
|
if debugUseLegacyVersionForReflectors {
|
||||||
return [(OngoingCallThreadLocalContext.version(), true)]
|
return [(OngoingCallThreadLocalContext.version(), true)]
|
||||||
@ -781,7 +784,7 @@ public final class OngoingCallContext {
|
|||||||
var allowP2P = allowP2P
|
var allowP2P = allowP2P
|
||||||
if debugUseLegacyVersionForReflectors {
|
if debugUseLegacyVersionForReflectors {
|
||||||
useModernImplementation = true
|
useModernImplementation = true
|
||||||
version = "4.0.2"
|
version = "4.0.3"
|
||||||
allowP2P = false
|
allowP2P = false
|
||||||
} else {
|
} else {
|
||||||
useModernImplementation = version != OngoingCallThreadLocalContext.version()
|
useModernImplementation = version != OngoingCallThreadLocalContext.version()
|
||||||
@ -830,7 +833,21 @@ public final class OngoingCallContext {
|
|||||||
if debugUseLegacyVersionForReflectors {
|
if debugUseLegacyVersionForReflectors {
|
||||||
for connection in filteredConnections {
|
for connection in filteredConnections {
|
||||||
if connection.username == "reflector" {
|
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
|
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
|
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: "")
|
}, videoCapturer: video?.impl, preferredVideoCodec: preferredVideoCodec, audioInputDeviceId: "")
|
||||||
|
|
||||||
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
|
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
|
videoContentType:(OngoingGroupCallVideoContentType)videoContentType
|
||||||
enableNoiseSuppression:(bool)enableNoiseSuppression
|
enableNoiseSuppression:(bool)enableNoiseSuppression
|
||||||
disableAudioInput:(bool)disableAudioInput
|
disableAudioInput:(bool)disableAudioInput
|
||||||
preferX264:(bool)preferX264;
|
preferX264:(bool)preferX264
|
||||||
|
logPath:(NSString * _Nonnull)logPath;
|
||||||
|
|
||||||
- (void)stop;
|
- (void)stop;
|
||||||
|
|
||||||
|
|||||||
@ -1391,7 +1391,8 @@ private:
|
|||||||
videoContentType:(OngoingGroupCallVideoContentType)videoContentType
|
videoContentType:(OngoingGroupCallVideoContentType)videoContentType
|
||||||
enableNoiseSuppression:(bool)enableNoiseSuppression
|
enableNoiseSuppression:(bool)enableNoiseSuppression
|
||||||
disableAudioInput:(bool)disableAudioInput
|
disableAudioInput:(bool)disableAudioInput
|
||||||
preferX264:(bool)preferX264 {
|
preferX264:(bool)preferX264
|
||||||
|
logPath:(NSString * _Nonnull)logPath {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self != nil) {
|
if (self != nil) {
|
||||||
_queue = queue;
|
_queue = queue;
|
||||||
@ -1429,10 +1430,8 @@ private:
|
|||||||
bool disableOutgoingAudioProcessing = false;
|
bool disableOutgoingAudioProcessing = false;
|
||||||
|
|
||||||
tgcalls::GroupConfig config;
|
tgcalls::GroupConfig config;
|
||||||
config.need_log = false;
|
|
||||||
#if DEBUG
|
|
||||||
config.need_log = true;
|
config.need_log = true;
|
||||||
#endif
|
config.logPath.data = std::string(logPath.length == 0 ? "" : logPath.UTF8String);
|
||||||
|
|
||||||
__weak GroupCallThreadLocalContext *weakSelf = self;
|
__weak GroupCallThreadLocalContext *weakSelf = self;
|
||||||
_instance.reset(new tgcalls::GroupInstanceCustomImpl((tgcalls::GroupInstanceDescriptor){
|
_instance.reset(new tgcalls::GroupInstanceCustomImpl((tgcalls::GroupInstanceDescriptor){
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
Subproject commit 644b8e5757cdd286ba665d2d3e5adf57f9b66f4c
|
Subproject commit 0ecfbd22fdc764d341a50236e6c1546884bafa3c
|
||||||
Loading…
x
Reference in New Issue
Block a user