ライブ配信レイアウトを作るNode.jsのフレームワーク

こんにちは。普段フロントエンド（とか）エンジニアをしていますが、趣味でRTAをやっている繋がりでオンラインやオフラインのゲームイベントの運営もやってたりします。

この記事、別の話をしようと思っていましたが、悩んだ結果、運営をやっている繋がりでNodeCGというフレームワークを使ったりmaintainerをしたりしている話をしようと思います。

追記：NodeCGのDiscordサーバーに日本語チャットを作ってあるので気軽に参加してください https://discord.gg/jgQRuJK

# NodeCG

https://nodecg.com
https://github.com/nodecg/nodecg

![NodeCG](https://raw.githubusercontent.com/nodecg/nodecg/master/media/splash.png)

一言で言うと「ライブ配信のレイアウトをWeb技術で作るフレームワーク」です。

ちょっと何言っているのかわからないですね。

噛み砕くと

- Twitch、YouTube Live、ニコニコ生放送などのライブ配信で
- 動的でリッチな見た目の画面表示を作るための
- 大枠の仕組みを提供するフルスタックフレームワーク

です。

まだどんなものかよくわからないので、サンプルを動かしてみましょう。下のはGamesDoneQuick[^1]というRTAイベントで使われた、NodeCGで作られたレイアウトです。(https://github.com/gamesdonequick/sgdq18-layouts)
![Kapture 2018-12-08 at 3.31.39.gif](https://qiita-image-store.s3.amazonaws.com/0/206860/dd53d5f4-3966-66f8-dbab-7e54758257b3.gif)

ついでなので、実際のイベントで動いているところを見てみましょう
https://youtu.be/tsUvZ9yiN_U?t=1433

[^1]: 同時視聴10万人以上、1週間で2億円以上寄付金を集めるマンモスチャリティイベント。RTAを披露して視聴者から寄付金を募る。

## おおざっぱな説明

バックエンドにexpressが使われ、HTML+CSS+JSでブラウザにレイアウトを表示します。

NodeCGでレイアウトを構築するときにつくる、フロントエンドとバックエンドがセットになったものをbundleと呼んでいます。1つまたは複数のbundleを組み合わせて1つのNodeCGアプリを作るイメージです。

1つのbundleは

- **dashboard**: レイアウトを操作するページ
- **graphics**: 表示するレイアウトのページ
- **extension**: サーバーサイド (今回は省略)

の3つに分かれています。

dashboardで操作すると、NodeCGのサーバーサイドを通してgraphicsに反映され、追加でサーバーサイドのロジックを書きたい場合extensionを定義する、といったものです。

![nodecg bundle architecture (1).png](https://qiita-image-store.s3.amazonaws.com/0/206860/12ce87de-adc7-0770-2ddd-eaf5b2a91bc3.png)

## つくってみる

せっかくなので何か作ってみましょう。必要なものはNode 8以上です。

### NodeCGをcloneして準備する
なかなかセットアップが原始的ですが、許してください。2018年ですが本当に`bower`という文字が見えています。許してください。(ここでしか使う必要はないです。)

```
$ npm i -g nodecg-cli bower
$ nodecg setup
$ mkdir bundles/hoge-layout
$ cd $_
$ npm init -y
```

### bundleの設定を書く

`bundles/hoge-layout`のほうのpackage.jsonに設定を書いていきます。https://nodecg.com/tutorial-5_manifest.html

```json:package.json
{
  "nodecg": {
    "compatibleRange": "^1.0.0",
    "dashboardPanels": [
      {
        "name": "sample-panel",
        "title": "Sample Panel",
        "file": "sample-panel.html"
      }
    ],
    "graphics": [
      {
        "file": "index.html",
        "width": 1280,
        "height": 720
      }
    ]
  }
}
```

### dashboardを書く

`dashboard`ディレクトリを作り、dashboardに数字をカウントアップするボタンを付けてみます。

```html:dashboard/sample-panel.html
<!DOCTYPE html>
<html>
<body>
	<button id="increment">+1</button>
	<script>
		const incrementEl = document.getElementById('increment')
		const countReplicant = nodecg.Replicant('count')

		incrementEl.addEventListener('click', () => {
			countReplicant.value += 1
		})
	</script>
</body>
</html>
```

### graphicsを書く

`graphics`ディレクトリを作り、数字を表示するレイアウトを作ってみます。

```html:graphics/index.html
<!DOCTYPE html>
<html>
<body>
	<div id="count"></div>
	<script>
		const countEl = document.getElementById('count')
		const countReplicant = nodecg.Replicant('count', {defaultValue: 0})

		countReplicant.on('change', newValue => {
			countEl.innerText = newValue
		})
	</script>
</body>
</html>
```

### 動かす

```
$ cd ../..
$ node .
```

これでNodeCGが起動するので、`localhost:9090`でdashboardを出してみます。
![Screen Shot 2018-12-08 at 01.42.41.png](https://qiita-image-store.s3.amazonaws.com/0/206860/5061259b-5fe0-5759-9d16-5aabea074b4d.png)

右上の`GRAPHICS`から、配信レイアウトを出せます
![Screen Shot 2018-12-08 at 01.43.56.png](https://qiita-image-store.s3.amazonaws.com/0/206860/ded3b949-6625-1950-4c68-b9902603495a.png)
...他に何も書いていないので数字しかいません。実際にレイアウトを作る際は、HTML、CSS、SVGなどを駆使して上の動画みたいなレイアウトを作ります。

さておき、dashboardのボタンを押してみましょう。
![Kapture 2018-12-08 at 1.52.14.gif](https://qiita-image-store.s3.amazonaws.com/0/206860/0b20bba9-1d24-328f-613d-5207fc200082.gif)
いい感じでリアルタイムに動いています。

### 実際の配信での使い方

今までブラウザでレイアウトを表示してきましたが、実施の配信で使うときはブラウザに写しません。何を使うかというと配信ソフトについているブラウザソースの機能です。

![Screen Shot 2018-12-08 at 03.39.53.png](https://qiita-image-store.s3.amazonaws.com/0/206860/55987916-205d-5042-4409-7bcd72c3c660.png)
![Screen Shot 2018-12-08 at 03.40.24.png](https://qiita-image-store.s3.amazonaws.com/0/206860/fbcffee4-bb80-a11e-8724-df74943df574.png)

例えばOBS Studioは、内蔵のChromiumを使って配信に直接表示することができ、ブラウザに写してそれをキャプチャ、といった煩わしい手順がいらないです。こういった理由で、このような動的なレイアウトはNodeCGでなくてもHTML+CSS+JSが使われることが多いです。

## すこし詳しく説明

全機能紹介するとキリがないので、抜粋しつつ

### 機能

dashboardとgraphicsの中のJSでは、`window.nodecg`が定義されていて、いろいろなプロパティがいますが、例で使ったのは`nodecg.Replicant`です。
これはNodeCGの中にある簡易的なデータベースのためのAPIで、代入された値を保存して同時に変更をlistenerに伝える仕組みです。dashboardで数を増やしたら、その瞬間サーバーを通してgraphicsに`'changed'`イベントが伝えられ、新しい値が伝わります。
Replicantは実際にファイルに保存されるため、何かの不具合でサーバーが落ちたとしても値が保存されます。（やり直しがないライブ配信ではかなり大事な機能です）

他にもいろいろ機能があります

- **message**: データのやり取りでなく、イベントを直接やり取りできる機能
- **sound**: 音声ファイルをアップロードしておき、キューを送ることでレイアウトで音を再生できる機能
- **asset**: 画像や動画ファイルをアップロードして、レイアウトやダッシュボードから参照できる機能

など。

基本的に薄く、unopinionatedに作られているため、例のようにplainなHTMLで作ったり、Reactで書いたりVueで書いたりなんでもできます。 (フレームワークの部分はPolymer 2でできていますが、中身を作る際は関係ありません)

### 中身の技術

Replicantの代入、変更、mutateに対してJSのProxyを挟むことで、変更を検知して各場所にWebSocketを通じて通知することで、すべての場所で同じデータを見ているような仕組みになっています。。

Proxyは興味深く、IE11で使用できずPolyfillも不可能なためまだ流行っていませんが、IE11を最初からサポートしていないNodeCGではこのような使い方ができています。(将来VueがリアクティビティをProxyで作り直すことも発表されています。)

(dashboardで値の変更)→(proxyがサーバーに通知)→(サーバーがgraphicsに通知)→(graphicsの表示が変更)

フロントエンドのリアクティブなフレームワークがHTMLとJSの間でリアクティビティを実現しているのと同じような関係を、クライアントとサーバーの壁を超えて実現しているあたり面白い仕組みだと思っています。

## Maintainerとして

個人的な話をすると、1年半くらい前、このフレームワークを使って関わっているイベントのレイアウトを作るのが、本格的にプログラミングを始めるきっかけでした。
その後、TypeScriptの型定義をつけたり、テストのリファクタリングしたりしていたら、maintainerにならないかと誘われました。それまでは作者のLangeが1人でメンテナンスしてきたこともあり、まだまだ発展途上なフレームワークなので、できるだけ貢献していきたいです。

あと、海外では幅広いイベントで使われているのですが、日本で使っているのが自分ともう1人しか知らないので、もっと流行ってほしいですね！