5
votes

I'm using Sprite Kit and an SKVideoNode to play back a short video. When the video node is added to the scene, be that before of after the initial set up, the player flashes black for a brief second.

I have tried using an AVPlayer for the video instead of the video directly, also I've used KVO to only load the SKVideoNode when the video says it's ready to play.

Either way I get a black flash before my video starts to play.

Also there doesn't seem to be a way to add an AVPlayerLayer to the SKScene/SKView, although I don't know if that would help.

Any suggestions on what to try next would be great.

Here's the code I'm using

- (void)didMoveToView:(SKView *)view
{
    if (!self.contentCreated) {
        [self createSceneContents];
    }
}

- (void)createSceneContents
{
    NSString *resourcePath = [[NSBundle mainBundle] pathForResource:@"IntroMovie" ofType:@"mov"];
    NSURL *introVideoURL = [NSURL fileURLWithPath:resourcePath];
    self.playerItem = [AVPlayerItem playerItemWithURL:introVideoURL];
    self.player = [[AVPlayer alloc] initWithPlayerItem:self.playerItem];

    [self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];

    SKSpriteNode *playButton = [SKSpriteNode spriteNodeWithImageNamed:@"PlayButton"];
    [playButton setPosition:CGPointMake(CGRectGetMidX(self.view.frame), (CGRectGetMidY(self.view.frame) / 5) * 3)];
    [self addChild:playButton];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"status"]) {
        if ([[change objectForKey:NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) {
            NSLog(@"Change has the following %@", change);
                [self.player prerollAtRate:0 completionHandler:^(BOOL done){
                    SKVideoNode *introVideo = [SKVideoNode videoNodeWithAVPlayer:self.player];
                    [introVideo setSize:CGSizeMake(self.size.width, self.size.height)];
                    [introVideo setPosition:self.view.center];
                    [self addChild:introVideo];
                    [introVideo play];
                    [self.playerItem removeObserver:self forKeyPath:@"status"];
                }];
        }
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

Here's an alternative piece of code which just plays the video and gives the same black flash between the video being loaded and it playing.

NSString *resourcePath = [[NSBundle mainBundle] pathForResource:@"IntroMovie" ofType:@"m4v"];
NSURL *introVideoURL = [NSURL fileURLWithPath:resourcePath];
self.playerItem = [AVPlayerItem playerItemWithURL:introVideoURL];
self.player = [[AVPlayer alloc] initWithPlayerItem:self.playerItem];

SKVideoNode *introVideo = [SKVideoNode videoNodeWithAVPlayer:self.player];
[introVideo setSize:CGSizeMake(self.size.width, self.size.height)];
[introVideo setPosition:self.view.center];
[self addChild:introVideo];
[introVideo play];
3
where skvideonode is initialized? can you post code of skvideonode?Ilario
Added the code where I'm using it. I tried the preroll code as you'll see and that didn't make any difference to things.Mark Reid
where call [introVideo play] ?Ilario
Sorry, I'd removed that in testing something else. Right after the node is added. I've added it back to the code. I also tried starting it before hand, but then still gives me the same result.Mark Reid
why you use an observer? you can load video in initWithSize and after with global variable SKVideoNode *_introVideo you can play the video from everywhereIlario

3 Answers

1
votes

I had a similar problem and solved it like this:

I exported the first frame of the video as PNG. Then I added a SKSpriteNode with that PNG in front of the SKVideoNode. When I start the video, I set the hidden property of the SKSpriteNode to YES.


Here is an simplified example of my code with the relevant parts:

-(void)didMoveToView:(SKView *)view {    

    // add first frame on top of the SKVideoNode 
    firstFrame = [SKSpriteNode spriteNodeWithImageNamed:@"firstFrame"];
    firstFrame.zPosition = 1;
    [self addChild:firstFrame];

    // here I add the SKVideoNode with AVPlayer  
    ......

    // Add KVO  
    [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];

}


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (object == player.currentItem && [keyPath isEqualToString:@"status"]) {
        if (player.currentItem.status == AVPlayerItemStatusReadyToPlay) {

            [introVideo play]; 
            firstFrame.hidden = YES;             
        }
    }
}


This is the best solution I've found so far. I hope that helps you!

1
votes

Having the same issue. My workaround :

videoNode = SKVideoNode(AVPlayer: player)
videoNode.hidden = true

player.addBoundaryTimeObserverForTimes([1/30.0], queue: dispatch_get_main_queue()) { [unowned self] () -> Void in
    self.videoNode.hidden = false
}
0
votes

strange issue... can you try without using AVPlayerItem ? like this:

  NSString *resourcePath = [[NSBundle mainBundle] pathForResource:@"IntroMovie" 
                                                           ofType:@"m4v"];
  NSURL *introVideoURL = [NSURL fileURLWithPath:resourcePath];

  AVPlayer *player = [AVPlayer playerWithURL: introVideoURL];

  SKVideoNode *introVideo = [[SKVideoNode alloc] initWithAVPlayer: player];

  [introVideo setSize:CGSizeMake(self.size.width, self.size.height)];
  [introVideo setPosition:self.view.center];
  [self addChild:introVideo];
  [introVideo play];