You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS 9和10中AVPlayer的status属性KVO不触发问题求助

解决iOS 9/10中AVPlayer status属性KVO不触发的问题

我之前也踩过这个坑!在iOS 9和10上AVPlayer的status KVO死活不触发,但iOS 11及以上完全正常,核心问题出在KVO注册时机和AVPlayer的状态流转逻辑上,结合你的代码我来拆解下:

问题原因

1. KVO添加时机太晚

在iOS 9/10的AVFoundation实现中,当你用AVPlayerItem初始化AVPlayer后,系统会立刻开始加载媒体资源,如果这时候你才去给AVPlayer添加status的KVO,很可能此时status已经变成AVPlayerStatusReadyToPlay了——KVO只会在状态变化发生后发送通知,错过初始状态变化的话自然收不到回调。而iOS 11调整了状态更新的时机,允许初始化后补加KVO仍能收到通知,所以没这个问题。

2. 提前获取未加载完成的资源时长

你代码里直接通过item.asset.duration获取时长,这时候asset的元数据还没加载完成,得到的是无效的CMTime(值为NaN),这种异常值可能间接干扰AVPlayer的状态流转逻辑,进一步导致KVO通知异常。

解决方案

方案一:调整KVO注册顺序(优先推荐)

先创建空的AVPlayer,先注册status的KVO,再关联AVPlayerItem,确保KVO在状态变化前就已经生效:

- (void)loadVideo {
    AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:VIDEO_URL]];
    
    // 1. 先创建空的AVPlayer
    self.player = [[AVPlayer alloc] init];
    // 2. 先添加KVO监听,确保不会错过状态变化
    [self.player addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    // 3. 再关联playerItem
    [self.player replaceCurrentItemWithPlayerItem:item];
    
    AVPlayerLayer *layer = (AVPlayerLayer *)self.playerView.layer;
    [layer setPlayer:self.player];
    
    __weak ViewController *weakSelf = self;
    self.token = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, NSEC_PER_SEC) queue:nil usingBlock:^(CMTime time) {
        Float64 currentTime = CMTimeGetSeconds(time);
        // 实时从currentItem获取有效时长
        Float64 duration = CMTimeGetSeconds(weakSelf.player.currentItem.asset.duration);
        if (!isnan(duration)) {
            [weakSelf updateTimeLabel:currentTime duration:duration];
        }
    }];
}

方案二:监听AVPlayerItem的status(备选)

AVPlayer的status其实依赖于当前AVPlayerItem的状态,直接监听AVPlayerItem的status有时候更可靠:

- (void)loadVideo {
    AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:VIDEO_URL]];
    // 先给playerItem添加status监听
    [item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    
    self.player = [[AVPlayer alloc] initWithPlayerItem:item];
    AVPlayerLayer *layer = (AVPlayerLayer *)self.playerView.layer;
    [layer setPlayer:self.player];
    
    __weak ViewController *weakSelf = self;
    self.token = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, NSEC_PER_SEC) queue:nil usingBlock:^(CMTime time) {
        Float64 currentTime = CMTimeGetSeconds(time);
        Float64 duration = CMTimeGetSeconds(item.asset.duration);
        if (!isnan(duration)) {
            [weakSelf updateTimeLabel:currentTime duration:duration];
        }
    }];
}

// 对应的observeValueForKeyPath修改
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"status"]) {
        if ([object isKindOfClass:[AVPlayerItem class]]) {
            AVPlayerItem *item = (AVPlayerItem *)object;
            switch (item.status) {
                case AVPlayerItemStatusReadyToPlay: {
                    NSLog(@"player item's status changed to AVPlayerItemStatusReadyToPlay");
                    // 此时获取的时长才是有效的
                    Float64 duration = CMTimeGetSeconds(item.asset.duration);
                    [self updateTimeLabel:0.0 duration:duration];
                } break;
                case AVPlayerItemStatusFailed:
                    NSLog(@"player item load failed: %@", item.error);
                    break;
                default:
                    NSLog(@"player item's status changed");
                    break;
            }
            [item removeObserver:self forKeyPath:@"status"];
        }
        // 如果同时用了方案一的AVPlayer监听,保留对应的处理逻辑
    }
}

额外注意事项

  • 务必在销毁时移除所有观察者:避免内存泄漏或崩溃,在控制器的dealloc方法中清理:
- (void)dealloc {
    if (self.player) {
        [self.player removeObserver:self forKeyPath:@"status" error:nil];
        if (self.token) {
            [self.player removeTimeObserver:self.token];
        }
    }
    if (self.player.currentItem) {
        [self.player.currentItem removeObserver:self forKeyPath:@"status" error:nil];
    }
}
  • 不要提前获取未加载的资源元数据:时长、分辨率等元数据需要等asset加载完成后才能拿到,必须在status变为ReadyToPlay后再获取。

内容的提问来源于stack exchange,提问作者inexcii

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:43:43