当AVPlayer被释放时,仍然存在与之注册的键值观察者。

34

我正在创建一个简单的媒体播放器应用程序。当我在UITableView中播放第一个链接并点击第二个链接时,我的应用程序崩溃了。

- (void)viewDidLoad {
        [super viewDidLoad];
        arrURL = [NSArray arrayWithObjects: @"http://yp.shoutcast.com/sbin/tunein-station.pls?id=148820", @"http://www.kcrw.com/pls/kcrwmusic.pls",@"http://yp.shoutcast.com/sbin/tunein-station.pls?id=175821",@"http://yp.shoutcast.com/sbin/tunein-station.pls?id=148820",@"http://yp.shoutcast.com/sbin/tunein-station.pls?id=70931",nil];
        url = [[NSURL alloc] init];    
    }

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

        return [arrURL count];
    }

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *MyIdentifier = @"MyIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
    if (cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] ;
    }
     cell.textLabel.text = [arrURL objectAtIndex:indexPath.row];
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    selectedSongIndex = indexPath.row;
    url = [[NSURL alloc] initWithString:[arrURL objectAtIndex:indexPath.row]];
    [self setupAVPlayerForURL:url];
    [player play];

    //[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (IBAction)btnPlay_Click:(id)sender {

    [player play];
    AVPlayerItem *item = player.currentItem;
    [item addObserver:self forKeyPath:@"timedMetadata" options:NSKeyValueObservingOptionInitial| NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOld| NSKeyValueObservingOptionPrior context:nil];
}
- (IBAction)btnPause_Click:(id)sender {

    [player pause];
}

- (IBAction)btnStop_Click:(id)sender {

    [player pause];
}
-(void) setupAVPlayerForURL: (NSURL*) url1 {
    AVAsset *asset = [AVURLAsset URLAssetWithURL:url1 options:nil];
    AVPlayerItem *anItem = [AVPlayerItem playerItemWithAsset:asset];

    player = [AVPlayer playerWithPlayerItem:anItem]; **//Application Crashed**
    [player addObserver:self forKeyPath:@"status" options:0 context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if([keyPath isEqualToString:@"timedMetadata"])
    {
        AVPlayerItem *item = (AVPlayerItem *)object;
        NSLog(@"Item.timedMetadata: %@",item.timedMetadata);
        NSLog(@"-- META DATA ---");
        //        AVPlayerItem *pItem = (AVPlayerItem *)object;
        for (AVMetadataItem *metaItem in item.timedMetadata) {
            NSLog(@"meta data = %@",[metaItem commonKey]);
            NSString *key = [metaItem commonKey]; //key = publisher , key = title
            NSString *value = [metaItem stringValue];
            NSLog(@"key = %@, value = %@", key, value);
            if([[metaItem commonKey] isEqualToString:@"title"])
            {
                self.lblTitle.text = [metaItem stringValue];
            }
        }
    }
    if (object == player && [keyPath isEqualToString:@"status"]) {
        if (player.status == AVPlayerStatusFailed) {
            NSLog(@"AVPlayer Failed");
        } else if (player.status == AVPlayerStatusReadyToPlay) {
            NSLog(@"AVPlayer Ready to Play");
        } else if (player.status == AVPlayerItemStatusUnknown) {
            NSLog(@"AVPlayer Unknown");
        }
    }
}

当应用程序崩溃时,我收到了这条消息。

***终止应用程序,原因是未捕获的异常'NSInternalInconsistencyException',原因:在仍然注册键值观察者的情况下释放AVPlayer类的0x165297c0实例。当前观察信息: (上下文:0x0,属性:0x1661d5d0>)'

应用程序仅在IOS 8中崩溃,在IOS 7中正常工作。 我做错了什么?

5个回答

52

我遇到了类似的问题。在iOS 7中它运行良好,但现在在iOS 8中崩溃。

解决方案是在释放对象之前先删除观察者。

当你替换或分配成员的新对象时,你会释放旧对象,因此你需要先删除观察者:

-(void) setupAVPlayerForURL: (NSURL*) url1 {
    AVAsset *asset = [AVURLAsset URLAssetWithURL:url1 options:nil];
    AVPlayerItem *anItem = [AVPlayerItem playerItemWithAsset:asset];
    if (player != nil)
        [player removeObserver:self forKeyPath:@"status"];
    player = [AVPlayer playerWithPlayerItem:anItem]; 
    [player addObserver:self forKeyPath:@"status" options:0 context:nil];
}

类似地,在btnStop_Click未被按下的情况下按下btnPlayClick:

- (IBAction)btnPlay_Click:(id)sender {
     if (player != nil && [player currentItem] != nil)
         [[player currentItem] removeObserver:self forKeyPath:@"timedMetadata"];
    AVPlayerItem *item = player.currentItem;
    [item addObserver:self forKeyPath:@"timedMetadata" options:NSKeyValueObservingOptionInitial|     NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOld| NSKeyValueObservingOptionPrior context:nil];
    [player play];
}

我将以下代码 if(player!= nil) { if(player.currentItem != nil) { [player.currentItem removeObserver:self forKeyPath:@"status"]; } } 替换了原来的 if (player != nil) [player removeObserver:self forKeyPath:@"status"];,现在运行良好。 - Mihir Oza
大家好,我也被这个问题困扰着,已经搜索了两天了,拜托帮我解决一下。我在这里发了个问题 - https://dev59.com/NYbca4cB1Zd3GeqPZbdD - NITHIN SHAHRUKH
1
我实现了你的代码,但是出现了错误。终止应用程序,原因是未捕获的异常 'NSRangeException',原因是无法从 <AVPlayer 0x17820a6e0> 中删除键路径 "status" 的观察者 <Player 0x178232e40>,因为它没有被注册为观察者。 - Hardik Kardani
这会抛出异常,指出“无法从<AVPlayerItem 0x174201930>中删除观察者<AudioPlayerViewController 0x1570020e0>的键路径“status”,因为它未被注册为观察者。” - Abuzar Amin
@AbuzarAmin针对此问题使用try catch。 `@try { [player removeObserver:self forKeyPath:@"status"]; } @catch (NSException *exception) { } @finally { }` - Sushil Sharma
感谢@Ralph提供如此简单的解决方案,我浪费了很多时间寻找答案。 - Sushil Sharma

3
-(void)viewWillDisappear:(BOOL)animated
{
[self.player removeObserver:self forKeyPath:@"status" context:nil];
}

你能提供一些关于你的解决方案的背景信息吗? - wogsland
1
虽然这段代码片段很受欢迎,可能会提供一些帮助,但如果它包括解释“如何”和“为什么”解决问题,那将会大有改进。请记住,您正在回答未来读者的问题,而不仅仅是现在提问的人!请编辑您的答案以添加解释,并指出适用的限制和假设。 - Toby Speight

2
使用KVO时,必须平衡对addObserver:forKeyPath:options:context:的调用和对removeObserver:forKeyPath:的调用(请参见KVO编程指南)。
尝试在停止按钮被点击时将视图控制器作为观察者移除,例如:
- (IBAction)btnStop_Click:(id)sender {
    [[player currentItem] removeObserver:self forKeyPath:@"timedMetadata"];
}

1
应用程序崩溃。崩溃日志:'无法从<AVPlayerItem 0x17dc9a10>中删除键路径“timedMetadata”的观察者<playerScreenViewController 0x17e933b0>,因为它未注册为观察者。' 应用程序仅在IOS 8设备上崩溃。 - Mihir Oza
1
mmcombe是正确的,除了错误是指AVPlayer的状态KVO。只需更改他的答案为[[player]removeObserver:self forKeyPath:@"status"]; - MDB983
我的应用在IOS8中崩溃,以下是我的崩溃日志:“由于未捕获异常 'NSInternalInconsistencyException' ,原因:'一个AVPlayerItem类的实例0x15edf250已被解除分配,而与之仍然注册有键值观察者。” - Mihir Oza

0

在使用@try @catch移除观察者之前,先智慧地验证一下是否正在观察该键:

@try {
    [self.player removeObserver:self forKeyPath:@"status" context:nil];
} @catch (id anException) {
    //do nothing, obviously it wasn't attached because an exception was thrown
    NSLog(@"status key not being observed");
}

0

当我使用AVPlayer时,我遇到了类似的问题,崩溃日志信息如下:

在仍然注册有键值观察者的情况下,类AVKeyPathFlattener的实例0x174034600已被释放。当前观察信息:(上下文:0x0,属性:0x17405d6d0>)

根据苹果的建议,我最初是在初始化AVPlayerItem对象后添加观察者,并在观察者的dealloc方法中移除观察者。因为我的观察者类对AVPlayerItem对象保持了强引用,所以在我的观察者对象被释放之前,它不应该被释放。我真的不知道为什么会发生这种情况。

所以我尝试通过使用BlocksKit来解决这个问题,现在它对我来说运行得很好。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接