投屏問題備忘

此處僅會提及遇到的具體問題及處理方式,相關SDK及控件具體的使用方式不做介紹。
背景需求:iOS端和TV端,播放、暫停、進度調節 互控且同步
有點亂,個人備忘

AirPlay

iOS下調取AirPlay Picker 的相關控件,關于這些蘋果相關的介紹極少。
MPVolumeView

樂播投屏 【樂聯、DLNA、公網】

樂播投屏SDK


【AirPlay問題】

  • AppleTV投屏后沒有聲音。
    原因:AVPlayer muted 被設置為YES

  • 投屏視頻播放完,電視會自動斷開,此時會存在兩種令人費解的情況
    a. currentItem 釋放
    b. currentItem 未被釋放
    在此情形下,嘗試調用play 方法無效。

    原因:后續追查相關機制
    解決方案:重置 AVPlayer及相關資源,注意相關觀察者移除及釋放。
    這里有個有意思的地方是,AVPlayer 一開始并未釋放,只是處理了相關資源(item及觀察者),在(播放到視頻結尾處)重復播放的時候 前兩次都沒有問題,但是第三次開始 addPeriodicTimeObserverForInterval的block回調就不在執行了。
    最終處理方式是連同 AVPlayer 均銷毀并重新生成實例,費解。

  • 視頻播放完成后的詭異狀態變更
    在使用iOS10.0 新增的 AVPlayerTimeControlStatus做播放暫停狀態判斷時,發現正常的播放 和 暫停 都是沒有問題的。 但是在視頻播放到結尾處時,觀察者方法中 會獲得如下狀態變更:
    --> AVPlayerTimeControlStatusPaused --> AVPlayerTimeControlStatusPlaying
    這里疑惑,為什么播放結束后 還會有個播放中的狀態回調?
    為了防止該狀態對相關邏輯造成干擾,過濾了如下狀態:

//注冊結束播放通知 
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(airPlayDidPlayEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.airPlayPlayer.currentItem];

#pragma mark - AirPlay
- (void)airPlayDidPlayEnd:(NSNotification *)notification{
    self.status = LBLelinkPlayStatusCommpleted;

//   以下為錯誤嘗試    
//    [self.airPlayPlayer pause];
//    if (self.strUrlCache) {
//        //重置播放資源 AVplayer 播放完成后資源會被銷毀
//        AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL URLWithString:self.strUrlCache]];
//        AVPlayerItem *item = [[AVPlayerItem alloc] initWithAsset:asset];
//        [self.airPlayPlayer replaceCurrentItemWithPlayerItem:item];
//    }


    //replaceCurrentItemWithPlayerItem: 特別注意!!! 該方法如在非主線程使用會引起崩潰
    //邏輯變更...
   ....
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    ...相關邏輯 略
    
    if (object == self.airPlayPlayer && [keyPath isEqualToString:@"timeControlStatus"]) {
        
        if (self.status == LBLelinkPlayStatusCommpleted) {
            //播放完成狀態 過濾
            return;
        }

        if (@available(iOS 10.0, *)) {
            AVPlayerTimeControlStatus status = [[change objectForKey:NSKeyValueChangeNewKey]integerValue];
            if (status == AVPlayerTimeControlStatusPaused) {
                // do something
                SYLog(@"AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate");
                self.status = LBLelinkPlayStatusPause;
            }
            
            if (status == AVPlayerTimeControlStatusPlaying) {
                self.status = LBLelinkPlayStatusPlaying;
            }
            
        } else {
            // Fallback on earlier versions
            // ios10.0之后才能夠監聽到暫停后繼續播放的狀態,ios10.0之前監測不到這個狀態
            //但是可以監聽到開始播放的狀態 AVPlayerStatus  status監聽這個屬性。
            SYLog(@"another ....");
        }
        
        ...相關邏輯 略
        return;
    }

 ...略
    
}
  • 播放進度記錄的處理
    self.airPlayTimeObserve = [self.airPlayPlayer addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.25, 10) queue:nil usingBlock:^(CMTime time){
        @strongify(self)
        if(!self){
            return;
        }
        if(!self.isAirPlay){
            return;
        }
        if(self.airPlayPlayer.status != AVPlayerStatusReadyToPlay){
            return;
        }
        AVPlayerItem *currentItem = self.airPlayPlayer.currentItem;
        if(currentItem.duration.timescale != 0){
            NSInteger currentTime = (NSInteger)CMTimeGetSeconds([currentItem currentTime]);

            NSInteger duration  = (NSInteger)CMTimeGetSeconds([currentItem duration]);
            
            //這里相當于一個定時器,視頻播放完成后,播放進度會一直累加。
            if (currentTime > duration) {
                currentTime = duration;
            }
            //狀態過濾  播放完成及暫停狀態下,跳過處理
            if (self.status == LBLelinkPlayStatusCommpleted ||self.status == LBLelinkPlayStatusPause) {
                return;
            }
            
            self.currentTime = currentTime;
            ...略
        }
    }];

【樂播相關問題】

  • 投屏設備搜索回調 過慢或無回調
//搜索設備
- (void)searchDevices:(searchBlock)searchBlock{
    self.searchBlock = searchBlock;
    
    //如果已有搜索設備則使用之前的搜索結果先行回調 (WIFI 處于鏈接情況下)
    if (self.arrLelinkServices.count > 0 && SparkReachabilityIsWIFI) {
        self.searchBlock(self.arrLelinkServices, nil);
    }
    //搜索狀態標記,用于嘗試重啟搜索
    if (!self.isSearchStart) {
        self.isSearchStart = YES;
    }
    //啟動搜索
    [self.lelinkBrowser searchForLelinkService];
}


//停止搜索
- (void)stopSearchDevices{
    [self.lelinkBrowser stop];
    self.isSearchStart = NO;
   //重置嘗試次數
    self.intRetrySearchLimit = 0;
}

// 搜索到服務時,會調用此代理方法,將設備列表在此方法中回調出來
// 注意:如果不調用stop,則當有服務信息和狀態更新以及新服務加入網絡或服務退出網絡時,會調用此代理將新的設備列表回調出來
- (void)lelinkBrowser:(LBLelinkBrowser *)browser didFindLelinkServices:(NSArray<LBLelinkService *> *)services {
    SYLog(@"搜索到設備數 %zd", services.count);
    //本地保存 設備數組
    self.arrLelinkServices = services;
    // 更新UI
    //    ...
    self.searchBlock(services, nil);
    
    //如果搜索設備為空 則手動重啟搜索服務
    if (services.count == 0 && self.isSearchStart) {
        SYLog(@"%@",@"[Info]: 搜索服務,進行嘗試模式...");
        //設置嘗試次數 2次
        if (self.intRetrySearchLimit >= 2) {
            return;
        }
        self.intRetrySearchLimit += 1;
        //搜索 刷新
        [browser searchForLelinkService];
    }
    
}

  • 設備連接 多設備投屏連接時,防止回調錯亂
- (void)connectDeviceLinkService:(LBLelinkService *)linkService connectBlock:(connectBlock)connectBlock{
    self.connectBlock = connectBlock;

    //服務連接 檢查服務是否可用
    if (!linkService.isLelinkServiceAvailable) {
        NSLog(@"service name : %@",linkService.lelinkServiceName);
        NSString *strDomain = @"com.feng.car.ErrorDomain";
        NSString *desc = NSLocalizedString(@"lelinkServiceAvailable  state no...", @"");
        NSDictionary *userInfo = @{NSLocalizedDescriptionKey: desc};
        NSError *error = [NSError errorWithDomain:strDomain code:-20002 userInfo:userInfo];
        self.connectBlock(nil, NO, error);
        
        SYLog(@"%@",@"[Info]: 服務不可用。。。。");
        return;
    }
   
    //新增邏輯 如果播放器 存在播放資源或播放中,則停止播放
    if(self.lelinkPlayer.lelinkConnection.isConnected){
        [self.lelinkPlayer stop];
        [self.lelinkPlayer.lelinkConnection disConnect];
    }

    self.lelinkConnection.lelinkService = linkService;
    [self.lelinkConnection connect];
    
}
  • 播放狀態回調 關聯UI變更邏輯
#pragma mark - LBLelinkPlayerDelegate
// 播放錯誤代理回調,根據錯誤信息進行相關的處理
- (void)lelinkPlayer:(LBLelinkPlayer *)player onError:(NSError *)error {
    if (error) {
        SYLog(@"%@",error);
        self.castBlock(LBLelinkPlayStatusError, nil, error);
    }
}

// 播放狀態代理回調
- (void)lelinkPlayer:(LBLelinkPlayer *)player playStatus:(LBLelinkPlayStatus)playStatus {

    //4G下 斷開鏈接后 回調延遲... 過濾方法  【可能會觸發云投屏功能】
    if (!self.linkService || !self.lelinkConnection.isConnected) {
        return;
    }
    
    SYLog(@"%lu",(unsigned long)playStatus);
    self.status = playStatus;
    self.castBlock(playStatus, nil, nil);
   ...略
}

// 播放進度信息回調
- (void)lelinkPlayer:(LBLelinkPlayer *)player progressInfo:(LBLelinkProgressInfo *)progressInfo {
   
    //4G下 斷開鏈接后 回調延遲... 過濾方法
    if (!self.linkService || !self.lelinkConnection.isConnected) {
        return;
    }
    self.castBlock(self.status, progressInfo, nil);
    
    SYLog(@"current time = %ld, duration = %ld",(long)progressInfo.currentTime,(long)progressInfo.duration);
    
    //本地記錄進度
    self.currentTime = progressInfo.currentTime;
    
   ... 略
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。