I'm trying to add an animated CALayer over my video and export it with AVAssetExportSession.
I'm animating the layer using CABasicAnimation set to my custom property.
However, it seems that func draw(in ctx: CGContext) is never called during an export for my custom layer, and no animation is played.
I found out that animating standard properties like borderWidth works fine, but custom properties are ignored.
Can someone help with that?
func export(standard: Bool) {
print("Exporting...")
let composition = AVMutableComposition()
//composition.naturalSize = CGSize(width: 300, height: 300)
// Video track
let videoTrack = composition.addMutableTrack(withMediaType: .video,
preferredTrackID: CMPersistentTrackID(1))!
let _videoAssetURL = Bundle.main.url(forResource: "emptyVideo", withExtension: "mov")!
let _emptyVideoAsset = AVURLAsset(url: _videoAssetURL)
let _emptyVideoTrack = _emptyVideoAsset.tracks(withMediaType: .video)[0]
try! videoTrack.insertTimeRange(CMTimeRange(start: .zero, duration: _emptyVideoAsset.duration),
of: _emptyVideoTrack, at: .zero)
// Root Layer
let rootLayer = CALayer()
rootLayer.frame = CGRect(origin: .zero, size: composition.naturalSize)
// Video layer
let video = CALayer()
video.frame = CGRect(origin: .zero, size: composition.naturalSize)
rootLayer.addSublayer(video)
// Animated layer
let animLayer = CustomLayer()
animLayer.progress = 0.0
animLayer.frame = CGRect(origin: .zero, size: composition.naturalSize)
rootLayer.addSublayer(animLayer)
animLayer.borderColor = UIColor.green.cgColor
animLayer.borderWidth = 0.0
let key = standard ? "borderWidth" : "progress"
let anim = CABasicAnimation(keyPath: key)
anim.fromValue = 0.0
anim.toValue = 50.0
anim.duration = 6.0
anim.beginTime = AVCoreAnimationBeginTimeAtZero
anim.isRemovedOnCompletion = false
animLayer.add(anim, forKey: nil)
// Video Composition
let videoComposition = AVMutableVideoComposition(propertiesOf: composition)
videoComposition.renderSize = composition.naturalSize
videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
// Animation tool
let animTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: video,
in: rootLayer)
videoComposition.animationTool = animTool
// Video instruction > Basic
let videoInstruction = AVMutableVideoCompositionInstruction()
videoInstruction.timeRange = CMTimeRange(start: .zero, duration: composition.duration)
videoComposition.instructions = [videoInstruction]
// Video-instruction > Layer instructions
let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
videoInstruction.layerInstructions = [layerInstruction]
// Session
let exportSession = AVAssetExportSession(asset: composition,
presetName: AVAssetExportPresetHighestQuality)!
exportSession.videoComposition = videoComposition
exportSession.shouldOptimizeForNetworkUse = true
var url = FileManager.default.temporaryDirectory.appendingPathComponent("\(arc4random()).mov")
url = URL(fileURLWithPath: url.path)
exportSession.outputURL = url
exportSession.outputFileType = .mov
_session = exportSession
exportSession.exportAsynchronously {
if let error = exportSession.error {
print("Fail. \(error)")
} else {
print("Ok")
print(url)
DispatchQueue.main.async {
let vc = AVPlayerViewController()
vc.player = AVPlayer(url: url)
self.present(vc, animated: true) {
vc.player?.play()
}
}
}
}
}
CustomLayer:
class CustomLayer: CALayer {
@NSManaged var progress: CGFloat
override init() {
super.init()
}
override init(layer: Any) {
let l = layer as! CustomLayer
super.init(layer: layer)
print("Copy. \(progress) \(l.progress)")
self.progress = l.progress
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override class func needsDisplay(forKey key: String) -> Bool {
let needsDisplayKeys = ["progress"]
if needsDisplayKeys.contains(key) {
return true
}
return super.needsDisplay(forKey: key)
}
override func display() {
print("Display. \(progress) | \(presentation()?.progress)")
super.display()
}
override func draw(in ctx: CGContext) {
// Save / restore ctx
ctx.saveGState()
defer { ctx.restoreGState() }
print("Draw. \(progress)")
ctx.move(to: .zero)
ctx.addLine(to: CGPoint(x: bounds.size.width * progress,
y: bounds.size.height * progress))
ctx.setStrokeColor(UIColor.red.cgColor)
ctx.setLineWidth(40)
ctx.strokePath()
}
}
Here's a full sample project if someone is interested:
https://www.dropbox.com/s/evkm60wkeb2xrzh/BrokenAnimation.zip?dl=0