Reputation: 3241
I am developing an application that includes functionality to play a video with per-frame animation. You can see an example of this functionality.
I already tried to add CAKeyFrameAnimation
to a sublayer of AVSynchronizedLayer
but had some troubles with it.
I also already tried to pre-render video with AVAssetExportSession
and it is working perfectly. But it's very slow. It needs up to 3 minutes to render the video.
Maybe there are other approaches to make this work?
Update:
This is how I implemented animation with AVSynchronizedLayer
:
let fullScreenAnimationLayer = CALayer()
fullScreenAnimationLayer.frame = videoRect
fullScreenAnimationLayer.geometryFlipped = true
values: [NSValue] = [], times: [NSNumber] = []
// fill values array with positions of face center for each frame
values.append(NSValue(CATransform3D: t))
// fill times with corresoinding time for each frame
times.append(NSNumber(double: (Double(j) / fps) / videoDuration)) // where fps = 25 (according to video file fps)
...
let transform = CAKeyframeAnimation(keyPath: "transform")
transform.duration = videoDuration
transform.calculationMode = kCAAnimationDiscrete
transform.beginTime = AVCoreAnimationBeginTimeAtZero
transform.values = values
transform.keyTimes = times
transform.removedOnCompletion = false
transform.fillMode = kCAFillModeForwards
fullScreenAnimationLayer.addAnimation(transform, forKey: "a_transform")
...
if let syncLayer = AVSynchronizedLayer(playerItem: player.currentItem) {
syncLayer.frame = CGRect(origin: CGPointZero, size: videoView.bounds.size)
syncLayer.addSublayer(fullScreenAnimationLayer)
videoView.layer.addSublayer(syncLayer)
}
Upvotes: 46
Views: 2575
Reputation: 2177
import AVFoundation
import UIKit
class ViewController: UIViewController {
var player: AVPlayer?
var displayLink: CADisplayLink?
var animationImageView: UIImageView?
override func viewDidLoad() {
super.viewDidLoad()
let videoURL = URL(fileURLWithPath: "path_to_your_video_file")
player = AVPlayer(url: videoURL)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = view.bounds
view.layer.addSublayer(playerLayer)
player?.play()
// Create an image view for per-frame animation
animationImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
animationImageView?.animationImages = [UIImage(named: "frame1")!, UIImage(named: "frame2")!, UIImage(named: "frame3")!]
animationImageView?.animationDuration = 1.0 // Adjust as needed
animationImageView?.startAnimating()
view.addSubview(animationImageView!)
// Start CADisplayLink
displayLink = CADisplayLink(target: self, selector: #selector(update))
displayLink?.add(to: .main, forMode: .default)
}
@objc func update() {
// Update animation position or any other logic here
// For example, you could sync the animation with the video playback time
guard let currentTime = player?.currentTime().seconds else { return }
// Update animation based on currentTime
}
}
Replace "frame1", "frame2", "frame3", etc. with the names of your animation frames. Adjust the animation duration and any other parameters as needed.
Upvotes: 1
Reputation: 122
Here's my opinion,
add AVPlayer layer property(AVPlayerLayer class) to a sublayer to a UIView layer, then manipulate the view animation.
for example,
AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:blahURL options:nil];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:urlAsset];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame = yourFrame;
UIView *videoView = [UIView new];
[videoView addSublayer:playerLayer];
then
give animations to videoView
Upvotes: -2