Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Terminating app reason: 'An instance of AVPlayer cannot remove a time observer that was added by a different instance of AVPlayer.'

I am using Apple's document demo. Here is the link for demo: AVPlayerDemo

my application is crashed after following steps:
1) Play song
2) fast forward from seek bar.
3) Click on next, 4) fast forward from seek bar.
Here is my crash Log:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'An instance of AVPlayer cannot remove a time observer that was added by a different instance of AVPlayer.'

And Here is the code for music player:

- (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;
}
like image 250
Mihir Oza Avatar asked Oct 31 '14 05:10

Mihir Oza


2 Answers

I got that error message because of the following code:

- (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!!!!
  }
}

An instance of AVPlayer cannot remove a time observer that was added by a different instance of AVPlayer

can happen any time the given time observer isn't associated with the AVPlayer. This could be either because it is associated with another AVPlayer or because it was previously removed from the AVPlayer in question.

like image 172
Heath Borders Avatar answered Oct 14 '22 03:10

Heath Borders


there are so many checks for this error. for me its time observer. check both time observer and if player.rate == 1.0

See the below:

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()
        }
    }
}
like image 6
Chandramani Avatar answered Oct 14 '22 02:10

Chandramani