From e9d958809a35edabf51569be5bc5801f1863db29 Mon Sep 17 00:00:00 2001 From: isaac <> Date: Sun, 26 Apr 2026 02:44:12 +0400 Subject: [PATCH] Fix --- submodules/Display/Source/ListView.swift | 11 +- .../SoftwareVideoLayerFrameManager.swift | 241 ------------------ 2 files changed, 9 insertions(+), 243 deletions(-) diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 3c307aaee8..4dccd9bd73 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -2664,6 +2664,13 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur } } + private var nextAnimationId: Int = 0 + private func takeNextAnimationId() -> Int { + let value = self.nextAnimationId + self.nextAnimationId += 1 + return value + } + private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, animateFullTransition: Bool, customAnimationTransition: ControlledTransition?, synchronous: Bool, synchronousLoads: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem originalScrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, forceInvertOffsetDirection: Bool = false, completion: () -> Void) { var scrollToItem: ListViewScrollToItem? var isExperimentalSnapToScrollToItem = false @@ -3319,7 +3326,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur animation.completion = { [weak self] _ in self?.updateItemNodesVisibilities(onlyPositive: false) } - self.layer.add(animation, forKey: "animation-\(ObjectIdentifier(animation))") + self.layer.add(animation, forKey: "animation-\(self.takeNextAnimationId())") if !completeOffset.isZero { for itemNode in self.itemNodes { itemNode.applyAbsoluteOffset(value: CGPoint(x: 0.0, y: -completeOffset), animationCurve: animationCurve, duration: animationDuration) @@ -3747,7 +3754,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur headerNode.removeFromSupernode() } } - self.layer.add(animation, forKey: "animation-\(ObjectIdentifier(animation))") + self.layer.add(animation, forKey: "animation-\(self.takeNextAnimationId()))") } for itemNode in self.itemNodes { diff --git a/submodules/SoftwareVideo/Sources/SoftwareVideoLayerFrameManager.swift b/submodules/SoftwareVideo/Sources/SoftwareVideoLayerFrameManager.swift index 7353213b80..9dc246a920 100644 --- a/submodules/SoftwareVideo/Sources/SoftwareVideoLayerFrameManager.swift +++ b/submodules/SoftwareVideo/Sources/SoftwareVideoLayerFrameManager.swift @@ -1,246 +1,5 @@ import Foundation -import UIKit -import Postbox -import TelegramCore import SwiftSignalKit -import CoreMedia -import UniversalMediaPlayer -import AVFoundation public let softwareVideoApplyQueue = Queue() public let softwareVideoWorkers = ThreadPool(threadCount: 3, threadPriority: 0.2) -private var nextWorker = 0 - -public final class SoftwareVideoLayerFrameManager { - private let fetchDisposable: Disposable - private var dataDisposable = MetaDisposable() - private let source = Atomic(value: nil) - private let hintVP9: Bool - - private var baseTimestamp: Double? - private var frames: [MediaTrackFrame] = [] - private var minPts: CMTime? - private var maxPts: CMTime? - - private let account: Account - private let resource: MediaResource - private let secondaryResource: MediaResource? - private let queue: ThreadPoolQueue - private let layerHolder: SampleBufferLayer? - private weak var layer: AVSampleBufferDisplayLayer? - - private var rotationAngle: CGFloat = 0.0 - private var aspect: CGFloat = 1.0 - - private var layerRotationAngleAndAspect: (CGFloat, CGFloat)? - - private var didStart = false - public var started: () -> Void = { } - - public init(account: Account, userLocation: MediaResourceUserLocation, userContentType: MediaResourceUserContentType, fileReference: FileMediaReference, layerHolder: SampleBufferLayer?, layer: AVSampleBufferDisplayLayer? = nil, hintVP9: Bool = false) { - var resource = fileReference.media.resource - var secondaryResource: MediaResource? - for attribute in fileReference.media.attributes { - if case .Video = attribute { - if let thumbnail = fileReference.media.videoThumbnails.first { - resource = thumbnail.resource - secondaryResource = fileReference.media.resource - } - } - } - - nextWorker += 1 - self.account = account - self.resource = resource - self.hintVP9 = hintVP9 - self.secondaryResource = secondaryResource - self.queue = ThreadPoolQueue(threadPool: softwareVideoWorkers) - self.layerHolder = layerHolder - self.layer = layer ?? layerHolder?.layer - self.layer?.videoGravity = .resizeAspectFill - self.layer?.masksToBounds = true - self.fetchDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: userLocation, userContentType: userContentType, reference: fileReference.resourceReference(resource)).start() - } - - deinit { - self.fetchDisposable.dispose() - self.dataDisposable.dispose() - } - - public func start() { - func stringForResource(_ resource: MediaResource?) -> String { - guard let resource = resource else { - return "" - } - if let resource = resource as? WebFileReferenceMediaResource { - return resource.url - } else { - return resource.id.stringRepresentation - } - } - Logger.shared.log("SoftwareVideo", "load video from \(stringForResource(self.resource)) or \(stringForResource(self.secondaryResource))") - let secondarySignal: Signal<(String, MediaResource)?, NoError> - if let secondaryResource = self.secondaryResource { - secondarySignal = self.account.postbox.mediaBox.resourceData(secondaryResource, option: .complete(waitUntilFetchStatus: false)) - |> map { data -> (String, MediaResource)? in - if data.complete { - return (data.path, secondaryResource) - } else { - return nil - } - } - } else { - secondarySignal = .single(nil) - } - - let firstResource = self.resource - - let firstReady: Signal<(String, MediaResource), NoError> = combineLatest( - self.account.postbox.mediaBox.resourceData(self.resource, option: .complete(waitUntilFetchStatus: false)), - secondarySignal - ) - |> mapToSignal { first, second -> Signal<(String, MediaResource), NoError> in - if first.complete { - return .single((first.path, firstResource)) - } else if let second = second { - return .single(second) - } else { - return .complete() - } - } - |> take(1) - - self.dataDisposable.set((firstReady - |> deliverOn(softwareVideoApplyQueue)).start(next: { [weak self] path, resource in - if let strongSelf = self { - let size = fileSize(path) - Logger.shared.log("SoftwareVideo", "loaded video from \(stringForResource(resource)) (file size: \(String(describing: size))") - - let _ = strongSelf.source.swap(SoftwareVideoSource(path: path, hintVP9: strongSelf.hintVP9, unpremultiplyAlpha: true)) - } - })) - } - - public func tick(timestamp: Double) { - softwareVideoApplyQueue.async { - if self.baseTimestamp == nil && !self.frames.isEmpty { - self.baseTimestamp = timestamp - } - - if let baseTimestamp = self.baseTimestamp { - var index = 0 - var latestFrameIndex: Int? - while index < self.frames.count { - if baseTimestamp + self.frames[index].position.seconds + self.frames[index].duration.seconds <= timestamp { - latestFrameIndex = index - //print("latestFrameIndex = \(index)") - } - index += 1 - } - if let latestFrameIndex = latestFrameIndex { - let frame = self.frames[latestFrameIndex] - for i in (0 ... latestFrameIndex).reversed() { - self.frames.remove(at: i) - } - if self.layer?.status == .failed { - self.layer?.flush() - } - /*if self.layerRotationAngleAndAspect?.0 != self.rotationAngle || self.layerRotationAngleAndAspect?.1 != self.aspect { - self.layerRotationAngleAndAspect = (self.rotationAngle, self.aspect) - var transform = CGAffineTransform(rotationAngle: CGFloat(self.rotationAngle)) - if !self.rotationAngle.isZero { - transform = transform.scaledBy(x: CGFloat(self.aspect), y: CGFloat(1.0 / self.aspect)) - } - self.layerHolder.layer.setAffineTransform(transform) - }*/ - self.layer?.enqueue(frame.sampleBuffer) - - if !self.didStart { - self.didStart = true - Queue.mainQueue().async { - self.started() - } - } - } - } - - self.poll() - } - } - - private var polling = false - - private func poll() { - if self.frames.count < 2 && !self.polling { - self.polling = true - let minPts = self.minPts - let maxPts = self.maxPts - self.queue.addTask(ThreadPoolTask { [weak self] state in - if state.cancelled.with({ $0 }) { - return - } - if let strongSelf = self { - var frameAndLoop: (MediaTrackFrame?, CGFloat, CGFloat, Bool)? - - var hadLoop = false - for _ in 0 ..< 1 { - frameAndLoop = (strongSelf.source.with { $0 })?.readFrame(maxPts: maxPts) - if let frameAndLoop = frameAndLoop { - if frameAndLoop.0 != nil || minPts != nil { - break - } else { - if frameAndLoop.3 { - hadLoop = true - } - //print("skip nil frame loop: \(frameAndLoop.3)") - } - } else { - break - } - } - if let loop = frameAndLoop?.3, loop { - hadLoop = true - } - - softwareVideoApplyQueue.async { - if let strongSelf = self { - strongSelf.polling = false - if let (_, rotationAngle, aspect, _) = frameAndLoop { - strongSelf.rotationAngle = rotationAngle - strongSelf.aspect = aspect - } - if let frame = frameAndLoop?.0 { - if strongSelf.minPts == nil || CMTimeCompare(strongSelf.minPts!, frame.position) < 0 { - var position = CMTimeAdd(frame.position, frame.duration) - for _ in 0 ..< 1 { - position = CMTimeAdd(position, frame.duration) - } - strongSelf.minPts = position - } - strongSelf.frames.append(frame) - strongSelf.frames.sort(by: { lhs, rhs in - if CMTimeCompare(lhs.position, rhs.position) < 0 { - return true - } else { - return false - } - }) - //print("add frame at \(CMTimeGetSeconds(frame.position))") - //let positions = strongSelf.frames.map { CMTimeGetSeconds($0.position) } - //print("frames: \(positions)") - } else { - //print("not adding frames") - } - if hadLoop { - strongSelf.maxPts = strongSelf.minPts - strongSelf.minPts = nil - //print("loop at \(strongSelf.minPts)") - } - strongSelf.poll() - } - } - } - }) - } - } -}