mirror of
https://github.com/HaishinKit/HaishinKit.swift.git
synced 2026-05-07 20:12:28 +00:00
112 lines
3.6 KiB
Swift
112 lines
3.6 KiB
Swift
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 layer’s 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
|