[WIP] Story collage

This commit is contained in:
Ilya Laktyushin
2024-11-27 18:41:03 +04:00
parent 0794812bae
commit d2b5476293
78 changed files with 6086 additions and 1224 deletions

View File

@@ -7,8 +7,8 @@ import SwiftSignalKit
final class UniversalTextureSource: TextureSource {
enum Input {
case image(UIImage)
case video(AVPlayerItem)
case image(UIImage, CGRect?)
case video(AVPlayerItem, CGRect?)
case entity(MediaEditorComposerEntity)
fileprivate func createContext(renderTarget: RenderTarget, queue: DispatchQueue, additional: Bool) -> InputContext {
@@ -28,7 +28,7 @@ final class UniversalTextureSource: TextureSource {
private let queue: DispatchQueue
private var mainInputContext: InputContext?
private var additionalInputContext: InputContext?
private var additionalInputContexts: [InputContext] = []
var forceUpdates = false
private var rate: Float = 1.0
@@ -48,7 +48,7 @@ final class UniversalTextureSource: TextureSource {
}
var mainImage: UIImage? {
if let mainInput = self.mainInputContext?.input, case let .image(image) = mainInput {
if let mainInput = self.mainInputContext?.input, case let .image(image, _) = mainInput {
return image
}
return nil
@@ -62,15 +62,11 @@ final class UniversalTextureSource: TextureSource {
self.update(forced: true)
}
func setAdditionalInput(_ input: Input?) {
func setAdditionalInputs(_ inputs: [Input]) {
guard let renderTarget = self.renderTarget else {
return
}
if let input {
self.additionalInputContext = input.createContext(renderTarget: renderTarget, queue: self.queue, additional: true)
} else {
self.additionalInputContext = nil
}
self.additionalInputContexts = inputs.map { $0.createContext(renderTarget: renderTarget, queue: self.queue, additional: true) }
self.update(forced: true)
}
@@ -79,7 +75,7 @@ final class UniversalTextureSource: TextureSource {
self.rate = rate
}
private var previousAdditionalOutput: MediaEditorRenderer.Input?
private var previousAdditionalOutput: [Int: MediaEditorRenderer.Input] = [:]
private var readyForMoreData = Atomic<Bool>(value: true)
private func update(forced: Bool) {
let time = CACurrentMediaTime()
@@ -89,7 +85,15 @@ final class UniversalTextureSource: TextureSource {
fps = 30
}
let needsDisplayLink = (self.mainInputContext?.needsDisplayLink ?? false) || (self.additionalInputContext?.needsDisplayLink ?? false)
var additionalsNeedDisplayLink = false
for context in self.additionalInputContexts {
if context.needsDisplayLink {
additionalsNeedDisplayLink = true
break
}
}
let needsDisplayLink = (self.mainInputContext?.needsDisplayLink ?? false) || additionalsNeedDisplayLink
if needsDisplayLink {
if self.displayLink == nil {
let displayLink = CADisplayLink(target: DisplayLinkTarget({ [weak self] in
@@ -122,22 +126,32 @@ final class UniversalTextureSource: TextureSource {
return
}
if let main {
self.output?.consume(main: main, additional: nil, render: true)
self.output?.consume(main: main, additionals: [], render: true)
}
let _ = self.readyForMoreData.swap(true)
})
} else {
let main = self.mainInputContext?.output(time: time)
var additional = self.additionalInputContext?.output(time: time)
if let additional {
self.previousAdditionalOutput = additional
} else if self.additionalInputContext != nil {
additional = self.previousAdditionalOutput
var additionals: [(Int, InputContext.Output?)] = []
var index = 0
for context in self.additionalInputContexts {
additionals.append((index, context.output(time: time)))
index += 1
}
for (index, output) in additionals {
if let output {
self.previousAdditionalOutput[index] = output
}
}
for (index, output) in additionals {
if output == nil {
additionals[index] = (index, self.previousAdditionalOutput[index])
}
}
guard let main else {
return
}
self.output?.consume(main: main, additional: additional, render: true)
self.output?.consume(main: main, additionals: additionals.compactMap { $0.1 }, render: true)
}
}
@@ -148,7 +162,7 @@ final class UniversalTextureSource: TextureSource {
func invalidate() {
self.mainInputContext?.invalidate()
self.additionalInputContext?.invalidate()
self.additionalInputContexts.forEach { $0.invalidate() }
}
private class DisplayLinkTarget {
@@ -168,6 +182,8 @@ protocol InputContext {
var input: Input { get }
var rect: CGRect? { get }
var useAsyncOutput: Bool { get }
func output(time: Double) -> Output?
func asyncOutput(time: Double, completion: @escaping (Output?) -> Void)
@@ -191,12 +207,14 @@ private class ImageInputContext: InputContext {
fileprivate var input: Input
private var texture: MTLTexture?
private var hasTransparency = false
fileprivate var rect: CGRect?
init(input: Input, renderTarget: RenderTarget, queue: DispatchQueue) {
guard case let .image(image) = input else {
guard case let .image(image, rect) = input else {
fatalError()
}
self.input = input
self.rect = rect
if let device = renderTarget.mtlDevice {
self.texture = loadTexture(image: image, device: device)
}
@@ -204,7 +222,7 @@ private class ImageInputContext: InputContext {
}
func output(time: Double) -> Output? {
return self.texture.flatMap { .texture($0, .zero, self.hasTransparency) }
return self.texture.flatMap { .texture($0, .zero, self.hasTransparency, self.rect) }
}
func invalidate() {
@@ -220,23 +238,26 @@ private class VideoInputContext: NSObject, InputContext, AVPlayerItemOutputPullD
fileprivate var input: Input
private var videoOutput: AVPlayerItemVideoOutput?
private var textureRotation: TextureRotation = .rotate0Degrees
fileprivate var rect: CGRect?
var playerItem: AVPlayerItem {
guard case let .video(playerItem) = self.input else {
guard case let .video(playerItem, _) = self.input else {
fatalError()
}
return playerItem
}
init(input: Input, renderTarget: RenderTarget, queue: DispatchQueue, additional: Bool) {
guard case .video = input else {
guard case let .video(_, rect) = input else {
fatalError()
}
self.input = input
self.rect = rect
super.init()
//TODO: mirror if self.additionalPlayer == nil && self.mirror
self.textureRotation = textureRotatonForAVAsset(self.playerItem.asset, mirror: additional)
self.textureRotation = textureRotatonForAVAsset(self.playerItem.asset, mirror: rect == nil ? additional : false)
let colorProperties: [String: Any] = [
AVVideoColorPrimariesKey: AVVideoColorPrimaries_ITU_R_709_2,
@@ -270,7 +291,7 @@ private class VideoInputContext: NSObject, InputContext, AVPlayerItemOutputPullD
if let pixelBuffer = videoOutput.copyPixelBuffer(forItemTime: requestTime, itemTimeForDisplay: &presentationTime) {
videoPixelBuffer = VideoPixelBuffer(pixelBuffer: pixelBuffer, rotation: self.textureRotation, timestamp: presentationTime)
}
return videoPixelBuffer.flatMap { .videoBuffer($0) }
return videoPixelBuffer.flatMap { .videoBuffer($0, self.rect) }
}
func invalidate() {
@@ -290,6 +311,8 @@ final class EntityInputContext: NSObject, InputContext, AVPlayerItemOutputPullDe
internal var input: Input
private var textureRotation: TextureRotation = .rotate0Degrees
var rect: CGRect?
var entity: MediaEditorComposerEntity {
guard case let .entity(entity) = self.input else {
fatalError()