pixi.jsで動画を描画する

pixi.jsでは動画も描画する機能があります。その方法をつらつらと書いていきます。
*2020-02-20追記*：v5版を追加、過去のv4版は後ろの方に移動しました。

# v5編
##### 前提
- pixi.js v5.2.0使用
- CORS対策（ローカルサーバー）必須

## 基本
PIXI.Spriteクラスの静的メソッドである`from`を使うのが最もシンプルだと思われます。

```js
// 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オプションを指定することで、振る舞いを変えることもできます。

```js
const videoSprite = PIXI.Sprite.from("./assets/my-video.mp4", {
  resourceOptions: {
    autoPlay: false,
    updateFPS: 30,
  }
});
```

ここでは特に重要と思われる2つのオプションだけ紹介します。
##### autoPlay
動画読み込み後、自動で再生するかどうかの設定。デフォルトでは`true`。
ただ、近年のブラウザはユーザー許可が無い状態では動画を再生できない仕様となっていることが多いため、trueにしてても再生されないことがあります。（ミュート状態にしているなど、ポリシーに適合した動画は再生可能です。例：[iOSでのポリシー](https://webkit.org/blog/6784/new-video-policies-for-ios/)）

autoPlayは`false`にして`click`、`pointerdown`などのユーザーイベントで再生を開始すると確実。

```js
const button = new Button(); // 自前のボタンクラスなど
app.stage.addChild(button);
button.on('pointerdown', ()=> {
  app.stage.removeChild(button);
  videoSprite.texture.baseTexture.resource.source.play();
});
```

##### updateFPS
１秒間に何回テクスチャを更新するかを数値で指定します。デフォルトは0で、rendererのrender命令毎に更新する設定になってます。
pixi.jsデフォルトのTickerは`requestFrameAnimation`を使用しているため、基本は６０回前後（端末に依ります）になると思われますが、もし元動画がFPS60以下の場合、更新に無駄が生じることになります。
パフォーマンスを気にする場合は適宜指定しておきます。


他のオプションについては以下を参照してください。
http://pixijs.download/dev/docs/PIXI.resources.html#.autoDetectResource


## 動画の制御
遠いですが、Spriteクラスから元となるHTML video要素（source）を辿って制御します。

```js
videoSprite.texture.baseTexture.resource.source.play();
```

ちなみにv4以前までのように`videoSprite.texture.baseTexture.source`でもアクセス可能ですが、こちらはdeprecatedになってます。

### 動画のプリロード
Pixi付属のローダーを使ってロード後にSpriteをセットアップすることもできます。

```js
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のみチェック

## はじめに
動画をただ再生するだけなら[公式サンプル](http://pixijs.io/examples/#/basics/video.js)にもあるように非常に簡単です。

```js
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）を辿って操作します。

```js
var videoTexture = PIXI.Texture.fromVideo(resrc['sample'].data);
var videoSprite = new PIXI.Sprite(videoTexture);
var source = videoTexture.baseTexture.source;

// あとの扱い方は普通のHTML5 videoと同じ
source.pause();
```
参考：[video要素のメソッドなど](https://www.w3schools.com/tags/ref_av_dom.asp)


なおテクスチャ元となるvideoBaseTextureは生成した段階で勝手に再生を始めてしまうので、都合が悪い場合は以下のような感じで初回プレイにpause()をかけます。
（自動再生については一応autoPlayという制御用プロパティがあるものの、インスタンス生成時に設定する手段がない）

```js
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関数を書き換えるという方法もあります。

```js
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してロードすることができます。

```js
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を使って以下のようにするほうが確実でした。

```js
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の状態で動画終点以上にシークするとテクスチャが開始地点で固まってしまう？ので、終点位置に反映させたい場合は以下のようにギリギリにシークする。

```js
source.currentTime = source.duration - 0.1;
```

#### v4でのパフォーマンス低下について
現状、v4系で動画を使用するとFPSがガタ落ちするようです。（環境による？）
https://github.com/pixijs/pixi.js/issues/4789

個人的にチェックした感だと、厳密にはv4.2.1あたりから急激に重くなりました。
原因はおそらくwebGLの処理に問題があると思われますが、とりあえず動画をメインにする場合はv4.1.1以下のバージョンを使用したほうが良さげです。

## 参考
- [pixi.jsコントリビューターの人による解説](http://www.html5gamedevs.com/topic/16450-pixi-loading-video-files/)
- [VideoBaseTextureのソース](https://github.com/pixijs/pixi.js/blob/f2c54d3decedc7d041d64488a07219f613637f68/src/core/textures/VideoBaseTexture.js)