(头注:Cocos2d-x3.2引擎已经集成了MP4播放功能了,本篇文章写于2.x版本)
游戏需要播放mp4文件展示游戏背景,在网上搜了好久,IOS平台比较容易实现,Android就不敢恭维了;
播放mp4需要分平台实现,悲催的是IOS和Android的原生开发都没有做过,所以只能从网上找资料;
目前实现的功能播放,跳过和播放完成之后回到游戏,IOS和Android一样都已经实现;
1、IOS实现:
首先,参考文章:点击打开链接,我是完全按照这篇文章的介绍做的,里面有源代码地址,相信各位了看了之后就回明白,至少也有方向了;
类说明:
LHVideoPlayerImplCpp.h/mm // cocos2dx中使用的播放MP4接口
LHVideoPlayerImpl.h/m // videoPlayer的oc接口
LHVideoPlayer.h/m // videoPlayer的实现,调用MPMoviePlayerController播放MP4
LHVideoOverlayView.h/m // videoPlayer的上层操作层,有跳过影片按钮。
我就功能点而言,介绍其中的两个类。
class LHVideoPlayerImplCpp {
public:
/**
* 开始播放MP4视频
* name 视频名称,不要带后缀".mp4"。(比如文件是test.mp4, 那么name就传"test")
* frameRect 视频显示的区域。全屏(Rect(0, 0, visibleSize.width, visibleSize.height))
*/
static void playMP4WithName(const char* name, cocos2d::Rect frameRect);
/**
* 设置跳过影片按钮title,默认无跳过影片功能
*/
static void setSkipTitle(const char* title);
};
第二个是LHVideoPlayer.h/m,这个是负责播放MP4。
- (void)playMP4WithName: (NSString *)name VideoFrame:(CGRect)rect
{
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
// 获取视频文件的名称
NSString *url = [[NSBundle mainBundle]pathForResource:name ofType:@"mp4"];
// 初始化player
_player = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL fileURLWithPath:url]];
[keyWindow.rootViewController.view addSubview: [_player view]];
// 设置player样式
[_player setControlStyle: MPMovieControlStyleNone];
[[_player view] setFrame: rect];
// 当MP4完成播放的回调
[[NSNotificationCenter defaultCenter]
addObserver:self selector:@selector(movieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification object:_player];
// 开始播放影片
[_player play];
// 上层操作层
_videoOverlayView = [ [LHVideoOverlayView alloc] initWithFrame: rect];
[keyWindow.rootViewController.view addSubview: _videoOverlayView];
}
b、这个方法是播放结束之后,移除播放view。
- (void)removePlayer:(MPMoviePlayerController*)player
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:player];
[player.view removeFromSuperview];
[_player release];
_player = nil;
[_videoOverlayView removeFromSuperview];
[_videoOverlayView release];
_videoOverlayView = nil;
}
3、播放完成,通知外界。playerPlayFinished 这个方法是空的,没有通知外界。我看了下,感觉没什么需要,所以没加。(原作者没加,不过考虑到我播放的mp4是挂在一个layer里面实现的,所以还是希望在播放结束的时候该layer能知道;所以,我另外添加了一个函数):
bool LHVideoPlayerImplCpp::getFinshState() {
return [LHVideoPlayerImpl getFinshState];
}
至于里面调用的函数就不写了,用于获取视频已经播放结束,然后在layer的visit里面处理:
void ShowMovieStart::visit() {
GUILayer::visit();
bool isfinish = false;
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
//
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
isfinish = LHVideoPlayerImplCpp::getFinshState();
#endif
if (isfinish) {
CCLog("视频播放结束了");
//做相关处理
}
}
注意看ios实现的部分,当视频结束时,做相关处理;
使用方法:
a、导入头文件
#include “LHVideoPlayerImplCpp.h”
b、开始调用接口,假设你要播放的是“loading.mp4”
Size visibleSize = Director::getInstance()->getVisibleSize();
LHVideoPlayerImplCpp::playMP4WithName(“loading”, Rect(0, 0, visibleSize.width, visibleSize.height));
LHVideoPlayerImplCpp::setSkipTitle(“Skip”);
c、影片结束之后,会自动移除视图;
IOS实现相对于Android来说比较容易,而且上诉源代码还提供了跳过功能,非常不错;
2、Android部分,这个比较复杂,一开始我根本不知道从何下手,只能从网上找,但是相关资料也不是很多,而且大多数不容易解决问题,有些可能是写给Android老手看的,像我Android开发只见皮毛者完全云里雾里,不过,看得资料多了,代码还是可以理解的,虽然有时候一知半解,但还是一步步实现了;之前只用过C++和脚本,lua Js什么的,java代码也没写过,不过语言从来不是问题,主要的是原理。
a、首先,如何把mp4播放出来;
视频播放Veiw实现VideoView.java文件:
package org.cocos2dx.common;
import java.io.FileDescriptor;
import java.io.IOException;
import android.app.Activity;
import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer;
import android.net.Uri;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
public class VideoView extends SurfaceView implements
SurfaceHolder.Callback,
View.OnTouchListener,
MediaPlayer.OnPreparedListener,
MediaPlayer.OnErrorListener,
MediaPlayer.OnInfoListener,
MediaPlayer.OnCompletionListener {
private static final String TAG = "VideoView";
private MediaPlayer mPlayer = null;
private Activity gameActivity;
private Uri resUri;
private AssetFileDescriptor fd;
private boolean surfaceCreated;
private OnFinishListener onFinishListener;
public VideoView(Activity context) {
super(context);
this.gameActivity = context;
final SurfaceHolder holder = getHolder();
holder.addCallback(this);
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
setOnTouchListener(this);
if (mPlayer != null) {
mPlayer.reset();
mPlayer.release();
mPlayer = null;
}
mPlayer = new MediaPlayer();
mPlayer = new MediaPlayer();
mPlayer.setScreenOnWhilePlaying(true);
mPlayer.setOnPreparedListener(this);
mPlayer.setOnCompletionListener(this);
mPlayer.setOnErrorListener(this);
mPlayer.setOnInfoListener(this);
//不应该在这里设置holder
//mPlayer.setDisplay(holder);
//mPlayer.prepareAsync();
}
public VideoView setOnFinishListener(OnFinishListener onFinishListener) {
this.onFinishListener = onFinishListener;
return this;
}
public void setVideo(Uri resUri) {
this.resUri = resUri;
try {
mPlayer.setDataSource(gameActivity, resUri);
} catch (Exception e) {
}
}
public void setVideo(AssetFileDescriptor fd) {
this.fd = fd;
try {
mPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceCreated(final SurfaceHolder holder) {
Log.i(TAG, "surfaceCreated");
surfaceCreated = true;
mPlayer.setDisplay(holder);
try {
mPlayer.prepare();
} catch (Exception e1) {
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i(TAG, "surfaceDestroyed");
surfaceCreated = false;
if(mPlayer != null){
mPlayer.stop();
mPlayer.reset();
}
}
@Override
public void onPrepared(MediaPlayer player) {
Log.i(TAG, "onPrepared");
int wWidth = getWidth();
int wHeight = getHeight();
//
int vWidth = mPlayer.getVideoWidth();
int vHeight = mPlayer.getVideoHeight();
//
float wRatio = (float) vWidth / (float) wWidth; //
float hRatio = (float) vHeight / (float) wHeight; //
float ratio = Math.max(wRatio, hRatio); //
vWidth = (int) Math.ceil((float) vWidth / ratio); //
vHeight = (int) Math.ceil((float) vHeight / ratio); //
//
getHolder().setFixedSize(vWidth, vHeight);
mPlayer.seekTo(posttion);
mPlayer.start();
}
private void dispose() {
mPlayer.release();
mPlayer = null;
resUri = null;
if (fd != null) {
try {
fd.close();
} catch (IOException e) {
e.printStackTrace();
}
fd = null;
}
}
@Override
public void onCompletion(MediaPlayer mp) {
Log.i(TAG, "onCompletion");
dispose();
if(onFinishListener != null)
onFinishListener.onVideoFinish();
}
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
return true;
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
return true;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//不响应点击事件
//stop();
}
return true;
}
public void stop() {
mPlayer.stop(); //
dispose();
if(onFinishListener != null) {
onFinishListener.onVideoFinish();
}
}
int posttion;
public void pause() {
posttion = mPlayer.getCurrentPosition();
mPlayer.pause();
}
/**
*
*/
public void resume() {
if(surfaceCreated){
mPlayer.start();
}else {
try {
if(resUri != null)
mPlayer.setDataSource(gameActivity, resUri);
else if (fd != null) {
mPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
}
} catch (Exception e) {
}
}
}
public interface OnFinishListener {
public void onVideoFinish();
}
}
主要用到MediaPlayer来实现,至于MediaPlayer怎么用,我刚开始也不大懂,从网上找资料,很多,有兴趣google之,这里实现了一个视频播放的主要功能,另外有停止(跳过)的功能;
下面是PlayeVideo文件:
package org.cocos2dx.common;
import java.io.*;
import org.cocos2dx.lib.Cocos2dxActivity;
import org.cocos2dx.common.VideoView.OnFinishListener;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;
import android.content.Context;
import android.view.LayoutInflater;
public class playvideo implements OnFinishListener {
ViewGroup viewgroup = null;
private static playvideo pv = null;
private static XXXX instance = null;
VideoView videoView;
boolean isvideofinished = false;
//跳过按钮的view
private View layout = null;
//跳过按钮
private Button skipbtn = null;
public static playvideo shareInstance() {
if(null == pv) {
pv = new playvideo();
}
return pv;
}
public void ShowVideo(String name) {
System.out.println("Android ShowVideo 111111");
if (null == instance) {
return;
}
Log.i("", "name=" + name);
videoView = new VideoView(instance);
videoView.setOnFinishListener(this);
viewgroup = (ViewGroup)instance.getWindow().getDecorView();
try {
AssetFileDescriptor afd = instance.getAssets().openFd(name);
videoView.setVideo(afd);
} catch (IOException e) {
e.printStackTrace();
}
viewgroup.addView(videoView);
videoView.setZOrderMediaOverlay(true);
//加一个新的界面
Context mContext = ShenMoJie.getContext();
//LAYOUT_INFLATER_SERVICE表示从xml文件中加载布局
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layout = inflater.inflate(R.layout.video, null);
//添加一层
viewgroup.addView(layout);
//找到对应的按钮
skipbtn = (Button)layout.findViewById(R.id.skipbutton);
skipbtn.setOnClickListener(listener);
//用相对布局定义控件的位置
int width = instance.getWindowManager().getDefaultDisplay().getWidth();
int height = instance.getWindowManager().getDefaultDisplay().getHeight();
System.out.println("width: "+width+"height: "+height);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)skipbtn.getLayoutParams();
//left, top, right, bottom
params.setMargins(width*5/6, 10, 0, 0);// 通过自定义坐标来放置你的控件
skipbtn.setLayoutParams(params);
}
private OnClickListener listener = new OnClickListener() {
@Override
public void onClick(View v) {
Button btn=(Button)v;
System.out.println("Android listener");
switch (btn.getId()) {
case R.id.skipbutton:
System.out.println("Android listener->skipbutton");
skipVideo();
break;
default:
break;
}
}
};
public static void playVideo(final String name) {
System.out.println("Android playVideo 111111");
if (instance != null ) {
System.out.println("Android playVideo 22222");
instance.runOnUiThread(new Runnable() {
@Override
public void run() {
System.out.println("Android playVideo 333333");
playvideo.shareInstance().ShowVideo(name);
}
});
}
}
public static boolean isVideoFinished() {
//System.out.println("Android isVideoFinished");
return playvideo.shareInstance().isvideofinished;
}
public static void skipVideo() {
playvideo.shareInstance().videoView.stop();
}
@Override
public void onVideoFinish() {
viewgroup.removeView(videoView);
viewgroup.removeView(layout);
videoView = null;
layout = null;
isvideofinished = true;
System.out.println("Android onVideoFinish");
}
public static void SetActivity(XXXX ptActivity) {
instance = ptActivity;
}
}
其中XXXX是继承之Cocos2dxActivity的实例,用SetActivity方法,将之传进来,播放PlayVideo,传一个视频文件名,这里需要用到JNI,C++调用java函数,网上也是一大堆的资料,知之为知之,不知百度之,再不知谷歌之,然后就可以实现实现游戏的播放了,注意在VideoView中实现了,播放完成之后会将试图从viewgourp中移除;
到这一步还比较简单,由于之前没有做过Android原生开发,所以连怎么添加一个button都不知道(跳过按钮),不过了解了一下之后,还是实现了,虽然代码不知道写得合不合理;
//加一个新的界面
Context mContext = ShenMoJie.getContext();
//LAYOUT_INFLATER_SERVICE表示从xml文件中加载布局
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layout = inflater.inflate(R.layout.video, null);
//添加一层
viewgroup.addView(layout);
//找到对应的按钮
skipbtn = (Button)layout.findViewById(R.id.skipbutton);
skipbtn.setOnClickListener(listener);
//用相对布局定义控件的位置
int width = instance.getWindowManager().getDefaultDisplay().getWidth();
int height = instance.getWindowManager().getDefaultDisplay().getHeight();
System.out.println("width: "+width+"height: "+height);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)skipbtn.getLayoutParams();
//left, top, right, bottom
params.setMargins(width*5/6, 10, 0, 0);// 通过自定义坐标来放置你的控件
skipbtn.setLayoutParams(params);
详情请看PlayVideo文件,下面是vidio.xml
就这样,Android版也实现了播放跳过功能;