当記事は、macOS 10.12.5
,openFrameworks 0.9.8
,Xcode8.3.3
の開発環境を想定したものとなります。
「動画の再生終了後に処理を書きたいけど、再生が終わったってどうやって検知したらいいんだろう……?」
そう思い悩んだ結果、イベントリスナーを作りました。
今回参考にしたものは、ofEventのリファレンスと以下のexampleです。
・events/customEventExample
exampleではシューティングゲームの当たり判定が実装されています。
#動画再生クラスの作成
動画再生にはofVideoPlayer
を用いていますが、動画再生が終了したという判定はプロパティのisPlaying
と言うbool
で判定するしかないようです。isPlaying
がtrue
の時は再生中、false
の時は再生していないと言うことになります。
しかしながら、update毎にofApp
で条件判定するのも使い回せないし格好悪いなぁ、と考えた結果、リスナー付きの動画再生クラスを作成することにしました。openFrameworks
では、イベント関連の処理を実装するにはofEvent
というクラスを利用します。
以下のように定義していきます。
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;
};
#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
を使用します。
#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;
#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
で呼び出し元に処理をしてほしいけどどうやって処理してもらおう!ってなった時にはこちらの方法を試して見てはいかがでしょうか。