SCNMaterialProperty's contents property on SCNMaterial is unable to render when assigned a AVPlayerLayer. Note this is only a issue on a physical device, works fine on the simulator (Xcode 6.0.1).
I am creating my SCNode as such:
SCNNode *videoBall = [SCNNode node];
videoBall.position = SCNVector3Make(-5, 5, -18);
videoBall.geometry = [SCNSphere sphereWithRadius:5];
videoBall.geometry.firstMaterial.locksAmbientWithDiffuse = YES;
videoBall.geometry.firstMaterial.diffuse.contents = [self videoLayer];
videoBall.geometry.firstMaterial.diffuse.contentsTransform = SCNMatrix4MakeScale(2, 1, 1);
videoBall.geometry.firstMaterial.diffuse.wrapS = SCNWrapModeMirror;
[[scene rootNode] addChildNode:videoBall];
I am creating the video layer as such:
- (AVPlayerLayer *)videoLayer {
if (_videoLayer == nil) {
AVPlayer *player = [AVPlayer playerWithURL:[NSURL URLWithString:#"http://devstreaming.apple.com/videos/wwdc/2014/609xxkxq1v95fju/609/609_sd_whats_new_in_scenekit.mov"]];
_videoLayer = [AVPlayerLayer layer];
[_videoLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
_videoLayer.frame = CGRectMake(0, 0, 1000, 1000);
_videoLayer.player = player;
[player play];
}
return _videoLayer;
}
So this works fine in the simulator. However on a iPhone 6 running iOS 8.0.2, I can hear the audio but the node is invisible.
Is this a bug or am I doing something wrong?
Related
I have a problem with Xcode 7.0 (7A220) and iOS 9.0. I add a video (.mp4 or mov, both not working) with an AVPlayer and SKVideoNode. On the simulator with iOS 8.4 it's working fine, but on iOS 9.0 (also simulator) the video is not showing. I can hear the audio though.
The code in my GameScene (of class SKScene):
-(void)didMoveToView:(SKView *)view {
NSURL *fileURL = [NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource:#"VideoName" ofType:#"mp4"]];
_player = [AVPlayer playerWithURL: fileURL];
_videoNode = [[SKVideoNode alloc] initWithAVPlayer:_player];
_videoNode.size = CGSizeMake(200, 100);
_videoNode.position = CGPointMake(200, 200);
_videoNode.zPosition = 1000;
[self addChild:_videoNode];
_player.volume = 0.5;
[_videoNode play];
}
In the UIViewController I have this:
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
/* Sprite Kit applies additional optimizations to improve rendering performance */
// I have set this to NO, but it doesn't help
skView.ignoresSiblingOrder = NO;
// Create and configure the scene.
CGRect rect = [[UIScreen mainScreen] bounds];
GameScene *videoScene = [[GameScene alloc] initWithSize:rect.size];
videoScene.scaleMode = SKSceneScaleModeAspectFill;
GameScene *scene = [GameScene unarchiveFromFile:#"GameScene"];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:videoScene];
}
So the app is not crashing, the video is playing (I can hear it), but the video is invisible on iOS 9 only. Does someone have an idea to fix it?
I am developing an application that include functionality to play video with per-frame animation.
You can see an example of such functionality.
I already tried to add CAKeyFrameAnimation to sublayer of AVSynchronizedLayer and have some troubles with it.
I also already tried to pre-render video with AVAssetExportSession, and it is working perfectly. But it's very slow. It needa up to 3 minutes to render such video.
Maybe there are other approaches to make it?
Update:
This is how I implement animation with AVSynchronizedLayer:
let fullScreenAnimationLayer = CALayer()
fullScreenAnimationLayer.frame = videoRect
fullScreenAnimationLayer.geometryFlipped = true
values: [NSValue] = [], times: [NSNumber] = []
// fill values array with positions of face center for each frame
values.append(NSValue(CATransform3D: t))
// fill times with corresoinding time for each frame
times.append(NSNumber(double: (Double(j) / fps) / videoDuration)) // where fps = 25 (according to video file fps)
...
let transform = CAKeyframeAnimation(keyPath: "transform")
transform.duration = videoDuration
transform.calculationMode = kCAAnimationDiscrete
transform.beginTime = AVCoreAnimationBeginTimeAtZero
transform.values = values
transform.keyTimes = times
transform.removedOnCompletion = false
transform.fillMode = kCAFillModeForwards
fullScreenAnimationLayer.addAnimation(transform, forKey: "a_transform")
...
if let syncLayer = AVSynchronizedLayer(playerItem: player.currentItem) {
syncLayer.frame = CGRect(origin: CGPointZero, size: videoView.bounds.size)
syncLayer.addSublayer(fullScreenAnimationLayer)
videoView.layer.addSublayer(syncLayer)
}
Here's my opinion,
add AVPlayer layer property(AVPlayerLayer class) to a sublayer to a UIView layer, then manipulate the view animation.
for example,
AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:blahURL options:nil];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:urlAsset];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame = yourFrame;
UIView *videoView = [UIView new];
[videoView addSublayer:playerLayer];
then
give animations to videoView
I want my character to move to the right by force. But it is not working on this code below. I don't know what I missed here, but the player stands still on the bottom without any movement. Any clue?
// MyScene.h
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
[self.player.physicsBody applyForce: CGVectorMake(100, 100)];
}
-(void)createSceneContents {
self.currentBackground = [Background generateBackground];
[self addChild: self.currentBackground];
self.physicsWorld.gravity = CGVectorMake(0, _gravity);
self.physicsWorld.contactDelegate = self;
Player *player = [[Player alloc]init];
player.size = CGSizeMake(80, 70);
player.position = CGPointMake([UIScreen mainScreen].applicationFrame.size.width/2, 40);
[self addChild:player];
}
//Player.h
#import "Player.h"
#import "common.h"
#implementation Player
- (instancetype)init {
self = [super initWithImageNamed:#"player.png"];
self.name = #"player";
NSLog(#"%f %f", self.size.width, self.size.height);
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(20, 70)];
self.physicsBody.dynamic = YES;
self.physicsBody.allowsRotation = NO;
self.physicsBody.affectedByGravity = YES;
self.physicsBody.collisionBitMask = ~poopCategory & groundCategory;
self.physicsBody.categoryBitMask = playerCategory;
self.physicsBody.contactTestBitMask = poopCategory;
self.zPosition = 100;
//self.anchorPoint = CGPointMake(0.5, 0);
return self;
}
It looks like you are using a property named player in the scene, but you are initializing a sprite named player but never storing it in the scene's property. NSLog self.player and you'll see it's nil.
I'm having issues making the animation use the AVPlayer time instead of the system time. the synchronized layer does not work properly and animations stay synchronized on the system time instead of the player time. I know the player do play. and if I pass CACurrentMediaTime() to the beginTime, the animation start right away as it should when not synchronized.
EDIT
I can see the red square in its final state since the beginning, which mean the animation has reach its end at the beginning because it is synchronized on the system time and not the AVPlayerItem time.
// play composition live in order to modifier
AVPlayerItem * playerItem = [AVPlayerItem playerItemWithAsset:composition];
AVPlayer * player = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerLayer * playerLayer = [AVPlayerLayer playerLayerWithPlayer:player ];
playerLayer.frame = [UIScreen mainScreen].bounds;
if (!playerItem) {
NSLog(#"playerItem empty");
}
// dummy time
playerItem.forwardPlaybackEndTime = totalDuration;
playerItem.videoComposition = videoComposition;
CALayer * aLayer = [CALayer layer];
aLayer.frame = CGRectMake(100, 100, 60, 60);
aLayer.backgroundColor = [UIColor redColor].CGColor;
aLayer.opacity = 0.f;
CAKeyframeAnimation * keyframeAnimation2 = [CAKeyframeAnimation animationWithKeyPath:#"opacity"];
keyframeAnimation2.removedOnCompletion = NO;
keyframeAnimation2.beginTime = 0.1;
keyframeAnimation2.duration = 4.0;
keyframeAnimation2.fillMode = kCAFillModeBoth;
keyframeAnimation2.keyTimes = #[#0.0, #1.0];
keyframeAnimation2.values = #[#0.f, #1.f];
NSLog(#"%f current media time", CACurrentMediaTime());
[aLayer addAnimation:keyframeAnimation2
forKey:#"opacity"];
[self.parentLayer addSublayer:aLayer];
AVSynchronizedLayer * synchronizedLayer =
[AVSynchronizedLayer synchronizedLayerWithPlayerItem:playerItem];
synchronizedLayer.frame = [UIScreen mainScreen].bounds;
[synchronizedLayer addSublayer:self.parentLayer];
[playerLayer addSublayer:synchronizedLayer];
The solution was that AVSynchronizedLayer doesn't work on the Simulator but works fine on a device.
I've set up a project as a test using AVSyncronizedLayer to move a red line (CALayer) across the screen as a movie plays.
When doing this I referenced the answer given here and have included that solution, but the animation doesn't start when the video does.
If anyone has any ideas where I'm going wrong that would be really helpful. The code is:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//create the red line sublayer
redLine = [CALayer layer];
redLine.backgroundColor = [UIColor redColor].CGColor;
redLine.frame = CGRectMake(engravingControl.frame.origin.x, engravingControl.center.y - (engravingControl.frame.size.height/2), 3, engravingControl.frame.size.height);
//create the AVplayer object
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:#"IMG_1306" withExtension:#"mov"];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame = CGRectMake(0, 0,400,300);
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
//add the subViews
[self.view.layer addSublayer:playerLayer];
[self.view.layer addSublayer:redLine];
CABasicAnimation *redLineMove;
redLineMove=[CABasicAnimation animationWithKeyPath:#"transform.translation.x"];
redLineMove.duration=10;
redLineMove.fromValue=[NSNumber numberWithFloat:engravingControl.frame.origin.x];
redLineMove.toValue=[NSNumber numberWithFloat:engravingControl.frame.size.width + engravingControl.frame.origin.x];
redLineMove.beginTime = AVCoreAnimationBeginTimeAtZero;
//create the sync layer
AVSynchronizedLayer *syncLayer = [AVSynchronizedLayer synchronizedLayerWithPlayerItem:playerItem];
[syncLayer addSublayer:redLine];
[redLine addAnimation:redLineMove forKey:#"redLineMove"];
[self.view.layer addSublayer:syncLayer];
[player play];
}
I discovered a solution to this. If you don't have the following line of code:
animation.removedOnCompletion = NO;
Then the animation never starts (I presume it is removed by CA before the movie starts to play).
Add that in, and it works as you would expect.