ねののお庭。

かりかりもふもふ。

【工作】画面共有した際にニコニコみたいにコメント流すアプリを作ってみた。

画面共有した際にニコニコみたいにコメント流すアプリケーション作ってみました。 アプリケーション名はReaction is All You Need. を略してRaynです。

画面共有している人はクライアントアプリのダウンロードが必須ですが、視聴者側はブラウザで完結します。

ソースはここにおいてます。github.com

そしてここから試せます。

raynw.azurewebsites.net

この手のアプリの先行例としてはパパコメントとかCommentScreenとかいろいろあるので車輪の再発明もいいところではありますね。 RaynはOSSなので好き勝手改造できる他、クライアント側がUnityなので文字だけじゃなくてコメントに合わせて3Dモデルを表示、とかも簡単に実装できます。

とはいえアプリケーション宣伝するつもりはあんまり無くて、いろいろ作ってる間に思ったこととか判明した事をつらつらと(ボヤキ程度ですが)を書き留めておきます。

構成

  • サーバ: ASP.NET Core (C#)
  • Webフロント: Vue.js + bluma(CSS)
  • クライアント: Unity (C#)

という感じで、webフロント以外はC#です。

webフロント、Blazor(WASMのお陰でC#がブラウザ上で動くやつ。サーバーサイドレンダリングじゃないよ)を使おうかと思ったんですが、(未だ何も分からん状態なので)Vue.jsに流れました。

サーバのホスト先にはAzure App Serviceを使ってます。楽ちん。

リアルタイム通信

結構苦労しました。いろいろ踏み抜いて。

視聴者側は原則ブラウザなので、まぁリアルタイム通信には(クライアント含めて)WebSocket使うよね~という事で一番最初pureなWebSocketで実装したんですが、まぁ 世の中 WebSocketがつながらない環境が結構ある という学びを得ました。WebSocketって結構古いプロトコルなんだけどなぁ...。

WebSocketを阻むものたちって記事があって知見が得られます。

問題は接続が阻まれるだけにとどまらず、Mono(Unityが使ってるC#のランタイム)のClientWebSocketクラスの実装が手を抜かれていて、proxy環境下で使い物になりません。 Monoの中の人はこんな事を言っています。

Our web socket implementation does not support any proxies or authentication. 
This is not a bug, but a missing feature and adding it is not currently on the roadmap.

github.com

おいおい嘘だろ、という感じです。Xamarinとかproxy環境下で通信する、とかよくあるユースケースな気がするけどそれでええんか...?という気がします。 httpさえつながれば問題ないという事なんでしょうか...。 なお、.NET (Core)の方はこのような問題はありません。

上記の2つ問題点、1.接続が安定しない、 2. プロキシ環境下でクライアントアプリがWebSocketを使えない、を避けるべくSignlRを使うことにしました。

SignalRはWebSocketが繋がらなかったらServer-Sent Eventsに、それでもつながらなかったらhttp poolingに~という感じでフォールバックするので接続の問題はクリアです。最悪http poolingなので、それなら環境に接続を阻まれる事は無いでしょう。

Monoのwebsocketのproxy問題も、最初からSignalR内部でWebSocketを使わないように設定しておけば、回避できます。

HubConnection connection = new HubConnectionBuilder()
    .WithUrl(url, option =>
    {
        option.Proxy = new WebProxy(proxy);
        // Monoのwebsocket実装はproxyが手抜きされてるので除外する。
        option.Transports = HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling;
    })
    .WithAutomaticReconnect()
    .Build();

option.Transports設定しなくても結果的には繋がるんですが、内部的にWebSocketだと繋がらないな~使えないな~という判断を下す時間が結構かかるので最初からWebSocketは除外しておくことが吉です。

UnityでSignalRを使うためには.NET Standard2.0のdllをnugetから拾ってくればよいので、雑に以下みたいにdllを抽出するスクリプトを吐き出すプログラムを書きました。依存が深い...。

github.com

これでなんか踏むのは最後やろ~とか思いきや、さらに踏みました。 私は基本的に開発はWindowsでやっていて、Windows上では全く問題なく動作しました。しかしMac用のバイナリをビルドするためMac引っ張り出して実機で動作確認してたら、MacOS上のMonoではSignalR動かないという残念な(たぶん)バグを踏み抜きました。

await connection.StartAsync();した時点で以下みたいなエラーに見舞われます。

f:id:nenoNaninu:20210502042134p:plain

対処する口探したんですが見つからなかった...。 しょうがないのでMac版クライアントはhttp pollingを実装してサーバにもpollingする口を作るなどの回避策を講じて対処しました。悲しい...。(先に動作確認しろ、という話ではあるんですが別にネイティブのライブラリ入れたわけでもないし...と思って油断してましたね)

というわけで、ブラウザ<->サーバ<->Unityクライアント間でリアルタイム通信するにしても、プロトコル別々にするの億劫だから全部SignalRでまとめちゃおう!みたいな事はやめた方が良さげ というのが今回の教訓。まさかWindowsで動いてMacOSで動かないとは...。 (MacOSでこのザマなので、いわんやモバイルをやという雰囲気がします、雰囲気ですが)

ならブラウザ<->サーバ<->Unityクライアント間でリアルタイムに通信したい場合なにがベストプラクティスなんですかね、というと良く分からない。ブラウザからの接続にはEnvoyプロキシ挟んでサーバとの通信は全てgRPCで取り纏めてしまうのがいいんでしょうか。それともブラウザはSignalRで、クライアントはまた別の何か、とかなんですかね。教えてエロい人。

SignalR、デフォルトでGroupの概念(?)というか実装が乗っかっているので一気通貫出来たら楽で良かったんですけどね~。

データベース

特にデータベースに拘りがないので、素朴にAzure Database for MySQLに乗っけていたんですが。 マネージドのDB、おもちゃとして放置しておくには高いねー...。という感情を得ました。 料金なんて最初から分かるでしょ、という話ではあるんですが。

おもちゃとして安上りにDB抱えておくなら、Azure SQL Database serverlessとかAmazon Aurora Serverlessとか使った方がいい感じしました。学び。

Azure App Service

これ衝撃的だったんですが、Azure App Service上にASP.NET Coreアプリをデプロイして、Visual Studioと接続する事でブレイクポイントはってデバッグする事ができます。知らなかった。

なお、ブレイクポイント貼れるのは App Serviceの構成時にOSをWindowsにした場合のみ。Linuxにするとブレイクポイントは貼れません。残念ながら。

UniTask

UniRx以上の人権を感じた。めっちゃ助かる。

おまけ

弊学の教員からの反応があった。すごいらしいです。わーい。