投屏问题备忘

此处仅会提及遇到的具体问题及处理方式,相关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 *)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 *)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;
    
   ... 略
}

你可能感兴趣的:(投屏问题备忘)