JavaScript
Chrome
parcel
FaceVTuber
バーチャルユーチューバー

誰でも簡単にバーチャルユーチューバー!FaceVTuberとdynamic import

FaceVTuberをリリースしました!!

追記:続編を書きました!!

バーチャルYoutuberになれる!FaceVTuberの定性的で定量的なUX改善

3/12にGoogleChromeとWebカメラさえあれば,バーチャルユーチューバーになれるFaceVTuberをリリースいたしました.

https://facevtuber.com

最近,キズナアイさんを筆頭とした,バーチャルユーチューバー流行ってますよね.
でも,Oculus LiftだったりHTC Viveだったりの高価なハードを用意する必要があったり,
Unityをセットアップして,それにPefectIKを入れて,モデルを用意して,リグをセットアップして・・・すごく大変!!

もっと手軽にバーチャルユーチューバーになりたい!!

という課題を解決したのがこのプロダクトです.

こんな感じで,デフォルトでモデルが入っていて,動画の出力もできます.

ほかにも,おかげさまで色々な方々に使っていただいており,外部のMMDモデルを入れて使っていただいたり,

ブルーバック・グリーンバックに対応しているので,こんな感じで,実況動画風に右下に置く形で使っていただいています!!

最近では,Webメディアに取り上げられたりもしました.

たぶん世界一手軽にバーチャルYouTuberになれるWebサイトが登場

このプロダクトは,2018年の1月8日に開発をはじめ,そこから約2ヵ月でローンチいたしました.
私自身,FE系のjavascriptはかなりご無沙汰で,モダンなjavascriptはかけないし,HTMLの知識もhtml5が流行った時ぐらいで,手癖でhtmlタグをHTMLタグと書いてしまうぐらいの実力でした.
そんな私がFaceVTuberを作った時に,"ハマった"Tipsをご紹介したいと思います.

FaceVTuberの特徴とビルドシステム

FaceVTuberには以下のような機能があります.

  • MMDモデルの描画
  • 顔認識
  • 動画出力
  • zipファイルからのMMDモデルのインポート

これらの機能ですが,すべてフロントエンドのjavascriptだけで構築しています.
そのためかなりの量のライブラリを利用しています.
今回,FaceVTuberで利用したビルドシステムはparcelです.

https://parceljs.org/

image.png

初期の開発段階では,babelifyやbrowserifyを単体で使っていました.しかし,だんだん機能が足りなくなってきたのと,色々とネットで使い方を調べるのですが,ドキュメントの内容が新旧合わさっており,コピペしても動かない.といったことが発生しました.
そして,そろそろwebpackの導入を考え始めたのですが,どうしてもReactやVueと同時に使うことが前提であったり,configファイルがあったりして,非常に難しく感じました.そこで,知り合いに教えていただいたのがparcelでした.
parcelはzero configurationと書かれている通り,本当に何も設定する必要がなく,私のようにjsに慣れていないものからすれば十分使いやすかったです.また,gruntやgulpのようなタスクランナーのような役割も担っているので,簡単なビルドであれば,本当にparcel一つで済むので,楽でした.
しかし,このparcelによるビルドシステムも意外なところでハマったことがあったので,それをご紹介したいと思います.

minifyと表示速度

最近のjsでは当たり前のようにminifyされていたりします.複数のjsのコードを1つのjsのコードにまとめて,サーバーとのハンドシェイクの数を減らすことで高速に描画する.といったテクニックです.しかし,今回の場合,このminifyが仇になりました.
FaceVTuberは顔認識やMMDの描画など機能が盛りだくさんです.そのため,ロジックのみでjsのファイルサイズが7MB程度に肥大化してしまいました.
これがどういうことが起こるかというと,GoogleChromeの実装として,jsのコードを全てダウンロードしてからでないと処理が始まりません.そのため,jsのダウンロードに時間がかかると,ファーストビューの表示速度が遅くなってしまう.という問題が発生してしまいました.

dynamic importの導入

dynamic importとは,動的にjsのモジュールを読み込む仕組みです.

Chrome、Safariで使えるJavaScriptのdynamic import(動的読み込み)

普通,parcelでimportと書いてしまうと,そこが構文解析されて,1つのjsのソースコードにminifyされてしまいます.しかし,

import('./module') 
    .then(module => {
        // 処理
    });

のように書くことで,parcelがminifyする際に自動で読み取って,実行時に非同期でサーバーにアクセスし,コードをダウンロードし,実行するように変換してくれます.
そのため,ファーストビューに必要なソースコードの量を小さくし,描画を高速に行うことができます.
しかし,このdynamic importでハマったところがありました.

dynamic importの構文解析にハマる.

FaceVTuberへは以下のようなコードがありました.

let object = null;

import('./moduleA').then(
    module => {
        object = new module.default();
    }
);

import('./moduleB').then(
    module => {
        const func = () => {
            const a = new module.default(object.value);
        };

        doucument.getElementById("button").addEventListener(
             "click",
             func,
             false
        );
    }
);

このコードはmoduleAとmoduleBを動的にimportしています.
moduleBでは,ボタンが押されると,func関数が呼ばれ,aという変数が初期化されることが分かると思います.
moduleAが読み込まれておらず,moduleBが読み込まれており,ボタンを押されたとき,この関数はエラーになると考えられます.
これはビルドをしたときエラーはでないのですが,実行時に,そんなことは関係なくエラーになりました.
parcelがビルドしたjsのソースコードを,GoogleChromeが読み込んだ直後,object.valueが読めない旨のエラーが出ていました.(not defined propertyだった気がします)
どうやら,parcelは賢く,このような構文が出てきたとき,静的に解析し,最適化してしまうようです.そのようなコードをブラウザで読んでしまうと,エラーになるようです.

dynamic importとparcelのbuild

parcelのビルドには2種類あります.

開発用のビルド.これは,モダンなjavascriptを書いたときにブラウザが解釈できるようにトランスパイルをします.また,開発用に使いやすいように一部の開発モジュールがバインドされたりします.

$ npx parcel index.html

そして,本番用のビルド.これは先ほどの開発用と同様にトランスパイルされるのと,minifyを行い,ソースコードをまとめてくれます.

$ npx parcel build index.html

開発時には気づかなかったのですが,本番用のビルドをしたときに,顔認識の速度が,60fpsから30fpsに落ちていました.
これはかなり致命的な挙動でした.原因を追ってみると,顔認識のモジュールをdynamic importした後から,極端な速度の低下が発生していました.
このバグの非常に厄介なところは,開発用のビルドでは起こらなかったことです.そのため,当たり前ではありますが,リリースする前の動作確認の際には必ず本番用ビルドすることをお勧めします.

解決法

これらのバグですが,解決しておりません.一部のモジュールのdynamic importには成功し,ファーストビューの高速化に成功しました.事例の部分ではファーストビューの表示に致命的な速度の低下にならなかったので,dynamic importをやめる.という解決策を取りました.なんでもdynamic importを使うと,このようなバグを引くことがあるので注意です.また,プロダクトを使ったユーザーの意見を聞きたいという思いのほうが強かったので,リリースの方を優先いたしました.これらのバグはすごく追いにくく,どこが原因かもわかりにくいです.そのため,この記事は一度読んで,頭の片隅に置いていただいて,ハマった時に思い出していただければと思います.

まとめ

今回はparcelを使った事例としてFaceVTuberをご紹介しました.
そして,制作の途中でdynamic importについて学び,parcelで使った際のハマった事例を紹介させていただきました.javascriptは色々な規格やツールが多く,ちゃんと使いこなすには,非常に難しい言語だと思います.しかし,わたしのようなあまりjsに慣れていない人でもバーチャルユーチューバーのWebアプリを作ることができるので,最近の技術の進化,ライブラリの拡充は本当にすごいです.

みなさんもよいjavascriptライフを!
そして,みなさん良いバーチャルユーチューバーライフを!!