46

我正在为 iPad 开发一个 iOS 应用程序,它需要在屏幕的某些部分播放视频。我有几个视频文件需要按照编译时未给出的顺序依次播放。它必须看起来好像只是一个视频播放。当从一个视频切换到下一个视频时,这两个视频的最后一帧或第一帧会显示一些延迟,这很好,但不应该出现没有内容的闪烁或白屏。视频不包含音频。重要的是要考虑内存使用情况。这些视频具有非常高的分辨率,并且可以同时播放多个不同的视频序列。

为了获得这一点,我已经尝试了一些解决方案。它们在下面列出:

1. 带有 AVComposition 的 AVPlayer,其中包含所有视频

在这个解决方案中,我有一个 AVPlayer,它将仅在使用 AVComposition 制作的 AVPlayerItem 上使用,该 AVComposition 包含彼此相邻的所有视频。当转到特定视频时,我会寻找下一个视频开始的合成时间。此解决方案的问题是,在寻找播放器时,它会快速显示它正在寻找的一些帧,这是不可接受的。似乎没有办法直接跳转到构图中的特定时间。我尝试通过制作刚刚完成的视频中最后一帧的图像来解决这个问题,然后在搜索时将其显示在 AVPLayer 前面,最后在搜索完成后将其删除。我正在使用 AVAssetImageGenerator 制作图像,但由于某种原因,图像的质量与视频不同,因此在视频上显示和隐藏图像时会有显着变化。另一个问题是 AVPlayer 使用大量内存,因为单个 AVPlayerItem 包含所有视频。

2.具有多个AVPlayerItems的AVPlayer

此解决方案为每个视频使用 AVPlayerItem,并在切换到新视频时替换 AVPlayer 的项目。这样做的问题是,当切换 AVPlayer 的项目时,它会在加载新项目时短时间显示白屏。要解决此问题,可以使用在加载时将图像放在最后一帧前面的解决方案,但仍然存在图像和视频质量不同且显着的问题。

3. 两个 AVPlayer 相互叠放轮流播放 AVPlayerItem

我尝试的下一个解决方案是让两个 AVPlayer 相互叠加,轮流播放 AVPlayerItems。因此,当播放器完成播放时,它将停留在视频的最后一帧。另一个 AVPlayer 将被带到前面(其 item 设置为 nil,因此它是透明的),并且下一个 AVPlayerItem 将插入到该 AVPlayer 中。一旦加载它就会开始播放,并且两个视频之间的流畅交易的错觉将完好无损。此解决方案的问题是内存使用情况。在某些情况下,我需要同时在屏幕上播放两个视频,这将导致 4 个 AVPlayer 同时加载一个 AVPlayerItem。这只是太多的内存,因为视频可以是非常高分辨率的。


是否有人对上面发布的整体问题和经过尝试的解决方案有一些想法、建议、评论或其他内容。

4

3 回答 3

53

所以这个项目现在已经在 App Store 中上线了,是时候回到这个帖子并分享我的发现并揭示我最终做了什么。

什么没有奏效

我在包含所有视频的大型 AVComposition 上使用的第一个选项不够好,因为没有办法在没有小的擦洗故障的情况下跳转到合成中的特定时间。此外,由于 API 无法为暂停提供帧保证,因此我在组合中的两个视频之间准确暂停视频时遇到了问题。

第三个有两个 AVPlayer 并让他们轮流在实践中效果很好。特别是在 iPad 4 或 iPhone 5 上。RAM 量较低的设备是一个问题,因为同时在内存中保存多个视频会消耗太多内存。特别是因为我必须处理非常高分辨率的视频。

我最终做了什么

好吧,左边是选项 2。在需要时为视频创建一个 AVPlayerItem 并将其提供给 AVPlayer。这个解决方案的好处是内存消耗。通过懒惰地创建 AVPlayerItems 并在不再需要它们时将它们丢弃可以将内存消耗保持在最低限度,这对于支持 RAM 有限的旧设备非常重要。这个解决方案的问题是,当从一个视频转到下一个视频时,在下一个视频加载到内存中时,会出现一个空白屏幕。我解决此问题的想法是在 AVPlayer 后面放置一个图像,该图像将在播放器缓冲时显示。我知道我需要与视频完全像素到像素完美的图像,所以我捕获的图像是视频最后一帧和第一帧的精确副本。

这个解决方案的问题

我遇到的问题是,如果视频/图像不是其本机大小或模块 4 缩放,则 UIImageView 内的图像位置与 AVPlayer 内视频的位置不同。换句话说,我对如何使用 UIImageView 和 AVPlayer 处理半像素有疑问。它似乎不是同样的方式。

我是如何解决的

我尝试了很多东西,因为我的应用程序以交互方式使用视频,并以不同的尺寸显示。我尝试更改 AVPlayerLayer 和 CALayer 的 magnificationfilter 和 minificationFilter 以使用相同的算法,但并没有真正改变任何东西。最后,我最终创建了一个 iPad 应用程序,它可以自动截取我需要的所有尺寸的视频,然后在视频缩放到特定尺寸时使用正确的图像。这提供了在我展示特定视频的所有尺寸中像素完美的图像。不是一个完美的工具链,但结果是完美的。

最后的反思

这个位置问题对我来说非常明显(因此解决非常重要)的主要原因是因为我的应用正在播放的视频内容是绘制动画,其中很多内容处于固定位置,并且只有一部分图片在移动。如果所有内容仅移动一个像素,则会产生非常明显且丑陋的故障。在今年的 WWDC 上,我与一位是 AVFoundation 专家的 Apple 工程师讨论了这个问题。当我向他介绍这个问题时,他的建议基本上是选择选项 3,但我向他解释说这是不可能的,因为内存消耗,而且我已经尝试了该解决方案。鉴于此,他说我选择了正确的解决方案,并要求我在视频缩放时为 UIImage/AVPlayer 定位提交错误报告。

于 2013-07-01T19:45:58.990 回答
18

您可能已经看过这个,但是您是否查看过AVQueuePlayer 文档

它是为AVPlayerItems在队列中播放而设计的,并且是 AVPlayer 的直接子类,因此只需以相同的方式使用它。你设置它如下:

AVPlayerItem *firstItem = [AVPlayerItem playerItemWithURL: firstItemURL];
AVPlayerItem *secondItem = [AVPlayerItem playerItemWithURL: secondItemURL];

AVQueuePlayer *player = [AVQueuePlayer queuePlayerWithItems:[NSArray arrayWithObjects:firstItem, secondItem, nil]];

[player play];

如果您想在运行时将新项目添加到队列中,只需使用此方法:

[player insertItem:thirdPlayerItem afterItem:firstPlayerItem];

我还没有测试过这是否会减少您提到的闪烁问题,但似乎这是要走的路。

于 2013-02-20T14:33:51.790 回答
1

更新——https: //youtu.be/7QlaO7WxjGg

这是您使用集合视图作为示例的答案,一次将播放 8 个(请注意,不需要任何类型的内存管理;您可以使用 ARC):

    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = (UICollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifier forIndexPath:indexPath];

    // Enumerate array of visible cells' indexPaths to find a match
    if ([self.collectionView.indexPathsForVisibleItems
         indexOfObjectPassingTest:^BOOL(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
             return (obj.item == indexPath.item);
         }]) dispatch_async(dispatch_get_main_queue(), ^{
             [self drawLayerForPlayerForCell:cell atIndexPath:indexPath];
         });


    return cell;
}

- (void)drawPosterFrameForCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    [self.imageManager requestImageForAsset:AppDelegate.sharedAppDelegate.assetsFetchResults[indexPath.item]
                                                          targetSize:AssetGridThumbnailSize
                                                         contentMode:PHImageContentModeAspectFill
                                                             options:nil
                                                       resultHandler:^(UIImage *result, NSDictionary *info) {
                                                           cell.contentView.layer.contents = (__bridge id)result.CGImage;
                                                       }];
}

- (void)drawLayerForPlayerForCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    cell.contentView.layer.sublayers = nil;
    [self.imageManager requestPlayerItemForVideo:(PHAsset *)self.assetsFetchResults[indexPath.item] options:nil resultHandler:^(AVPlayerItem * _Nullable playerItem, NSDictionary * _Nullable info) {
        dispatch_sync(dispatch_get_main_queue(), ^{
            if([[info objectForKey:PHImageResultIsInCloudKey] boolValue]) {
                [self drawPosterFrameForCell:cell atIndexPath:indexPath];
            } else {
                AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:[AVPlayer playerWithPlayerItem:playerItem]];
                [playerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
                [playerLayer setBorderColor:[UIColor whiteColor].CGColor];
                [playerLayer setBorderWidth:1.0f];
                [playerLayer setFrame:cell.contentView.layer.bounds];
                [cell.contentView.layer addSublayer:playerLayer];
                [playerLayer.player play];
            }
        });
    }];
}

drawPosterFrameForCell 方法将图像放置在无法播放视频的位置,因为它存储在 iCloud 上,而不是设备上。

无论如何,这是起点;一旦你了解了它是如何工作的,你就可以做你想做的所有事情,而不会出现你描述的记忆方面的任何故障。

于 2016-06-15T10:48:04.457 回答