In the process of video editing, there are often scenes that need to add more personalized content to the video, such as subtitles, watermarks or expressions, etc. At this time, an animation overlay layer needs to be added to the video. In terms of implementation, AVFoundation and CoreAnimation need to be used together.

Using CoreAnimation in AVFoundation is the same as using CoreAnimation elsewhere in iOS, but the biggest difference is in the time model. When CoreAnimation is normally used, the time model depends on the system host. However, video animations have their own timelines and need to support stop, pause, rewind or fast forward effects. Therefore, it is not possible to directly add time-based animations to a video using the system host’s time model.

For this feature, AVFoundation provides two tools in two scenarios to achieve this effect

  • You need to add the CoreAnimation to play the video, providing the AVSynchronizedLayer class
  • Need to join CoreAnimation when export video, provide AVVideoCompositionCoreAnimationTool

It is not clear why two classes are required to implement the same effect, but doing so makes it difficult to ensure that the coordinates of the layer are consistent with the size when implementing the Layer with CoreAnimation.

1. AVSynchronizedLayer and Playback

Start by implementing a simple CALayer to display a caption consisting of a string

    CGFloat fontSize = 32.0f;
    UIFont *font = [UIFont fontWithName:@"GillSans-Bold" size:fontSize];
    NSDictionary *attrs = @{NSFontAttributeName : font, NSForegroundColorAttributeName : (id) [UIColor whiteColor].CGColor};
    NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"hello yasic" attributes:attrs];
    CGSize textSize = [@"hello yasic" sizeWithAttributes:attrs];
    CATextLayer *layer = [CATextLayer layer];
    layer.opacity = 0.0;
    layer.string = string;
    layer.frame = CGRectMake((SCREEN_WIDTH - textSize.width)/2.0, (SCREEN_HEIGHT - textSize.height)/2.0, textSize.width, textSize.height);
    layer.backgroundColor = [UIColor clearColor].CGColor;
Copy the code

And then I’m going to add a CoreAnimation to this layer

    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
    animation.values = @[@0.0f, @1.0f, @1.0f, @0.0f];
    animation.keyTimes = @[@0.0f, @0.3f, @0.7f, @1.0f];
    animation.beginTime = CMTimeGetSeconds(CMTimeMake(1.1));
    animation.duration = 5.0f;
    animation.removedOnCompletion = NO;
    [layer addAnimation:animation forKey:@"opacity"];
Copy the code

It’s important to set the start time. If you want to represent the beginning of a movie, you can’t beginTime with 0.0 because CoreAnimation will change the beginTime of 0.0 to CACurrentMediaTime(), So use AVCoreAnimationBeginTimeAtZero instead.

Also, be careful to set the removedOnCompletion property of the CoreAnimation to NO, otherwise the animation will be removed from the layer tree after one execution during playback, and the animation will not appear after that.

CoreAnimation provides CAAnimationGroup to combine multiple animations, but the book recommends avoiding CAAnimationGroup for video animations.

This layer is finally added to AVSynchronizedLayer

    AVSynchronizedLayer *syncLayer = [AVSynchronizedLayer synchronizedLayerWithPlayerItem:self.avPlayerItem];
    [syncLayer addSublayer:[self makeTextLayer]];
    syncLayer.frame = CGRectMake(0, 0, 1280, 720);
    [self.layer addSublayer:syncLayer];
Copy the code

As you can see, the AVSynchronizedLayer is bound to the AVPlayerItem to synchronize time.

2. AVVideoCompositionCoreAnimationTool and export

CALayer also needs to be created when exporting

CALayer *animationLayer = [CALayer layer];
animationLayer.frame = CGRectMake(0.0.1280.f, 720.f);
CALayer *videoLayer = [CALayer layer];
videoLayer.frame = CGRectMake(0.0.1280.f, 720.f);                            
[animationLayer addSublayer:videoLayer];
[animationLayer addSublayer:[self makeTextLayer]];
animationLayer.geometryFlipped = YES; // Avoid misalignment
AVVideoCompositionCoreAnimationTool *animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:animationLayer];
Copy the code

After get animationTool bind them to the AVMutableVideoComposition, can be used for export.

AVMutableVideoComposition *finalVideocomposition = [videoComposition copy];
finalVideocomposition.animationTool = animationTool;
Copy the code