美文网首页
投屏问题备忘

投屏问题备忘

作者: Saylor_Lone | 来源:发表于2019-03-15 17:51 被阅读0次

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

    相关文章

      网友评论

          本文标题:投屏问题备忘

          本文链接:https://www.haomeiwen.com/subject/pjrrmqtx.html