Cocos2d-x 勉強第6回「ムービーを再生してみる」

木内智史之介(シャッチョー)
ミンカさんけっこんしてくださいおねがいします(ズザー
SEGAさん、DIVAの筐体ください(ズザー

今回の目標

ミンカちゃんのクリスマスパーティー

http://minka-teatime.peatix.com/

突然ですが、拙者これに参加してくるでござる。
楽しみである半面、自分がただのファンになってしまったのではないかとハラハラしています…。
(結婚フラグは折れていませんように!)

今回の目標は「ミンカと一言話してくる!」ではなく…「ムービーを再生してみる」になります。

再生する動画はこれ

再生するムービーはミンカの動画を拝借させていただきます(ズザー

はい惚れた!智史之介は今ミンカに惚れ直しましたよー!

下調べ、下準備

グーグル職人の朝は早…くはないが、毎度ながらググるところから始まりです。
しかしのっけから苦難の道のりが待ち受けていそうな嫌な予感しかしない!
なかなか、cocos2d-xにおける動画再生の「オフィシャルな」対応が見つからないのだ。 サウンドの時のDenshionみたいな。

あちゃー!やるしかないかなー、これはやるしかないかなー。自作っていうの?俺がやっちゃうしかないかなー!
(訳: 先人は既におりますが、先人の内容を元に勉強させていただきます、ありがとうございます!)

先人達の記録

ビルド成功までの遠い道のりが…

まずは使用イメージをかためる

CocosDenshionを参考にしてこんな感じを想定します。

1
Befool::SimpleVideoEngine::sharedEngine()->playVideo("hoge.mp4");

よし、いいだろう。ケチのつけようがない完璧な想定をしてしまったな…。

トップレイヤーを改修してみる

まず最初に手をつけるのは、やはり入り口。HelloWorldScene.cppだ。
僕はミクさん大好きなので、過去使用したミクさん画像をそのまま使わせてもらう。
もちろんミンカの画像でもOKだ。
なに、長澤まさみがいい?いいに決まっているだろ!

ちなみに、先ほど想定した使用イメージがここで登場します。

Classes/HelloWorldScene.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "befool/SimpleVideoEngine.h"

bool HelloWorld::init()
{
    // snip...

    // 再生ボタン
    // もちろんミクさんの画像です。ペロペロ。
    auto playItem = MenuItemImage::create(
                                           "miku.JK.jpg",
                                           "miku.JK.jpg",
                                           CC_CALLBACK_1(HelloWorld::playVideoCallback, this));
    playItem->setPosition(Point(origin.x + visibleSize.width/2,
                                origin.y + visibleSize.height - playItem->getContentSize().height));
    auto menu = Menu::create(playItem, NULL);
    menu->setPosition(Point::ZERO);
    this->addChild(menu, 1);

    // snip...
}

/**
 * 再生ボタンが押された際の処理
 *
 * @return  void
 */
void HelloWorld::playVideoCallback(Object *pSender)
{
    befool::SimpleVideoEngine::getShared()->playVideo("renai-circulation.mp4");
}

ミドルレイヤーを改修してみる

今のままビルドしても、もちろん通るわけがないので、befool::SimpleVideoEngineの実装をしてあげる必要性があります。
このあたりは、

http://www.gethugames.in/blog/2013/09/cocos2d-x-cross-platform-video-player-ios.html

こちらの記事を参考にさせていただきました。

befool/SimpleVideoEngine.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef __BEFOOL_SIMPLEVIDEOENGINE_H__
#define __BEFOOL_SIMPLEVIDEOENGINE_H__

#include "cocos2d.h"

namespace befool
{
    using namespace cocos2d;

    class SimpleVideoEngine : public cocos2d::CCObject
    {
        public:
            void playVideo(char *path);
            static SimpleVideoEngine *getShared();
    };
}

#endif // __BEFOOL_SIMPLEVIDEOENGINE_H__

befool/SimpleVideoEngine.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include "SimpleVideoEngine.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
#include "iOSWrapper.h"
#endif

namespace befool {
    static SimpleVideoEngine *instance = NULL;

    /**
     * インスタンスの取得
     *
     * @return  befool::SimpleVideoEngine
     */
    SimpleVideoEngine *SimpleVideoEngine::getShared()
    {
        if (! instance) {
            instance = new SimpleVideoEngine();
        }
        return instance;
    }


    /**
     * 動画の再生
     *
     * @return  void
     */
    void SimpleVideoEngine::playVideo(char *path)
    {
#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS
        iOSWrapper::getShared()->playVideo(path);
#endif
    }
}

befool/iOSWrapper.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef __BEFOOL_IOSWRAPPER_H__
#define __BEFOOL_IOSWRAPPER_H__

#include "cocos2d.h"

namespace befool
{
    using namespace cocos2d;

    class iOSWrapper : public cocos2d::CCObject
    {
        public:
            void playVideo(char *path);
            static iOSWrapper *getShared();
    };
}

#endif // __BEFOOL_IOSWRAPPER_H__

befool/iOSWrapper.mm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "iOSWrapper.h"
#include "EAGLView.h"

namespace befool {
    static iOSWrapper *instance = NULL;

    void iOSWrapper::playVideo(char *path)
    {
        NSString *string, *stringPath;

        stringPath = [NSString stringWithUTF8String:path];
        string = [[NSBundle mainBundle] pathForResource:stringPath ofType:@"mp4"];

        [[CCEAGLView sharedEGLView] playTutorialVideo:string];
    }

    iOSWrapper *iOSWrapper::getShared()
    {
        if (!instance) {
            instance = new iOSWrapper();
        }
        return instance;
    }
}

「.mm」ファイルとは?

.mm拡張子のファイルは、どうやらC++のコードと、Objective-Cのコードを同居させることができるらしい。まじか!
これに関しては別途機会を用意して記事にしたいですな。

ローレイヤーを改修してみる

これをビルドしたら通りそうなものだけど、まだ通りません。EAGLView::playTutorialVideoが未定義なためですね。
このメソッドを、EAGLViewに追加で定義してあげることにします。

cocos2dx/platform/ios/EAGLView.h

1
2
3
4
5
6
7
8
9
10
+ #import <MediaPlayer/MediaPlayer.h>

  @private
      NSString *              markedText_;
      CGRect                  caretRect_;
      CGRect                  originalRect_;
      NSNotification*         keyboardShowNotification_;
      BOOL                    isKeyboardShown_;
      BOOL                    isKeyboardShown_;
+     MPMoviePlayerController *player;

cocos2dx/platform/ios/EAGLView.mm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
 * 動画の再生
 */
- (void) playTutorialVideo:(NSString *)path
{
    NSURL                           *url;

    url                         =   [NSURL fileURLWithPath:path];
    player                      =   [[MPMoviePlayerController alloc] initWithContentURL:url];
    player.view.frame           =   CGRectMake(0, 0, self.frame.size.height, self.frame.size.width);
    player.fullscreen           =   YES;
    player.scalingMode          =   MPMovieScalingModeNone;
    player.controlStyle         =   MPMovieControlStyleNone;
    [self                           addSubview:player.view];
    [player                         play];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(removeVideo)
                                                 name:MPMoviePlayerPlaybackStateDidChangeNotification object:nil];
}

/**
 * 動画の除去
 */
- (void) removeVideo
{
    if (player.playbackState == MPMoviePlaybackStatePaused || player.playbackState == MPMoviePlaybackStateStopped) {

        NSLog(@"Remove Video");

        [player.view                removeFromSuperview];
        [player                     release];
        player                  =   nil;

        [[NSNotificationCenter defaultCenter] removeObserver:self
                                                        name:MPMoviePlayerPlaybackStateDidChangeNotification
                                                      object:nil];

        if (cocos2d::Director::sharedDirector()->isPaused()) {
            cocos2d::Director::sharedDirector()->resume();
        }
    }
}

さあ、ここまで来ればビルドも通るだろう!と思いきや、

ld: symbol(s) not found for architecture -i386

というようなエラーがでてビルドが失敗となってしまう。どうやらライブラリが不足しているらしい。
ぐぐってみると「.framework」のようなものを追加してあげると解決されるそうです。今回追加するのは「MediaPlayer.framework」になります。

HelloCpp > Build Phases > Link Binary With Libraries > +

から「MediaPlayer.framework」を追加します。

これで、ビルド成功!!

まとめ

動作イメージ

ソースコード

https://github.com/8823-scholar/cocos2d-x-study/commit/4caa0562b7c44454b26b646fce27f7f3e806d8d0

実はAndroid非対応

ソースコードを見れば分かるのですが、実はAndroidを考慮できていません。
Androidへの対応は次の機会にでも行います。

次回に向けて

動画の再生に関する、オフィシャルなサポートを切に願ってやみません!
動画の再生ってあんまりニーズがないんだろうか。。

次回は、「アニメーションgifって使えるの?」というのをやってみようと思います。