How to play video in cocos2dx with DirectShow


#1

The purpose of this article is to illustrate how to insert a video during the game that is developed with the currently prevailing 2D open source game engine cocos2dx.
Such is the situation where you need play a video after or before a level, a stage or a scene. However, there is no such function available in the cocos2dx library under win32 platform,
thus I decide to write my own method to support in video playing.

First of all, I extract the video play code from windows SDK platform which can be found under your windows SDK install directory with the name
C:Files\Microsoft Platform SDK\Samples\Multimedia\DirectShow\Players\Cutscene. Please take reference to your local path.
Later, I create its header file and implementation file respectively, with the name “movieplay.h” and “movieplay.cpp”. (When I am writing this article, the cocos2dx version I use is 2.0.2)

NOTE: For reasons that cocos2dx encapsulates the Winmian function and message loop in elsewhere, I will use dllexport for the movie play function,
or otherwise there is no alternative to control winproc function.

MOVIEPLAY.H

#include <Windows.h>

#define WM_VIDEOEVENT WM_APP + 11

*declspec HRESULT PlayCutscene;
*declspec(dllexport) void CleanupInterfaces();

_*declspec void MovieHandleEvent;
MOVIEPLAY.CPP
view plainprint?
#include <tchar.h>
#include <stdio.h>
#include <dshow.h>
#include <atlbase.h>
#include <strsafe.h>
#include “CCDirector.h”
#include “ccMacros.h”
#include “movieplay.h”
IGraphBuilder pGB = NULL;
IMediaControl
pMC = NULL;
IVideoWindow pVW = NULL;
IMediaEventEx
pME = NULL;
BOOL g_bContinue=FALSE, g_bUserInterruptedPlayback=FALSE;
#define RELEASE (i)
HRESULT PlayMedia;
HRESULT GetInterfaces;
HRESULT PlayCutscene
{
HRESULT hr;
if
return E_POINTER;
// Initialize COM
if ))
return hr;
// Get DirectShow interfaces
if ))
{
CoUninitialize;
return hr;
}
UpdateWindow;
SetForegroundWindow;
SetFocus;
PlayMedia;
if && g_bUserInterruptedPlayback)
hr = S_FALSE;
return hr;
}
HRESULT GetInterfaces
{
HRESULT hr = S_OK;
CoCreateInstance&pGB);
pGB~~>QueryInterface&pMC);
pGB~~>QueryInterface&pVW);
// Get interface to allow the app to wait for completion of playback
pGB~~>QueryInterface&pME);
pME~~>SetNotifyWindowhwnd,WM_VIDEOEVENT,0);
return S_OK;
}
void CleanupInterfaces
{
// Release the DirectShow interfaces
cocos2d::CCDirector::sharedDirector~~>startAnimation;
pME~~>SetNotifyWindow;
// Stop playback and exit
if
pMC~~>Stop;
RELEASE (pGB);
RELEASE (pMC);
RELEASE (pVW);
RELEASE (pME);
CoUninitialize;
}
void CloseApp
{
g_bContinue = FALSE;
//PostMessage;
}

HRESULT PlayMedia
{
USES_CONVERSION;
WCHAR wFileName[MAX_PATH];
BOOL bSleep=TRUE;
HRESULT hr = S_OK;
if
return E_POINTER;
wcsncpy, MAX_PATH-1);
wFileName[MAX_PATH~~ 1] = 0;
hr = pGB~~>RenderFile;
// Set windows position
pVW~~>put_Ownerhwnd);
pVW~~>put_WindowStyle;
RECT grc;
GetClientRect;
pVW~~>SetWindowPosition;
pME~~>SetNotifyWindowhwnd,WM_VIDEOEVENT,0);
// Display first frame of the movie
hr = pMC~~>Pause;
// Start playback
hr = pMC~~>Run;
}
void MovieHandleEvent {
HRESULT hr = S_OK;
g_bContinue = TRUE;
MSG msg;
long lEventCode;
LONG_PTR lpParam1, lpParam2;
// Enter a loop of checking for events and sampling keyboard input
while ,SUCCEEDED (hr)) {
hr = pME~~>FreeEventParams;
if {
CleanupInterfaces;
break;
}
}
}

Well, I have created the movieplay function and next step is to integrate it into windows message loop. As I have said above, cocos2dx encapsulates its winmain function in elsewhere.
To be specific, winmain is located in “CCApplication.cpp” and WndProc is located in “CCEGLView.cpp”. Let’s take a quick look.

CCApplication.cpp
view plainprint?
int CCApplication::run {


if {
// Quit message loop.
break;
}
// Deal with windows message.
if ) {
TranslateMessage;
DispatchMessage;
}
return msg.wParam;
}

When you see the TranslateMessage and DispatchMessage, I think you are catching on the bus.

CCEGLView.cpp
view plainprint?
bool CCEGLView::Create
{
bool bRet = false;
do
{
CC_BREAK_IF;
HINSTANCE hInstance = GetModuleHandle;
WNDCLASS wc; // Windows Class Structure
// Redraw On Size, And Own DC For Window.
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc =*WindowProc; // WndProc Handles Messages
wc.cbClsExtra = 0; // No Extra Window Data
wc.cbWndExtra = 0; // No Extra Window Data
wc.hInstance = hInstance; // Set The Instance
wc.hIcon = LoadIcon( NULL, IDI_WINLOGO ); // Load The Default Icon
wc.hCursor = LoadCursor( NULL, IDC_ARROW ); // Load The Arrow Pointer
wc.hbrBackground = NULL; // No Background Required For GL
wc.lpszMenuName = NULL; // We Don’t Want A Menu
wc.lpszClassName = kWindowClassName; // Set The Class Name

CC_BREAK_IF(! RegisterClass(&wc) && 1410 != GetLastError());

}

Once again, the above mentioned create method you have been using for many years in WIN32 application.

Next step comes the core part. I will add a new message id in the WndProc function to allow movieplay function to send message to our client window,
and the client window will perform some according actions. (place movieplay.h and movieplay.cpp under cocos2dx\platform\win32 or anywhere you like,
as long as you include in correct path )

CCEGLView.cpp

view plainprint?
#include “movieplay.h”
LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)…
switch (message) {

case WM_VIDEOEVENT:
CCDirector::sharedDirector()~~>stopAnimation;
MovieHandleEvent;
break;

}
}

Final step is to use in the cocos2dx project. Add two scene classes, the first scene is just a placeholder for the movie to be initialized.

SplashScene.h
view plainprint?
#ifndef SPLASH_SCENE_H
#define SPLASH_SCENE_H
#include “cocos2d.h”
#include “SimpleAudioEngine.h”
class SplashScene : public cocos2d::CCLayerColor
{
public:
// Here’s a difference. Method ‘init’ in cocos2d-x returns bool, instead of returning ‘id’ in cocos2d-iphone
virtual bool init;
void NextScene;
// there’s no ‘id’ in cpp, so we recommand to return the exactly class pointer
static cocos2d::CCScene* scene;
// implement the “static node()” method manually
CREATE_FUNC;
};
view plainprint?
#endif
view plainprint?

SplashScene.cpp
view plainprint?
#include “SplashScene.h”
#include “MainMovieScene.h”
#include “SimpleAudioEngine.h”
#include “cocos2d.h”

using namespace cocos2d;
using namespace CocosDenshion;
CCScene* SplashScene::scene
{
CCScene * scene = NULL;
do
{
// ‘scene’ is an autorelease object
scene = CCScene::create;
CC_BREAK_IF;
// ‘layer’ is an autorelease object
SplashScene **layer = SplashScene::create;
CC_BREAK_IF;
// add layer as a child to scene
scene~~>addChild;
} while ;
// return the scene
return scene;
}
// on “init” you need to initialize your instance
bool SplashScene::init
{
bool bRet = false;
do
{
//////////////////////////////////////////////////////////////////////////
// super init first
//////////////////////////////////////////////////////////////////////////
CC_BREAK_IF);
CC_BREAK_IF));
this~~>runAction,
CCCallFunc::actionWithTarget),
NULL));
bRet = true;
} while ;
return bRet;
}
void SplashScene::NextScene
{
CCDirector::sharedDirector~~>replaceScene);
}

MainMovieScene.h
view plainprint?
#ifndef MAINMOVIE_SCENE_H
#define MAINMOVIE_SCENE_H
#include “cocos2d.h”
#include “SimpleAudioEngine.h”
class MainMovie : public cocos2d::CCLayer
{
public:
// Here’s a difference. Method ‘init’ in cocos2d-x returns bool, instead of returning ‘id’ in cocos2d-iphone
virtual bool init;
void NextScene;
// there’s no ‘id’ in cpp, so we recommand to return the exactly class pointer
static cocos2d::CCScene* scene;
// implement the “static node()” method manually
CREATE_FUNC;
};
#endif
MainMovieScene.cpp
view plainprint?
#include “MainMovieScene.h”
#include “HelloWorldScene.h”
#include “SimpleAudioEngine.h”
#include “movieplay.h”
#include “cocos2d.h”
#include “CHWNDMGR.h”

using namespace cocos2d;
using namespace CocosDenshion;
// extern HWND g_hwnd;
// extern HINSTANCE g_hInstance;
bool isVideoDone = false;
CCScene* MainMovie::scene
{
CCScene * scene = NULL;
do
{
// ‘scene’ is an autorelease object
scene = CCScene::create;
CC_BREAK_IF;
// ‘layer’ is an autorelease object
MainMovie **layer = MainMovie::create;
CC_BREAK_IF;
// add layer as a child to scene
scene~~>addChild;
} while ;
// return the scene
return scene;
}
// on “init” you need to initialize your instance
bool MainMovie::init
{
bool bRet = false;
do
{
//////////////////////////////////////////////////////////////////////////
// super init first
//////////////////////////////////////////////////////////////////////////
CC_BREAK_IF);

if {
PlayCutscene,
CHwndMgr::GetSingleton.GetInstance,
CHwndMgr::GetSingleton.GetHwnd);
isVideoDone = !isVideoDone;
}
this~~>runAction,
CCCallFunc::actionWithTarget),
NULL));
bRet = true;
} while ;
return bRet;
}
void MainMovie::NextScene
{
CCDirector::sharedDirector~~>replaceScene);
}
NOTE, when the app starts it first calls splashScene as a placeholder to call movieScene, and when movieScene is end , it calls the real first scene~~ HelloworldScene.
So here, this demo runs as a introduction video.

CHWNDMGR class is a singleton class to store HWND and HINSTANCE for the app.

view plainprint?
#ifndef CHWNDMGR_H
#define CHWNDMGR_H
#include “Singleton.h”
#include <Windows.h>
class CHwndMgr: public CSingleton {
public:
void SetHwnd
HWND GetHwnd
void SetInstance
HINSTANCE GetInstance
private:
HWND m_hwnd;
HINSTANCE m_hInstance;
};
#endif

view plainprint?
#ifndef SINGLETON_H
#define SINGLETON_H
#include <assert.h>
#include <stdafx.h>
BOO_BEGIN
template
class CSingleton {
static T** m_pcSingleton ;
public:
CSingleton {
assert ;
long offset = 1~~ 1 ;
m_pcSingleton = this + offset) ;
}
~CSingleton {
assert ;
m_pcSingleton = 0 ;
}
static T& GetSingleton {
assert ;
return ;
}
static T** GetSingletonPtr {
#ifdef DEBUG
if
**asm { int 3 }
#endif
assert ;
return ;
}
};
#endif
Put the CHWNDMGR in main.cpp
view plainprint?
#include “main.h”
#include “AppDelegate.h”
#include “CCEGLView.h”
#include “MACRO.h”
#include “CHWNDMGR.h”
#include <Windows.h>
USING_NS_CC;
// uncomment below line, open debug console
// #define USE_WIN32_CONSOLE
CHwndMgr hMgr;
int APIENTRY
tWinMain
{
UNREFERENCED_PARAMETER;
UNREFERENCED_PARAMETER;
#ifdef USE_WIN32_CONSOLE
AllocConsole;
freopen;
freopen;
freopen;
#endif
// create the application instance
AppDelegate app;
CCEGLView* eglView = CCEGLView::sharedOpenGLView;
eglView~~>setFrameSize(SCREEN_WIDTH, SCREEN_HEITH);
eglView~~>setViewPortInPoints;
// g_hwnd = eglView~~>getHWnd();
// g_hInstance = hInstance;
hMgr.SetHwnd(eglView->getHWnd());
hMgr.SetInstance(hInstance);

SetWindowText(CHwndMgr::GetSingleton().GetHwnd(),L“The Legend of Hero”);
SetFocus(CHwndMgr::GetSingleton().GetHwnd());

int ret = CCApplication::sharedApplication()->run();

#ifdef USE_WIN32_CONSOLE
FreeConsole();
#endif

return ret;
}

Some people may find it is strange why I should add a new message in Wndproc? The cause is that if you do not add this message, when you play the video in your client window,
you cannot move! Yes, you cannot move the window when the move is playing. Because DirectShow will create a hidden window inside your client window, and as a result,
you have to wait for the movie end to move the window. Write at last, I am just a newbie for cocos2dx and all that related to programming. Any comments are welcome.


#2

Hi, i’m new on this stuffs so i just wonder if you used any specific dimensions on your video.

I mean… should i create many videos with diferent dimensions or i do one video with same dimensions than my background?
And do you know if this still works on v3.15?