pixi.jsでは動画も描画する機能があります。その方法をつらつらと書いていきます。
2020-02-20追記:v5版を追加、過去のv4版は後ろの方に移動しました。
v5編
前提
- pixi.js v5.2.0使用
- CORS対策(ローカルサーバー)必須
基本
PIXI.Spriteクラスの静的メソッドであるfrom
を使うのが最もシンプルだと思われます。
// app本体生成
const app = new PIXI.Application();
document.body.appendChild(app.view);
// 動画をベースとするSpriteクラスを作成
const videoSprite = PIXI.Sprite.from("./assets/my-video.mp4");
app.stage.addChild(videoSprite);
オプション
fromメソッドにresourceOptionsオプションを指定することで、振る舞いを変えることもできます。
const videoSprite = PIXI.Sprite.from("./assets/my-video.mp4", {
resourceOptions: {
autoPlay: false,
updateFPS: 30,
}
});
ここでは特に重要と思われる2つのオプションだけ紹介します。
autoPlay
動画読み込み後、自動で再生するかどうかの設定。デフォルトではtrue
。
ただ、近年のブラウザはユーザー許可が無い状態では動画を再生できない仕様となっていることが多いため、trueにしてても再生されないことがあります。(ミュート状態にしているなど、ポリシーに適合した動画は再生可能です。例:iOSでのポリシー)
autoPlayはfalse
にしてclick
、pointerdown
などのユーザーイベントで再生を開始すると確実。
const button = new Button(); // 自前のボタンクラスなど
app.stage.addChild(button);
button.on('pointerdown', ()=> {
app.stage.removeChild(button);
videoSprite.texture.baseTexture.resource.source.play();
});
updateFPS
1秒間に何回テクスチャを更新するかを数値で指定します。デフォルトは0で、rendererのrender命令毎に更新する設定になってます。
pixi.jsデフォルトのTickerはrequestFrameAnimation
を使用しているため、基本は60回前後(端末に依ります)になると思われますが、もし元動画がFPS60以下の場合、更新に無駄が生じることになります。
パフォーマンスを気にする場合は適宜指定しておきます。
他のオプションについては以下を参照してください。
http://pixijs.download/dev/docs/PIXI.resources.html#.autoDetectResource
動画の制御
遠いですが、Spriteクラスから元となるHTML video要素(source)を辿って制御します。
videoSprite.texture.baseTexture.resource.source.play();
ちなみにv4以前までのようにvideoSprite.texture.baseTexture.source
でもアクセス可能ですが、こちらはdeprecatedになってます。
動画のプリロード
Pixi付属のローダーを使ってロード後にSpriteをセットアップすることもできます。
const loader = PIXI.Loader.shared;
// const loader = new PIXI.Loader(); // 新規にローダーを作る場合
loader.add("キー名", "./assets/my-video.mp4");
loader.load((loader, resources)=> {
const videoSprite = PIXI.Sprite.from(resources["キー名"].data);
})
ちなみにロード完了判定にはcanplaythrough
を使っているようです。
https://github.com/englercj/resource-loader/blob/bfb58b639eb03b91508db67a351fae95fc3beaff/src/load_strategies/MediaElementLoadStrategy.ts#L60
ただiOSはplaysinline属性を付けたりなどしないとロードがうまく動作しないようなので、サポートする場合は自前のローダーを用意する必要があるかもしれません。(要検証)
その他
- v4にあったパフォーマンス低下について修正されたかどうかは不明です。分かり次第追記します。
- v4まで
PIXI.VideoBaseTexture
というpublicなクラスがあったんですが、v5では直接触れなくなりました(内部的には存在)。ただfrom
が十分便利になったため、特に不都合はなさそうです。
v4編
前提
- pixi.js v4系使用(動画再生自体はv3でも可能)
- CORS対策(ローカルサーバー)必須
- Google Chromeのみチェック
はじめに
動画をただ再生するだけなら公式サンプルにもあるように非常に簡単です。
var app = new PIXI.Application(800, 600, { transparent: true });
document.body.appendChild(app.view);
// URLからテクスチャ生成
var texture = new PIXI.Texture.fromVideo('./assets/sample-video.mp4');
// スプライト生成
var videoSprite = new PIXI.Sprite(texture);
// 画面サイズにリサイズ
videoSprite.width = app.renderer.width;
videoSprite.height = app.renderer.height;
app.stage.addChild(videoSprite);
PIXI.Texture.fromVideoには動画のパスもしくはvideo要素そのものを渡すことができ、読み込みなども自動で行ってくれます。
ただ細かい制御をする方法がいまいち判然としなかったので、色々調べてまとめました。
動画の制御
SpriteやTextureクラスから直接制御する方法は無いようなので、元となるvideo要素(source)を辿って操作します。
var videoTexture = PIXI.Texture.fromVideo(resrc['sample'].data);
var videoSprite = new PIXI.Sprite(videoTexture);
var source = videoTexture.baseTexture.source;
// あとの扱い方は普通のHTML5 videoと同じ
source.pause();
なおテクスチャ元となるvideoBaseTextureは生成した段階で勝手に再生を始めてしまうので、都合が悪い場合は以下のような感じで初回プレイにpause()をかけます。
(自動再生については一応autoPlayという制御用プロパティがあるものの、インスタンス生成時に設定する手段がない)
var videoTexture = PIXI.Texture.fromVideo('assets/sample.mp4');
var source = videoTexture.baseTexture.source;
// 初回のplayを無効化する
source.addEventListener('play', (function() {
return function func() {
this.pause();
this.removeEventListener('play', func, false); // リスナ解除
};
})(), false);
やや強引ですが、再生可能状態になると実行されるPIXI.VideoBaseTextureの_onCanPlay関数を書き換えるという方法もあります。
PIXI.VideoBaseTexture.prototype._onCanPlay = function() {
this.hasLoaded = true;
if (this.source) {
this.source.removeEventListener('canplay', this._onCanPlay);
this.source.removeEventListener('canplaythrough', this._onCanPlay);
this.width = this.source.videoWidth;
this.height = this.source.videoHeight;
// prevent multiple loaded dispatches..
if (!this.__loaded)
{
this.__loaded = true;
this.emit('loaded', this);
}
if (this._isSourcePlaying())
{
this._onPlayStart();
}
else if (this.autoPlay)
{
// 以下を無効にする
// this.source.play();
}
}
};
動画のプリロード
動画を事前にロードしておきたいときもあります。
pixi付属のPIXI.loaderは動画読み込みに対応しているようなので、画像などと同様、addしてロードすることができます。
var pixiLoader = PIXI.loader;
var app = new PIXI.Application(800, 600, { transparent: true });
document.body.appendChild(app.view);
// ロード完了後の処理
pixiLoader.once('complete', function() {
var resrc = PIXI.loader.resources;
// resrc['sample'].dataはvideo要素そのもの
var videoTexture = PIXI.Texture.fromVideo(resrc['sample'].data);
var videoSprite = new PIXI.Sprite(videoTexture);
videoSprite.width = app.renderer.width;
videoSprite.height = app.renderer.height;
app.stage.addChild(videoSprite);
});
pixiLoader.add('sample', './assets/sample.mp4');
pixiLoader.load();
ただ使ってみて、PIXI.loaderは動画のロードが完了していないうちからcomplete(progress)イベントを発火してしまうことがあり、個人的にはあんまり信用できませんでした。
なので古典的ながら、addEventListenerを使って以下のようにするほうが確実でした。
var videoTexture = PIXI.Texture.fromVideo('assets/sample.mp4');
var source = videoTexture.baseTexture.source;
// canplaythrough発火をもってロード完了とする
source.addEventListener('canplaythrough', (function() {
return function func() {
this.removeEventListener('canplaythrough', func, false); // リスナ解除
// 以下ロード完了後の処理...
};
})(), false);
その他メモ
- loopの初期設定はtrue。ループさせない場合は
source.loop = false;
とする - loopがfalseの状態で動画終点以上にシークするとテクスチャが開始地点で固まってしまう?ので、終点位置に反映させたい場合は以下のようにギリギリにシークする。
source.currentTime = source.duration - 0.1;
v4でのパフォーマンス低下について
現状、v4系で動画を使用するとFPSがガタ落ちするようです。(環境による?)
https://github.com/pixijs/pixi.js/issues/4789
個人的にチェックした感だと、厳密にはv4.2.1あたりから急激に重くなりました。
原因はおそらくwebGLの処理に問題があると思われますが、とりあえず動画をメインにする場合はv4.1.1以下のバージョンを使用したほうが良さげです。