最近刚转了游戏,来公司不久就接到一个任务就是做一个视频播放的功能,自己花了3天时间,暂时实现了一个简易的功能,特写篇博客,以作记录。
参考地址如下:
http://blog.csdn.net/xiaominghimi/article/details/6870259
http://blog.csdn.net/kaitiren/article/details/11832851
http://blog.csdn.net/candyforever/article/details/8905852
实现功能:
Lua与C++的互调,C++与Object-c的互调, C++与Java的互相调用。
iOS视频的播放,android的视频播放。
声明:
pc 环境是 MAC OS 10.9.3
cocos2d-x 是2.2.3版本,其他版本可能部分API不同。
一,iOS实现
1 首先我们创建测试工程,我用的是Lua.
2 打开cocos2d-x 里的projects文件夹找到我们创建的工程打开,我们的工程文件夹,可以看到如下工程
3 打开proj.ios工程,看见xxxx.xcodeproj文件,双击打开,看见如下工程目录:
4.其中Other Sources里放得就是iOS相关的代码,如果是c++语言的工程,应该可以看到一个ios的文件夹,如下图,
5,下载这个文件夹 http://pan.baidu.com/s/1c0vPiuO,放到Other Sources里,如图:
6,添加MediaPlayer.framework类库到iOS工程,如图:
7,在AppController.h里添加如下代码:
1 #import "CCVideoPlayeriOS/CCVideoPlayer.h" 2 #import "cocos2d.h" 3 using namespace cocos2d; 4 5 @class RootViewController; 6 7 @interface AppController : NSObject <UIAccelerometerDelegate, UIAlertViewDelegate, UITextFieldDelegate,UIApplicationDelegate,CCVideoPlayerDelegate> { 8 UIWindow *window; 9 RootViewController *viewController; 10 CCObject * vedioDelegate; 11 } 12 13 @property(nonatomic,retain)UIWindow * window; 14 15 -(void)playVedio:(const char *)filePath delegate:(CCObject *)delegate skip:(bool)skip; 16 17 @end
代码解释:
1)引入了CCVideoPlayeriOS里的CCVideoPlayer.h然后 AppController实现了CCVideoPlayerDelegate协议
2)引入了cocos2d.h头文件和cocos2d命名空间,目的是,添加CCObject * vedioDelegate 成员变量
3)添加了播放函数 -(void)playVedio:(const char *)filePath delegate:(CCObject *)delegate skip:(bool)skip;
参数 filePath->要播放的文件路劲
delegate->是播放完的回调代理
8,在AppController.m里添加如下代码
1 //引入VedioPlatform头文件 2 #import "VedioPlatform.h" 3 4 @implementation AppController 5 6 #pragma mark - 7 #pragma mark Application lifecycle 8 9 // cocos2d application instance 10 static AppDelegate s_sharedApplication; 11 12 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 13 14 // Override point for customization after application launch. 15 16 // Add the view controller‘s view to the window and display. 17 window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]]; 18 EAGLView *__glView = [EAGLView viewWithFrame: [window bounds] 19 pixelFormat: kEAGLColorFormatRGBA8 20 depthFormat: GL_DEPTH_COMPONENT16 21 preserveBackbuffer: NO 22 sharegroup: nil 23 multiSampling: NO 24 numberOfSamples: 0 ]; 25 26 [__glView setMultipleTouchEnabled:YES]; 27 // Use RootViewController manage EAGLView 28 viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil]; 29 viewController.wantsFullScreenLayout = YES; 30 viewController.view = __glView; 31 32 // Set RootViewController to window 33 if ( [[UIDevice currentDevice].systemVersion floatValue] < 6.0) 34 { 35 // warning: addSubView doesn‘t work on iOS6 36 [window addSubview: viewController.view]; 37 } 38 else 39 { 40 // use this method on ios6 41 [window setRootViewController:viewController]; 42 } 43 44 [window makeKeyAndVisible]; 45 46 [[UIApplication sharedApplication] setStatusBarHidden: YES]; 47 48 cocos2d::CCApplication::sharedApplication()->run(); 49 50 //将自己添加为CCVideoPlayer的播放代理,主要为了播放完成的回调 51 [CCVideoPlayer setDelegate:self]; 52 53 return YES; 54 } 55 56 57 - (void)applicationWillResignActive:(UIApplication *)application { 58 /* 59 Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 60 Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 61 */ 62 cocos2d::CCDirector::sharedDirector()->pause(); 63 } 64 65 - (void)applicationDidBecomeActive:(UIApplication *)application { 66 /* 67 Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 68 */ 69 cocos2d::CCDirector::sharedDirector()->resume(); 70 } 71 72 - (void)applicationDidEnterBackground:(UIApplication *)application { 73 /* 74 Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 75 If your application supports background execution, called instead of applicationWillTerminate: when the user quits. 76 */ 77 cocos2d::CCApplication::sharedApplication()->applicationDidEnterBackground(); 78 } 79 80 - (void)applicationWillEnterForeground:(UIApplication *)application { 81 /* 82 Called as part of transition from the background to the inactive state: here you can undo many of the changes made on entering the background. 83 */ 84 cocos2d::CCApplication::sharedApplication()->applicationWillEnterForeground(); 85 } 86 87 - (void)applicationWillTerminate:(UIApplication *)application { 88 /* 89 Called when the application is about to terminate. 90 See also applicationDidEnterBackground:. 91 */ 92 } 93 94 95 #pragma mark - 96 #pragma mark Memory management 97 98 - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { 99 /* 100 Free up as much memory as possible by purging cached data objects that can be recreated (or reloaded from disk) later. 101 */ 102 cocos2d::CCDirector::sharedDirector()->purgeCachedData(); 103 } 104 105 106 - (void)dealloc { 107 [super dealloc]; 108 } 109 110 #pragma mark - Play Vedio(是所有播放相关的代码) 111 112 //播放视频 113 //filePath视频地址, 114 //delegate播完回调代理也就是VedioPlatform对象, 115 //skip是否可以跳过 116 -(void)playVedio:(const char *)filePath delegate:(CCObject *)delegate skip:(bool)skip{ 117 vedioDelegate = delegate;//保存代理指针 118 BOOL isOk = YES; 119 @try { 120 CCSize size = CCDirector::sharedDirector()->getWinSize(); 121 //设置播放的尺寸 122 [CCVideoPlayer setScrrenSize:CGSizeMake(size.width, size.height)]; 123 [CCVideoPlayer setNoSkip:!skip]; 124 //将c字符串转化为oc字符串 125 NSString * stringFilePath = [NSString stringWithFormat:@"%s",filePath]; 126 //开始播放 127 [CCVideoPlayer playMovieWithFile:stringFilePath]; 128 } 129 @catch (NSException *exception) { 130 NSLog(@"%@",exception); 131 isOk = NO; 132 } 133 @finally { 134 if(!isOk){ 135 //如果发生异常,回调播放完成 136 VedioPlatform * delegate = (VedioPlatform *)vedioDelegate; 137 delegate->moviePlaybackFinished(); 138 delegate = nil; 139 } 140 } 141 } 142 143 //以下是CCVideoPlayerDelegate的实现 144 145 //开始播放 146 - (void) movieStartsPlaying{ 147 148 } 149 150 //播放完成 151 - (void) moviePlaybackFinished{ 152 //回调代理的播放完成 153 VedioPlatform * delegate = (VedioPlatform *)vedioDelegate; 154 delegate->moviePlaybackFinished(); 155 delegate = nil; 156 }
解释:
1)VedioPlatform下边会实现。
2)在didFinishLaunchingWithOption函数里将自己设置为CCVideoPlayer(头文件已经导入该类)的代理,
3)#pragma mark - Play Vedio(是所有播放相关的代码)的代码实现。
9,添加IOSPlayVedio类,注意是c++类,但是是在.mm里实现的,如图:
1)在IOSPlayVedio.h添加如下代码:
1 #include "cocos2d.h" 2 using namespace cocos2d; 3 4 class IOSPlayVedio{ 5 public: 6 static void playVedio4iOS(const char * filePath,CCObject * delegate,bool skip); 7 };
只添加了一个供c++调用的static void playVedio4iOS(const char * filePath,CCObject * delegate,bool skip);静态函数。
2)在IOSPlayVedio.mm里实现如下:
1 #include "IOSPlayVedio.h" 2 #include "AppController.h" 3 4 void IOSPlayVedio::playVedio4iOS(const char * filePath,CCObject * delegate,bool skip){ 5 AppController *app = (AppController*) [[UIApplication sharedApplication] delegate]; 6 [app playVedio:filePath delegate:delegate skip:skip]; 7 }
调用了AppController的playVedio函数。
10,在Classes里添加VedioPlatform类,注意这回是.cpp文件,见图:
1)在VedioPlatform.h里生命如下函数:
1 #include "cocos2d.h" 2 using namespace cocos2d; 3 4 5 class VedioPlatform : CCObject{ 6 private: 7 int successCallBack; 8 public: 9 //创建 10 CREATE_FUNC(VedioPlatform); 11 bool init(); 12 public: 13 void setSuccessPlayCallBack(int funcAddress);//步骤1,播放成功回调 14 void play(const char * filePath,bool skip);//步骤2,播放 15 public: 16 void moviePlaybackFinished(); 17 };
解释,
1.1)实例变量int successCallBack; 这个是保存播放完成后回调Lua的函数的地址。
1.2)setSuccessPlayCallBack函数顾名思义,开放给Lua的接口,就是给Lua设置播放完成回调函数的地址的接口。
1.3)play(const char * filePath,bool skip);开放给Lua的接口,就是lua调用播放,filePath文件路径,skip是否可以跳过
1.4)void moviePlaybackFinished();是开放给AppController的代理接口,AppController在视频播放完了会回调这个函数。
2) 在VedioPlatform.m里的实现如下:
1 #include "VedioPlatform.h" 2 #include "CCLuaEngine.h" 3 4 //单例变量保存了VedioPlatform对象 5 static VedioPlatform * shardObj = NULL; 6 7 //依据不同的平台导入不同的头文件 8 #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) 9 //如果是Android得话,导入如下: 10 #include <jni.h>//android的jni 11 #include "platform/android/jni/JniHelper.h"//cocos2d专为jni封装的工具类 12 #include <android/log.h>//android 的日志 13 //这个是java回调c++的c函数,貌似c++类函数不可以,我也没有验证。 14 extern "C" 15 { 16 //finishAction 在Java里生命了这样一个函数。 17 //Java_com_zdl_VedioTest_VedioActivity这是类的完整路径 18 JNIEXPORT void JNICALL Java_com_zdl_VedioTest_VedioActivity_finishAction(JNIEnv *env, jobject thiz, jstring key, jstring message) 19 { 20 //调用了播放完成的函数 21 shardObj->moviePlaybackFinished(); 22 } 23 } 24 25 #else 26 //如果是iOS得话,只引用IOSPlayVedio就可以了,还是oc简单。 27 #include "IOSPlayVedio.h" 28 #endif 29 30 #pragma mark - 生命周期 31 bool VedioPlatform::init(){ 32 //将Lua回调的函数指针地址初始化为0 33 successCallBack = 0; 34 35 //判空 36 if(shardObj!= NULL){ 37 //不为空前release; 38 shardObj->release(); 39 } 40 //在赋值,并retain 41 shardObj = this; 42 shardObj->retain(); 43 44 return true; 45 } 46 47 #pragma mark - 视频播放 48 49 void VedioPlatform::setSuccessPlayCallBack(int funcAddress){ 50 successCallBack = funcAddress;//保存Lua回调的函数指针地址 51 } 52 53 void VedioPlatform::play(const char * filePath,bool skip){ 54 #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) 55 //Android视频播放代码 56 JniMethodInfo minfo;//jni函数结构体 57 // 通过JniHelper来获取Java的静态函数, 58 // minfo->传入的函数结构体信息,调用完成后会给minfo赋值。 59 // "com/zdl/VedioTest/VedioTest"->是你Java静态函数的完整类路径。 60 // "playVedio"->静态函数名称 61 // "(Ljava/lang/String;Z)V"->静态函数的参数和返回值描述,这个大家百度下具体格式,我这儿就不详细的说了,当前这的意思是 viod playVedio(String a,boolean b);这样一个函数。 62 bool isHave = JniHelper::getStaticMethodInfo(minfo,"com/zdl/VedioTest/VedioTest","playVedio","(Ljava/lang/String;Z)V"); 63 64 //判断是否有这个函数 65 if (isHave) { 66 //取得Java函数结构体里的执行栈描述 67 JNIEnv* env = minfo.env; 68 CCLOG("zhangdongli*************FilePath:%s\n",filePath); 69 //将Lua里传过来的文件名转化为全路径。 70 char const * newFilePath = CCFileUtils::sharedFileUtils()->fullPathForFilename(filePath).c_str(); 71 CCLOG("zhangdongli*************FileFullPath:%s\n",newFilePath); 72 //将c字符串转化为c++描述的java字符串,具体jni的java数据描述,大家百度。 73 jstring jfilePath = env->NewStringUTF(newFilePath); 74 //将是否可以跳过的c bool 转化为jni描述的java的boolean 75 jboolean jskip = skip; 76 //调用java的playVedio函数,将jfilePath和jskip传入。 77 minfo.env->CallStaticVoidMethod(minfo.classID, minfo.methodID,jfilePath,jskip); 78 }else{ 79 CCLOG("zhangdongli**************** not find playVedio(String a,Boolean b) function"); 80 } 81 #else 82 //直接调用我们实现的IOSPlayVedio的播放函数,将this传入,作为播放完成的代理。 83 IOSPlayVedio::playVedio4iOS(filePath,this,skip); 84 #endif 85 } 86 87 #pragma mark - 播放回调 88 89 void VedioPlatform::moviePlaybackFinished(){ 90 //判断是否指定了lua得回调函数 91 if(successCallBack > 0){ 92 //通过cocos2d实现的Lua引擎,回调lua函数 93 CCLuaEngine * engine = (CCLuaEngine *)CCScriptEngineManager::sharedManager()->getScriptEngine(); 94 //注意这个函数,版本不同 名称不同。 95 engine->executeEventWithArgs(successCallBack, CCArray::create()); 96 } 97 }
解释,
2.1)静态变量 static VedioPlatform * shardObj = NULL;是保存了每次播放的VedioPlatform地址,类似于单体。
2.2)导入#include "IOSPlayVedio.h"头文件,将要调用它的playVedio4iOS函数。
2.3)在init函数里,retain shardObj变量,因为在cocos2d里是手动内存管理的,然后每次赋值之前要release,之所以没有在析构函数里做做最后的release是因为,一直不销毁它,只用程序关闭才销毁,那样系统会自动清理这个变量。
2.4)setSuccessPlayCallBack函数里只是简单的保存了Lua回调函数的地址。
2.5)play函数里首先依据平台宏判断是android还是iOS,具体实现大家看下代码,里边有注释
11.接下来是实现C++与lua的互调了。
Cocos2d-x Lua 播放视频(iOS&android)