这已被证实是一个框架错误(发生在 Mac Catalyst 上,但不是 iOS 或 iPadOS),看来罪魁祸首是
AVVideoCompositionCoreAnimationTool
?
/// Exports a video with the target animating.
func exportVideo() {
let destinationURL = createExportFileURL(from: Date())
guard let videoURL = Bundle.main.url(forResource: "black_video", withExtension: "mp4") else {
delegate?.exporterDidFailExporting(exporter: self)
print("Can't find video")
return
}
// Initialize the video asset
let asset = AVURLAsset(url: videoURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
guard let assetVideoTrack: AVAssetTrack = asset.tracks(withMediaType: AVMediaType.video).first,
let assetAudioTrack: AVAssetTrack = asset.tracks(withMediaType: AVMediaType.audio).first else { return }
let composition = AVMutableComposition()
guard let videoCompTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)),
let audioCompTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else { return }
videoCompTrack.preferredTransform = assetVideoTrack.preferredTransform
// Get the duration
let videoDuration = asset.duration.seconds
// Get the video rect
let videoSize = assetVideoTrack.naturalSize.applying(assetVideoTrack.preferredTransform)
let videoRect = CGRect(origin: .zero, size: videoSize)
// Initialize the target layers and animations
animationLayers = TargetView.initTargetViewAndAnimations(atPoint: CGPoint(x: videoRect.midX, y: videoRect.midY), atSecondsIntoVideo: 2, videoRect: videoRect)
// Set the playback speed
let duration = CMTime(seconds: videoDuration,
preferredTimescale: CMTimeScale(600))
let appliedRange = CMTimeRange(start: .zero, end: duration)
videoCompTrack.scaleTimeRange(appliedRange, toDuration: duration)
audioCompTrack.scaleTimeRange(appliedRange, toDuration: duration)
// Create the video layer.
let videolayer = CALayer()
videolayer.frame = CGRect(origin: .zero, size: videoSize)
// Create the parent layer.
let parentlayer = CALayer()
parentlayer.frame = CGRect(origin: .zero, size: videoSize)
parentlayer.addSublayer(videolayer)
let times = timesForEvent(startTime: 0.1, endTime: duration.seconds - 0.01)
let timeRangeForCurrentSlice = times.timeRange
// Insert the relevant video track segment
do {
try videoCompTrack.insertTimeRange(timeRangeForCurrentSlice, of: assetVideoTrack, at: .zero)
try audioCompTrack.insertTimeRange(timeRangeForCurrentSlice, of: assetAudioTrack, at: .zero)
}
catch let compError {
print("TrimVideo: error during composition: \(compError)")
delegate?.exporterDidFailExporting(exporter: self)
return
}
// Add all the non-nil animation layers to be exported.
for layer in animationLayers.compactMap({ $0 }) {
parentlayer.addSublayer(layer)
}
// Configure the layer composition.
let layerComposition = AVMutableVideoComposition()
layerComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
layerComposition.renderSize = videoSize
layerComposition.animationTool = AVVideoCompositionCoreAnimationTool(
postProcessingAsVideoLayer: videolayer,
in: parentlayer)
let instructions = initVideoCompositionInstructions(
videoCompositionTrack: videoCompTrack, assetVideoTrack: assetVideoTrack)
layerComposition.instructions = instructions
// Creates the export session and exports the video asynchronously.
guard let exportSession = initExportSession(
composition: composition,
destinationURL: destinationURL,
layerComposition: layerComposition) else {
delegate?.exporterDidFailExporting(exporter: self)
return
}
// Execute the exporting
exportSession.exportAsynchronously(completionHandler: {
if let error = exportSession.error {
print("Export error: \(error), \(error.localizedDescription)")
}
self.delegate?.exporterDidFinishExporting(exporter: self, with: destinationURL)
})
}
不确定如何实现执行与此可重现案例相同的动画的自定义合成器:
class AnimationCreator: NSObject {
// MARK: - Target Animations
/// Creates the target animations.
static func addAnimationsToTargetView(_ targetView: TargetView, startTime: Double) {
// Add the appearance animation
AnimationCreator.addAppearanceAnimation(on: targetView, defaultBeginTime: AVCoreAnimationBeginTimeAtZero, startTime: startTime)
// Add the pulse animation.
AnimationCreator.addTargetPulseAnimation(on: targetView, defaultBeginTime: AVCoreAnimationBeginTimeAtZero, startTime: startTime)
// Add the rotation animation
AnimationCreator.addRotationAnimation(to: targetView, beginTime: AVCoreAnimationBeginTimeAtZero, startTime: startTime)
}
/// Adds the appearance animation to the target
private static func addAppearanceAnimation(on targetView: TargetView, defaultBeginTime: Double = 0, startTime: Double = 0) {
// Starts the target transparent and then turns it opaque at the specified time
targetView.targetImageView.layer.opacity = 0
let appear = CABasicAnimation(keyPath: "opacity")
appear.duration = .greatestFiniteMagnitude // stay on screen forever
appear.fromValue = 1.0 // Opaque
appear.toValue = 1.0 // Opaque
appear.beginTime = defaultBeginTime + startTime
targetView.targetImageView.layer.add(appear, forKey: "appear")
}
/// Adds a pulsing animation to the target.
private static func addTargetPulseAnimation(on targetView: TargetView, defaultBeginTime: Double = 0, startTime: Double = 0) {
let targetPulse = CABasicAnimation(keyPath: "transform.scale")
targetPulse.fromValue = 1 // Regular size
targetPulse.toValue = 1.1 // Slightly larger size
targetPulse.duration = 0.4
targetPulse.beginTime = defaultBeginTime + startTime
targetPulse.autoreverses = true
targetPulse.repeatCount = .greatestFiniteMagnitude
targetView.targetImageView.layer.add(targetPulse, forKey: "pulse_animation")
}
/// Adds a spinning animation to the target.
private static func addRotationAnimation(to targetView: TargetView, beginTime: Double = 0, startTime: Double = 0) {
let rotation: CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.toValue = Double.pi * 2 // rotate in a complete circle
rotation.duration = 1.0
rotation.isCumulative = true
rotation.repeatCount = .greatestFiniteMagnitude
rotation.beginTime = beginTime
targetView.targetImageView.layer.add(rotation, forKey: "rotation_animation")
}
}
我无法确定为什么
CABasicAnimation
不起作用,但CAKeyframeAnimation
可以。已验证的工作代码。如果您有任何疑问,请告诉我。
import UIKit
import AVFoundation
class AnimationCreator: NSObject {
// MARK: - Target Animations
/// Creates the target animations.
static func addAnimationsToTargetView(_ targetView: TargetView, startTime: Double) {
// Add the appearance animation
AnimationCreator.addAppearanceAnimation(on: targetView, defaultBeginTime: AVCoreAnimationBeginTimeAtZero, startTime: startTime)
// Add the pulse animation.
AnimationCreator.addTargetPulseAnimation(on: targetView, defaultBeginTime: AVCoreAnimationBeginTimeAtZero, startTime: startTime)
// Add the rotation animation
AnimationCreator.addRotationAnimation(to: targetView, beginTime: AVCoreAnimationBeginTimeAtZero, startTime: startTime)
}
/// Adds the appearance animation to the target
private static func addAppearanceAnimation(on targetView: TargetView, defaultBeginTime: Double = 0, startTime: Double = 0) {
// Starts the target transparent and then turns it opaque at the specified time
targetView.targetImageView.layer.opacity = 1.0
let animation = CAKeyframeAnimation(keyPath: #keyPath(CALayer.opacity))
animation.duration = 2.25
animation.repeatCount = .zero
animation.values = [
CGFloat(0.0),
CGFloat(1.0)
] as [CGFloat]
animation.beginTime = AVCoreAnimationBeginTimeAtZero
animation.isRemovedOnCompletion = false
targetView.targetImageView.layer.add(animation, forKey: nil)
}
/// Adds a pulsing animation to the target.
private static func addTargetPulseAnimation(on targetView: TargetView, defaultBeginTime: Double = 0, startTime: Double = 0) {
let animation = CAKeyframeAnimation(keyPath: "transform.scale")
animation.duration = 2.25
animation.repeatCount = .infinity
animation.values = [1.0, 1.1]
animation.beginTime = AVCoreAnimationBeginTimeAtZero
animation.isRemovedOnCompletion = false
targetView.targetImageView.layer.add(animation, forKey: nil)
}
/// Adds a spinning animation to the target.
private static func addRotationAnimation(to targetView: TargetView, beginTime: Double = 0, startTime: Double = 0) {
let animation = CAKeyframeAnimation(keyPath: "transform.rotation")
animation.duration = 1.0
animation.repeatCount = .infinity
animation.values = [0.0, Double.pi * 2]
animation.beginTime = AVCoreAnimationBeginTimeAtZero
animation.isRemovedOnCompletion = false
targetView.targetImageView.layer.add(animation, forKey: nil)
}
}