Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "SoftwareVideo",
module_name = "SoftwareVideo",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/TelegramCore:TelegramCore",
"//submodules/AccountContext:AccountContext",
"//submodules/MediaPlayer:UniversalMediaPlayer",
],
visibility = [
"//visibility:public",
],
)
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>
@@ -0,0 +1,66 @@
import Foundation
import UIKit
import AVFoundation
import SwiftSignalKit
private final class SampleBufferLayerImplNullAction: NSObject, CAAction {
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
}
}
private final class SampleBufferLayerImpl: AVSampleBufferDisplayLayer {
override func action(forKey event: String) -> CAAction? {
return SampleBufferLayerImplNullAction()
}
}
public final class SampleBufferLayer {
public let layer: AVSampleBufferDisplayLayer
private let enqueue: (AVSampleBufferDisplayLayer) -> Void
public var isFreed: Bool = false
fileprivate init(layer: AVSampleBufferDisplayLayer, enqueue: @escaping (AVSampleBufferDisplayLayer) -> Void) {
self.layer = layer
self.enqueue = enqueue
}
deinit {
if !self.isFreed {
self.enqueue(self.layer)
}
}
}
private let pool = Atomic<[AVSampleBufferDisplayLayer]>(value: [])
public func clearSampleBufferLayerPoll() {
let _ = pool.modify { _ in return [] }
}
public func takeSampleBufferLayer() -> SampleBufferLayer {
var layer: AVSampleBufferDisplayLayer?
let _ = pool.modify { list in
var list = list
if !list.isEmpty {
layer = list.removeLast()
}
return list
}
if layer == nil {
layer = SampleBufferLayerImpl()
}
return SampleBufferLayer(layer: layer!, enqueue: { layer in
Queue.mainQueue().async {
layer.flushAndRemoveImage()
layer.setAffineTransform(CGAffineTransform.identity)
#if targetEnvironment(simulator)
#else
let _ = pool.modify { list in
var list = list
list.append(layer)
return list
}
#endif
}
})
}
@@ -0,0 +1,246 @@
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<SoftwareVideoSource?>(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 "<none>"
}
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()
}
}
}
})
}
}
}