状態、時間、および 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]];
}
}