HTML5時代のリアルタイムデータ取得
ユーザー生放送のHTML5プレイヤー化が完了し、モダンな作りになってきたニコニコ生放送のプレイヤーですが、内部で使われている方法もそれに合わせてかなりモダンなものとなっています。
今回、Chrome拡張によるニコ生コメントビューワーを作るにあたり、どのようにリアルタイムなコメント取得を行っているのかを調べたところ、WebSocketを使っていることが分かったので、そこで得られた知見を書いていこうと思います。
WebSocketとは
大雑把に言えば、「ブラウザ上でリアルタイム通信を行う技術」です。
今までブラウザ上でチャットのようなリアルタイム(に見せかけた)通信を行うためには、一定時間ごとにサーバーに取得しに行ったり、クライアントからのリクエストをサーバーが握ったままにしておき、データ更新の必要があるタイミングでレスポンスを返すという「ロングポーリング」と呼ばれる裏技的なテクニックを用いる必要がありましたが、これらはサーバーのリソースを多く消費したり、実装が複雑になりがちといった問題がありました。
WebSocketを使うと、こういった問題をシンプルに解決することが出来ます。
(より詳しい説明は他に譲ります。てへぺろ。)
プレイヤーの通信を覗いてみる
さて、とっつきのための前提知識はおおよそ整ったかと思われるので、早速実際にニコ生のコメントを取得してみようと思います。
まず、ブラウザで適当な ユーザー生放送 を開き、開発者ツールで通信内容を覗いてみましょう。(公式放送の場合、Flash版プレイヤーが立ち上がる可能性があるため。HTML5版プレイヤーで視聴出来る放送なら公式放送でもOKです。)
(以下、全てGoogleChromeの場合とさせて頂きます。他のブラウザをご使用の場合は適宜調べて下さい。見たいものは通信内容なのでそこまで大きく操作は変わらないはず。)
GoogleChrome 66なら、F12またはCtrl+Shift+iキーで表示される開発者ツール上の Network
というタブが該当します。
これを開いたら、一度ページを更新します。
放送を開いてから開発者ツールを開いた場合、ページを開いたときの通信が見られないからです。
さて、Nameという欄にブラウザが投げたリクエストがズラッと並んでいると思いますが、今回見たいのはWebSocketの通信だけなので、表示をフィルタリングします。
WS という部分をクリックしましょう。
[WS] = WebSocket の略です。
すると、 websocket
という行と、 {数字の羅列?audience_token=適当な文字列}
のような2つだけになったかと思います。
このうちどちらでも良いのでクリックし、 Frames
を選ぶと、晴れてWebSocketの通信内容を見られるようになります、おめでとうございます。
↑ Frame タブを選択
↑WebSocketの通信(イベントメッセージ)が見られる!!
この2つのログの違いですが、 websocket
のほうを覗いてみると、コメント情報が格納されているのが分かると思います。
こいつがどこから来ているのかというと、Headersタブに書いてあります。
この放送では ws://omsg102.live.nicovideo.jp:83/websocket
というコメントサーバーに接続しているようです。
ではもう一つのほうは、ということでひとつずつメッセージを覗いていくと、初めの方は放送を受信するために必要なセッション情報を受信し、たまにプレイヤー側から自発的にheartbeat(セッションを維持するための通信)を行っているようです。
通信の始めの方をよく見ると、接続すべきコメントサーバーの情報も含まれていることがわかります。
どうやらニコ生は、こういったメタ情報の取得にもWebSocketを使っているようです。
これに関しては完全に盲点で、始め僕はてっきり通常のAPIとのHTTP通信で取得しているものと勘違いしており、これに気がつくまでかなり時間がかかってしまいました。
ちなみに、 audience_token
のほうはどこにあるのかというと、実はページのDOMの中にJSONとして埋め込まれています。
これがわかっていれば話は早く、JavaScriptで取得する場合は
const audience_token = JSON.parse(document.getElementById("embedded-data").getAttribute("data-props"));
として、プロパティを辿っていけばOKです。
(ブラウザを経由しない外部クライアントの場合はどうすればいいか調べきれませんでした…。http://live.nicovideo.jp/api/getplayerstatus/{放送ID}
というAPIを使えばいいかなと思ったのですが、そこにそれらしき情報はありませんでした。)
(まあ、ブラウザ上で動かすという制約がない場合は直接TCPストリームでデータを取得すれば良いので、わざわざWebSocketを使う必要はないかもしれませんが)
(ちなみに、そっちの方法はすでにネット上に情報が溢れているので、そちらをご参照ください。)
実際にやってみた and 蛇足
※ここからはほぼ蛇足です。
なんで調べたかといえば、冒頭でも一瞬出しましたが、 Chrome拡張で動作するコメントビューワー が作りたかったからです。
余談ですが、このコードでは自力でWebSocketを作っていません。
アプリの生成するWebSocketのインスタンス生成をhookし、メッセージの受信イベントに相乗りすることでデータを取得しています。
こうすることで新たなWebSocketセッションを張らずに済むので、ニコニコのサーバーにも優しいのではないかと思います。
こういう仕組みにした理由としては、自前でAPIからデータを得ようと思うと自分でメッセージの生成や送信を行う必要があり、 ちゃんとハンドラ書くのめんどくさい サーバーに思わぬ負荷が掛かってしまう可能性があったからです。
…あ、hookといっても怪しい方法を取っているわけではなく、WebSocketの生成に対してこういうコードを入れているだけです。
// index.tsx (window as any).WebSocket = new Proxy(WebSocket, { construct(target: any, args: any[]) { const ws = new target(...args); // 生成したWebSocketをインスタンスに保存 pageWebsocketRepository.addWebSocket(ws); return ws; } });
Proxy, 知らなかった…とても便利…。(教えてくれたフォロワーさんありがとうございます)
上記Chrome拡張はそのうちストアでも公開する予定なので、その際はぜひ使ってみてください。
PullRequestも歓迎しております。(とはいえまだ機能がないに等しいのですが…)
それでは。