Swiftgram/submodules/TelegramUI/Sources/SoftwareVideoLayerFrameManager.swift
Peter Iakovlev e9a4a9347a Revert "Rename directories [skip ci]"
This reverts commit 789438a27450dcbdee6065ebf096198ed3b90fec
2020-03-01 10:06:51 +00:00

135 lines
5.4 KiB
Swift

import Foundation
import UIKit
import Postbox
import TelegramCore
import SyncCore
import SwiftSignalKit
import CoreMedia
import UniversalMediaPlayer
private let applyQueue = Queue()
private let workers = ThreadPool(threadCount: 2, threadPriority: 0.09)
private var nextWorker = 0
final class SoftwareVideoLayerFrameManager {
private let fetchDisposable: Disposable
private var dataDisposable = MetaDisposable()
private let source = Atomic<SoftwareVideoSource?>(value: nil)
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 queue: ThreadPoolQueue
private let layerHolder: SampleBufferLayer
private var rotationAngle: CGFloat = 0.0
private var aspect: CGFloat = 1.0
private var layerRotationAngleAndAspect: (CGFloat, CGFloat)?
init(account: Account, fileReference: FileMediaReference, resource: MediaResource, layerHolder: SampleBufferLayer) {
nextWorker += 1
self.account = account
self.resource = resource
self.queue = ThreadPoolQueue(threadPool: workers)
self.layerHolder = layerHolder
layerHolder.layer.videoGravity = .resizeAspectFill
layerHolder.layer.masksToBounds = true
self.fetchDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(resource)).start()
}
deinit {
self.fetchDisposable.dispose()
self.dataDisposable.dispose()
}
func start() {
self.dataDisposable.set((self.account.postbox.mediaBox.resourceData(self.resource, option: .complete(waitUntilFetchStatus: false)) |> deliverOn(applyQueue)).start(next: { [weak self] data in
if let strongSelf = self, data.complete {
let _ = strongSelf.source.swap(SoftwareVideoSource(path: data.path))
}
}))
}
func tick(timestamp: Double) {
applyQueue.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
}
index += 1
}
if let latestFrameIndex = latestFrameIndex {
let frame = self.frames[latestFrameIndex]
for i in (0 ... latestFrameIndex).reversed() {
self.frames.remove(at: i)
}
if self.layerHolder.layer.status == .failed {
self.layerHolder.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.layerHolder.layer.enqueue(frame.sampleBuffer)
}
}
self.poll()
}
}
private var polling = false
private func poll() {
if self.frames.count < 3 && !self.polling {
self.polling = true
let maxPts = self.maxPts
self.queue.addTask(ThreadPoolTask { [weak self] state in
if state.cancelled.with({ $0 }) {
return
}
if let strongSelf = self {
let frameAndLoop = (strongSelf.source.with { $0 })?.readFrame(maxPts: maxPts)
applyQueue.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 {
strongSelf.minPts = frame.position
}
strongSelf.frames.append(frame)
}
if let loop = frameAndLoop?.3, loop {
strongSelf.maxPts = strongSelf.minPts
strongSelf.minPts = nil
}
strongSelf.poll()
}
}
}
})
}
}
}