美文网首页一个苹果程序员iOS Developer
iOS实录6:iOS中实现全景播放器(GVR For iOS)

iOS实录6:iOS中实现全景播放器(GVR For iOS)

作者: 南华coder | 来源:发表于2017-04-21 15:49 被阅读446次

    [这是第六篇]
    导语:VR是个比较火的话题,在iOS中集成全景和VR播放功能,是个值得考虑和去实践的idea。目前有一些能帮助我们快速实现VR的项目,其中Google提供的GVRSDK(Google VR SDK)就是非常好的代表,基于此,我们可以快速地实现一个性能不错的全景和VR播放器。(图片全景播放+视频全景播放)

    一、背景

    1、集成GVRSDK(Google VR SDK)很容易。目前最新版本是1.40.0
    2、GVRSDK提供了图片的视频的全景和VR播放接口。
    3、全景图片和全景视频功能可以支持手势处理和重力感应,支持VR播放
    4、在Google VR SDK的基础上做了简单的定制,可以达到较好的效果。

    二、项目中集成GVRSDK

    1、Podfile文件中定义
    target 'QSUseGoogleVRDemo' do
        #google VR SDK
      pod 'GVRSDK'
    end
    
    2、安装GVRSDK
    pod install
    
    • 通过pod install安装慢的话,建议使用 pod install --verbose --no-repo-update

    • GVRSDK下载下来的库比较大,约320MB。获取GVRSDK库的同时,还会获取与其相关的GoogleToolboxForMac和GTMSessionFetcher

    • 遇到的问题:pod install安装下来的不是最新版本,是1.0.1版本。原因是:Cocoapods的全局缓存带来的,好的解决办法是,删除全局的缓存。使用命令如下:

      sudo rm -fr ~/Library/Caches/CocoaPodssudo 
      rm -fr ~/.cocoapods/repos/master
      

    三、全景图片的播放器

    1、GVRSDK提供的
    • GVRSDK提供全景图片播放的类是GVRPanoramaView,它支持两个load接口,分别如下:

        - (void)loadImage:(UIImage *)image;
      
        - (void)loadImage:(UIImage *)image ofType:(GVRPanoramaImageType)imageType;
      

    接口分析:

    1. 从接口中可以看出,并不可以直接load线上的图片资源,所以在使用这两个接口之前,需要先从网络上下载图片资源。

    2. 枚举类型GVRPanoramaImageType的有两个可选值。 kGVRPanoramaImageTypeMono和 kGVRPanoramaImageTypeStereoOverUnder,前者指定单个图像源图像,后者指有上下两部分图像源的图像,上半部分对应左眼,下半部对应右眼。demo中使用的是kGVRPanoramaImageTypeMono,这也是loadImage: 接口默认使用的参数值。

    3. GVRPanoramaView的父类GVRWidgetView,从GVRWidgetView头文件中看出,它可以允许操作某些属性,如在View上是否显示信息按钮、跳转VR的按钮等,如展示模式(嵌入父View/全景/全景+VR/)。我们根据需要在GVRPanoramaView的子类中设置好值。GVRWidgetView还提供代理,可以帮我开发者去了解GVRPanoramaView的load情况(如load成功或失败)。

    2、我们可以做的

    1)实现图片的下载。使用SDWebImage库提供的下载器去下载图片是个不错的办法。但是我选择了自己实现一个下载器QSDownloadManager。目的是不仅仅支持图片的下载,还支持视频等资源的下载,此外QSDownloadManager还支持多任务下载,下载进度的监听,暂停下载 以及 资源的断点下载,支持文件的缓存和清理。QSDownloadManager的接口定义如下:

    @interface QSDownloadManager : NSObject
    
    + (instancetype)sharedInstance;
    
    /**
     *  下载
     */
    - (void)download:(NSString *)url progress:(QSDownloadProgressBlock)progressBlock completedBlock:(QSDownloadCompletedBlock)completedBlock;
    
    /**
     *  取消下载
     */
    - (void)cancelDownLoad:(NSString *)url;
    
    
    #pragma mark - 缓存文件的大小 & 删除
    /**
     所有缓存资源大小
     */
    - (NSString *)getAllCacheFileSizeString;
    
    /**
     *  删除url对应的资源 && 如果url对应的资源还在下载,立即取消下载并清除对应未下载完整的资源
     */
    - (void)deleteFileCache:(NSString *)url;
    
    /**
     *  清空所有下载资源
     */
    - (void)deleteAllFileCache;
    
    @end
    

    2)增加placeholderView和ProgressHUD
    这是为了在图片播放前(图片下载过程 和 图片加载到GVRSDK),让用户等待不再那么无聊。此外,为了简单起见,placeholderView只是一张纯白的View。

    3、图片的全景播放QSPanoramaView

    1)QSPanoramaView继承GVRPanoramaView,实现GVRWidgetViewDelegate协议方法。对外提供两个接口

    //GVRPanoramaView.h
    /**
     全景图片播放 + VR
     */
    @interface QSPanoramaView : GVRPanoramaView
    /**
    加载线上图片
    */
    - (void)loadImageUrl:(NSURL *)imageUrl;
    
    - (void)loadImageUrl:(NSURL *)imageUrl ofType:(GVRPanoramaImageType)imageType;
    
    @end
    

    说明:你也可以直接使用GVRPanoramaView的接口。

    2)主要代码实现

    - (void)loadImageUrl:(NSURL *)imageUrl{
    
        [self loadImageUrl:imageUrl ofType:kGVRPanoramaImageTypeMono];
    }
    
    - (void)loadImageUrl:(NSURL *)imageUrl ofType:(GVRPanoramaImageType)imageType{
    
        if ([self.imageUrl isEqual:imageUrl]) {
            return;
        }
    
        [self cancelCurrentDownLoad];
        self.imageUrl = imageUrl;
        self.placeholderView.alpha = 1.0f; //遮盖
        [MBProgressHUD showHUDWithContent:@"图片加载中..." toView:self];
    
        [[QSDownloadManager sharedInstance]download:[imageUrl absoluteString]
                                       progress:^(CGFloat progress, NSString *speed, NSString *remainingTime) {
    
                                           NSLog(@"当前下载进度:%.2lf%%,当前的下载速度是:%@,还需要时间:%@,",progress * 100,speed,remainingTime);
        
                                       } completedBlock:^(NSString *fileCacheFile) {
                                           
                                           NSData *imageData = [NSData dataWithContentsOfFile:fileCacheFile];
                                           UIImage *image = [UIImage imageWithData:imageData];
                                           [self loadImage:image ofType:imageType];
                                       }];
    }
    
    4、效果图

    1)图片load过程,显示ProgressHUD

    图片load过程.png

    2)图片全景播放中,支持重力感应和手势控制,调整视角,实现360度全方位的观看。右下角是VR播放的按钮

    图片全景播放.png

    3)VR播放的效果,分屏,支持360度全方位的观看

    图片VR播放.png

    四、全景视频的播放器

    1、GVRSDK提供的
    • GVRSDK提供全景图片播放的类是GVRVideoView,它支持load和对视频源播放、暂停和停止的控制,接口分别如下:

       - (void)loadFromUrl:(NSURL*)videoUrl;
      
       - (void)loadFromUrl:(NSURL*)videoUrl ofType:(GVRVideoType)videoType;
      
       - (void)pause;
      
       - (void)play;
      
       - (void)stop;
      

    接口分析:

    1. loadFromUrl:中的参数videoUrl,不仅可以是线上的视频源的URL,还可以是本地的视频资源的URL,比GVRPanoramaView的load接口更强大些。我们可以不用去操心下载视频的问题的。(QSDownloadManager完全能驾驭下载视频,但是这里不需要了)

    2. 枚举类型GVRVideoType的有三个可选值。 kGVRVideoTypeMono、 kGVRVideoTypeStereoOverUnder 和 kGVRVideoTypeSphericalV2,kGVRVideoTypeMono代表单个视频源的视频,kGVRVideoTypeStereoOverUnder是有上下两部分视频源的视频,kGVRVideoTypeSphericalV2代表是球形视频源的视频。

    3. GVRVideoView的也是父类GVRWidgetView。

    4. GVRVideoView还提供GVRVideoViewDelegate,代理中方法可以获得视频的播放进度。

    2、我们能做的

    1)增加placeholderView、ProgressHUD、播放进度条和播放按钮

    • 在视频加载的同时,使用placeholderView和ProgressHUD遮盖其上,告诉用户app在干啥,加载结束后,默认不立即play,出现play按钮,点击play按钮,视频才真正的去play。播放进度条显示视频播放的进度。

    2)处理耳机的插入和拔出

    • 增加耳机的处理,是因为耳机的插入和拔出会影响视频的播放,提供简单的UI处理,如拔出耳机,视频暂停,出现play按钮,点击按钮,继续播放,这些简单的UI使用户操作更加自然。
    3、视频的全景播放QSVideoView#####

    1)QSVideoView继承GVRVideoView,实现GVRWidgetViewDelegate和GVRVideoViewDelegate协议方法。对外提供两个接口

    //QSVideoView
    @interface QSVideoView : GVRVideoView
    
    /**
     加载线上视频
     */
    - (void)loadFromOnlineUrl:(NSURL*)videoUrl;
    
    - (void)loadFromOnlineUrl:(NSURL *)videoUrl ofType:(GVRVideoType)videoType;
    
    @end
    

    2)主要代码实现

    - (void)loadFromOnlineUrl:(NSURL*)videoUrl{
    
        [self loadFromOnlineUrl:videoUrl ofType:kGVRVideoTypeMono];
    }
    
    - (void)loadFromOnlineUrl:(NSURL *)videoUrl ofType:(GVRVideoType)videoType{
    
        if ([self.videoUrl isEqual:videoUrl]) {
            return;
        }
    
        self.videoUrl = videoUrl;
    
        [MBProgressHUD showHUDWithContent:@"视频加载中..." toView:self];
        self.placeholderView.alpha = 1.0f;  //遮盖
        [self loadFromUrl:videoUrl ofType:videoType];
    }
    
    #pragma mark - GVRVideoViewDelegate & GVRWidgetViewDelegate
    - (void)widgetView:(GVRWidgetView *)widgetView didLoadContent:(id)content {
    
        NSLog(@"视频加载结束...");
        [UIView animateWithDuration:1.0 delay:1.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        
            self.placeholderView.alpha = 0;
        
        } completion:^(BOOL finished) {
        
            [_placeholderView removeFromSuperview];
            _placeholderView = nil;
            [MBProgressHUD hideHUDInView:self];
           [self seekTo:0];
        }];
    }
    
    - (void)widgetView:(GVRWidgetView *)widgetView didFailToLoadContent:(id)content withErrorMessage:(NSString *)errorMessage {
        NSLog(@"Failed to load video: %@", errorMessage);
    
        [MBProgressHUD hideHUDInView:self];
        UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"⚠️"  message:@"视频加载失败..." delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
        [alertView show];
    }
    
    
    - (void)videoView:(GVRVideoView*)videoView didUpdatePosition:(NSTimeInterval)position {
        CGFloat progress = position / videoView.duration;
        NSLog(@"播放进度: %lf", progress);
        BOOL isAnimation = (progress == 0);
        [self.playProgressView setProgress:progress animated:isAnimation];
    }
    
    - (void)audioRouteChangeListenerCallback:(NSNotification*)notification{
    
        NSDictionary *interuptionDict = notification.userInfo;
        NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
        switch (routeChangeReason) {
           case AVAudioSessionRouteChangeReasonNewDeviceAvailable:{
                NSLog(@"耳机插入");
                [self play];
                [self updateUI];
            }
                break;
            case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:{
                NSLog(@"耳机拔出,停止播放操作");
                [self pause];
                [self updateUI];
            }
                break;
            case AVAudioSessionRouteChangeReasonCategoryChange:
                // called at start - also when other audio wants to play
                NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
                break;
        }
    }
    
    4、效果图#####

    1) 视频加载过程中

    视频加载中.png

    2) 视频加载成功后,出现play按钮

    加载成功后.png

    3) 点击play按钮,播放线上视频,NavigationBar下面是细长的播放进度条,底部是切换到VR的按钮

    视频播放中.png

    4) 视频的VR播放

    视频VR.png

    五、总结

    • GVR For IOS 可以帮助普通的开发者们,在iOS上快速实现全景视频和图片的播放,这篇文章只是记录了在了解GVR的同时,对它做的一点点优化工作。
    • 在优化工作成功中,最复杂的工作莫过于为优化编写的下载器,虽然没有派上太大用场,但是希望有机会在真正的生产环境中去使用,去发现问题,去完善。
    • PanoramaMD360Player4iOS也是不错的库

    具体的代码实现见源码QSUseGoogleVRDemo

    相关文章

      网友评论

      本文标题:iOS实录6:iOS中实现全景播放器(GVR For iOS)

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