为什么 AVPlayer 初始加载这么慢?

Why is AVPlayer initial loading so slow?

我在 iOS 开发了一个播放视频列表的应用程序。数据结构是我发送请求到我自己的服务器并在我创建我的 viewController.

的视图后立即获取所有视频的 URL

之后,我开始依次播放 URL 的第一项。 问题是,对于第一个视频,实际加载需要超过 10 秒;但对于有时比第一个视频更长更大的其余视频,加载时间要短得多(可能 1 或 2 秒)。

通常我的第一个视频非常短而且很小(平均 200 KB),但它仍然比我的第二个视频 1 mb(长 5 倍,小 5 倍)需要更长的时间来加载。

过去 3 天我一直在研究这个问题,我尝试了很多不同的方法,我将在下面提到,但我的问题是 "Why this happens?" 不是解决问题的方法。我想知道为什么会这样,这样我就可以用我对 AVFoundation 的了解来解决它,或者编写我自己的播放器来最终为我解决这个问题。

这是我初始化它的代码:

self.player = [AVPlayer new];
self.playerView = [[NZPlayerView alloc] initWithPlayer:self.player];
self.playerView.videoGravity = AVLayerVideoGravityResizeAspect;
self.playerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.playerView];
// Constraints

请注意,您在上面看到的 NZPlayerView 是我的,它只是一个视图,里面有一个 AVPlayerLayer 并为我处理一些额外的事情,比如应用程序进入后台并返回等。 我不认为这种观点会导致任何问题,因为其他开发人员使用其他方法初始化播放器时,这个问题似乎一直存在。

在我创建视图并向我自己的服务器发送请求后,我得到了一个 URL 的列表,然后我使用下面的代码

一个一个地播放
AVPlayerItem *newCurrentItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:[[self.selectedMovementModel videoURL] url]]];
[self.player replaceCurrentItemWithPlayerItem:newCurrentItem];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];

然后,我使用 [self.player playImmediatelyAtRate:1]; 方法播放视频播放器,这是我实际尝试最小化初始停顿的方法之一,我以前使用 [self.player play]; 方法播放视频播放器

此视频播放完毕后,我决定是否循环播放视频,如果不循环播放,则返回相同的功能并播放下一个 selectedMovementModel

我也在使用 RACObserve 观察 self.player.statusself.player.rateself.player.timeControlStatusself.player.reasonForWaitingToPlay,其工作方式与 KVO 相同除了在我解雇 viewController 之后我不必摆脱它所以那里也没有问题。

我尝试过的方法是使用:loadValuesAsynchronouslyForKeys:completionHandler: 在我创建新的 currentItem 并在它的资产上。我为我的新 currentItem.

preferredForwardBufferDuration 设置为一个非常小的数字

我也试过将我的播放器的automaticallyWaitsToMinimizeStalling 属性设置为false,但都无济于事。 我还使用 xCode 仪器分析了我的网络,看起来我的第一个视频和其他视频没有什么不同。 iOS 开始分块下载视频,然后在可用时立即播放这些块,即使我配置我的播放器在开始之前下载整个视频,我的第一个视频应该比说我的第二个,因为它要小得多。 我的一个猜测是,它可能需要更长的时间,因为播放器是第一次与我的主机建立连接,然后播放器在未知的时间内保持该会话。如果来自两个不同网站的两个视频的加载时间相对相似,这将是有道理的,但事实并非如此。第一个还是比较费时间。

另一种我宁愿不惜一切代价避免的解决方案是,我会在我的服务器响应之前创建一个我的播放器实例,甚至在之前的页面上创建一个实例,并用一个非常小和短的视频加载它,这样它就可以在用户必须看到长时间加载之前做它必须做的事情。

但在采取这种绝望的措施之前,我更想知道是什么导致了问题。

编辑 1 **:我在应用程序中创建了一个单例,它有一个我在所有视图控制器中使用的视频播放器的实例。我用 URL 初始化它并加载它。但是对于列表的第一个视频,我的视图控制器中仍然存在相同的问题。我还认为也许我的视频播放器没有加载缺少界面但结果没有变化。 我将尝试阅读 Telegram 的代码,因为我知道他们正在使用 AVPlayer,即使在大多数情况下他们会在播放视频之前下载视频,但对于他们如何初始化视频播放器可能会有一些线索。

提前谢谢大家。

AVPlayer 初始延迟的原因是一种非常罕见的情况,可能永远不会发生在任何人身上,但考虑到自己找到答案可能很有趣并且奇怪地令人满意,我将 post它。

经过进一步调查,我发现我的 Xcode 调试器中有日志显示我的视频 URL 的某项任务正在“取消”,错误代码为“-999”。这当然很有趣,因为我还没有告诉我的视频播放器开始播放。

我还意识到,在调用 viewController 的 viewDidLoad 方法前后,我的应用程序大约使用了超过 180 MB 的内存。

考虑到我没有任何关于取消哪个任务的权限(我自己的请求没有被取消)。我决定调整 NSURLSessionDataTask class 的取消方法,我发现了我的问题。初始化我的模型列表时,我向每个模型添加了一个函数,用于确定我的视频是否应使用以下代码循环播放。

    _defineWeakSelf;
    AVAsset *asset = [AVAsset assetWithURL:[NSURL URLWithString:[self.videoURL url]]];
    [asset loadValuesAsynchronouslyForKeys:@[@"duration"] completionHandler:^{
        AVKeyValueStatus status = [asset statusOfValueForKey:@"duration" error:nil];

        weakSelf._shouldLoop = YES;
        if (status == AVKeyValueStatusLoaded)
        {
            CGFloat fullDurationSeconds = CMTimeGetSeconds([asset duration]);
            if (weakSelf._movementType == MovementTypeTime)
                weakSelf._shouldLoop = (fullDurationSeconds < weakSelf.duration);
            else
                weakSelf._shouldLoop = YES;
        }
    }];

请注意,_defineWeakSelf 类似于@strongify 和@weakify,只是它更易于使用。

这就是问题所在。每次在创建我的模型时使用此功能时,都会创建一个新的后台线程并发送一个新请求以同时实际检索视频。如果最多有 30-40 个模型,这在我的笔记本电脑上就不是什么大问题,但是有 90 多个模型,想象一下 iOS phone 会发生这种情况。当然,没有设备能够同时发送如此多的请求,其中一些请求被一遍又一遍地取消(比如我的第一个视频),当它们完成后,延迟就会消失。

至此这个问题已经解决了。所有其他建议,包括社区其他成员提供的解决方案,都更加有效,并且实际上将初始实例化延迟减少到最低限度。所以,谢谢大家。

*我也查看了Telegram对AVPlayer的使用,发现我的初始化和它非常相似,所以你也可以在他们的Github.

上查看