SceneKIt+ AVFoundation 打造VR播放器(2)

下面是我寫的播放器

SceneKIt+ AVFoundation 打造VR播放器(1)
支持VR,全景,視頻縮放,本地,網絡視頻播放,實時獲取視頻幀,獲取播放時間,獲取緩存時間,播放,暫停

2017-06-22 17_47_06.gif

要想完成一個Vr播放器,需要完成兩個功能

1、寫一個可以實時獲取視頻幀的播放器
2、寫一個可以渲染每一幀圖片為全景圖片的view

SCN3DPlayerView 圖片渲染

用于渲染每一幀圖片為全景圖片
使用的是 <UIKit/UIKit.h><SceneKit/SceneKit.h> <GLKit/GLKit.h>

下面是SCN3DPlayerView的一些方法

//
//  SCN3DPlayerView.h
//  SCN3DPlayer
//
//  Created by 俞濤濤 on 16/11/11.
//  Copyright ? 2016年 俞濤濤. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <SceneKit/SceneKit.h>
#import <GLKit/GLKit.h>


/////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 觸摸,重力,觸摸重力

 - SCN3DInteractive_Touch: 觸摸
 - SCN3DInteractive_Motion: 重力
 - SCN3DInteractive_MotionAndTouch: 觸摸和重力
 */
typedef NS_ENUM(NSInteger, SCN3DInteractive_) {
    SCN3DInteractive_Touch,
    SCN3DInteractive_Motion,
    SCN3DInteractive_MotionAndTouch,
};

/**
 形狀模式枚舉

 - SCN3DDisplayMode_Plane_Normal: 普通模式
 - SCN3DDisplayMode_Plane_Slide: 平面180模式
 - SCN3DDisplayMode_Tube: 圓柱模式
 - SCN3DDisplayMode_Sphere: 球模式
 - SCN3DDisplayMode_VR360: 全景模式
 - SCN3DDisplayMode_VRGlass: VR雙屏模式
 */
typedef NS_ENUM(NSUInteger, SCN3DDisplayMode_) {
    SCN3DDisplayMode_Plane_Normal = 0,
    SCN3DDisplayMode_Plane_Slide,
    SCN3DDisplayMode_Tube,
    SCN3DDisplayMode_Sphere,
    SCN3DDisplayMode_VR360,
    SCN3DDisplayMode_VRGlass,
};

/////////////////////////////////////////////////////////////////////////////////////////////////////////

@interface SCN3DPlayerView : UIView

@property (nonatomic, strong) SCNScene *scene;
@property (nonatomic, strong) SCNView  *scViewLeft;
@property (nonatomic, strong) SCNView  *scViewRight;
@property (nonatomic, strong) SCNNode  *shapeNode;
@property (nonatomic, strong) SCNNode  *cameraNode;

//_________________________________________________________________________________________________

/**
 設置使用重力,還是手指觸摸,或者兩者都持

 @param interactive SCN3DInteractive_ 枚舉參數
 */
- (void)setInteractiveMode:(SCN3DInteractive_)interactive;

/**
 設置sceneKit 渲染模式,有Vr ,全景,等等模式

 @param displayMode SCN3DDisplayMode_ 枚舉參數
 */
- (void)setVideoDisplayMode:(SCN3DDisplayMode_)displayMode;

/**
 是否,水平啟用,垂直啟用

 @param horEnabled Yes or no
 @param verEnabled yes or no
 */
- (void)setHorizontalEnabled:(BOOL)horEnabled verticalEnabled:(BOOL)verEnabled;

//是否開啟重力傳感器
- (void)setGSensorMotionEnabled:(BOOL)GSensorEnabled;
//是否開啟縮放功能
- (void)setPinchScaleEnabled:(BOOL)pinchEnabled;
//設置縮放范圍
- (void)setMinScale:(float)minScale maxScale:(float)maxScale;
//設置旋轉范圍
- (void)setVerticalMinRotate:(float)minRotate maxRotate:(float)maxRotate;
//設置紋理坐標平移
- (void)setTextureOffsetX:(float)x offsetY:(float)y;
//設置紋理坐標縮放
- (void)setTextureScaleX:(float)x ScaleY:(float)y;

//_________________________________________________________________________________________________
//設置寬高比
- (void)setVideoAspectRatio:(float)aspectRatio;
//設置當前橫屏或者豎屏
- (void)setCurrentOrientation:(UIInterfaceOrientation)orientation;
//設置當前縮放比例
- (void)setCurrentScale:(float)curScale;
//設置當前旋轉角度
- (void)setCurrentRotateX:(float)rotateX rotateY:(float)rotateY;

//_________________________________________________________________________________________________
//設置幀視頻圖像
- (void)setFramesPerVideoImage:(UIImage *)image;

@end


下面是一些重要的方法

下面是一些枚舉的定義

/**
 觸摸,重力,觸摸重力

 - SCN3DInteractive_Touch: 觸摸
 - SCN3DInteractive_Motion: 重力
 - SCN3DInteractive_MotionAndTouch: 觸摸和重力
 */
typedef NS_ENUM(NSInteger, SCN3DInteractive_) {
    SCN3DInteractive_Touch,
    SCN3DInteractive_Motion,
    SCN3DInteractive_MotionAndTouch,
};

/**
 形狀模式枚舉

 - SCN3DDisplayMode_Plane_Normal: 普通模式
 - SCN3DDisplayMode_Plane_Slide: 平面180模式
 - SCN3DDisplayMode_Tube: 圓柱模式
 - SCN3DDisplayMode_Sphere: 球模式
 - SCN3DDisplayMode_VR360: 全景模式
 - SCN3DDisplayMode_VRGlass: VR雙屏模式
 */
typedef NS_ENUM(NSUInteger, SCN3DDisplayMode_) {
    SCN3DDisplayMode_Plane_Normal = 0,
    SCN3DDisplayMode_Plane_Slide,
    SCN3DDisplayMode_Tube,
    SCN3DDisplayMode_Sphere,
    SCN3DDisplayMode_VR360,
    SCN3DDisplayMode_VRGlass,
};

初始化SCNScene,SCNView

- (void)initScene {
    self.scene  = [SCNScene scene];
    self.scViewLeft = [[SCNView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) options:nil];
    self.scViewLeft.backgroundColor = [UIColor blackColor];
    self.scViewLeft.scene = self.scene;
    self.scViewLeft.antialiasingMode = SCNAntialiasingModeMultisampling4X;
    [self addSubview:self.scViewLeft];
    
    self.cameraNode = [SCNNode node];
    self.cameraNode.camera = [SCNCamera camera];
    [self.scene.rootNode addChildNode:self.cameraNode];
    
    UIPinchGestureRecognizer *pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchScale:)];
    [self addGestureRecognizer:pinchGestureRecognizer];
}

根據不同的模式,設置相應的參數

- (void)initSceneNodeWithMode:(SCN3DDisplayMode_)displayMode {
    [self.shapeNode   removeFromParentNode];
    [self.scViewRight removeFromSuperview];
    [self initParameterSetting];
    switch (displayMode) {
        case SCN3DDisplayMode_Plane_Normal: {
            float width  = 2.0;
            float height = width / self.videoAspRatio;
            self.shapeNode = [SCNNode nodeWithGeometry:[SCNPlane planeWithWidth:width height:height]];
            self.shapeNode.geometry.firstMaterial.diffuse.wrapT = SCNWrapModeClamp;
            self.shapeNode.geometry.firstMaterial.diffuse.wrapS = SCNWrapModeClamp;
            self.shapeNode.geometry.firstMaterial.cullMode = SCNCullBack;
            self.cameraNode.position = SCNVector3Make(0, 0, 1.5);
            self.cameraNode.camera.usesOrthographicProjection = YES;
        }
            break;
        case SCN3DDisplayMode_Plane_Slide: {
            float width  = 2.0;
            float height = width / self.videoAspRatio;
            self.shapeNode = [SCNNode nodeWithGeometry:[SCNPlane planeWithWidth:width height:height]];
            self.shapeNode.geometry.firstMaterial.diffuse.wrapT = SCNWrapModeRepeat;
            self.shapeNode.geometry.firstMaterial.diffuse.wrapS = SCNWrapModeRepeat;
            self.shapeNode.geometry.firstMaterial.cullMode = SCNCullBack;
            self.cameraNode.position = SCNVector3Make(0, 0, 1.5);
            self.cameraNode.camera.usesOrthographicProjection = YES;
            
        }
            break;
        case SCN3DDisplayMode_Tube: {
            SCNTube *tube = [SCNTube tubeWithInnerRadius:1.0 outerRadius:1.0 height:1.0];
            tube.radialSegmentCount = 96;
            self.shapeNode = [SCNNode nodeWithGeometry:tube];
            self.shapeNode.geometry.firstMaterial.diffuse.wrapT = SCNWrapModeClamp;
            self.shapeNode.geometry.firstMaterial.diffuse.wrapS = SCNWrapModeClamp;
            self.shapeNode.geometry.firstMaterial.cullMode = SCNCullBack;
            
            self.cameraNode.position = SCNVector3Make(0, 0, 1.5);
            self.cameraNode.camera.usesOrthographicProjection = YES;
        }
            break;
        case SCN3DDisplayMode_Sphere: {
            
            SCNSphere *sphere = [SCNSphere sphereWithRadius:1.0];
            sphere.segmentCount = 96;
            self.shapeNode = [SCNNode nodeWithGeometry:sphere];
            self.shapeNode.geometry.firstMaterial.diffuse.wrapT = SCNWrapModeClamp;
            self.shapeNode.geometry.firstMaterial.diffuse.wrapS = SCNWrapModeClamp;
            self.shapeNode.geometry.firstMaterial.cullMode = SCNCullBack;// 設置剔除內表面
            self.cameraNode.position = SCNVector3Make(0, 0, 1.5);
            self.cameraNode.camera.usesOrthographicProjection = YES;
        }
            break;
        case SCN3DDisplayMode_VR360: {
  
            SCNSphere *sphere = [SCNSphere sphereWithRadius:1.0];
            sphere.segmentCount = 96;
            self.shapeNode = [SCNNode nodeWithGeometry:sphere];
            self.shapeNode.geometry.firstMaterial.diffuse.wrapT = SCNWrapModeClamp;
            self.shapeNode.geometry.firstMaterial.diffuse.wrapS = SCNWrapModeClamp;
            self.shapeNode.geometry.firstMaterial.cullMode = SCNCullFront;
            self.shapeNode.geometry.firstMaterial.doubleSided = false; // 設置只渲染一個表面
            self.cameraNode.position = SCNVector3Make(0, 0, 1.5);
            self.cameraNode.camera.usesOrthographicProjection = NO;
            
//            SCNMatrix4 contentsTransform = self.shapeNode.geometry.firstMaterial.diffuse.contentsTransform;
////            SCNMatrix4 contentsTransform2 = self.shapeNode.transform;
//       
//            contentsTransform =SCNMatrix4Rotate(contentsTransform, 0, M_PI, 0, 0);
//            self.shapeNode.transform = self.shapeNode.geometry.firstMaterial.diffuse.contentsTransform;
     
            
 
        }
            break;
        case SCN3DDisplayMode_VRGlass: {
            SCNSphere *sphere = [SCNSphere sphereWithRadius:1.0];
            sphere.segmentCount = 96;
            self.shapeNode = [SCNNode nodeWithGeometry:sphere];
            self.shapeNode.geometry.firstMaterial.diffuse.wrapT = SCNWrapModeClamp;
            self.shapeNode.geometry.firstMaterial.diffuse.wrapS = SCNWrapModeClamp;
            self.shapeNode.geometry.firstMaterial.cullMode = SCNCullFront;// 設置剔除外表面
            self.shapeNode.geometry.firstMaterial.doubleSided = false; // 設置只渲染一個表面
//            self.cameraNode.position = SCNVector3Make(0, 0, 1.5);
            self.cameraNode.camera.usesOrthographicProjection = NO;
            
            self.scViewLeft.frame  = CGRectMake(0, 0, self.frame.size.width / 2, self.frame.size.height);
            self.scViewRight = [[SCNView alloc] initWithFrame:CGRectMake(self.frame.size.width / 2, 0, self.frame.size.width / 2, self.frame.size.height) options:nil];
            self.scViewRight.backgroundColor = [UIColor blackColor];
            self.scViewRight.scene = self.scene;
            self.scViewRight.antialiasingMode = SCNAntialiasingModeMultisampling4X;
            [self addSubview:self.scViewRight];
        }
            break;
            
        default:
            break;
    }
    
    self.cameraNode.camera.zNear = 0.01f;
    self.cameraNode.camera.zFar  = 100.0f;
    self.shapeNode.castsShadow = NO;
    self.shapeNode.position = SCNVector3Make(0, 0, 0);
    [self.scene.rootNode addChildNode:self.shapeNode];
    
    self.modelMatrix = self.shapeNode.transform;
    [self setCurrentScale:1.0];
    [self setFramesPerVideoImage:nil];
}

手指滑動,捏合的相關處理

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint currLoc = [touch locationInView:self];
    CGPoint lastLoc = [touch previousLocationInView:self];
    CGPoint moveDiff = CGPointMake(currLoc.x - lastLoc.x, currLoc.y - lastLoc.y);
    
    float rotX = -1 * GLKMathDegreesToRadians(moveDiff.y / 5.0);
    float rotY = -1 * GLKMathDegreesToRadians(moveDiff.x / 5.0);
    
    rotX = self.verticalEnabled   ? rotX : 0;
    rotY = self.horizontalEnabled ? rotY : 0;
    [self changeNodeTransformWithRotateX:rotX / self.curScale rotateY:rotY / self.curScale];
}

- (void)changeNodeTransformWithRotateX:(float)rotX rotateY:(float)rotY {
    switch (self.displayMode) {
        case SCN3DDisplayMode_Plane_Normal: {}
            break;
        case SCN3DDisplayMode_Plane_Slide: {
            SCNMatrix4 contentsTransform = self.shapeNode.geometry.firstMaterial.diffuse.contentsTransform;
            contentsTransform = SCNMatrix4Translate(contentsTransform, rotY, 0, 0);
            contentsTransform = SCNMatrix4Translate(contentsTransform, 0, rotX, 0);
            self.shapeNode.geometry.firstMaterial.diffuse.contentsTransform = contentsTransform;
        }
            break;
        case SCN3DDisplayMode_Tube: {
            self.rotateX += rotX;
            self.rotateY += rotY;
            float minRotate = self.minRotateX / self.curScale;
            float maxRotate = self.maxRotateX / self.curScale;
            
            minRotate = minRotate < (- M_PI / 2) ? (- M_PI / 2) : minRotate;
            maxRotate = maxRotate > (+ M_PI / 2) ? (+ M_PI / 2) : maxRotate;
            self.rotateX = self.rotateX < minRotate ? minRotate : self.rotateX;
            self.rotateX = self.rotateX > maxRotate ? maxRotate : self.rotateX;
            
            SCNMatrix4 modelViewMatrix = SCNMatrix4Identity;
            modelViewMatrix = SCNMatrix4Rotate(modelViewMatrix, -self.rotateY, 0, 1, 0);
            modelViewMatrix = SCNMatrix4Rotate(modelViewMatrix, -self.rotateX, 1, 0, 0);
            modelViewMatrix = SCNMatrix4Scale(modelViewMatrix, self.curScale, self.curScale, self.curScale);
            self.shapeNode.transform = modelViewMatrix;
        }
            break;
        case SCN3DDisplayMode_Sphere: {
            self.rotateX += rotX;
            self.rotateY += rotY;
            float minRotate = self.minRotateX / self.curScale;
            float maxRotate = self.maxRotateX / self.curScale;
            
            minRotate = minRotate < (- M_PI / 2) ? (- M_PI / 2) : minRotate;
            maxRotate = maxRotate > (+ M_PI / 2) ? (+ M_PI / 2) : maxRotate;
            self.rotateX = self.rotateX < minRotate ? minRotate : self.rotateX;
            self.rotateX = self.rotateX > maxRotate ? maxRotate : self.rotateX;
            
            SCNMatrix4 modelViewMatrix = SCNMatrix4Identity;
            modelViewMatrix = SCNMatrix4Rotate(modelViewMatrix, -self.rotateY, 0, 1, 0);
            modelViewMatrix = SCNMatrix4Rotate(modelViewMatrix, -self.rotateX, 1, 0, 0);
            modelViewMatrix = SCNMatrix4Scale(modelViewMatrix, self.curScale, self.curScale, self.curScale);
            self.shapeNode.transform = modelViewMatrix;
        }
            break;
            
        default: {
            self.rotateX += -rotX;
            self.rotateY += -rotY;
            float minRotate = self.minRotateX / self.curScale;
            float maxRotate = self.maxRotateX / self.curScale;
            
            minRotate = minRotate < (- M_PI / 2) ? (- M_PI / 2) : minRotate;
            maxRotate = maxRotate > (+ M_PI / 2) ? (+ M_PI / 2) : maxRotate;
            self.rotateX = self.rotateX < minRotate ? minRotate : self.rotateX;
            self.rotateX = self.rotateX > maxRotate ? maxRotate : self.rotateX;
//            NSLog(@"self.rotateX = %f, %f", self.rotateX, GLKMathRadiansToDegrees(self.rotateX));
            
            SCNMatrix4 modelViewMatrix = SCNMatrix4Identity;
            modelViewMatrix = SCNMatrix4Rotate(modelViewMatrix, -self.rotateY, 0, 1, 0);
            modelViewMatrix = SCNMatrix4Rotate(modelViewMatrix, -self.rotateX, 1, 0, 0);
            modelViewMatrix = SCNMatrix4Scale(modelViewMatrix, self.curScale, self.curScale, self.curScale);
            self.shapeNode.transform = modelViewMatrix;
        }
            break;
    }
}

- (void)handlePinchScale:(UIPinchGestureRecognizer *)paramSender {
    if (!self.pinchEnabled) return;
    if (paramSender.state != UIGestureRecognizerStateEnded && paramSender.state != UIGestureRecognizerStateFailed) {
        if (paramSender.scale != NAN && paramSender.scale != 0.0) {
            
            float scale = (paramSender.scale - 1) * 0.50;
            self.curScale = scale + self.prevScale;
            
            if (self.curScale < self.minScale) {
                self.curScale = self.minScale;
            }
            else if (self.curScale > self.maxScale) {
                self.curScale = self.maxScale;
            }
            
            [self setCurrentScale:self.curScale];
        }
    } else if(paramSender.state == UIGestureRecognizerStateEnded) {
        self.prevScale = self.curScale;
    }
}

重力感應相關設置

- (void)startGSENSORMotion {
    float gFPS = 30.0f;
    if (self.displayMode == SCN3DDisplayMode_VRGlass) {
        gFPS = 120.0f;
    }
    self.motionManager = [[CMMotionManager alloc] init];
    self.motionManager.deviceMotionUpdateInterval = 1.0f / gFPS;
    self.motionManager.gyroUpdateInterval = 1.0f / gFPS;
    self.motionManager.showsDeviceMovementDisplay = YES;
    self.rotateX = 0.0f;
    self.rotateY = 0.0f;
    
    NSOperationQueue* motionQueue = [[NSOperationQueue alloc] init];
    [self.motionManager startDeviceMotionUpdatesToQueue:motionQueue withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {
        float  damping = 30.0;
        double rotateX = -motion.rotationRate.y / damping;  // X 軸旋轉
        double rotateY = -motion.rotationRate.x / damping;  // Y 軸旋轉
        
        switch (self.orientation) {
            case UIDeviceOrientationLandscapeRight:
                rotateX = +motion.rotationRate.x / damping;
                rotateY = -motion.rotationRate.y / damping;
                break;
            case UIDeviceOrientationLandscapeLeft:
                rotateX = -motion.rotationRate.x / damping;
                rotateY = +motion.rotationRate.y / damping;
                break;
            case UIDeviceOrientationPortrait:
            default:
                break;
        }
        [self changeNodeTransformWithRotateX:(rotateY / 2) / self.curScale rotateY:rotateX / self.curScale];
    }];
}

- (void)stopGSENSORMotion {
    [self.motionManager stopDeviceMotionUpdates];
    self.motionManager = nil;
}

還有一些相關設置,這里就不介紹了,大家可以自己看,在.h文件里面都有相關注釋,如有錯誤,請指正,謝謝
源代碼
如果喜歡的話,就star一下

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,548評論 25 708
  • 下面是我寫的播放器 支持VR,全景,視頻縮放,本地,網絡視頻播放,實時獲取視頻幀,獲取播放時間,獲取緩存時間,播放...
    hhjdk閱讀 1,032評論 0 1
  • 上趕著不是買賣!??! 我有一個愿望就是好好的睡一覺,安靜的看一會兒書……最近真的太累了,一邊是工作上的...
    親愛的小魚老師閱讀 388評論 0 2