投屏問(wèn)題備忘

此處僅會(huì)提及遇到的具體問(wèn)題及處理方式,相關(guān)SDK及控件具體的使用方式不做介紹。
背景需求:iOS端和TV端,播放、暫停、進(jìn)度調(diào)節(jié) 互控且同步
有點(diǎn)亂,個(gè)人備忘

AirPlay

iOS下調(diào)取AirPlay Picker 的相關(guān)控件,關(guān)于這些蘋(píng)果相關(guān)的介紹極少。
MPVolumeView

樂(lè)播投屏 【樂(lè)聯(lián)、DLNA、公網(wǎng)】

樂(lè)播投屏SDK


【AirPlay問(wèn)題】

  • AppleTV投屏后沒(méi)有聲音。
    原因:AVPlayer muted 被設(shè)置為YES

  • 投屏視頻播放完,電視會(huì)自動(dòng)斷開(kāi),此時(shí)會(huì)存在兩種令人費(fèi)解的情況
    a. currentItem 釋放
    b. currentItem 未被釋放
    在此情形下,嘗試調(diào)用play 方法無(wú)效。

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

  • 視頻播放完成后的詭異狀態(tài)變更
    在使用iOS10.0 新增的 AVPlayerTimeControlStatus做播放暫停狀態(tài)判斷時(shí),發(fā)現(xiàn)正常的播放 和 暫停 都是沒(méi)有問(wèn)題的。 但是在視頻播放到結(jié)尾處時(shí),觀(guān)察者方法中 會(huì)獲得如下?tīng)顟B(tài)變更:
    --> AVPlayerTimeControlStatusPaused --> AVPlayerTimeControlStatusPlaying
    這里疑惑,為什么播放結(jié)束后 還會(huì)有個(gè)播放中的狀態(tài)回調(diào)?
    為了防止該狀態(tài)對(duì)相關(guān)邏輯造成干擾,過(guò)濾了如下?tīng)顟B(tài):

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

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

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


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

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    ...相關(guān)邏輯 略
    
    if (object == self.airPlayPlayer && [keyPath isEqualToString:@"timeControlStatus"]) {
        
        if (self.status == LBLelinkPlayStatusCommpleted) {
            //播放完成狀態(tài) 過(guò)濾
            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之后才能夠監(jiān)聽(tīng)到暫停后繼續(xù)播放的狀態(tài),ios10.0之前監(jiān)測(cè)不到這個(gè)狀態(tài)
            //但是可以監(jiān)聽(tīng)到開(kāi)始播放的狀態(tài) AVPlayerStatus  status監(jiān)聽(tīng)這個(gè)屬性。
            SYLog(@"another ....");
        }
        
        ...相關(guān)邏輯 略
        return;
    }

 ...略
    
}
  • 播放進(jìn)度記錄的處理
    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]);
            
            //這里相當(dāng)于一個(gè)定時(shí)器,視頻播放完成后,播放進(jìn)度會(huì)一直累加。
            if (currentTime > duration) {
                currentTime = duration;
            }
            //狀態(tài)過(guò)濾  播放完成及暫停狀態(tài)下,跳過(guò)處理
            if (self.status == LBLelinkPlayStatusCommpleted ||self.status == LBLelinkPlayStatusPause) {
                return;
            }
            
            self.currentTime = currentTime;
            ...略
        }
    }];

【樂(lè)播相關(guān)問(wèn)題】

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


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

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

  • 設(shè)備連接 多設(shè)備投屏連接時(shí),防止回調(diào)錯(cuò)亂
- (void)connectDeviceLinkService:(LBLelinkService *)linkService connectBlock:(connectBlock)connectBlock{
    self.connectBlock = connectBlock;

    //服務(wù)連接 檢查服務(wù)是否可用
    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]: 服務(wù)不可用。。。。");
        return;
    }
   
    //新增邏輯 如果播放器 存在播放資源或播放中,則停止播放
    if(self.lelinkPlayer.lelinkConnection.isConnected){
        [self.lelinkPlayer stop];
        [self.lelinkPlayer.lelinkConnection disConnect];
    }

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

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

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

// 播放進(jìn)度信息回調(diào)
- (void)lelinkPlayer:(LBLelinkPlayer *)player progressInfo:(LBLelinkProgressInfo *)progressInfo {
   
    //4G下 斷開(kāi)鏈接后 回調(diào)延遲... 過(guò)濾方法
    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);
    
    //本地記錄進(jìn)度
    self.currentTime = progressInfo.currentTime;
    
   ... 略
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,481評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,241評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,939評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,697評(píng)論 6 409
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,182評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,406評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,933評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,772評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,973評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,638評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,866評(píng)論 1 285
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,644評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,953評(píng)論 2 373