Deepak Sharma
Deepak Sharma

Reputation: 6487

AVPlayerLayer shrink animation

I am trying to do a "shrink" animation on AVPlayerLayer but as soon as I do it the video content simply disappears off the screen. Animating the UIView holding AVPlayerLayer works but not the AVPlayerLayer. What is the right way to animate it ?

 CGRect oldBounds = playerLayer.bounds;
 CGRect newBounds = oldBounds;
 newBounds.size = CGSizeZero;
 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"bounds"];
 animation.duration = 5.f;
 animation.beginTime = CACurrentMediaTime();
 animation.fromValue = [NSValue valueWithCGRect:oldBounds];
 animation.toValue = [NSValue valueWithCGRect:newBounds];


[playerLayer addAnimation:animation forKey:@"shrink"];

Upvotes: 3

Views: 1432

Answers (2)

CIFilter
CIFilter

Reputation: 8677

This is unsurprising. AVPlayerLayer is a special layer class, since it doesn't exist within the QuartzCore framework or have a CA class prefix. It's also highly optimized to render video frames to the display. Attempting to add an animation to AVPlayerLayer, either indirectly through UIKit or directly through Core Animation, results in weird behavior for me.

Probably the only way to apply any sort of animation to AVPlayerLayer is to just create a snapshot of the host view or render the layer contents directly into a graphics context. I wouldn't be surprised, however, if the latter doesn't work, since AVPlayerLayer might not contain a backing store that Core Animation is capable of rendering.

EDIT: Here's a Playground solution for this issue, though it does rely on using UIKit, not Core Animation alone. I haven't tested on device or in the Simulator, but this appears to work:

import UIKit
import AVFoundation
import PlaygroundSupport

let rootView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 600.0, height: 400.0))
rootView.backgroundColor = .white

let playerLayerView = UIView()
playerLayerView.frame.size = CGSize(width: 480.0, height: 270.0)
playerLayerView.center = CGPoint(x: rootView.bounds.midX, y: rootView.bounds.midY)

guard let videoUrl = Bundle.main.url(
    forResource: "HEVC_3_iPhone",
    withExtension: "m4v")
    else { fatalError() }

let player = AVPlayer(url: videoUrl)
player.play()

let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = playerLayerView.bounds

playerLayerView.layer.addSublayer(playerLayer)

rootView.addSubview(playerLayerView)

UIView.animate(
    withDuration: 1.0,
    delay: 0.0,
    options: [.autoreverse, .repeat],
    animations: {
        playerLayerView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
    },
    completion: nil
)

PlaygroundPage.current.liveView = rootView

Here's what it looks like:

If I understand correctly, the "problem"—although this isn't a problem as much as a pipeline necessity—with AVPlayerLayer is that it sits outside of the standard UIKit rendering and compositing pipeline. Along with other special layer classes, like CAMetalLayer, AVPlayerLayer is rendered in a different process because AVFoundation needs direct draw access to the screen that isn't bounded by UIKit's render loop. So attempting to attach animations directly to the layer doesn't work.

Placing that layer in a UIView, however, enables Core Animation to composite AVPlayerLayer into a view hierarchy, which means geometry changes to the view will impact the presentation of the video player layer. AVFoundation still most likely renders the video frames the way it wants but provides a texture for Core Animation to composite however it wants.

It's possible that this can result in subtle player timing jitters since you're forcing what was previously an independently rendered video into UIKit's render loop. In general, it's tricky to synchronize these special layers because Core Animation has to inevitably wait for various processes to all finish providing a rendered bitmap for their layers so it can composite everything together into a single screen refresh.

But that's all speculation on my part.

Upvotes: 1

eric frazer
eric frazer

Reputation: 1649

iOS turns off animations by default to a layer when it's being controlled by a view. However, when you animate properties on a UIView, it briefly turns on animation capability for the layer, and sends the animation parameters to the layer to execute, then turns animation capability on the layer back off. That is: do the animations on it's controlling UIView, not the layer.

That said, I can't figure out how to get my AVPlayerLayer to animate through my UIView either. What I stated above was how it's supposed to work, but I can't get it to work at the moment either. Heh.

Upvotes: 1

Related Questions