下面是我寫的播放器
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一下