此處僅會提及遇到的具體問題及處理方式,相關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;
... 略
}