Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

# Conflicts:
#	submodules/MediaPlayer/Sources/FFMpegFileReader.swift
This commit is contained in:
Mikhail Filimonov 2025-01-16 11:30:14 +01:00
commit 0794a1cdb9
57 changed files with 2945 additions and 626 deletions

View file

@ -461,7 +461,6 @@ public final class OngoingGroupCallContext {
#if os(iOS)
let audioDevice: OngoingCallContext.AudioDevice?
#endif
let sessionId = UInt32.random(in: 0 ..< UInt32(Int32.max))
let joinPayload = Promise<(String, UInt32)>()
let networkState = ValuePromise<NetworkState>(NetworkState(isConnected: false, isTransitioningFromBroadcastToRtc: false), ignoreRepeated: true)
@ -507,14 +506,12 @@ public final class OngoingGroupCallContext {
self.tempStatsLogFile = EngineTempBox.shared.tempFile(fileName: "CallStats.json")
let tempStatsLogPath = self.tempStatsLogFile.path
#if os(iOS)
if sharedAudioDevice == nil {
self.audioDevice = OngoingCallContext.AudioDevice.create(enableSystemMute: false)
} else {
self.audioDevice = sharedAudioDevice
}
#if os(iOS)
self.audioDevice = sharedAudioDevice
let audioDevice = self.audioDevice
#endif
#endif
var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)?
var audioLevelsUpdatedImpl: (([NSNumber]) -> Void)?
var activityUpdatedImpl: (([UInt32]) -> Void)?
@ -882,7 +879,7 @@ public final class OngoingGroupCallContext {
}
}
func stop(account: Account, reportCallId: CallId?) {
func stop(account: Account?, reportCallId: CallId?, debugLog: Promise<String?>) {
self.context.stop()
let logPath = self.logPath
@ -892,16 +889,18 @@ public final class OngoingGroupCallContext {
}
let tempStatsLogPath = self.tempStatsLogFile.path
debugLog.set(.single(nil))
let queue = self.queue
self.context.stop({
queue.async {
if !statsLogPath.isEmpty {
if !statsLogPath.isEmpty, let account {
let logsPath = callLogsPath(account: account)
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
let _ = try? FileManager.default.moveItem(atPath: tempStatsLogPath, toPath: statsLogPath)
}
if let callId = reportCallId, !statsLogPath.isEmpty, let data = try? Data(contentsOf: URL(fileURLWithPath: statsLogPath)), let dataString = String(data: data, encoding: .utf8) {
if let callId = reportCallId, !statsLogPath.isEmpty, let data = try? Data(contentsOf: URL(fileURLWithPath: statsLogPath)), let dataString = String(data: data, encoding: .utf8), let account {
let engine = TelegramEngine(account: account)
let _ = engine.calls.saveCallDebugLog(callId: callId, log: dataString).start(next: { result in
switch result {
@ -1270,9 +1269,9 @@ public final class OngoingGroupCallContext {
}
}
public func stop(account: Account, reportCallId: CallId?) {
public func stop(account: Account?, reportCallId: CallId?, debugLog: Promise<String?>) {
self.impl.with { impl in
impl.stop(account: account, reportCallId: reportCallId)
impl.stop(account: account, reportCallId: reportCallId, debugLog: debugLog)
}
}

View file

@ -3,11 +3,6 @@ import SwiftSignalKit
import CoreMedia
import ImageIO
private struct PayloadDescription: Codable {
var id: UInt32
var timestamp: Int32
}
private struct JoinPayload: Codable {
var id: UInt32
var string: String
@ -18,11 +13,6 @@ private struct JoinResponsePayload: Codable {
var string: String
}
private struct KeepaliveInfo: Codable {
var id: UInt32
var timestamp: Int32
}
private struct CutoffPayload: Codable {
var id: UInt32
var timestamp: Int32
@ -370,6 +360,16 @@ private final class MappedFile {
}
public final class IpcGroupCallBufferAppContext {
struct KeepaliveInfo: Codable {
var id: UInt32
var timestamp: Int32
}
struct PayloadDescription: Codable {
var id: UInt32
var timestamp: Int32
}
private let basePath: String
private var audioServer: NamedPipeReader?
@ -460,7 +460,7 @@ public final class IpcGroupCallBufferAppContext {
private func updateCallIsActive() {
let timestamp = Int32(Date().timeIntervalSince1970)
let payloadDescription = PayloadDescription(
let payloadDescription = IpcGroupCallBufferAppContext.PayloadDescription(
id: self.id,
timestamp: timestamp
)
@ -477,7 +477,7 @@ public final class IpcGroupCallBufferAppContext {
guard let keepaliveInfoData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
return
}
guard let keepaliveInfo = try? JSONDecoder().decode(KeepaliveInfo.self, from: keepaliveInfoData) else {
guard let keepaliveInfo = try? JSONDecoder().decode(IpcGroupCallBufferAppContext.KeepaliveInfo.self, from: keepaliveInfoData) else {
return
}
if keepaliveInfo.id != self.id {
@ -587,7 +587,7 @@ public final class IpcGroupCallBufferBroadcastContext {
return
}
guard let payloadDescription = try? JSONDecoder().decode(PayloadDescription.self, from: payloadDescriptionData) else {
guard let payloadDescription = try? JSONDecoder().decode(IpcGroupCallBufferAppContext.PayloadDescription.self, from: payloadDescriptionData) else {
self.statusPromise.set(.single(.finished(.error)))
return
}
@ -646,7 +646,7 @@ public final class IpcGroupCallBufferBroadcastContext {
guard let currentId = self.currentId else {
preconditionFailure()
}
let keepaliveInfo = KeepaliveInfo(
let keepaliveInfo = IpcGroupCallBufferAppContext.KeepaliveInfo(
id: currentId,
timestamp: Int32(Date().timeIntervalSince1970)
)
@ -795,3 +795,319 @@ public func deserializePixelBuffer(data: Data) -> CVPixelBuffer? {
}
}
}
public final class IpcGroupCallEmbeddedAppContext {
public struct JoinPayload: Codable, Equatable {
public var id: UInt32
public var data: String
public var ssrc: UInt32
public init(id: UInt32, data: String, ssrc: UInt32) {
self.id = id
self.data = data
self.ssrc = ssrc
}
}
public struct JoinResponse: Codable, Equatable {
public var data: String
public init(data: String) {
self.data = data
}
}
struct KeepaliveInfo: Codable {
var id: UInt32
var timestamp: Int32
var joinPayload: JoinPayload?
init(id: UInt32, timestamp: Int32, joinPayload: JoinPayload?) {
self.id = id
self.timestamp = timestamp
self.joinPayload = joinPayload
}
}
struct PayloadDescription: Codable {
var id: UInt32
var timestamp: Int32
var activeRequestId: UInt32?
var joinResponse: JoinResponse?
init(id: UInt32, timestamp: Int32, activeRequestId: UInt32?, joinResponse: JoinResponse?) {
self.id = id
self.timestamp = timestamp
self.activeRequestId = activeRequestId
self.joinResponse = joinResponse
}
}
private let basePath: String
private let id: UInt32
private let isActivePromise = ValuePromise<Bool>(false, ignoreRepeated: true)
public var isActive: Signal<Bool, NoError> {
return self.isActivePromise.get()
}
private var isActiveCheckTimer: SwiftSignalKit.Timer?
private var joinPayloadValue: JoinPayload? {
didSet {
if let joinPayload = self.joinPayloadValue, joinPayload != oldValue {
self.joinPayloadPromise.set(.single(joinPayload))
}
}
}
private let joinPayloadPromise = Promise<JoinPayload>()
public var joinPayload: Signal<JoinPayload, NoError> {
return self.joinPayloadPromise.get()
}
private var nextActiveRequestId: UInt32 = 0
private var activeRequestId: UInt32? {
didSet {
if self.activeRequestId != oldValue {
self.updateCallIsActive()
}
}
}
public var joinResponse: JoinResponse? {
didSet {
if self.joinResponse != oldValue {
self.updateCallIsActive()
}
}
}
private var callActiveInfoTimer: SwiftSignalKit.Timer?
public init(basePath: String) {
self.basePath = basePath
let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil)
self.id = UInt32.random(in: 0 ..< UInt32.max)
self.updateCallIsActive()
let callActiveInfoTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
self?.updateCallIsActive()
}, queue: .mainQueue())
self.callActiveInfoTimer = callActiveInfoTimer
callActiveInfoTimer.start()
let isActiveCheckTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
self?.updateKeepaliveInfo()
}, queue: .mainQueue())
self.isActiveCheckTimer = isActiveCheckTimer
isActiveCheckTimer.start()
}
deinit {
self.callActiveInfoTimer?.invalidate()
self.isActiveCheckTimer?.invalidate()
}
private func updateCallIsActive() {
let timestamp = Int32(Date().timeIntervalSince1970)
let payloadDescription = IpcGroupCallEmbeddedAppContext.PayloadDescription(
id: self.id,
timestamp: timestamp,
activeRequestId: self.activeRequestId,
joinResponse: self.joinResponse
)
guard let payloadDescriptionData = try? JSONEncoder().encode(payloadDescription) else {
return
}
guard let _ = try? payloadDescriptionData.write(to: URL(fileURLWithPath: payloadDescriptionPath(basePath: self.basePath)), options: .atomic) else {
return
}
}
private func updateKeepaliveInfo() {
let filePath = keepaliveInfoPath(basePath: self.basePath)
guard let keepaliveInfoData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
return
}
guard let keepaliveInfo = try? JSONDecoder().decode(KeepaliveInfo.self, from: keepaliveInfoData) else {
return
}
if keepaliveInfo.id != self.id {
self.isActivePromise.set(false)
return
}
let timestamp = Int32(Date().timeIntervalSince1970)
if keepaliveInfo.timestamp < timestamp - Int32(keepaliveTimeout) {
self.isActivePromise.set(false)
return
}
self.isActivePromise.set(true)
self.joinPayloadValue = keepaliveInfo.joinPayload
}
public func startScreencast() -> UInt32? {
if self.activeRequestId == nil {
let id = self.nextActiveRequestId
self.nextActiveRequestId += 1
self.activeRequestId = id
return id
} else {
return nil
}
}
public func stopScreencast() {
self.activeRequestId = nil
let timestamp = Int32(Date().timeIntervalSince1970)
let cutoffPayload = CutoffPayload(
id: self.id,
timestamp: timestamp
)
guard let cutoffPayloadData = try? JSONEncoder().encode(cutoffPayload) else {
return
}
guard let _ = try? cutoffPayloadData.write(to: URL(fileURLWithPath: cutoffPayloadPath(basePath: self.basePath)), options: .atomic) else {
return
}
}
}
public final class IpcGroupCallEmbeddedBroadcastContext {
public enum Status {
public enum FinishReason {
case screencastEnded
case callEnded
case error
}
case active(id: UInt32?, joinResponse: IpcGroupCallEmbeddedAppContext.JoinResponse?)
case finished(FinishReason)
}
private let basePath: String
private var timer: SwiftSignalKit.Timer?
private let statusPromise = Promise<Status>()
public var status: Signal<Status, NoError> {
return self.statusPromise.get()
}
private var currentId: UInt32?
private var callActiveInfoTimer: SwiftSignalKit.Timer?
private var keepaliveInfoTimer: SwiftSignalKit.Timer?
private var screencastCutoffTimer: SwiftSignalKit.Timer?
public var joinPayload: IpcGroupCallEmbeddedAppContext.JoinPayload? {
didSet {
if self.joinPayload != oldValue {
self.writeKeepaliveInfo()
}
}
}
public init(basePath: String) {
self.basePath = basePath
let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil)
let callActiveInfoTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
self?.updateCallIsActive()
}, queue: .mainQueue())
self.callActiveInfoTimer = callActiveInfoTimer
callActiveInfoTimer.start()
let screencastCutoffTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
self?.updateScreencastCutoff()
}, queue: .mainQueue())
self.screencastCutoffTimer = screencastCutoffTimer
screencastCutoffTimer.start()
}
deinit {
self.endActiveIndication()
self.callActiveInfoTimer?.invalidate()
self.keepaliveInfoTimer?.invalidate()
self.screencastCutoffTimer?.invalidate()
}
private func updateScreencastCutoff() {
let filePath = cutoffPayloadPath(basePath: self.basePath)
guard let cutoffPayloadData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
return
}
guard let cutoffPayload = try? JSONDecoder().decode(CutoffPayload.self, from: cutoffPayloadData) else {
return
}
let timestamp = Int32(Date().timeIntervalSince1970)
if let currentId = self.currentId, currentId == cutoffPayload.id && cutoffPayload.timestamp > timestamp - 10 {
self.statusPromise.set(.single(.finished(.screencastEnded)))
return
}
}
private func updateCallIsActive() {
let filePath = payloadDescriptionPath(basePath: self.basePath)
guard let payloadDescriptionData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
self.statusPromise.set(.single(.finished(.error)))
return
}
guard let payloadDescription = try? JSONDecoder().decode(IpcGroupCallEmbeddedAppContext.PayloadDescription.self, from: payloadDescriptionData) else {
self.statusPromise.set(.single(.finished(.error)))
return
}
let timestamp = Int32(Date().timeIntervalSince1970)
if payloadDescription.timestamp < timestamp - 4 {
self.statusPromise.set(.single(.finished(.callEnded)))
return
}
if let currentId = self.currentId {
if currentId != payloadDescription.id {
self.statusPromise.set(.single(.finished(.callEnded)))
} else {
self.statusPromise.set(.single(.active(id: payloadDescription.activeRequestId, joinResponse: payloadDescription.joinResponse)))
}
} else {
self.currentId = payloadDescription.id
self.writeKeepaliveInfo()
let keepaliveInfoTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
self?.writeKeepaliveInfo()
}, queue: .mainQueue())
self.keepaliveInfoTimer = keepaliveInfoTimer
keepaliveInfoTimer.start()
self.statusPromise.set(.single(.active(id: payloadDescription.activeRequestId, joinResponse: payloadDescription.joinResponse)))
}
}
private func writeKeepaliveInfo() {
guard let currentId = self.currentId else {
preconditionFailure()
}
let keepaliveInfo = IpcGroupCallEmbeddedAppContext.KeepaliveInfo(
id: currentId,
timestamp: Int32(Date().timeIntervalSince1970),
joinPayload: self.joinPayload
)
guard let keepaliveInfoData = try? JSONEncoder().encode(keepaliveInfo) else {
preconditionFailure()
}
guard let _ = try? keepaliveInfoData.write(to: URL(fileURLWithPath: keepaliveInfoPath(basePath: self.basePath)), options: .atomic) else {
preconditionFailure()
}
}
private func endActiveIndication() {
let _ = try? FileManager.default.removeItem(atPath: keepaliveInfoPath(basePath: self.basePath))
}
}

View file

@ -7,13 +7,13 @@ import TelegramUIPreferences
import TgVoip
import TgVoipWebrtc
private let debugUseLegacyVersionForReflectors: Bool = {
private func debugUseLegacyVersionForReflectors() -> Bool {
#if DEBUG && false
return true
#else
return false
#endif
}()
}
private struct PeerTag: Hashable, CustomStringConvertible {
var bytes: [UInt8] = Array<UInt8>(repeating: 0, count: 16)
@ -510,21 +510,21 @@ public final class OngoingCallVideoCapturer {
self.impl.setIsVideoEnabled(value)
}
public func injectPixelBuffer(_ pixelBuffer: CVPixelBuffer, rotation: CGImagePropertyOrientation) {
public func injectSampleBuffer(_ sampleBuffer: CMSampleBuffer, rotation: CGImagePropertyOrientation, completion: @escaping () -> Void) {
var videoRotation: OngoingCallVideoOrientation = .rotation0
switch rotation {
case .up:
videoRotation = .rotation0
case .left:
videoRotation = .rotation90
case .right:
videoRotation = .rotation270
case .down:
videoRotation = .rotation180
default:
videoRotation = .rotation0
case .up:
videoRotation = .rotation0
case .left:
videoRotation = .rotation90
case .right:
videoRotation = .rotation270
case .down:
videoRotation = .rotation180
default:
videoRotation = .rotation0
}
self.impl.submitPixelBuffer(pixelBuffer, rotation: videoRotation.orientation)
self.impl.submitSampleBuffer(sampleBuffer, rotation: videoRotation.orientation, completion: completion)
}
public func video() -> Signal<OngoingGroupCallContext.VideoFrameData, NoError> {
@ -819,7 +819,7 @@ public final class OngoingCallContext {
}
#endif
if debugUseLegacyVersionForReflectors {
if debugUseLegacyVersionForReflectors() {
return [(OngoingCallThreadLocalContext.version(), true)]
} else {
var result: [(version: String, supportsVideo: Bool)] = [(OngoingCallThreadLocalContext.version(), false)]
@ -860,9 +860,9 @@ public final class OngoingCallContext {
var useModernImplementation = true
var version = version
var allowP2P = allowP2P
if debugUseLegacyVersionForReflectors {
if debugUseLegacyVersionForReflectors() {
useModernImplementation = true
version = "5.0.0"
version = "12.0.0"
allowP2P = false
} else {
useModernImplementation = version != OngoingCallThreadLocalContext.version()
@ -879,7 +879,23 @@ public final class OngoingCallContext {
}
}
let unfilteredConnections = [connections.primary] + connections.alternatives
var unfilteredConnections: [CallSessionConnection]
unfilteredConnections = [connections.primary] + connections.alternatives
if version == "12.0.0" {
for connection in unfilteredConnections {
if case let .reflector(reflector) = connection {
unfilteredConnections.append(.reflector(CallSessionConnection.Reflector(
id: 123456,
ip: "91.108.9.38",
ipv6: "",
isTcp: true,
port: 595,
peerTag: reflector.peerTag
)))
}
}
}
var reflectorIdList: [Int64] = []
for connection in unfilteredConnections {
@ -911,11 +927,17 @@ public final class OngoingCallContext {
switch connection {
case let .reflector(reflector):
if reflector.isTcp {
if signalingReflector == nil {
signalingReflector = OngoingCallConnectionDescriptionWebrtc(reflectorId: 0, hasStun: false, hasTurn: true, hasTcp: true, ip: reflector.ip, port: reflector.port, username: "reflector", password: hexString(reflector.peerTag))
if version == "12.0.0" {
/*if signalingReflector == nil {
signalingReflector = OngoingCallConnectionDescriptionWebrtc(reflectorId: 0, hasStun: false, hasTurn: true, hasTcp: true, ip: reflector.ip, port: reflector.port, username: "reflector", password: hexString(reflector.peerTag))
}*/
} else {
if signalingReflector == nil {
signalingReflector = OngoingCallConnectionDescriptionWebrtc(reflectorId: 0, hasStun: false, hasTurn: true, hasTcp: true, ip: reflector.ip, port: reflector.port, username: "reflector", password: hexString(reflector.peerTag))
}
continue connectionsLoop
}
continue connectionsLoop
}
case .webRtcReflector:
break
@ -962,22 +984,37 @@ public final class OngoingCallContext {
directConnection = nil
}
#if DEBUG && false
#if DEBUG && true
var customParameters = customParameters
if let initialCustomParameters = try? JSONSerialization.jsonObject(with: (customParameters ?? "{}").data(using: .utf8)!) as? [String: Any] {
var customParametersValue: [String: Any]
customParametersValue = initialCustomParameters
customParametersValue["network_standalone_reflectors"] = true as NSNumber
customParametersValue["network_use_mtproto"] = true as NSNumber
customParametersValue["network_skip_initial_ping"] = true as NSNumber
customParameters = String(data: try! JSONSerialization.data(withJSONObject: customParametersValue), encoding: .utf8)!
if version == "12.0.0" {
customParametersValue["network_use_tcponly"] = true as NSNumber
customParameters = String(data: try! JSONSerialization.data(withJSONObject: customParametersValue), encoding: .utf8)!
}
if let reflector = filteredConnections.first(where: { $0.username == "reflector" && $0.reflectorId == 1 }) {
filteredConnections = [reflector]
if let value = customParametersValue["network_use_tcponly"] as? Bool, value {
filteredConnections = filteredConnections.filter { connection in
if connection.hasTcp {
return true
}
return false
}
allowP2P = false
}
}
#endif
/*#if DEBUG
if let initialCustomParameters = try? JSONSerialization.jsonObject(with: (customParameters ?? "{}").data(using: .utf8)!) as? [String: Any] {
var customParametersValue: [String: Any]
customParametersValue = initialCustomParameters
customParametersValue["network_kcp_experiment"] = true as NSNumber
customParameters = String(data: try! JSONSerialization.data(withJSONObject: customParametersValue), encoding: .utf8)!
}
#endif*/
let context = OngoingCallThreadLocalContextWebrtc(
version: version,
customParameters: customParameters,