4

I'm trying to copy Apple's behavior in video playback that allows the user to stretch the video image to fill the bounds.

@interface FHVideoPlayerView : UIView
@end
@interface FHVideoPlayerView

+ (Class)layerClass
{
    return [AVPlayerLayer class];
}

- (void)setAspectMode:(FHVideoPlayerAspectMode)aspectMode animated:(BOOL)animated
{
    FHVideoPlayerAspectMode current = [self aspectMode];
    FHVideoPlayerAspectMode final   = aspectMode;

    NSString *fromValue;
    NSString *toValue;

    AVPlayerLayer *layer = (AVPlayerLayer *)[self layer];

    switch (current) {
        case FHVideoPlayerAspectFill:
            fromValue = AVLayerVideoGravityResizeAspectFill;
            break;
        case FHVideoPlayerAspectFit:
            fromValue = AVLayerVideoGravityResizeAspect;
            break;
       default:
           break;
    }

    switch (final) {
        case FHVideoPlayerAspectFill:
            toValue = AVLayerVideoGravityResizeAspectFill;
            break;
        case FHVideoPlayerAspectFit:
            toValue = AVLayerVideoGravityResizeAspect;
            break;
        default:
            break;
    }

    if (toValue != fromValue) {
        if (animated == YES) {
            // Manually added CABasicAnimation based on the understanding the implicit animations are disabled for CALayers that back a UIView
            CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"videoGravity"];
            [layer addAnimation:animation forKey:@"animateVideoGravity"];

            [CATransaction begin];
            [CATransaction setAnimationDuration:0.333];
        }

        [layer setVideoGravity:toValue];

        if (animated == YES) {
            [CATransaction commit];
        }
    }
}

This works just fine when I try and animate a numeric property such as opacity. But, you'll notice from the class reference, AVPlayerLayer's videoGravity property is an NSString. The class reference does points out that it is animatable.

So my question is: How!?

I believe this is probably related somehow to CALayer's content property and possibly contentsGravity.

edelaney05
  • 6,822
  • 6
  • 41
  • 65

2 Answers2

6

As voromax pointed out, this is a bug in iOS 5.0. I reverse engineered -[AVPlayerLayer setVideoGravity:] implementation in iOS 5.1 in order to understand how the animation was supposed to work. Here is how to workaround the bug and have a nice animation on iOS 5.0.

@implementation VideoPlayerView

+ (Class) layerClass
{
    return [AVPlayerLayer class];
}

- (AVPlayerLayer *) playerLayer
{
    return (AVPlayerLayer *)[self layer];
}

- (NSString *) videoGravity
{
    return self.playerLayer.videoGravity;
}

- (void) setVideoGravity:(NSString *)videoGravity
{
    self.playerLayer.videoGravity = videoGravity;

    // Workaround a bug in iOS 5.0
    float avFoundationVersion = [[[NSBundle bundleForClass:[AVPlayerLayer class]] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey] floatValue];
    if (avFoundationVersion < 292.24f)
    {
        @try
        {
            NSString *contentLayerKeyPath = [NSString stringWithFormat:@"%1$@%2$@.%3$@%2$@", @"player", [@"layer" capitalizedString], @"content"]; // playerLayer.contentLayer
            CALayer *contentLayer = [self.playerLayer valueForKeyPath:contentLayerKeyPath];
            if ([contentLayer isKindOfClass:[CALayer class]])
                [contentLayer addAnimation:[CABasicAnimation animation] forKey:@"sublayerTransform"];
        }
        @catch (NSException *exception)
        {
        }
        self.bounds = self.bounds;
    }
}

@end
0xced
  • 25,219
  • 10
  • 103
  • 255
  • So I'm just curious, what have you got going on with those format specifiers? I know you're trying to obfuscate the string, but can you explain the specifiers? – sudo rm -rf May 08 '12 at 05:37
  • Awesome workaround! This bugged me a lot when 5.0 came out, it's finally fixed in 5.1 but adding that fix is a great idea. – steipete May 08 '12 at 08:24
  • Haha `AVPlayerLayer`. I love that name. :) – Jacob Relkin May 08 '12 at 08:26
  • sudo rm -rf: These format specifiers contain parameter indexes, such as 1$. That means that he is using the second one, “Layer”, twice: in “playerLayer” and “contentLayer”. Think of it like addressing capture groups in regexes. – Martin Winter May 08 '12 at 09:02
  • Martin Winter & 0xced: but, why obfuscate the string at all? O_o – edelaney05 May 09 '12 at 00:34
  • Because the `contentLayer` property is not documented and Apple could reject your app if they discover you are using an undocumented API. – 0xced May 09 '12 at 11:15
  • Understood. I don't know the "rules" or how this is looked upon in the SO community, but this answer is very comprehensive and I believe it deserves to be the accepted answer. – edelaney05 May 11 '12 at 18:00
1

It was a bug in iOS 5.0 and iOS 5.0.1.

After updating to iOS 5.1 the video gravity changing animation begun to work again.

voromax
  • 3,369
  • 2
  • 30
  • 53
  • 1
    Hm... seems you are correct. Although Apple's video playback view controllers were not impacted by this. So maybe there is another way to animate video gravity? – edelaney05 Mar 09 '12 at 20:56
  • I think they just did not recompile their MPMoviePlayer since iOS 4.3 – voromax Mar 10 '12 at 08:26
  • I don't believe so - they added a new property (airPlayVideoActive) and a new Notification. [API diffs 4.3 to 5.0](https://developer.apple.com/library/ios/#releasenotes/General/iOS50APIDiff/index.html) – edelaney05 Mar 11 '12 at 21:01
  • 1
    Starting with iOS 5.1 you do not even need the `CABasicAnimation` code, just change the `videoGravity` property and it will animate automatically. – 0xced Apr 26 '12 at 13:36