4

我有一个处理 AVPlayer(和 AVPlayerItem)的类,它向委托报告返回状态、时间和 timedMetadata。

效果很好,除了大约 70-80% 的时间,初始 timedMetadata 不是“观察到的键值”。但是,在错过了第一个 timedMetadata 实例之后,似乎可以毫无问题地观察到所有其他 timedMetadata。

作为一个临时修复,我已经开始在视频的开头嵌入虚拟的 timedMetadata 标签,可以说除了“踢轮胎”之外什么都不做,之后一切正常。然而,这似乎很笨拙。我怀疑要么我以次优方式设置 AVPlayerItem 和 KVO,要么这里只是一个错误。

任何关于为什么会发生这种情况的想法都非常感谢!下面的代码......

// CL: Define constants for the key-value observation contexts.
static const NSString *ItemStatusContext;
static const NSString *ItemMetadataContext;
static const NSString *ItemPlaybackForcastContext;


- (id)initWithURL:(NSURL *)url
{
    if (self = [super init]) {

        __weak TFPAVController *_self = self;

        AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
        NSString *tracksKey = @"tracks";

        [asset loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:tracksKey] completionHandler:
         ^{
             dispatch_async(dispatch_get_main_queue(),
                            ^{
                                NSError *error = nil;
                                AVKeyValueStatus status = [asset statusOfValueForKey:tracksKey error:&error];

                                if (status == AVKeyValueStatusLoaded) {
                                    AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset];
                                    [item addObserver:_self forKeyPath:@"status" options:0 context:&ItemStatusContext];
                                    [item addObserver:_self forKeyPath:@"timedMetadata" options:0 context:&ItemMetadataContext];
                                    [item addObserver:_self forKeyPath:@"playbackLikelyToKeepUp" options:0 context:&ItemPlaybackForcastContext];

                                    [[NSNotificationCenter defaultCenter] addObserver:_self
                                                                             selector:@selector(playerItemDidReachEnd:)
                                                                                 name:AVPlayerItemDidPlayToEndTimeNotification
                                                                               object:item];

                                    AVPlayer *player = [AVPlayer playerWithPlayerItem:item];
                                    _self.totalRunTime = CMTimeGetSeconds(item.duration);
                                    [_self.delegate avPlayerNeedsView:player];

                                    _self.playerItem = item;
                                    _self.player = player;
                                }
                                else {
                                    NSLog(@"The asset's tracks were not loaded: %@ // [%@ %@]",
                                          error.localizedDescription,
                                          NSStringFromClass([self class]),
                                          NSStringFromSelector(_cmd));
                                }

                                _self.playerObserver = [_self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, _FrameRate_) 
                                                                                                  queue:NULL
                                                                                             usingBlock: ^(CMTime time) {
                                                                                                 _self.currentVideoTime = CMTimeGetSeconds([_self.playerItem currentTime]);
                                                                                             }];
                            });
         }];
    }

    return self;
}
#pragma mark - KVO Response Methods
- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary *)change 
                       context:(void *)context 
{    
        __weak TFPAVController *_self = self;

    if (context == &ItemStatusContext) {
        dispatch_async(dispatch_get_main_queue(),
                       ^{
                           if (((AVPlayerItem *)object).status == AVPlayerItemStatusReadyToPlay) {

                               [_self.delegate videoIsLoadedInPlayer:_self];
                           }
                       });
        return;
    }
    else if (context == &ItemMetadataContext) {
        dispatch_async(dispatch_get_main_queue(),
                       ^{
                           [_self checkMetaDataForPlayerItem: (AVPlayerItem *)object];
                       });
        return;
    }
    else if (context == &ItemPlaybackForcastContext) {
        dispatch_async(dispatch_get_main_queue(),
                       ^{
                           AVPlayerItem *playerItem = object;                           
                           if (CMTimeGetSeconds([playerItem currentTime]) <= 0) return;

                           NSDictionary *notificationDictionary = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:playerItem.playbackLikelyToKeepUp] 
                                                                                              forKey:kAVPlayerStateKey];

                           [[NSNotificationCenter defaultCenter] postNotificationName:kAVPlayerNotification 
                                                                               object:self 
                                                                             userInfo:notificationDictionary];
                        });
        return;
    }

    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];

}

- (void)checkMetaDataForPlayerItem:(AVPlayerItem *)item
{
    NSMutableDictionary *metaDict = [NSMutableDictionary dictionary];

    // CL: make sure there's stuff there
    if (item.timedMetadata != nil && [item.timedMetadata count] > 0) {
        // CL: if there is, cycle through the items and create a Dictionary
        for (AVMetadataItem *metadata in item.timedMetadata) {
            [metaDict setObject:[metadata valueForKey:@"value"] forKey:[metadata valueForKey:@"key"]];
        }
        // CL: pass it to the delegate
        [self.delegate parseNewMetaData:[NSDictionary dictionaryWithDictionary:metaDict]];
    }
}
4

1 回答 1

1

啊,KVO。可能是苹果有史以来最糟糕的设计决策之一。

我想它不再相关,但猜测你遇到的问题是,有时当你将自己添加为观察者时,你试图观察的值已经分配给键,所以你的观察者选择器不叫。

为避免这种情况,您可以在调用时添加NSKeyValueObservingOptionInitialoptions并且addObserver:forKeyPath:options:context:您的观察者方法将立即使用当前值调用。

于 2015-05-21T14:03:58.123 回答