Cocos2d-x Cross Platform Video Player – Part 1 iOS

Cross Platform (iOS and Android) Video playing solution is very much possible in cocos2d-x 2.1.5. But as far as I searched there is not single tutorial / forum discussion that explains it with ease. So here I am summarising how I achieved it. There is no Video Player control in cocos2d-x. So we need to fall back to native video players of iOS and Android platforms. Android Video Playback in 2nd part of this tutorial.

Objective:

To write a single function that plays a video in either iOS / Android, given its file name. Also, the video should be made skippable, by tapping on it. The game’s sound track should be muted when the video gets played, since it might have its own soundtrack and the game’s sound track will be resumed, when the video is stopped.

Download

Download project @ https://github.com/saiy2k/cocos2dx-videoplayer-ios/archive/master.zip Complete Project can be found in GITHUB.

1. Generic Wrapper Class

To handle Features, which require separate implementations on iOS and Android, I implemented wrapper class(WrapperClass.h, WrapperClass.cpp), which will be called from the game code. Then there will be separate Platform dependent wrapper classes, which will get called from WrapperClass. The only functionality of WrapperClass is to forward the flow to platform dependent functions. Here is the code of the Wrapper Class:

//WrapperClass.h
class WrapperClass : public cocos2d::CCObject {
public:
    void                            playVideo(const char *vidPath);
    static WrapperClass             *getShared();
};

//WrapperClass.cpp
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
#include "misc/iOSWrapper.h"
#endif

static WrapperClass *instance   =   NULL;

void WrapperClass::playVideo(const char *vidPath) {
    AudioManager::sharedManager()->stopBG();
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
    iOSWrapper::getShared()->playVideo(vidPath);
#elif CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
    CCApplication::sharedApplication()->playVideo(vidPath);
#endif
}

WrapperClass *WrapperClass::getShared() {
    if (!instance) {
        instance                =   new WrapperClass();
    }
    return                          instance;
}

The `playVideo` function just calls platform specific function from here. Since playback in Android requires JNI calls to pass the flow to JAVA side, I implemented the required function in CCApplication, which already has few JNI calling functions. Also, this function stops the background music, since the Video will be having its own Audio Track. Game’s sound track will be resumed when the video gets over.

2. Playing Video in iOS

The `iOSWrapper` singleton (iOSWrapper.h, iOSWrapper.mm) called in above code is another wrapper that bridges the CPP side of cocos2d-x and Objective-C part of native `UIView` code. The `playVideo` function of `iOSWrapper` class is as follows:

void iOSWrapper::playVideo(const char *vidPath) {
    NSString                        *string, *stringPath;
    stringPath                  =   [NSString stringWithUTF8String:vidPath];
    string                      =   [[NSBundle mainBundle] pathForResource:stringPath ofType:@"mp4"];
    [[EAGLView sharedEGLView] playTutorialVideo:string];
}

The above code converts the date type of file name from `char *` to `NSString` and then find its path in the Bundle and calls another custom function `playTutorialVideo()` written in `EAGLView`. That function is responsible for actual video play. Add the following in `EAGLView` to complete the iOS part.

 (void) playTutorialVideo:(NSString *)path {

    NSURL                           *url;

    url                         =   [NSURL fileURLWithPath:path];
    player                      =   [[MPMoviePlayerController alloc] initWithContentURL:url];
    player.view.frame           =   CGRectMake(0, 0, self.frame.size.height, self.frame.size.width);
    player.fullscreen           =   YES;
    player.scalingMode          =   MPMovieScalingModeNone;
    player.controlStyle         =   MPMovieControlStyleNone;
    [self                           addSubview:player.view];
    [player                         play];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(removeVideo)
                                                 name:MPMoviePlayerPlaybackStateDidChangeNotification object:nil];
}

Now we are completely into iPhone Native part, where we use Objective-C and deal with UIView and UIViewControllers, completely away from cocos2d-x land. The above function creates a `MPMoviePlayerController`,  loads the video with given path and plays it. MPMoviePlayerController throws notifications instead of calling delegate callback functions, in case of internal events. So the above code also registers with NSNotificationCenter to capture video pause / stop events.

(void) removeVideo {
    if (player.playbackState == MPMoviePlaybackStatePaused || player.playbackState ==   MPMoviePlaybackStateStopped) {
        [player.view removeFromSuperview];
        [player release];
        player = nil;

        [[NSNotificationCenter defaultCenter] removeObserver:self
        name:MPMoviePlayerPlaybackStateDidChangeNotification
        object:nil];

        AudioManager::sharedManager()->playBG();
    }
}

The above function is registered to handle notifications of MPMoviePlayerController and will get invoked on any change in state. We need to remove the controller, only when the video playback is over and so we check for the Stopped/Paused state and when they happen, we remove the controls, release from memory, resumes the game background music.

Skip on Touch

Not all players will sit through the videos / instructions, but they want to directly get hooked up into the gameplay. So, we will make the video stop and disappear, when user taps on it. To do it, just add the following block of code inside `touchesBegan` function of EAGLView, immediately after `if (isKeyboardShown_)` block:

if (player) {

    [player stop];
    [player.view removeFromSuperview];
    [player release];
    player = nil;

    [[NSNotificationCenter defaultCenter] removeObserver:self
    name:MPMoviePlayerPlaybackStateDidChangeNotification
    object:nil];

    AudioManager::sharedManager()->playBG();

    return;
}

To keep the posts short, I will write about Android part of the Video Player in next post. Once all this is done, setup a Layer (HelloWorldScene.hHelloWorldScene.cpp) with a menu button, whose handler has the following one line of code:

WrapperClass::getShared()->playVideo("sampleVideo");

If everything is done right, you will have a button in screen, which when clicked, stops the game sound track, and plays the video. Once the video is over or skipped, the game sound track resumes.

Download

Download project @ https://github.com/saiy2k/cocos2dx-videoplayer-ios/archive/master.zip Complete Project can be found in GITHUB.

Android

Android Video playback is explained in the second part of this tutorial.

Extended EPGLTransitionView

Get the project @ https://github.com/saiy2k/EPGLTransitionView

I was searching for some techniques to do flip screen transitions as in Flipboard iphone app and I found this awesome project, EPGLTransitionView.

EPGLTransitionView is an excellent project to do some cool UIViewController transitions. It basically works by taking screenshots of the two views you would like to switch and manipulating the screenshots with OpenGL commands for cool transition effects. Official URL for the project is http://www.memention.com/blog/2010/02/28/Bells-and-Whistles.html

The project comes with 3 basic transitions:

fall turn flip

Reverse Animation:

The class Demo2Transition in the ‘Demo Project’ provided is exactly what I wanted. But it lacked certain features like turning in reversed direction and turning from bottom up.

So I downloaded the project from github, tinkered with the Demo2Transition class and in a while found a way to reverse the animation. Actually reversing was not a big deal, the animation is controlled by a control variable f, which determines the turned position of view at any given time. I just reversed the initial and final values and it worked flawlessly.

Up Down Transition:

I am not much into OpenGL before, so pulling this’up down turn transition’ took me some time with lots of trial and errors. I did this by changing the order of vertices and texture co-ordinates in which the image is read.

Now both of reversal and up down transition and working great except for 1 frame flicker in ‘reverse animation’. Working on fixing it.

My fork:

I forked the project and committed my changes to the forked repo @ https://github.com/saiy2k/EPGLTransitionView

I also sent a pull request to the original repo, lets see if its accepted. I done a few open source projects before, but this is the first time that I contribute to some one else’s project.

Usage:

I changed the ‘init’ method in the original project into the following:

– (id)initWithView1:(UIView*)view1 andView2:(UIView*)view2 delegate:(id)_delegate fwdDirection:(BOOL)fwd

where `view1` is the UIView which is currently being active and `view2` is the UIView which need to be shown, `delegate` is the transition class that conforms to `EPGLTransitionViewDelegate` protocol, and `fwd` is a flag that determines the direction of transition.

The EPGL TransitionView can reverse the transition only if the Transition class supports it by implementing the `isVertical` property. If it isn’t then, the value of `fwd` doesn’t have any effect.

A sample use case is given below:

UIViewController *controller = [[[UIViewController alloc] init] autorelease];
controller.delegate = self;
controller.view.frame = CGRectMake(0, 20, 320, 460);
NSObject<EPGLTransitionViewDelegate> *transition;
EPGLTransitionView *glview;

transition = [[[Demo2Transition alloc] init] autorelease];
[transition setIsVertical:YES];
glview = [[EPGLTransitionView alloc]
initWithView1:self.view
andView2:controller.view
delegate:transition
fwdDirection:NO];

[glview startTransition];
[self presentModalViewController:controller animated:NO];

Happy flipping.