Cocos2dx播放mp4文件(IOS和Android)

(头注: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的上层操作层,有跳过影片按钮。
我就功能点而言,介绍其中的两个类。
第一个是LHVideoPlayerImplCpp.h/mm文件,这个是负责给2dx调用的。该类有两个静态方法:

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。
a、这个方法是播放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、影片结束之后,会自动移除视图;
git地址: 点击打开链接

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版也实现了播放跳过功能;










你可能感兴趣的:(Cocos2dx播放mp4文件(IOS和Android))