4

状態、時間、および timedMetadata をデリゲートに報告する AVPlayer (および AVPlayerItem) を処理するクラスがあります。

約 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。おそらく、Apple のこれまでで最悪の設計上の決定の 1 つです。

もう関係ないと思いますが、おそらく問題は、観察しようとしている値が、オブザーバーとして自分自身を追加するときにキーに割り当てられていることがあるため、オブザーバーセレクター呼ばれません。

これを回避するNSKeyValueObservingOptionInitialには、optionswhen callingaddObserver:forKeyPath:options:context:に を追加すると、observer メソッドが現在の値ですぐに呼び出されます。

于 2015-05-21T14:03:58.123 に答える