Files
HaishinKit.swift/HaishinKit/Sources/Screen/AssetScreenObject.swift
2026-02-07 16:16:14 +09:00

112 lines
3.6 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import AVFoundation
import CoreImage
#if !os(visionOS)
/// An object that manages offscreen rendering an asset resource.
public final class AssetScreenObject: ScreenObject, ChromaKeyProcessable {
public var chromaKeyColor: CGColor?
/// The reading incidies whether assets reading or not.
public var isReading: Bool {
return reader?.status == .reading
}
/// The video is displayed within a player layers bounds.
public var videoGravity: AVLayerVideoGravity = .resizeAspect {
didSet {
guard videoGravity != oldValue else {
return
}
invalidateLayout()
}
}
private var reader: AVAssetReader? {
didSet {
if let oldValue, oldValue.status == .reading {
oldValue.cancelReading()
}
}
}
private var sampleBuffer: CMSampleBuffer? {
didSet {
guard sampleBuffer != oldValue else {
return
}
if sampleBuffer == nil {
cancelReading()
return
}
invalidateLayout()
}
}
private var startedAt: CMTime = .zero
private var videoTrackOutput: AVAssetReaderTrackOutput?
private var outputSettings = [
kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_32BGRA
] as [String: Any]
/// Prepares the asset reader to start reading.
public func startReading(_ asset: AVAsset) throws {
reader = try AVAssetReader(asset: asset)
guard let reader else {
return
}
let videoTrack = asset.tracks(withMediaType: .video).first
if let videoTrack {
let videoTrackOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: outputSettings)
videoTrackOutput.alwaysCopiesSampleData = false
reader.add(videoTrackOutput)
self.videoTrackOutput = videoTrackOutput
}
startedAt = CMClock.hostTimeClock.time
reader.startReading()
sampleBuffer = videoTrackOutput?.copyNextSampleBuffer()
}
/// Cancels and stops the reader's output.
public func cancelReading() {
reader = nil
sampleBuffer = nil
videoTrackOutput = nil
}
override public func makeBounds(_ size: CGSize) -> CGRect {
guard parent != nil, let image = sampleBuffer?.formatDescription?.dimensions.size else {
return super.makeBounds(size)
}
let bounds = super.makeBounds(size)
switch videoGravity {
case .resizeAspect:
let scale = min(bounds.size.width / image.width, bounds.size.height / image.height)
let scaleSize = CGSize(width: image.width * scale, height: image.height * scale)
return super.makeBounds(scaleSize)
case .resizeAspectFill:
return bounds
default:
return bounds
}
}
override public func makeImage(_ renderer: some ScreenRenderer) -> CIImage? {
guard let sampleBuffer, let pixelBuffer = sampleBuffer.imageBuffer else {
return nil
}
return CIImage(cvPixelBuffer: pixelBuffer).transformed(by: videoGravity.scale(
bounds.size,
image: pixelBuffer.size
))
}
override func draw(_ renderer: some ScreenRenderer) {
super.draw(renderer)
let duration = CMClock.hostTimeClock.time - startedAt
if let sampleBuffer, sampleBuffer.presentationTimeStamp <= duration {
self.sampleBuffer = videoTrackOutput?.copyNextSampleBuffer()
}
}
}
#endif