AVPlayer和视频的截图

55

我试图在一个更大的视图中截取 AVPlayer 的屏幕截图。我只想构建一个测试框架,因此私有的 APIs 或任何方法都可以使用,因为该框架将不会在发布到 AppStore 时包含。

我已经尝试过使用:

  • UIGetScreenImage():在模拟器上运行良好,但在设备上无效。
  • snapshotviewafterscreenupdates:它显示了视图,但我无法从中创建一个 UIImage
  • drawViewInHierarchyrenderInContext 将无法与 AVPlayer 一起使用。
  • 我不想使用 AVAssetGenerator 来从视频中获取图像,因为很难获得视频播放器作为其他视图的子视图的正确坐标。

如果renderInContext不能直接工作,你可以先做snapshotviewafterscreenupdates然后再对该快照进行renderInContext吗? - SomeGuy
我也遇到了这个问题,我认为唯一的方法就是在适当的时间从AVAsset中获取一个静止画面... - wfbarksdale
也被这个问题困扰着... (https://github.com/brentvatne/react-native-video/issues/95) - gre
有什么解决方案吗?可以使用GLKView渲染视频吗? - Crossle Song
使用OpenGL渲染视频效果良好。 - Crossle Song
显示剩余3条评论
6个回答

10

我知道你不想使用AVAssetImageGenerator,但是我也进行了广泛的研究,我相信目前唯一的解决方案就是使用AVAssetImageGenerator。获取正确的坐标并不像你说的那么困难,因为你应该能够获取播放器的当前时间。在我的应用程序中,以下代码完美地工作:

-(UIImage *)getAVPlayerScreenshot 
{
    AVURLAsset *asset = (AVURLAsset *)self.playerItem.asset;
    AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
    imageGenerator.requestedTimeToleranceAfter = kCMTimeZero;
    imageGenerator.requestedTimeToleranceBefore = kCMTimeZero;
    CGImageRef thumb = [imageGenerator copyCGImageAtTime:self.playerItem.currentTime
                                              actualTime:NULL
                                                   error:NULL];
    UIImage *videoImage = [UIImage imageWithCGImage:thumb];
    CGImageRelease(thumb);
    return videoImage;
}

OP明确指出他们不想使用AVAssetImageGenerator。 - JAL
1
@JAL 是的,但目前这是唯一的方法,我已经给出了一个解决方案,以解决他不想使用它的原因。因此,我认为这仍然是可能的最佳答案。 - Bob de Graaf
3
@JAL,楼主可以说明他们想要什么,有时候答案是你不能够按照自己想要的方式去做。或者这样做的好处是什么。尽管楼主可能不喜欢,但其他人可以从答案中受益。 - mfaani

1

以下是Bob答案的Swift版本。我正在使用AVQueuePlayer,但对于普通的AVPlayer也适用。

public func getImageSnapshot() -> UIImage? {
    guard let asset = player.currentItem?.asset else { return nil }
    
    let imageGenerator = AVAssetImageGenerator(asset: asset);
    imageGenerator.requestedTimeToleranceAfter = CMTime.zero;
    imageGenerator.requestedTimeToleranceBefore = CMTime.zero;
    
    do {
        let thumb = try imageGenerator.copyCGImage(at: player.currentTime(), actualTime: nil);
        let image = UIImage(cgImage: thumb);
        return image;
    } catch {
        print("⛔️ Failed to get video snapshot: \(error)");
    }
    return nil;
}

1

OP 明确指定他们不想使用 AVAssetImageGenerator。 - JAL
有关于为什么CoreGraphics与AVPlayer不兼容的官方文档吗? - Varrry
1
非常感谢您的回答。它非常有效,所有其他解决方案在实际设备上都无法正常工作,特别是在流媒体时... - Viktor Todorov

0

这里是截取整个屏幕(包括AVPlayer)的代码。您只需要在视频播放器上方添加一个UIImageView,它一直隐藏,直到我们截取屏幕时再显示,然后再次隐藏。

func takeScreenshot() -> UIImage? {
    //1 Hide all UI you do not want on the screenshot
    self.hideButtonsForScreenshot()

    //2 Create an screenshot from your AVPlayer
    if let url = (self.overlayPlayer?.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: self.overlayPlayer!.currentTime(), actualTime: nil) {
            let videoImage = UIImage(cgImage: thumb)
            //Note: create an image view on top of you videoPlayer in the exact dimensions, and display it before taking the screenshot
            // mine is created in the storyboard
            // 3 Put the image from the screenshot in your screenshotPhotoView and unhide it
            self.screenshotPhotoView.image = videoImage
            self.screenshotPhotoView.isHidden = false
        }
    }
    
    //4 Take the screenshot
    let bounds = UIScreen.main.bounds
    UIGraphicsBeginImageContextWithOptions(bounds.size, true, 0.0)
    self.view.drawHierarchy(in: bounds, afterScreenUpdates: true)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    //5 show all UI again that you didn't want on your screenshot
    self.showButtonsForScreenshot()
    //6 Now hide the screenshotPhotoView again
    self.screenshotPhotoView.isHidden = true
    self.screenshotPhotoView.image = nil
    return image
}

-3

如果你想要拍下当前屏幕截图,只需要在任何动作事件上调用以下方法即可获得Image对象。

-(UIImage *) screenshot
{
    UIGraphicsBeginImageContext(self.view.bounds.size);
    [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *sourceImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    //now we will position the image, X/Y away from top left corner to get the portion we want
    UIGraphicsBeginImageContext(sourceImage.size);
    [sourceImage drawAtPoint:CGPointMake(0, 0)];
    UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    //To write image on divice.
    //UIImageWriteToSavedPhotosAlbum(croppedImage,nil, nil, nil);

    return croppedImage;
}

希望这能对你有所帮助。

这与这个答案几乎完全相同,而renderInContext无法与AVPlayer一起使用。 - JAL
  • (UIImage *)imageFromVideoAtPath:(NSString *)path atTime:(NSTimeInterval)time { NSURL *videoURL = [NSURL fileURLWithPath:path]; MPMoviePlayerController *moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:videoURL]; [moviePlayer prepareToPlay]; UIImage *thumbnail = [moviePlayer thumbnailImageAtTime:time timeOption:MPMovieTimeOptionNearestKeyFrame]; [moviePlayer stop]; return thumbnail; }
你可以试试这个。
- iO.S-C.

-5
CGRect grabRect = CGRectMake(0,0,320,568);// size you want to take screenshot of. 

if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
   UIGraphicsBeginImageContextWithOptions(grabRect.size, NO, [UIScreen mainScreen].scale);
 } else {
      UIGraphicsBeginImageContext(grabRect.size);
}
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(ctx, -grabRect.origin.x, -grabRect.origin.y);
[self.view.layer renderInContext:ctx];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

4
不行,那样做不行。AVPlayer无法与renderInContext一起使用。 - vodkhang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接