终止应用程序原因:“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