多个视频与AVPlayer

48

我正在为iPad开发一款iOS应用程序,需要在屏幕的某些部分播放视频。我有几个需要按照未知顺序依次播放的视频文件。视频之间切换时可以显示上一个或下一个视频的最后或第一帧,但不能出现闪烁或无内容的白屏现象。这些视频不包含音频。必须考虑内存使用情况。这些视频具有非常高的分辨率,可以同时播放多个不同的视频序列。

为了实现这一点,我已经尝试了一些解决方案,它们如下:

1. 使用AVComposition的AVPlayer播放所有视频

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

2. 同时使用多个AVPlayerItem的AVPlayer

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

3. 两个AVPlayer叠在一起轮流播放AVPlayerItem

我尝试过的下一个解决方案是有两个AVPlayer叠在一起,轮流播放AVPlayerItems。因此,当其中一个播放器播放完毕时,它将停留在视频的最后一帧。另一个AVPlayer将被移到前台(其项目设置为nil,因此是透明的),并且下一个AVPlayerItem将插入该AVPlayer。只要加载完成,它就会开始播放,两个视频之间平滑转换的幻象将保持完整。这种解决方案的问题是内存使用量。在某些情况下,我需要同时在屏幕上播放两个视频,这将导致同时有4个具有已加载AVPlayerItem的AVPlayer。由于视频可能具有非常高的分辨率,这简单地太多内存。


是否有人对总体问题和上述尝试的解决方案有想法、建议、评论或其他东西。

3个回答

54

这个项目现在已经在App Store上线了,是时候回到这个帖子并分享我的发现,以及我最终采取的方法。

不起作用的解决方案

第一种选项是将所有视频放在一个大的AVComposition中,但是由于没有办法在组合中跳转到特定时间点,而且在组合中准确暂停两个视频之间的问题上遇到挑战,因为API无法提供暂停的帧保证。

第三种使用两个AVPlayer轮流播放的方法在实践中表现良好。 特别是在iPad 4或iPhone 5上。 但是,具有较少内存的设备是一个问题,因为同时将多个视频保存在内存中会消耗太多内存。 特别是因为我必须处理分辨率非常高的视频。

我最终采取的方法

那么,只剩下第二个选项了。在需要时为视频创建AVPlayerItem,并将其提供给AVPlayer。 这种解决方案的好处是内存消耗小。通过只有在需要时才懒惰地创建AVPlayerItems并在它们不再需要时将其丢弃,可以将内存消耗最小化,这对于支持内存有限的旧设备非常重要。但是这种解决方案的问题在于从一个视频到另一个视频时会出现空白屏幕,因为下一个视频正在加载到内存中。我想解决这个问题的方法是将AVPlayer后面放置一个显示正在缓冲的图像。我知道需要与视频完全相同的像素大小的图像,因此我捕获了最后和第一帧视频的完全副本。 这种解决方案在实践中表现良好。

这种解决方案的问题

然而,如果视频/图像不是其原生大小或模4缩放,则UIImageView中图像的位置与AVPlayer中视频的位置不同。换句话说,我在UIImageView和AVPlayer处理半个像素的方式上遇到了问题。它们似乎不同。

我如何解决它

由于我的应用程序以交互方式展示不同大小的视频,因此我尝试了许多方法。我尝试更改AVPlayerLayer和CALayer的magnificationfilter和minificationFilter以使用相同的算法,但实际上并没有改变什么。最后,我创建了一个iPad应用程序,可以自动以所需的所有大小截取视频截图,然后在缩放视频到特定大小时使用正确的图像。这给出了在显示特定视频的所有大小时都是像素完美的图像。虽然工具链不完美,但结果是完美的。

最终反思

这个位置问题对我非常明显(因此非常重要),主要原因是我应用程序播放的视频内容是绘制动画,其中大部分内容都是固定的位置,只有一部分图片在移动。如果所有内容都向左或向右移动一个像素,这会导致非常明显且难看的故障。在今年的WWDC上,我与一位精通AVFoundation的苹果工程师讨论了这个问题。当我向他介绍问题时,他的建议基本上是选择第三个选项,但我向他解释了那不可能,因为内存消耗,并且我已经尝试过那种解决方案。因此,他说我选择了正确的解决方案,并要求我针对当视频缩放时UIImage/AVPlayer位置问题提交错误报告。


1
最佳解决方案是捕获视频的第一帧并将其添加为背景,然后当视图出现时,将视频层添加为子视图并删除背景。 - Hashem Aboonajmi
1
嗨,Peter,你有你的解决方案的代码吗? - Charlie

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];

我还没有测试过这个方法是否能解决你提到的闪烁问题,但看起来这应该是正确的做法。


5
谢谢您的回答。不幸的是,当AVQueuePlayer从一个项目转到另一个项目时会出现闪烁。 - Peter
3
可以确认到目前为止,AVQueuePlayer仍然无法平稳地切换视频。 - Andres Canella
你需要合并这些视频,我创建了一个代码片段来实现这个功能:https://gist.github.com/davidlondono/fedef4d1a42d9ad38657c3626c2b275d - David Alejandro Londoño Mejía
可以在这里提建议:https://stackoverflow.com/questions/54745359/avplayer-shows-black-screen-when-playing-multiple-videos-in-swift3-ios - Anand Gautam

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上而不是设备上。 总之,这是起点;一旦你理解了它的工作原理,你可以做所有你想做的事情,而不会出现你所描述的内存问题或其他故障。

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