openFrameworksで動画の再生終了を検知する

当記事は、`macOS 10.12.5` ,`openFrameworks 0.9.8`,`Xcode8.3.3`の開発環境を想定したものとなります。

**「動画の再生終了後に処理を書きたいけど、再生が終わったってどうやって検知したらいいんだろう……？」**

そう思い悩んだ結果、イベントリスナーを作りました。
今回参考にしたものは、[ofEventのリファレンス](http://openframeworks.cc/documentation/events/ofEvent/)と以下のexampleです。
・events/customEventExample
exampleではシューティングゲームの当たり判定が実装されています。

#動画再生クラスの作成
動画再生には`ofVideoPlayer`を用いていますが、動画再生が終了したという判定はプロパティの`isPlaying`と言う`bool`で判定するしかないようです。`isPlaying`が`true`の時は再生中、`false`の時は再生していないと言うことになります。
しかしながら、update毎に`ofApp`で条件判定するのも使い回せないし格好悪いなぁ、と考えた結果、リスナー付きの動画再生クラスを作成することにしました。`openFrameworks`では、イベント関連の処理を実装するには`ofEvent`というクラスを利用します。
以下のように定義していきます。

```EventVideo.hpp
class EventVideo{
    public:
    enum E_GIMMICK{G_P_CHAIR,G_P_BED,G_M_CHAIR,G_M_BED};
    E_GIMMICK mGimmick;
    ofEvent <E_GIMMICK> mEndEvent;
    ofVideoPlayer mPlayer;
    void setup(string path,ofLoopType type,E_GIMMICK app);
    void update();
    void draw(float x , float y , float width , float height);
    void play();
    void stop();
    void pause();
    void setSpeed(float speed);
    void setFrame(int frameNum);
    void closeMovie();
    int getTotalNumFrames();
    bool mIsPlayed;
};
```
```EventVideo.cpp
#include "EventVideo.hpp"

void EventVideo::setup(string path , ofLoopType state , E_GIMMICK gimmick){
    mIsPlayed = false;
    mPlayer.load(path);
    mPlayer.setLoopState(state);
    mGimmick = gimmick;
}

void EventVideo::update(){
    mPlayer.update();
    bool isPlaying = mPlayer.isPlaying();
    if(mPlayer.isInitialized() && !mIsPlayed && !mPlayer.isPlaying()){
        ofNotifyEvent(mEndEvent , mGimmick);
        mIsPlayed = true;
    }
}

void EventVideo::draw(float x,float y,float width,float height){
    mPlayer.draw(x,y,width,height);
}

void EventVideo::play(){
    mPlayer.play();
    mIsPlayed = false;
}
void EventVideo::stop(){
    mPlayer.stop();
    mIsPlayed = true;
}

void EventVideo::pause(){
    mPlayer.setPaused(true);
}

void EventVideo::setSpeed(float speed){
    mPlayer.setSpeed(speed);
}

void EventVideo::setFrame(int flameNum){
    mPlayer.setFrame(flameNum);
}

void EventVideo::closeMovie(){
    mPlayer.closeMovie();
}

int EventVideo::getTotalNumFrames(){
    return mPlayer.getTotalNumFrames();
}
```
見てもらうとわかる通り、`EventVideo`クラスは`ofVideoPlayer`クラスのインスタンスと`ofEvent`クラスのインスタンスを内包したものになります。動画を扱うクラスをもっといじって実装することも考えましたが、工数の都合上このような形で実装しました。
`setup`メソッドの引数である`path`は動画のパス、`ofLoopType`の `state`は動画をループさせるのかどうか、`E_GIMMICK`は筆者の作品独自の`enum`定義で、どの種類の動画を流すのかを渡しています。
この辺りの引数については必要だと思うものを各自適宜追加してください。ただ、私見になりますが、動画再生クラスなので`path`と`ofLoopType`については渡してあげた方が便利だと思います！
`update`ではプレイヤーの更新と、終わったかどうかの判定を行います。`mIsPlayed`は独自の`bool`変数で、動画再生前か再生後かを判別するためのものです。動画クラスの`isPlaying`は現在再生中かどうかの検知しかできないため、このような変数を追加しています。動画の再生が終了した場合には、`ofNotifyEvent`で呼び出し元に通知します。
それ以降の処理は、呼び出し元から`ofVideoPlayer`のメソッドをそのまま呼び出しています。

#呼び出し元のクラスの実装
次に、呼び出し元のクラスを実装します。`Arduino`と連携する時にも使う、`ofAddListener`,`ofRemoveListener`を使用します。

```ofApp.hpp
#include "EventVideo.hpp"
class ofApp : public BaseApp{
    
	public:
		void setup();
		void update();
		void draw();
		void keyPressed(int key);
		void keyReleased(int key);
		void mouseMoved(int x, int y );
		void mouseDragged(int x, int y, int button);
		void mousePressed(int x, int y, int button);
		void mouseReleased(int x, int y, int button);
		void mouseEntered(int x, int y);
		void mouseExited(int x, int y);
		void windowResized(int w, int h);
		void dragEvent(ofDragInfo dragInfo);
		void gotMessage(ofMessage msg);
        void exit();
        void endMovie(CONST::E_GIMMICK & app);
        EventVideo mVideo;

```

```ofApp.cpp
#include "ofApp.hpp"

void ofApp::setup(){
	mVideo.setup("動画のパス", OF_LOOP_NONE, EventVideo::G_M_BED);
        ofAddListener(mVideo->mEndEvent, this, &ofApp::endMovie);
}

void ofApp::update(){
        mVideo.update();
}

void ofApp::draw(){
        mVideo.draw(0, 0, ofGetWidth(), ofGetHeight());
}

void ofApp::endMovie(EventVideo::E_GIMMICK & app){
        //動画終了後の処理
}

void ofApp::exit(){
        ofRemoveListener(mVideo->mEndEvent, this, &ofApp::endMovie); 
}
```
`setup`で引数に情報を入れた後、`ofAddListener`でイベントを設定します。引数は左から順に、対応する`ofEvent`のインスタンス、`Listener`クラス（大体`this`でいいと思います）、通知が来た際に実行するメソッドのアドレスを指定します。
`update`,`draw`は動画の表示のために呼びます。
そして、`endMovie`は動画が終了した際に呼ばれるメソッドです。例えば筆者の場合は引数から`enum`を受け取り、`switch`文にて条件分岐し、どの動画を流したかによって動画再生後の処理を分けました。

#イベント検知には`ofEvent`
今回は動画再生の検知に使用しましたが、`example`にもある通りシューティングの当たり判定など、様々なことに応用できます。
ポイントは、
・呼び出し先に`ofEvent`のインスタンスを定義する。
・呼び出し元に処理をお願いしたいタイミングで`ofNotifyEvent`を実行する。
・呼び出し元で`ofAddListener`をしてあげる。
・必要がなくなったら`ofRemoveListener`でリスナーの設定を削除する。

`event`処理になるので`delegate`のようにコールバックする形ですね。
`openFrameworks`で呼び出し元に処理をしてほしいけどどうやって処理してもらおう！ってなった時にはこちらの方法を試して見てはいかがでしょうか。