Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

swift: How to take screenshot of AVPlayerLayer()

How to take screenshot of AVplayerLayer. I tried with the following code it works well, it captures the entire view as it was

func screenShotMethod() {
    let window = UIApplication.shared.delegate!.window!!
    //capture the entire window into an image
    UIGraphicsBeginImageContextWithOptions(window.bounds.size, false, UIScreen.main.scale)
    window.drawHierarchy(in: window.bounds, afterScreenUpdates: false)
    let windowImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    //now position the image x/y away from the top-left corner to get the portion we want
    UIGraphicsBeginImageContext(view.frame.size)
    windowImage?.draw(at: CGPoint(x: -view.frame.origin.x, y: -view.frame.origin.y))
    let croppedImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext();
    //embed image in an imageView, supports transforms.
    let resultImageView = UIImageView(image: croppedImage)
    UIImageWriteToSavedPhotosAlbum(croppedImage, nil, nil, nil)
}

But the problem is when i tried the same code running on iPhone(device) it returns black image.i don't know what was wrong

Any suggestions would be greatly helpful!

like image 475
Raghuram Avatar asked Feb 03 '17 08:02

Raghuram


2 Answers

Here is the code that is working for me in swift 4:

var videoImage = UIImage()

if let url = (player.currentItem?.asset as? AVURLAsset)?.url {

      let asset = AVAsset(url: url)

      let imageGenerator = AVAssetImageGenerator(asset: asset)
      imageGenerator.requestedTimeToleranceAfter = CMTime.zero
      imageGenerator.requestedTimeToleranceBefore = CMTime.zero

      if let thumb: CGImage = try? imageGenerator.copyCGImage(at: player.currentTime(), actualTime: nil) {
            //print("video img successful")
            videoImage = UIImage(cgImage: thumb)
       }

}
like image 173
brontea Avatar answered Oct 21 '22 05:10

brontea


A few days ago, we also ran into the same issue. Where, if we take the screenshot of a screen which has a video player in it; The screenshot looks fine in the simulator.But, on the device, it was a black screen.

After a lot of attempts, I failed and finally end up with a patch (not sure if it is a correct way of solving the problem). But, the solution did the trick and I was able to get the screenshot on the device as well and simulator as well.

Following is a way I used to solve the issue.

1 -> Get a single frame at current time from the video (Public method is already available for this)

2 -> Use this thumbnail in the place of CALayer (add it to hierarchy)

3 -> Once we are done remove the thumbnail from the memory (remove from hierarchy)

Following is a demo sample for the same (The given solution is in Objective-c though the question asked is in Swift).

Objective - C solution

  - (void)SnapShot {
       UIImage *capturedImage = [self getASnapShotWithAVLayer];
    }
    - (UIImage *)getASnapShotWithAVLayer {
        //Add temporary thumbnail One
        UIImageView *temporaryViewForVideoOne = [[UIImageView alloc] initWithFrame:self.videoViewOne.bounds];
        temporaryViewForVideoOne.contentMode = UIViewContentModeScaleAspectFill;
        UIImage *imageFromCurrentTimeForVideoOne = [self takeVideoSnapShot:_playerItem1];
        int orientationFromVideoForVideoOne = [self getTheActualOrientationOfVideo:self.playerItem1];
        if(orientationFromVideoForVideoOne == 0)
        {
            orientationFromVideoForVideoOne = 3;
        }
        else if (orientationFromVideoForVideoOne == 90)
        {
            orientationFromVideoForVideoOne = 0;
        }
        imageFromCurrentTimeForVideoOne =
        [UIImage imageWithCGImage:[imageFromCurrentTimeForVideoOne CGImage]
                            scale:[imageFromCurrentTimeForVideoOne scale]
                      orientation: orientationFromVideoForVideoOne];
        UIImage *rotatedImageFromCurrentContextForVideoOne = [self normalizedImage:imageFromCurrentTimeForVideoOne];
        temporaryViewForVideoOne.clipsToBounds = YES;
        temporaryViewForVideoOne.image = rotatedImageFromCurrentContextForVideoOne;
        [self.videoViewOne addSubview:temporaryViewForVideoOne];
        CGSize imageSize = CGSizeZero;
        UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
        if (UIInterfaceOrientationIsPortrait(orientation)) {
            imageSize = [UIScreen mainScreen].bounds.size;
        } else {
            imageSize = CGSizeMake([UIScreen mainScreen].bounds.size.height, [UIScreen mainScreen].bounds.size.width);
        }

        UIGraphicsBeginImageContextWithOptions(imageSize, NO, [[UIScreen mainScreen] scale]);
        CGContextRef context = UIGraphicsGetCurrentContext();
        for (UIWindow *window in [[UIApplication sharedApplication] windows]) {
            CGContextSaveGState(context);
            CGContextTranslateCTM(context, window.center.x, window.center.y);
            CGContextConcatCTM(context, window.transform);
            CGContextTranslateCTM(context, -window.bounds.size.width * window.layer.anchorPoint.x, -window.bounds.size.height * window.layer.anchorPoint.y);
            if (orientation == UIInterfaceOrientationLandscapeLeft) {
                CGContextRotateCTM(context, M_PI_2);
                CGContextTranslateCTM(context, 0, -imageSize.width);
            } else if (orientation == UIInterfaceOrientationLandscapeRight) {
                CGContextRotateCTM(context, -M_PI_2);
                CGContextTranslateCTM(context, -imageSize.height, 0);
            } else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
                CGContextRotateCTM(context, M_PI);
                CGContextTranslateCTM(context, -imageSize.width, -imageSize.height);
            }
            if (![window respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
                [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES];
            } else {
                [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES];
            }
            CGContextRestoreGState(context);
        }
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        [temporaryViewForVideoOne removeFromSuperview];
        [temporaryViewForVideoTwo removeFromSuperview];
        return image;
    }
    -(UIImage *)takeVideoSnapShot: (AVPlayerItem *) playerItem{
        AVURLAsset *asset = (AVURLAsset *) playerItem.asset;
        AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
        imageGenerator.requestedTimeToleranceAfter = kCMTimeZero;
        imageGenerator.requestedTimeToleranceBefore = kCMTimeZero;
        CGImageRef thumb = [imageGenerator copyCGImageAtTime:playerItem.currentTime
                                                  actualTime:NULL
                                                       error:NULL];
        UIImage *videoImage = [UIImage imageWithCGImage:thumb];
        CGImageRelease(thumb);
        return videoImage;
    }
    -(int)getTheActualOrientationOfVideo:(AVPlayerItem *)playerItem
    {
        AVAsset *asset = playerItem.asset;
        NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
        AVAssetTrack *track = [tracks objectAtIndex:0];
        CGAffineTransform videoAssetOrientation_ = [track preferredTransform];
        CGFloat videoAngle  = RadiansToDegrees(atan2(videoAssetOrientation_.b, videoAssetOrientation_.a));
        int  orientation = 0;
        switch ((int)videoAngle) {
            case 0:
                orientation = UIImageOrientationRight;
                break;
            case 90:
                orientation = UIImageOrientationUp;
                break;
            case 180:
                orientation = UIImageOrientationLeft;
                break;
            case -90:
                orientation = UIImageOrientationDown;
                break;
            default:
                //Not found
                break;
        }
        return orientation;
    }
    - (UIImage *)normalizedImage:(UIImage *)imageOf {
        if (imageOf.imageOrientation == UIImageOrientationUp) return imageOf;

        UIGraphicsBeginImageContextWithOptions(imageOf.size, NO, imageOf.scale);
        [imageOf drawInRect:(CGRect){0, 0, imageOf.size}];
        UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return normalizedImage;
    }

Swift solution

func snapShot() {
    let capturedImage: UIImage? = getASnapShotWithAVLayer()
}

func getASnapShotWithAVLayer() -> UIImage {
    //Add temporary thumbnail One
    let temporaryViewForVideoOne = UIImageView(frame: videoViewOne.bounds) //replace videoViewOne with you view which is showing AVPlayerContent
    temporaryViewForVideoOne.contentMode = .scaleAspectFill
    var imageFromCurrentTimeForVideoOne: UIImage? = takeVideoSnapShot(playerItem1)
    var orientationFromVideoForVideoOne: Int = getTheActualOrientationOfVideo(playerItem1)
    if orientationFromVideoForVideoOne == 0 {
        orientationFromVideoForVideoOne = 3
    }
    else if orientationFromVideoForVideoOne == 90 {
        orientationFromVideoForVideoOne = 0
    }

    imageFromCurrentTimeForVideoOne = UIImage(cgImage: imageFromCurrentTimeForVideoOne?.cgImage, scale: imageFromCurrentTimeForVideoOne?.scale, orientation: orientationFromVideoForVideoOne)
    let rotatedImageFromCurrentContextForVideoOne: UIImage? = normalizedImage(imageFromCurrentTimeForVideoOne)
    temporaryViewForVideoOne.clipsToBounds = true
    temporaryViewForVideoOne.image = rotatedImageFromCurrentContextForVideoOne
    videoViewOne.addSubview(temporaryViewForVideoOne) //Replace videoViewOne with your view containing AVPlayer
    var imageSize = CGSize.zero
    let orientation: UIInterfaceOrientation = UIApplication.shared.statusBarOrientation
    if UIInterfaceOrientationIsPortrait(orientation) {
        imageSize = UIScreen.main.bounds.size
    }
    else {
        imageSize = CGSize(width: CGFloat(UIScreen.main.bounds.size.height), height: CGFloat(UIScreen.main.bounds.size.width))
    }
    UIGraphicsBeginImageContextWithOptions(imageSize, false, UIScreen.main.scale())
    let context: CGContext? = UIGraphicsGetCurrentContext()
    for window: UIWindow in UIApplication.shared.windows {
        context.saveGState()
        context.translateBy(x: window.center.x, y: window.center.y)
        context.concatenate(window.transform)
        context.translateBy(x: -window.bounds.size.width * window.layer.anchorPoint.x, y: -window.bounds.size.height * window.layer.anchorPoint.y)
        if orientation == .landscapeLeft {
            context.rotate(by: M_PI_2)
            context.translateBy(x: 0, y: -imageSize.width)
        }
        else if orientation == .landscapeRight {
            context.rotate(by: -M_PI_2)
            context.translateBy(x: -imageSize.height, y: 0)
        }
        else if orientation == .portraitUpsideDown {
            context.rotate(by: .pi)
            context.translateBy(x: -imageSize.width, y: -imageSize.height)
        }

        if !window.responds(to: Selector("drawViewHierarchyInRect:afterScreenUpdates:")) {
            window.drawHierarchy(in: window.bounds, afterScreenUpdates: true)
        }
        else {
            window.drawHierarchy(in: window.bounds, afterScreenUpdates: true)
        }
        context.restoreGState()
    }
    let image: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    temporaryViewForVideoOne.removeFromSuperview()
    return image!
}

func takeVideoSnapShot(_ playerItem: AVPlayerItem) -> UIImage {
    let asset: AVURLAsset? = (playerItem.asset as? AVURLAsset)
    let imageGenerator = AVAssetImageGenerator(asset)
    imageGenerator.requestedTimeToleranceAfter = kCMTimeZero
    imageGenerator.requestedTimeToleranceBefore = kCMTimeZero
    let thumb: CGImageRef? = try? imageGenerator.copyCGImage(atTime: playerItem.currentTime(), actualTime: nil)
    let videoImage = UIImage(cgImage: thumb)
    CGImageRelease(thumb)
    return videoImage
}

func getTheActualOrientationOfVideo(_ playerItem: AVPlayerItem) -> Int {
    let asset: AVAsset? = playerItem.asset
    let tracks: [Any]? = asset?.tracks(withMediaType: AVMediaTypeVideo)
    let track: AVAssetTrack? = (tracks?[0] as? AVAssetTrack)
    let videoAssetOrientation_: CGAffineTransform? = track?.preferredTransform
    let videoAngle: CGFloat? = RadiansToDegrees(atan2(videoAssetOrientation_?.b, videoAssetOrientation_?.a))
    var orientation: Int = 0
    switch Int(videoAngle) {
        case 0:
            orientation = .right
        case 90:
            orientation = .up
        case 180:
            orientation = .left
        case -90:
            orientation = .down
        default:
            //Not found
    }
    return orientation
}

func normalizedImage(_ imageOf: UIImage) -> UIImage {
    if imageOf.imageOrientation == .up {
        return imageOf
    }
    UIGraphicsBeginImageContextWithOptions(imageOf.size, false, imageOf.scale)
    imageOf.draw(in: (CGRect))
    let normalizedImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return normalizedImage!
}
like image 2
PrafulD Avatar answered Oct 21 '22 05:10

PrafulD