Various improvements

This commit is contained in:
Isaac
2024-12-06 22:15:52 +08:00
parent 1abaeddfad
commit 4e964d4546
12 changed files with 570 additions and 1995 deletions

View File

@@ -57,14 +57,14 @@ public struct PresentationCallState: Equatable {
public enum VideoState: Equatable {
case notAvailable
case inactive
case active(isScreencast: Bool)
case paused(isScreencast: Bool)
case active(isScreencast: Bool, endpointId: String)
case paused(isScreencast: Bool, endpointId: String)
}
public enum RemoteVideoState: Equatable {
case inactive
case active
case paused
case active(endpointId: String)
case paused(endpointId: String)
}
public enum RemoteAudioState: Equatable {

View File

@@ -105,7 +105,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case disableReloginTokens(Bool)
case disableCallV2(Bool)
case experimentalCallMute(Bool)
case autoBenchmarkReflectors(Bool)
case conferenceCalls(Bool)
case benchmarkReflectors
case enableLocalTranslation(Bool)
case preferredVideoCodec(Int, String, String?, Bool)
@@ -132,7 +132,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.web.rawValue
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .disableCallV2, .experimentalCallMute, .autoBenchmarkReflectors, .benchmarkReflectors, .enableLocalTranslation:
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .disableCallV2, .experimentalCallMute, .conferenceCalls, .benchmarkReflectors, .enableLocalTranslation:
return DebugControllerSection.experiments.rawValue
case .logTranslationRecognition, .resetTranslationStates:
return DebugControllerSection.translation.rawValue
@@ -249,7 +249,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 51
case .experimentalCallMute:
return 52
case .autoBenchmarkReflectors:
case .conferenceCalls:
return 53
case .benchmarkReflectors:
return 54
@@ -1345,12 +1345,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .autoBenchmarkReflectors(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Auto-Benchmark Reflectors [Restart]", value: value, sectionId: self.section, style: .blocks, updated: { value in
case let .conferenceCalls(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Conference [WIP]", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.autoBenchmarkReflectors = value
settings.conferenceCalls = value
return PreferencesEntry(settings)
})
}).start()
@@ -1577,11 +1577,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.disableCallV2(experimentalSettings.disableCallV2))
entries.append(.experimentalCallMute(experimentalSettings.experimentalCallMute))
var defaultAutoBenchmarkReflectors = false
if case .internal = sharedContext.applicationBindings.appBuildType {
defaultAutoBenchmarkReflectors = true
}
entries.append(.autoBenchmarkReflectors(experimentalSettings.autoBenchmarkReflectors ?? defaultAutoBenchmarkReflectors))
entries.append(.conferenceCalls(experimentalSettings.conferenceCalls))
entries.append(.benchmarkReflectors)
entries.append(.enableLocalTranslation(experimentalSettings.enableLocalTranslation))

View File

@@ -41,6 +41,193 @@ protocol CallControllerNodeProtocol: AnyObject {
}
public final class CallController: ViewController {
public enum Call: Equatable {
case call(PresentationCall)
case groupCall(PresentationGroupCall)
public static func ==(lhs: Call, rhs: Call) -> Bool {
switch lhs {
case let .call(lhsCall):
if case let .call(rhsCall) = rhs {
return lhsCall === rhsCall
} else {
return false
}
case let .groupCall(lhsGroupCall):
if case let .groupCall(rhsGroupCall) = rhs {
return lhsGroupCall === rhsGroupCall
} else {
return false
}
}
}
public var context: AccountContext {
switch self {
case let .call(call):
return call.context
case let .groupCall(groupCall):
return groupCall.accountContext
}
}
public var peerId: EnginePeer.Id {
switch self {
case let .call(call):
return call.peerId
case let .groupCall(groupCall):
return groupCall.peerId
}
}
public func requestVideo() {
switch self {
case let .call(call):
call.requestVideo()
case let .groupCall(groupCall):
groupCall.requestVideo()
}
}
public func disableVideo() {
switch self {
case let .call(call):
call.disableVideo()
case let .groupCall(groupCall):
groupCall.disableVideo()
}
}
public func disableScreencast() {
switch self {
case let .call(call):
(call as? PresentationCallImpl)?.disableScreencast()
case let .groupCall(groupCall):
groupCall.disableScreencast()
}
}
public func switchVideoCamera() {
switch self {
case let .call(call):
call.switchVideoCamera()
case let .groupCall(groupCall):
groupCall.switchVideoCamera()
}
}
public func toggleIsMuted() {
switch self {
case let .call(call):
call.toggleIsMuted()
case let .groupCall(groupCall):
groupCall.toggleIsMuted()
}
}
public func setCurrentAudioOutput(_ output: AudioSessionOutput) {
switch self {
case let .call(call):
call.setCurrentAudioOutput(output)
case let .groupCall(groupCall):
groupCall.setCurrentAudioOutput(output)
}
}
public var isMuted: Signal<Bool, NoError> {
switch self {
case let .call(call):
return call.isMuted
case let .groupCall(groupCall):
return groupCall.isMuted
}
}
public var audioLevel: Signal<Float, NoError> {
switch self {
case let .call(call):
return call.audioLevel
case let .groupCall(groupCall):
var audioLevelId: UInt32?
return groupCall.audioLevels |> map { audioLevels -> Float in
var result: Float = 0
for item in audioLevels {
if let audioLevelId {
if item.1 == audioLevelId {
result = item.2
break
}
} else {
if item.1 != 0 {
audioLevelId = item.1
result = item.2
break
}
}
}
return result
}
}
}
public var isOutgoing: Bool {
switch self {
case let .call(call):
return call.isOutgoing
case .groupCall:
return false
}
}
public func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void) {
switch self {
case let .call(call):
call.makeOutgoingVideoView(completion: completion)
case let .groupCall(groupCall):
groupCall.makeOutgoingVideoView(requestClone: false, completion: { a, _ in
completion(a)
})
}
}
public var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> {
switch self {
case let .call(call):
return call.audioOutputState
case let .groupCall(groupCall):
return groupCall.audioOutputState
}
}
public func debugInfo() -> Signal<(String, String), NoError> {
switch self {
case let .call(call):
return call.debugInfo()
case .groupCall:
return .single(("", ""))
}
}
public func answer() {
switch self {
case let .call(call):
call.answer()
case .groupCall:
break
}
}
public func hangUp() -> Signal<Bool, NoError> {
switch self {
case let .call(call):
return call.hangUp()
case let .groupCall(groupCall):
return groupCall.leave(terminateIfPossible: false)
}
}
}
private var controllerNode: CallControllerNodeProtocol {
return self.displayNode as! CallControllerNodeProtocol
}
@@ -55,7 +242,7 @@ public final class CallController: ViewController {
private let sharedContext: SharedAccountContext
private let account: Account
public let call: PresentationCall
public let call: CallController.Call
private let easyDebugAccess: Bool
private var presentationData: PresentationData
@@ -78,7 +265,10 @@ public final class CallController: ViewController {
public var restoreUIForPictureInPicture: ((@escaping (Bool) -> Void) -> Void)?
public init(sharedContext: SharedAccountContext, account: Account, call: PresentationCall, easyDebugAccess: Bool) {
public var onViewDidAppear: (() -> Void)?
public var onViewDidDisappear: (() -> Void)?
public init(sharedContext: SharedAccountContext, account: Account, call: CallController.Call, easyDebugAccess: Bool) {
self.sharedContext = sharedContext
self.account = account
self.call = call
@@ -103,10 +293,84 @@ public final class CallController: ViewController {
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
switch call {
case let .call(call):
self.disposable = (call.state
|> deliverOnMainQueue).start(next: { [weak self] callState in
self?.callStateUpdated(callState)
})
case let .groupCall(groupCall):
let accountPeerId = groupCall.account.peerId
let videoEndpoints: Signal<(local: String?, remote: PresentationGroupCallRequestedVideo?), NoError> = groupCall.members
|> map { members -> (local: String?, remote: PresentationGroupCallRequestedVideo?) in
guard let members else {
return (nil, nil)
}
var local: String?
var remote: PresentationGroupCallRequestedVideo?
for participant in members.participants {
if let video = participant.requestedPresentationVideoChannel(minQuality: .thumbnail, maxQuality: .full) ?? participant.requestedVideoChannel(minQuality: .thumbnail, maxQuality: .full) {
if participant.peer.id == accountPeerId {
local = video.endpointId
} else {
if remote == nil {
remote = video
}
}
}
}
return (local, remote)
}
|> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs == rhs
})
var startTimestamp: Double?
self.disposable = (combineLatest(queue: .mainQueue(),
groupCall.state,
videoEndpoints
)
|> deliverOnMainQueue).start(next: { [weak self] callState, videoEndpoints in
guard let self else {
return
}
let mappedState: PresentationCallState.State
switch callState.networkState {
case .connecting:
mappedState = .connecting(nil)
case .connected:
let timestamp = startTimestamp ?? CFAbsoluteTimeGetCurrent()
startTimestamp = timestamp
mappedState = .active(timestamp, nil, Data())
}
var mappedLocalVideoState: PresentationCallState.VideoState = .inactive
var mappedRemoteVideoState: PresentationCallState.RemoteVideoState = .inactive
if let local = videoEndpoints.local {
mappedLocalVideoState = .active(isScreencast: false, endpointId: local)
}
if let remote = videoEndpoints.remote {
mappedRemoteVideoState = .active(endpointId: remote.endpointId)
}
if case let .groupCall(groupCall) = self.call {
var requestedVideo: [PresentationGroupCallRequestedVideo] = []
if let remote = videoEndpoints.remote {
requestedVideo.append(remote)
}
groupCall.setRequestedVideoList(items: requestedVideo)
}
self.callStateUpdated(PresentationCallState(
state: mappedState,
videoState: mappedLocalVideoState,
remoteVideoState: mappedRemoteVideoState,
remoteAudioState: .active,
remoteBatteryLevel: .normal
))
})
}
self.callMutedDisposable = (call.isMuted
|> deliverOnMainQueue).start(next: { [weak self] value in
@@ -148,16 +412,15 @@ public final class CallController: ViewController {
}
override public func loadDisplayNode() {
var useV2 = true
if let data = self.call.context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_disable_callui_v2"] {
useV2 = false
}
if !useV2 {
self.displayNode = CallControllerNode(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call)
self.isContentsReady.set(.single(true))
} else {
let displayNode = CallControllerNodeV2(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), easyDebugAccess: self.easyDebugAccess, call: self.call)
let displayNode = CallControllerNodeV2(
sharedContext: self.sharedContext,
account: self.account,
presentationData: self.presentationData,
statusBar: self.statusBar,
debugInfo: self.call.debugInfo(),
easyDebugAccess: self.easyDebugAccess,
call: self.call
)
self.displayNode = displayNode
self.isContentsReady.set(displayNode.isReady.get())
@@ -168,7 +431,6 @@ public final class CallController: ViewController {
}
restoreUIForPictureInPicture(completion)
}
}
self.displayNodeDidLoad()
self.controllerNode.toggleMute = { [weak self] in
@@ -335,7 +597,11 @@ public final class CallController: ViewController {
self.presentingViewController?.dismiss(animated: false, completion: nil)
}
self.peerDisposable = (combineLatest(self.account.postbox.peerView(id: self.account.peerId) |> take(1), self.account.postbox.peerView(id: self.call.peerId), self.sharedContext.activeAccountsWithInfo |> take(1))
self.peerDisposable = (combineLatest(queue: .mainQueue(),
self.account.postbox.peerView(id: self.account.peerId) |> take(1),
self.account.postbox.peerView(id: self.call.peerId),
self.sharedContext.activeAccountsWithInfo |> take(1)
)
|> deliverOnMainQueue).start(next: { [weak self] accountView, view, activeAccountsWithInfo in
if let strongSelf = self {
if let accountPeer = accountView.peers[accountView.peerId], let peer = view.peers[view.peerId] {
@@ -363,12 +629,16 @@ public final class CallController: ViewController {
}
self.idleTimerExtensionDisposable.set(self.sharedContext.applicationBindings.pushIdleTimerExtension())
self.onViewDidAppear?()
}
override public func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.idleTimerExtensionDisposable.set(nil)
self.onViewDidDisappear?()
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {

File diff suppressed because it is too large Load Diff

View File

@@ -32,7 +32,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
private let account: Account
private let presentationData: PresentationData
private let statusBar: StatusBar
private let call: PresentationCall
private let call: CallController.Call
private let containerView: UIView
private let callScreen: PrivateCallScreen
@@ -90,7 +90,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
statusBar: StatusBar,
debugInfo: Signal<(String, String), NoError>,
easyDebugAccess: Bool,
call: PresentationCall
call: CallController.Call
) {
self.sharedContext = sharedContext
self.account = account
@@ -172,9 +172,6 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
isRemoteBatteryLow: false,
isEnergySavingEnabled: !self.sharedContext.energyUsageSettings.fullTranslucency
)
if let peer = call.peer {
self.updatePeer(peer: peer)
}
self.isMicrophoneMutedDisposable = (call.isMuted
|> deliverOnMainQueue).startStrict(next: { [weak self] isMuted in
@@ -308,9 +305,9 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
switch callState.state {
case .active:
switch callState.videoState {
case .active(let isScreencast), .paused(let isScreencast):
case .active(let isScreencast, _), .paused(let isScreencast, _):
if isScreencast {
(self.call as? PresentationCallImpl)?.disableScreencast()
self.call.disableScreencast()
} else {
self.call.disableVideo()
}
@@ -489,23 +486,45 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
self.remoteVideo = nil
default:
switch callState.videoState {
case .active(let isScreencast), .paused(let isScreencast):
case .active(let isScreencast, let endpointId), .paused(let isScreencast, let endpointId):
if isScreencast {
self.localVideo = nil
} else {
if self.localVideo == nil, let call = self.call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: false) {
if self.localVideo == nil {
switch self.call {
case let .call(call):
if let call = call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: false) {
self.localVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
}
case let .groupCall(groupCall):
if let groupCall = groupCall as? PresentationGroupCallImpl {
if let videoStreamSignal = groupCall.video(endpointId: endpointId) {
self.localVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
}
}
}
}
}
case .inactive, .notAvailable:
self.localVideo = nil
}
switch callState.remoteVideoState {
case .active, .paused:
if self.remoteVideo == nil, let call = self.call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: true) {
case .active(let endpointId), .paused(let endpointId):
if self.remoteVideo == nil {
switch self.call {
case let .call(call):
if let call = call as? PresentationCallImpl, let videoStreamSignal = call.video(isIncoming: true) {
self.remoteVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
}
case let .groupCall(groupCall):
if let groupCall = groupCall as? PresentationGroupCallImpl {
if let videoStreamSignal = groupCall.video(endpointId: endpointId) {
self.remoteVideo = AdaptedCallVideoSource(videoStreamSignal: videoStreamSignal)
}
}
}
}
case .inactive:
self.remoteVideo = nil
}

View File

@@ -183,7 +183,7 @@ public final class PresentationCallImpl: PresentationCall {
self.isVideo = startWithVideo
if self.isVideo {
self.videoCapturer = OngoingCallVideoCapturer()
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .active(isScreencast: self.isScreencastActive), remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal))
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .active(isScreencast: self.isScreencastActive, endpointId: ""), remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal))
} else {
self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: self.isVideoPossible ? .inactive : .notAvailable, remoteVideoState: .inactive, remoteAudioState: .active, remoteBatteryLevel: .normal))
}
@@ -418,19 +418,19 @@ public final class PresentationCallImpl: PresentationCall {
case .notAvailable:
mappedVideoState = .notAvailable
case .active:
mappedVideoState = .active(isScreencast: self.isScreencastActive)
mappedVideoState = .active(isScreencast: self.isScreencastActive, endpointId: "")
case .inactive:
mappedVideoState = .inactive
case .paused:
mappedVideoState = .paused(isScreencast: self.isScreencastActive)
mappedVideoState = .paused(isScreencast: self.isScreencastActive, endpointId: "")
}
switch callContextState.remoteVideoState {
case .inactive:
mappedRemoteVideoState = .inactive
case .active:
mappedRemoteVideoState = .active
mappedRemoteVideoState = .active(endpointId: "")
case .paused:
mappedRemoteVideoState = .paused
mappedRemoteVideoState = .paused(endpointId: "")
}
switch callContextState.remoteAudioState {
case .active:
@@ -453,7 +453,7 @@ public final class PresentationCallImpl: PresentationCall {
mappedVideoState = previousVideoState
} else {
if self.isVideo {
mappedVideoState = .active(isScreencast: self.isScreencastActive)
mappedVideoState = .active(isScreencast: self.isScreencastActive, endpointId: "")
} else if self.isVideoPossible && sessionState.isVideoPossible {
mappedVideoState = .inactive
} else {

View File

@@ -77,18 +77,18 @@ private final class FetchImpl {
let partRange: Range<Int64>
let fetchRange: Range<Int64>
let fetchedData: Data
let decryptedData: Data
let cleanData: Data
init(
partRange: Range<Int64>,
fetchRange: Range<Int64>,
fetchedData: Data,
decryptedData: Data
cleanData: Data
) {
self.partRange = partRange
self.fetchRange = fetchRange
self.fetchedData = fetchedData
self.decryptedData = decryptedData
self.cleanData = cleanData
}
}
@@ -148,6 +148,48 @@ private final class FetchImpl {
case cdn(CdnData)
}
private final class DecryptionState {
let aesKey: Data
var aesIv: Data
let decryptedSize: Int64
var offset: Int = 0
init(aesKey: Data, aesIv: Data, decryptedSize: Int64) {
self.aesKey = aesKey
self.aesIv = aesIv
self.decryptedSize = decryptedSize
}
func tryDecrypt(data: Data, offset: Int, loggingIdentifier: String) -> Data? {
if offset == self.offset {
var decryptedData = data
if self.decryptedSize == 0 {
Logger.shared.log("FetchV2", "\(loggingIdentifier): not decrypting part \(offset) ..< \(offset + data.count) (decryptedSize == 0)")
return nil
}
if decryptedData.count % 16 != 0 {
Logger.shared.log("FetchV2", "\(loggingIdentifier): not decrypting part \(offset) ..< \(offset + data.count) (decryptedData.count % 16 != 0)")
}
let decryptedDataCount = decryptedData.count
decryptedData.withUnsafeMutableBytes { rawBytes -> Void in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
self.aesIv.withUnsafeMutableBytes { rawIv -> Void in
let iv = rawIv.baseAddress!.assumingMemoryBound(to: UInt8.self)
MTAesDecryptBytesInplaceAndModifyIv(bytes, decryptedDataCount, self.aesKey, iv)
}
}
if self.offset + decryptedData.count > self.decryptedSize {
decryptedData.count = Int(self.decryptedSize) - self.offset
}
self.offset += decryptedData.count
Logger.shared.log("FetchV2", "\(loggingIdentifier): decrypted part \(offset) ..< \(offset + data.count) (new offset is \(self.offset))")
return decryptedData
} else {
return nil
}
}
}
private final class FetchingState {
let fetchLocation: FetchLocation
let partSize: Int64
@@ -160,6 +202,7 @@ private final class FetchImpl {
var pendingParts: [PendingPart] = []
var completedRanges = RangeSet<Int64>()
var decryptionState: DecryptionState?
var pendingReadyParts: [PendingReadyPart] = []
var completedHashRanges = RangeSet<Int64>()
var pendingHashRanges: [PendingHashRange] = []
@@ -174,7 +217,8 @@ private final class FetchImpl {
maxPartSize: Int64,
partAlignment: Int64,
partDivision: Int64,
maxPendingParts: Int
maxPendingParts: Int,
decryptionState: DecryptionState?
) {
self.fetchLocation = fetchLocation
self.partSize = partSize
@@ -183,6 +227,7 @@ private final class FetchImpl {
self.partAlignment = partAlignment
self.partDivision = partDivision
self.maxPendingParts = maxPendingParts
self.decryptionState = decryptionState
}
deinit {
@@ -373,6 +418,12 @@ private final class FetchImpl {
if self.state == nil {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): initializing to .datacenter(\(self.datacenterId))")
var decryptionState: DecryptionState?
if let encryptionKey = self.encryptionKey, let decryptedSize = self.decryptedSize {
decryptionState = DecryptionState(aesKey: encryptionKey.aesKey, aesIv: encryptionKey.aesIv, decryptedSize: decryptedSize)
self.onNext(.reset)
}
self.state = .fetching(FetchingState(
fetchLocation: .datacenter(self.datacenterId),
partSize: self.defaultPartSize,
@@ -380,7 +431,8 @@ private final class FetchImpl {
maxPartSize: 1 * 1024 * 1024,
partAlignment: 4 * 1024,
partDivision: 1 * 1024 * 1024,
maxPendingParts: 6
maxPendingParts: 6,
decryptionState: decryptionState
))
}
guard let state = self.state else {
@@ -396,6 +448,25 @@ private final class FetchImpl {
do {
var removedPendingReadyPartIndices: [Int] = []
if let decryptionState = state.decryptionState {
while true {
var removedSomePendingReadyPart = false
for i in 0 ..< state.pendingReadyParts.count {
if removedPendingReadyPartIndices.contains(i) {
continue
}
let pendingReadyPart = state.pendingReadyParts[i]
if let resultData = decryptionState.tryDecrypt(data: pendingReadyPart.cleanData, offset: Int(pendingReadyPart.fetchRange.lowerBound), loggingIdentifier: self.loggingIdentifier) {
removedPendingReadyPartIndices.append(i)
removedSomePendingReadyPart = true
self.commitPendingReadyPart(state: state, partRange: pendingReadyPart.partRange, fetchRange: pendingReadyPart.fetchRange, data: resultData)
}
}
if !removedSomePendingReadyPart {
break
}
}
} else {
for i in 0 ..< state.pendingReadyParts.count {
let pendingReadyPart = state.pendingReadyParts[i]
if state.completedHashRanges.isSuperset(of: RangeSet<Int64>(pendingReadyPart.fetchRange)) {
@@ -422,7 +493,7 @@ private final class FetchImpl {
break
}
let dataToHash = pendingReadyPart.decryptedData.subdata(in: Int(partLocalHashRange.lowerBound) ..< Int(partLocalHashRange.upperBound))
let dataToHash = pendingReadyPart.cleanData.subdata(in: Int(partLocalHashRange.lowerBound) ..< Int(partLocalHashRange.upperBound))
let localHash = MTSha256(dataToHash)
if localHash != hashRange.data {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): failed to verify \(pendingReadyPart.fetchRange): hash mismatch")
@@ -438,13 +509,14 @@ private final class FetchImpl {
}
}
if !checkFailed {
self.commitPendingReadyPart(state: state, partRange: pendingReadyPart.partRange, fetchRange: pendingReadyPart.fetchRange, data: pendingReadyPart.decryptedData)
self.commitPendingReadyPart(state: state, partRange: pendingReadyPart.partRange, fetchRange: pendingReadyPart.fetchRange, data: pendingReadyPart.cleanData)
} else {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): unable to find \(pendingReadyPart.fetchRange) hash check failed")
}
}
}
for index in removedPendingReadyPartIndices.reversed() {
}
for index in removedPendingReadyPartIndices.sorted(by: >) {
state.pendingReadyParts.remove(at: index)
}
}
@@ -452,8 +524,10 @@ private final class FetchImpl {
var requiredHashRanges = RangeSet<Int64>()
for pendingReadyPart in state.pendingReadyParts {
//TODO:check if already have hashes
if state.decryptionState == nil {
requiredHashRanges.formUnion(RangeSet<Int64>(pendingReadyPart.fetchRange))
}
}
requiredHashRanges.subtract(state.completedHashRanges)
for pendingHashRange in state.pendingHashRanges {
requiredHashRanges.subtract(RangeSet<Int64>(pendingHashRange.range))
@@ -613,7 +687,8 @@ private final class FetchImpl {
maxPartSize: self.cdnPartSize * 2,
partAlignment: self.cdnPartSize,
partDivision: 1 * 1024 * 1024,
maxPendingParts: 6
maxPendingParts: 6,
decryptionState: nil
))
self.update()
}, error: { [weak self] error in
@@ -661,7 +736,8 @@ private final class FetchImpl {
maxPartSize: self.defaultPartSize,
partAlignment: 4 * 1024,
partDivision: 1 * 1024 * 1024,
maxPendingParts: 6
maxPendingParts: 6,
decryptionState: nil
))
self.update()
@@ -819,7 +895,16 @@ private final class FetchImpl {
partRange: partRange,
fetchRange: fetchRange,
fetchedData: verifyPartHashData.fetchedData,
decryptedData: data
cleanData: data
))
} else if state.decryptionState != nil {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): stashing data part \(partRange) (aligned as \(fetchRange)) for decryption")
state.pendingReadyParts.append(FetchImpl.PendingReadyPart(
partRange: partRange,
fetchRange: fetchRange,
fetchedData: data,
cleanData: data
))
} else {
self.commitPendingReadyPart(
@@ -837,7 +922,8 @@ private final class FetchImpl {
maxPartSize: self.cdnPartSize * 2,
partAlignment: self.cdnPartSize,
partDivision: 1 * 1024 * 1024,
maxPendingParts: 6
maxPendingParts: 6,
decryptionState: nil
))
case let .cdnRefresh(cdnData, refreshToken):
self.state = .reuploadingToCdn(ReuploadingToCdnState(

View File

@@ -1115,7 +1115,7 @@ func multipartFetch(
continueInBackground: Bool = false,
useMainConnection: Bool = false
) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
if network.useExperimentalFeatures, let _ = resource as? TelegramCloudMediaResource, !(resource is SecretFileMediaResource) {
if network.useExperimentalFeatures, let _ = resource as? TelegramCloudMediaResource {
return multipartFetchV2(
accountPeerId: accountPeerId,
postbox: postbox,

View File

@@ -69,6 +69,7 @@ swift_library(
"//submodules/AppBundle",
"//submodules/UIKitRuntimeUtils",
"//submodules/TelegramPresentationData",
"//submodules/Components/MultilineTextComponent",
],
visibility = [
"//visibility:public",

View File

@@ -317,6 +317,10 @@ public class ShareRootControllerImpl {
presentationDataPromise.set(.single(presentationData))
var immediatePeerId: PeerId?
#if DEBUG
// Xcode crashes
immediatePeerId = nil
#else
if #available(iOS 13.2, *), let sendMessageIntent = self.getExtensionContext()?.intent as? INSendMessageIntent {
if let contact = sendMessageIntent.recipients?.first, let handle = contact.customIdentifier, handle.hasPrefix("tg") {
let string = handle.suffix(from: handle.index(handle.startIndex, offsetBy: 2))
@@ -325,6 +329,7 @@ public class ShareRootControllerImpl {
}
}
}
#endif
/*let account: Signal<(SharedAccountContextImpl, Account, [AccountWithInfo]), ShareAuthorizationError> = internalContext.sharedContext.accountManager.transaction { transaction -> (SharedAccountContextImpl, LoggingSettings) in
return (internalContext.sharedContext, transaction.getSharedData(SharedDataKeys.loggingSettings)?.get(LoggingSettings.self) ?? LoggingSettings.defaultSettings)

View File

@@ -885,6 +885,39 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.groupCallDisposable = (callManager.currentGroupCallSignal
|> deliverOnMainQueue).start(next: { [weak self] call in
if let strongSelf = self {
if strongSelf.immediateExperimentalUISettings.conferenceCalls {
let mappedCall = call.flatMap(CallController.Call.groupCall)
if mappedCall != strongSelf.callController?.call {
strongSelf.callController?.dismiss()
strongSelf.callController = nil
strongSelf.hasOngoingCall.set(false)
if let call {
mainWindow.hostView.containerView.endEditing(true)
strongSelf.hasGroupCallOnScreenPromise.set(true)
let callController = CallController(sharedContext: strongSelf, account: call.accountContext.account, call: .groupCall(call), easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild)
callController.onViewDidAppear = { [weak strongSelf] in
if let strongSelf {
strongSelf.hasGroupCallOnScreenPromise.set(true)
}
}
callController.onViewDidDisappear = { [weak strongSelf] in
if let strongSelf {
strongSelf.hasGroupCallOnScreenPromise.set(false)
}
}
strongSelf.callController = callController
strongSelf.mainWindow?.present(callController, on: .calls)
strongSelf.hasOngoingCall.set(true)
} else {
strongSelf.hasOngoingCall.set(false)
}
}
} else {
if call !== strongSelf.groupCallController?.call {
strongSelf.groupCallController?.dismiss(closing: true, manual: false)
strongSelf.groupCallController = nil
@@ -943,6 +976,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
}
}
}
})
let callSignal: Signal<PresentationCall?, NoError> = .single(nil)
@@ -1178,7 +1212,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
if let currentCallController = self.callController {
if currentCallController.call === call {
if currentCallController.call == .call(call) {
self.navigateToCurrentCall()
return
} else {
@@ -1188,7 +1222,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
self.mainWindow?.hostView.containerView.endEditing(true)
let callController = CallController(sharedContext: self, account: call.context.account, call: call, easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild)
let callController = CallController(sharedContext: self, account: call.context.account, call: .call(call), easyDebugAccess: !GlobalExperimentalSettings.isAppStoreBuild)
self.callController = callController
callController.restoreUIForPictureInPicture = { [weak self, weak callController] completion in
guard let self, let callController else {

View File

@@ -62,6 +62,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var dynamicStreaming: Bool
public var enableLocalTranslation: Bool
public var autoBenchmarkReflectors: Bool?
public var conferenceCalls: Bool
public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings(
@@ -101,7 +102,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
liveStreamV2: false,
dynamicStreaming: false,
enableLocalTranslation: false,
autoBenchmarkReflectors: nil
autoBenchmarkReflectors: nil,
conferenceCalls: false
)
}
@@ -142,7 +144,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
liveStreamV2: Bool,
dynamicStreaming: Bool,
enableLocalTranslation: Bool,
autoBenchmarkReflectors: Bool?
autoBenchmarkReflectors: Bool?,
conferenceCalls: Bool
) {
self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory
@@ -181,6 +184,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.dynamicStreaming = dynamicStreaming
self.enableLocalTranslation = enableLocalTranslation
self.autoBenchmarkReflectors = autoBenchmarkReflectors
self.conferenceCalls = conferenceCalls
}
public init(from decoder: Decoder) throws {
@@ -223,6 +227,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.dynamicStreaming = try container.decodeIfPresent(Bool.self, forKey: "dynamicStreaming_v2") ?? false
self.enableLocalTranslation = try container.decodeIfPresent(Bool.self, forKey: "enableLocalTranslation") ?? false
self.autoBenchmarkReflectors = try container.decodeIfPresent(Bool.self, forKey: "autoBenchmarkReflectors")
self.conferenceCalls = try container.decodeIfPresent(Bool.self, forKey: "conferenceCalls") ?? false
}
public func encode(to encoder: Encoder) throws {
@@ -265,6 +270,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
try container.encode(self.dynamicStreaming, forKey: "dynamicStreaming")
try container.encode(self.enableLocalTranslation, forKey: "enableLocalTranslation")
try container.encodeIfPresent(self.autoBenchmarkReflectors, forKey: "autoBenchmarkReflectors")
try container.encodeIfPresent(self.conferenceCalls, forKey: "conferenceCalls")
}
}