天天看點

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

0x00 進退兩難

在利用​

​AVPlayer​

​​播放網絡 mp3 時,因為有監聽狀态,是以移除監聽是必須的

不然會出現以下錯誤:

​​

​An instance 0x174016100 of class AVPlayerItem was deallocated while key value observers were still registered with it.​

不移除觀察者時,導緻的崩潰:

Trapped uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x174016100 of class AVPlayerItem was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x1708215a0> (
<NSKeyValueObservance 0x17484d530: Observer: 0x15c14ca00, Key path: status, Options: <New: YES, Old: YES, Prior: NO> Context: 0x0, Property: 0x174848d00>
<NSKeyValueObservance 0x17484e9a0: Observer: 0x15c14ca00, Key path: playbackLikelyToKeepUp, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x174841f20>
<NSKeyValueObservance 0x174454610: Observer: 0x15c14ca00, Key path: playbackBufferEmpty, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x174849ff0>
)'      

然而,有個蛋疼的操作出現了

當移除觀察者時

另外一個Bug出現了

​​

​Trapped uncaught exception 'NSInvalidArgumentException', reason: 'An instance of AVPlayer cannot remove a time observer that was added by a different instance of AVPlayer.'​

這移除不行!

這不移除也不行!

這要怎麼玩??

0x01 情況分析

1.播放音頻

1.1移除前一個音頻的監聽狀态(如果存在)

1.2添加新的監聽狀态

2.狀态監聽回調方法

2.1成功則播放

2.2失敗

2.2.1第1次失敗,嘗試再次播放 -> 走步驟1

2.2.2第2次失敗,移除監聽狀态

就是在步驟 ​

​2.2.2​

​,出現了進退兩難的情況

0x02 解決方案

既然移除是必須的

那就隻能走移除!

但是移除又會導緻:

​​

​Trapped uncaught exception 'NSInvalidArgumentException', reason: 'An instance of AVPlayer cannot remove a time observer that was added by a different instance of AVPlayer.'​

隻能前進

如何後退呢?

如果你能想到的話

你已經知道我要說什麼了

如果你還沒想到

繼續往下看

iOS中有個捕獲異常,防止崩潰的機制:​

​@try @catch​

@try {
  // 可能存在異常
  [self.player removeTimeObserver:self.timeObserver];
} @catch (NSException *exception) {
  // 檢視異常
  NSLog(@"RemoveObserver:%@",exception);
} @finally {
  // 去掉 finally 也可以
}      

雖然這個捕獲異常的機制

捕獲的異常有限

但這裡确實能​​

​防止​

​​應用崩潰

​​

​Nice!​

0x03 後續

2020-07-28 18:04:36

在移除監聽時,應該判斷​

​timeObserver​

​​是否存在

并在移除後​​

​置空​

- (void)removeObserver
{
    if (self.player.currentItem) {
        @try {
            [self.player.currentItem removeObserver:self forKeyPath:@"status"];
            [self.player.currentItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];
            [self.player.currentItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];
            
            [self.player replaceCurrentItemWithPlayerItem:nil];
            
            if (self.timeObserver) {
                [self.player removeTimeObserver:self.timeObserver];
                self.timeObserver = nil;
            }
            
        } @catch (NSException *exception) {
            NSLog(@"removeObserver:%@",exception);
        } @finally {
            
        }
    }
}      

就是因為前面沒置空

是以導緻後面移除時

跟​​

​AVPlayer​

​比對不上

0x04 繼續

2020-07-28 18:09:13

遇到一個加載 mp3 url 後

無法播放的Bug:

​​

​Error Domain=AVFoundationErrorDomain Code=-11819 "無法完成操作" UserInfo={NSLocalizedDescription=無法完成操作, NSLocalizedRecoverySuggestion=請稍後再試。}​

接着,是以其他的 mp3 url 資源都無法播放了

所有報錯都是這樣:

​​

​Error Domain=AVFoundationErrorDomain Code=-11819 "無法完成操作" UserInfo={NSLocalizedDescription=無法完成操作, NSLocalizedRecoverySuggestion=請稍後再試。, NSURL=https://xxx/xxx/xxx.mp3, NSUnderlyingError=0x174a51670 {Error Domain=NSOSStatusErrorDomain Code=268435459 "(null)"}}​

代碼使用了同一個​

​AVPlayer​

​​執行個體

猜測原因是

​​

​AVPlayer​

​​内部有緩存

導緻後面加載的 url 都不請求了

最終嘗試如下操作

解決了這個蛋疼的問題

在回調方法裡

​​

​- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context​

​ 針對播放失敗狀态所做的處理

if ([keyPath isEqualToString:@"status"]) {
    switch ([[change valueForKey:@"new"] integerValue]) {
        case AVPlayerItemStatusFailed: {
            NSError * error = _player.currentItem.error;
            if (error) {
                    NSInteger code = erro.code;
                    NSInteger count = erro.userInfo.count;
                    
                    if (code == -11819 &&
                        count == 2) {
                        
                        [self removeObserver];
                        _player = nil;
                    }
            }
            // 可以嘗試重新加載
            // 或者其他操作
        }
        break;
    }
}      

0x05 再續

有些音頻即使再次加載

也還是不能播放

錯誤有2種:

第一種:-11800,AVErrorUnknown,未知錯誤 T_T

​Error Domain=AVFoundationErrorDomain Code=-11800 "這項操作無法完成" UserInfo={NSLocalizedFailureReason=發生未知錯誤(-16041), NSLocalizedDescription=這項操作無法完成, NSUnderlyingError=0x2801d9a70 {Error Domain=NSOSStatusErrorDomain Code=-16041 "(null)"}}​

第二種:byte range length mismatch T_T

​Error Domain=NSURLErrorDomain Code=-1 "未知錯誤" UserInfo={NSLocalizedDescription=未知錯誤, NSUnderlyingError=0x283db2be0 {Error Domain=CoreMediaErrorDomain Code=-12939 "byte range length mismatch - should be length 16384 is length 24137" UserInfo={NSDescription=byte range length mismatch - should be length 16384 is length 24137, NSURL=http://xxx/xxx/xxx.mp3}}}​

iOS 驗證碼輸入框