终止应用程序原因:“AVPlayer 的实例无法删除由 AVPlayer 的不同实例添加的时间观察者。”

Posted

技术标签:

【中文标题】终止应用程序原因:“AVPlayer 的实例无法删除由 AVPlayer 的不同实例添加的时间观察者。”【英文标题】:Terminating app reason: 'An instance of AVPlayer cannot remove a time observer that was added by a different instance of AVPlayer.' 【发布时间】:2014-10-31 05:44:24 【问题描述】:

我正在使用 Apple 的文档演示。这是演示的链接: AVPlayerDemo

我的应用程序在执行以下步骤后崩溃了: 1) 播放歌曲 2) 从搜索栏快进。 3)点击下一步, 4) 从搜索栏快进。 这是我的崩溃日志:

由于未捕获的异常而终止应用程序 'NSInvalidArgumentException',原因:'AVPlayer 的实例不能 删除由不同实例添加的时间观察者 AVPlayer。

这是音乐播放器的代码:

- (NSTimeInterval) playableDuration

    //  use loadedTimeRanges to compute playableDuration.
    AVPlayerItem * item = player.currentItem;

    if (item.status == AVPlayerItemStatusReadyToPlay) 
        NSArray * timeRangeArray = item.loadedTimeRanges;

        CMTimeRange aTimeRange = [[timeRangeArray objectAtIndex:0] CMTimeRangeValue];

        double startTime = CMTimeGetSeconds(aTimeRange.start);
        double loadedDuration = CMTimeGetSeconds(aTimeRange.duration);

        NSLog(@"get time range, its start is %f seconds, its duration is %f seconds.", startTime/60, loadedDuration/60);
        return (NSTimeInterval)(startTime + loadedDuration);
    
    else
    
        return(CMTimeGetSeconds(kCMTimeInvalid));
    
  
-(NSTimeInterval)currentItemPlayableDuration

    //  use loadedTimeRanges to compute playableDuration.
    AVPlayerItem * item = player.currentItem;

    if (item.status == AVPlayerItemStatusReadyToPlay) 
        NSArray * timeRangeArray = item.loadedTimeRanges;

        CMTime currentTime = player.currentTime;

        __block CMTimeRange aTimeRange;

        [timeRangeArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) 

       aTimeRange = [[timeRangeArray objectAtIndex:0] CMTimeRangeValue];

       if(CMTimeRangeContainsTime(aTimeRange, currentTime))
            *stop = YES;

        ];

        CMTime maxTime = CMTimeRangeGetEnd(aTimeRange);

        return CMTimeGetSeconds(maxTime);
    
    else
    
        return(CMTimeGetSeconds(kCMTimeInvalid));
    
  
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

    if(player!= nil)
    
        [player.currentItem removeObserver:self forKeyPath:@"status"];
    
    [player pause];
    player = nil;
    btnPlay.hidden=true;
    btnPause.hidden=false;
    selectedSongIndex = indexPath.row;
    url = [[NSURL alloc] initWithString:[arrURL objectAtIndex:indexPath.row]];
    [self setupAVPlayerForURL:url];
    //[player play];
    AVPlayerItem *item = player.currentItem;
    [item addObserver:self forKeyPath:@"timedMetadata" options:NSKeyValueObservingOptionInitial| NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOld| NSKeyValueObservingOptionPrior context:nil];
    [player play];
    //[tableView deselectRowAtIndexPath:indexPath animated:YES];
  #pragma mark - Player Methods
- (IBAction)btnBack_Click:(id)sender 
    int index = selectedSongIndex;
    if (index==0) 
        if(player!= nil)
        
            [player.currentItem removeObserver:self forKeyPath:@"status"];
        
        [player pause];
        player = nil;
        selectedSongIndex = [arrURL count]-1;
        url = [[NSURL alloc] initWithString:[arrURL objectAtIndex:selectedSongIndex]];
        [self setupAVPlayerForURL:url];
        AVPlayerItem *item = player.currentItem;
        [item addObserver:self forKeyPath:@"timedMetadata" options:NSKeyValueObservingOptionInitial| NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOld| NSKeyValueObservingOptionPrior context:nil];
        [player play];
    
    else if(index >= 0 && index < [arrURL count])
    
        if(player!= nil)
        
            [player.currentItem removeObserver:self forKeyPath:@"status"];
        
        [player pause];
        player = nil;
        index = selectedSongIndex - 1;
        selectedSongIndex = index;
        url = [[NSURL alloc] initWithString:[arrURL objectAtIndex:index]];
        [self setupAVPlayerForURL:url];
        AVPlayerItem *item = player.currentItem;
        [item addObserver:self forKeyPath:@"timedMetadata" options:NSKeyValueObservingOptionInitial| NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOld| NSKeyValueObservingOptionPrior context:nil];
        [player play];
    

- (IBAction)btnPlay_Click:(id)sender 

    btnPlay.hidden=true;
    btnPause.hidden=false;
    //url = [[NSURL alloc] initWithString:[arrURL objectAtIndex:indexPath.row]];
    if([strPlay isEqualToString:@"ViewWillAppear"] || [strPlay isEqualToString:@"Stop"])
    
        [currentTimeSlider setValue:0.0];
        lblStart.text = @"0:00";
        [self setupAVPlayerForURL:url];
    
    AVPlayerItem *item = player.currentItem;
    [item addObserver:self forKeyPath:@"timedMetadata" options:NSKeyValueObservingOptionInitial| NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOld| NSKeyValueObservingOptionPrior context:nil];
    [player play];


- (IBAction)btnPause_Click:(id)sender 
    btnPlay.hidden=false;
    btnPause.hidden=true;
    strPlay = @"Pause";
    [player pause];

- (IBAction)btnStop_Click:(id)sender 
    btnPlay.hidden=false;
    btnPause.hidden=true;
    strPlay = @"Stop";
    //[player removeObserver:self forKeyPath:@"status"];
    [player pause];
    player = nil;


- (IBAction)btnNext_Click:(id)sender 
    int index = selectedSongIndex;
    if(selectedSongIndex == [arrURL count]-1)
    
        if(player!= nil)
        
            [player.currentItem removeObserver:self forKeyPath:@"status"];
        
        [player pause];
        player = nil;

        AVPlayerItem *item = player.currentItem;
        [item removeObserver:self forKeyPath:@"timedMetadata"];

        selectedSongIndex=0;
        url = [[NSURL alloc] initWithString:[arrURL objectAtIndex:0]];
        [self setupAVPlayerForURL:url];
        AVPlayerItem *item1 = player.currentItem;
        [item1 addObserver:self forKeyPath:@"timedMetadata" options:NSKeyValueObservingOptionInitial| NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOld| NSKeyValueObservingOptionPrior context:nil];
        [player play];
    
    else if(index < [arrURL count])
    
        if(player!= nil)
        
            [player.currentItem removeObserver:self forKeyPath:@"status"];
        
        [player pause];
        player = nil;

        AVPlayerItem *item = player.currentItem;
        [item removeObserver:self forKeyPath:@"timedMetadata"];
        index = selectedSongIndex+1;
        selectedSongIndex = index;
        url = [[NSURL alloc] initWithString:[arrURL objectAtIndex:index]];
        [self setupAVPlayerForURL:url];
        AVPlayerItem *item1 = player.currentItem;
        [item1 addObserver:self forKeyPath:@"timedMetadata" options:NSKeyValueObservingOptionInitial| NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOld| NSKeyValueObservingOptionPrior context:nil];
        [player play];
    
  
-(void) setupAVPlayerForURL: (NSURL*) url1 
    AVAsset *asset = [AVURLAsset URLAssetWithURL:url1 options:nil];
    AVPlayerItem *anItem = [AVPlayerItem playerItemWithAsset:asset];
    if(player!= nil)
    
        [player.currentItem removeObserver:self forKeyPath:@"status"];
    
    player = [AVPlayer playerWithPlayerItem:anItem];
    [player.currentItem addObserver:self forKeyPath:@"status" options:0 context:nil];
    [player play];
  
- (CMTime)playerItemDuration

    AVPlayerItem *thePlayerItem = [player currentItem];
    if (thePlayerItem.status == AVPlayerItemStatusReadyToPlay)
    

        return([thePlayerItem duration]);
    

    return(kCMTimeInvalid);

- (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.currentItem && [keyPath isEqualToString:@"status"]) 
        if (player.status == AVPlayerStatusFailed) 
            NSLog(@"AVPlayer Failed");
         else if (player.currentItem.status == AVPlayerItemStatusReadyToPlay) 
            NSLog(@"AVPlayer Ready to Play");

            NSTimeInterval totalSeconds = CMTimeGetSeconds(player.currentItem.asset.duration);
            int minutes = (int)totalSeconds / 60;
            int seconds = (int)totalSeconds % 60;
            NSString *minutes1;
            NSString *seconds1;
            if (minutes < 10) 
                minutes1=[NSString stringWithFormat:@"%02d",minutes];
                seconds1=[NSString stringWithFormat:@"%02d",seconds];
            
            lblEnd.text = [NSString stringWithFormat:@"%@:%@",minutes1,seconds1];
            NSLog(@"lblEnd Duration: %@",lblEnd.text);
            double interval = .1f;

            CMTime playerDuration = [self playerItemDuration]; // return player duration.
            if (CMTIME_IS_INVALID(playerDuration))
            
                return;
            
            double duration = CMTimeGetSeconds(playerDuration);
            if (isfinite(duration))
            
                CGFloat width = CGRectGetWidth([currentTimeSlider bounds]);
                interval = 0.5f * duration / width;
            

            /* Update the scrubber during normal playback. */
            id timeObserver = [player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(interval, NSEC_PER_SEC)
                                                                 queue:NULL
                                                            usingBlock:
                             ^(CMTime time)
                             
                                 [self syncScrubber];
                                 NSLog(@"Available: %f",[self availableDuration]);
                             ];

         else if (player.status == AVPlayerItemStatusUnknown) 
            NSLog(@"AVPlayer Unknown");
        
    

- (IBAction)currentTimeSliderValueChanged:(id)sender 
    CMTime playerDuration = [self playerItemDuration];
    double duration = CMTimeGetSeconds(playerDuration);
    float minValue = [currentTimeSlider minimumValue];
    float maxValue = [currentTimeSlider maximumValue];
    double time = CMTimeGetSeconds([player currentTime]);

    int32_t timeScale = self.player.currentItem.asset.duration.timescale;
    [player seekToTime:CMTimeMake((maxValue - minValue) * time / duration + minValue, 1)];


- (NSTimeInterval) availableDuration;

    int result1 = 0;
    NSArray *loadedTimeRanges = [[self.player currentItem] loadedTimeRanges];
    if([loadedTimeRanges count]>0)
    
        CMTimeRange timeRange = [[loadedTimeRanges objectAtIndex:0] CMTimeRangeValue];
        Float64 startSeconds = CMTimeGetSeconds(timeRange.start);
        Float64 durationSeconds = CMTimeGetSeconds(timeRange.duration);
        NSTimeInterval result = startSeconds + durationSeconds;
        result1 =result;
    
    return result1;
   
#pragma mark -
#pragma mark Music scrubber control

/* Cancels the previously registered time observer. */
-(void)removePlayerTimeObserver

    if (mTimeObserver)
    
        [self.player removeTimeObserver:mTimeObserver];
        mTimeObserver = nil;
    


/* Requests invocation of a given block during media playback to update the movie scrubber control. */
-(void)initScrubberTimer

    double interval = .1f;

    CMTime playerDuration = [self playerItemDuration];
    if (CMTIME_IS_INVALID(playerDuration))
    
        return;
    
    double duration = CMTimeGetSeconds(playerDuration);
    if (isfinite(duration))
    
        CGFloat width = CGRectGetWidth([self.currentTimeSlider bounds]);
        interval = 0.5f * duration / width;
    

    /* Update the scrubber during normal playback. */
    __weak playerScreenViewController *weakSelf = self;
     mTimeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(interval, NSEC_PER_SEC)
                                                               queue:NULL /* If you pass NULL, the main queue is used. */
                                                          usingBlock:^(CMTime time)
                     
                         [weakSelf syncScrubber];
                     ];


/* Set the scrubber based on the player current time. */
- (void)syncScrubber

    CMTime playerDuration = [self playerItemDuration];
    if (CMTIME_IS_INVALID(playerDuration))
    
        currentTimeSlider.minimumValue = 0.0;
        return;
    

    double duration = CMTimeGetSeconds(playerDuration);
    if (isfinite(duration))
    
        float minValue = [self.currentTimeSlider minimumValue];
        float maxValue = [self.currentTimeSlider maximumValue];
        double time = CMTimeGetSeconds([self.player currentTime]);

        [self.currentTimeSlider setValue:(maxValue - minValue) * time / duration + minValue];

        int minutes = (int)time / 60;
        int seconds = (int)time % 60;
        NSString *minutes1;
        NSString *seconds1;
        if (minutes < 10) 
            minutes1=[NSString stringWithFormat:@"%02d",minutes];
            seconds1=[NSString stringWithFormat:@"%02d",seconds];
        
        lblStart.text = [NSString stringWithFormat:@"%@:%@",minutes1,seconds1];
        [currentTimeSlider setValue:(maxValue - minValue) * time / duration + minValue];

        int difference = duration-time;

        if (difference == 0) 
            [self removePlayerTimeObserver];
            [self btnNext_Click:nil];
        
    


/* The user is dragging the movie controller thumb to scrub through the movie. */
- (IBAction)beginScrubbing:(id)sender

    mRestoreAfterScrubbingRate = [self.player rate];
    [self.player setRate:0.f];

    /* Remove previous timer. */
    [self removePlayerTimeObserver];


/* Set the player current time to match the scrubber position. */
- (IBAction)scrub:(id)sender

    if ([sender isKindOfClass:[UISlider class]] && !isSeeking)
    
        isSeeking = YES;
        UISlider* slider = sender;

        CMTime playerDuration = [self playerItemDuration];
        if (CMTIME_IS_INVALID(playerDuration)) 
            return;
        

        double duration = CMTimeGetSeconds(playerDuration);
        if (isfinite(duration))
        
            float minValue = [slider minimumValue];
            float maxValue = [slider maximumValue];
            float value = [slider value];

            double time = duration * (value - minValue) / (maxValue - minValue);

            [self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC) completionHandler:^(BOOL finished) 
                dispatch_async(dispatch_get_main_queue(), ^
                    isSeeking = NO;
                );
            ];
        
    


/* The user has released the movie thumb control to stop scrubbing through the movie. */
- (IBAction)endScrubbing:(id)sender

    if (!mTimeObserver)
    
        CMTime playerDuration = [self playerItemDuration];
        if (CMTIME_IS_INVALID(playerDuration))
        
            return;
        

        double duration = CMTimeGetSeconds(playerDuration);
        if (isfinite(duration))
        
            CGFloat width = CGRectGetWidth([self.currentTimeSlider bounds]);
            double tolerance = 0.5f * duration / width;

            __weak playerScreenViewController *weakSelf = self;
            mTimeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(tolerance, NSEC_PER_SEC) queue:NULL usingBlock:
                             ^(CMTime time)
                             
                                 [weakSelf syncScrubber];
                             ];
        
    

    if (mRestoreAfterScrubbingRate)
    
        [self.player setRate:mRestoreAfterScrubbingRate];
        mRestoreAfterScrubbingRate = 0.f;
    


- (BOOL)isScrubbing

    return mRestoreAfterScrubbingRate != 0.f;


-(void)enableScrubber

    self.currentTimeSlider.enabled = YES;


-(void)disableScrubber

    self.currentTimeSlider.enabled = NO;

【问题讨论】:

得到了我的解决方案。这是链接:[AVPlayer 已被释放,而键值观察者仍向其注册。][1] [1]:***.com/questions/26256982/… 【参考方案1】:

由于以下代码,我收到了该错误消息:

- (void)didPlay 
  if (!_boundaryTimeObserver) 
    _boundaryTimeObserver = 
    [_player addBoundaryTimeObserverForTimes:[NSArray<NSValue *> arrayWithObjects:
                                            [NSValue valueWithCMTime:boundaryTime],
                                              nil]
                                       queue:NULL
                                  usingBlock:^() ];
  

- (void)didStop 
  if (_boundaryTimeObserver) 
    [_player removeTimeObserver:_boundaryTimeObserver];
    // FORGOT TO SET _boundaryTimeObserver to NIL!!!!
  

AVPlayer 的实例无法移除已添加的时间观察器 通过不同的 AVPlayer 实例

可以在给定时间观察者不与AVPlayer 关联的任何时候发生。这可能是因为它与另一个 AVPlayer 相关联,或者因为它之前已从相关的 AVPlayer 中删除。

【讨论】:

5 年后我又遇到了这个错误,你的回答很有帮助 基本上如果你玩重用相同的AVPlayer和计时器,那么在删除它后不要忘记将计时器设置为nil self.timer = nil【参考方案2】:

有很多检查这个错误。对我来说它的时间观察者。检查时间观察者和如果 player.rate == 1.0

见下文:

override func viewDidDisappear(_ animated: Bool) 
    if (self.timeObserver != nil) 
        if player.rate == 1.0  // it is required as you have to check if player is playing
            player.removeTimeObserver(timeObserver)
            player.pause()
        
    

【讨论】:

以上是关于终止应用程序原因:“AVPlayer 的实例无法删除由 AVPlayer 的不同实例添加的时间观察者。”的主要内容,如果未能解决你的问题,请参考以下文章

由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“setObjectForKey:键不能为零”[重复]

由于未捕获的异常 NSInvalidArgumentException 而终止应用程序,原因:索引无效

由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“-[Home isEqualToString:]

由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“-[QBDDXMLElement attributeFloatValueForName:withDefa

由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“线程违规:预期主线程”

由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“无法序列化 CPDistributedMessagingCenter