iOSでCocos2dxを使ったアプリで動画を再生したい場合のことがcocos2d-x 勉強第6回「ムービーを再生してみる」 - 株式会社BEFOOLに書いてある(そのサイトが参照しているのがCocos2d-x Cross Platform Video Player – Part 1 iOS | Gethu Games – Blog)。
やり方としては、cocos2dxのライブラリ内であるcocos2dx/platform/ios/CCEAGLView に手を加えて、UIView
を継承したCCEAGLView
に、簡単に動画を再生できるMediaPlayer.frameworkのMPMoviePlayerControllerをaddSubviewしている。
その通りで再生できるんだけど、これだと動画が一番上に表示されてしまって、その上にメニューとかをCocos2dxのスプライトで出そうとしても動画に隠されてしまう。それは困るということで、動画を一番下に持って行きたい。addSubview
の代わりに、insertSubview:atIndex:0 としてみたが、動画が変なふうに再生されてしまうようになった。詳しくは、アプリをランドスケープモード専用にしているのに、動画はポートレイトの画面レイアウトのまま再生しようとする。
なんかiOSアプリの動作がよくわかってないので、どうしてそうなるのか、また解決方法もよくわからなかった。Orientationが変更された時にUIWindow
のRootViewController
へのコールバックが呼ばれてUIView
のCALayer
とかを辿るんじゃないかと勝手に想像してあれこれやってみたが、うまくいかない…。問題はeaglView
のbounds
がポートレイトの縦長比率になっているのが問題だと思うんだけど、どこでorientation
をハンドリングしてるのかわからなかった。
しかたがないので無理やりやってみた。まずCCEAGLView
に動画の再生を突っ込むのはやめて、cocos2dxのプロジェクトを作成した時にiOSの機種固有ソースとして生成されるAppController
をいじる。まずヘッダにいろいろ追加する。動画の現在の再生位置とかを取得したいので、MPMoviePlayerController
じゃなくAVFoundation.frameworkのAVPlayer
に変更した:
...
#import <AVFoundation/AVPlayer.h> #import <AVFoundation/AVPlayerItem.h> #import <AVFoundation/AVPlayerLayer.h>
... @interface AppController : NSObject { UIWindow *window;
UIView* mainUiview; AVPlayer* player; AVPlayerItem* playerItem; AVPlayerLayer* playerLayer; }
@property(nonatomic, readonly) RootViewController* viewController;
- (void) playVideo:(NSString *)path; - (void) stopVideo; - (void) pauseVideo: (BOOL)pause; - (void) seekVideo: (float) position; - (void) getVideoInfo: (float*)now duration:(float*)duration;
@end
|
AppControllerの実装ファイル .mmでRootViewController
へのビューとしてCCEAGLView
を与えているところを、動画をその下で再生したいので一段かまして、UIView
を挟んでやる。でそうするとなぜかbounds
にorientation
が反映されなくなってしまうので、フレームサイズを無理やりランドスケープ(横長)にしてやる。また動画を見せるためにCocos2dxで描画してない領域を透過させてやる必要があるので、opaque = NO
をセットする:
#import <AVFoundation/AVAsset.h>
+static AppController* theController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + + theController = self;
...
+ CGRect rect = [window bounds]; + { + + float w = rect.size.width, h = rect.size.height; + if (w < h) { + rect.size.width = h; rect.size.height = w; + } + } - CCEAGLView *eaglView = [CCEAGLView viewWithFrame: [window bounds] + CCEAGLView *eaglView = [CCEAGLView viewWithFrame: rect pixelFormat: kEAGLColorFormatRGBA8 depthFormat: GL_DEPTH24_STENCIL8_OES preserveBackbuffer: NO sharegroup: nil multiSampling: NO numberOfSamples: 0]; + eaglView.opaque = NO; + + + UIView* uiview = [[UIView alloc] initWithFrame: [window bounds]]; + [uiview addSubview:eaglView]; + mainUiview = uiview;
_viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil]; _viewController.wantsFullScreenLayout = YES; - _viewController.view = eaglView; + _viewController.view = uiview;
|
動画再生関連のメソッド:
- (void) stopVideo { [self killMoviePlayer]; }
- (void) pauseVideo: (BOOL)pause { if (pause) [player pause]; else [player play]; }
- (void) playVideo:(NSString *)path { NSURL* url = [NSURL fileURLWithPath:path]; playerItem = [[AVPlayerItem alloc] initWithURL:url]; player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
CALayer* parentLayer = self->mainUiview.layer; playerLayer = [AVPlayerLayer playerLayerWithPlayer:player]; playerLayer.frame = parentLayer.bounds; [parentLayer insertSublayer:playerLayer atIndex:0]; [playerLayer addObserver:self forKeyPath:@"readyForDisplay" options:NSKeyValueObservingOptionNew context:NULL]; }
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"readyForDisplay"]) { [playerLayer removeObserver:self forKeyPath:@"readyForDisplay" context:NULL];
[player play]; } }
-(void)killMoviePlayer { if (player == nil) return; [player pause]; [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
[playerLayer removeFromSuperlayer]; player = nil; playerItem = nil; playerLayer = nil; }
- (void) seekVideo: (float) position { if (player == nil) return; CMTime time = playerItem.asset.duration; time.value *= position; [player seekToTime: time]; }
- (void) getVideoInfo: (float*)now duration:(float*)duration { if (player == nil) { *now = *duration = 0; return; } *now = CMTimeGetSeconds(player.currentTime); *duration = CMTimeGetSeconds(playerItem.asset.duration); }
|
Cocos2dxで画面をクリアする設定はCCDirector::setGLDefaultValues()
内で
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
とダイレクトに書かれていて、アルファが1.0なのでこのままでは下に描かれている動画を上書きしてしまう。直接ここを書き換えてもいいんだけど一応ライブラリ内なので、アプリのソースClassees/AppDelegate::applicationDidFinishLaunching()
内で、glview->setDesignResolutionSize()
した後に glClearColor(0,0,0,0);
としてやる。
でiOSWrapperからAppController
内の動画関連のメソッドを呼び出すようにしてやり、リンクする追加ライブラリとCoreMedia.frameworkを追加してやることで、動画の上にスプライトで再生・一時停止ボタンや現在再生中の位置などを表示できるようになった。
動画の下にもなにか背景を描画したいとかなった場合にはどうしたらいいのかしらん。
追記
英語のページでのAndroid版のほうは、Intentを飛ばして別のアクティビティを起動して、動画を再生していた:
http://www.gethugames.in/blog/2013/09/cocos2d-x-cross-platform-video-player-part-2-android.html
それだったらiOSでも動画再生は別のViewControllerに切り替えてしまってもいいんじゃないの?