スキー旅行で一人ハッカソンをした話2019
昨年のスキー旅行に続いて今年も一人ハッカソンをしてきたのでその紹介です。
今年は忙しかった事もありうやむやにしようと思っていたのですが、直前になって「今年も何か作ってくるよな??」という圧力を受けたので準備期間2週間のハッカソンが幕を開けたのでした。
作成方針
昨年の罰金システムは、誰かがスマホで罰金ボタンを押すと「○○アウト〜」という効果音が流れて罰金が1000円加算されるというものでした(ハックされて数兆の罰金が発生した人もいたが)。罰金機能そのものよりも、突然アウトの効果音が流れてきて、何故押されたのかとか誰が押したのかという探り合いが始まったりする部分がウケていました。
昨年得た知見をもとに、今年は参加者間のコミュニケーションを誘発してみんなで楽しめるというコンセプトで作ってみました。
作ったもの
まずは作ったものの紹介です。
Webカメラで撮った宴会部屋の様子をテレビ番組に見立てて、テロップなどの効果を出して遊ぶようなものを作ってみました。ついでに今回スキー旅行に参加出来なかった人達のために、ビデオ会議で参加している気分を味わえる機能も作ってみました。
ここから実装した機能や利用した技術についてご紹介します。
基本構成
TV画面はブラウザ上で動作するWebアプリケーションで、ブラウザをフルスクリーン表示にしてテレビ上に表示しています。フロントエンドはReact、バックエンドはFirebaseを利用しています。Firebaseの機能で使っているのは、ファイルストレージとFireStoreのみです。
テレビ画面
テレビ画面はノートPCやスマートフォンのフロントカメラで撮った映像をvideoタグ上で表示しているだけです。
※iframe越しにカメラへのアクセスが出来ないみたいなので、サンプルを実行する場合には右上の「EDIT ON CODEPEN」を押してアクセスしてみて下さい。
テロップ
以下のものを実装しました。
- 時計
- サイドテロップ(右上に常時表示)
- 緊急ニュース
- 逮捕
- 字幕
常時表示している時計以外の部分はユーザー側で自由に出したり編集したりする事が出来ます。
技術的にはビデオタグの上にCSSでデザインした要素を置いているだけです。
宴会会場ではみんな基本的に画面は見ていないので、ただ表示するだけでは誰にも気付かれないため、ニュースや逮捕テロップの表示時に効果音や音声を乗せて注意を引くようにしてみました。
音声についてはブラウザの読み上げ機能を利用しています。
クライアント
テロップは参加者のスマートフォンから自由に設定出来るようにしました。今回は面倒なので認証等は設けず、特定のURLにアクセスするだけで利用出来るようにしました。クライアント側のUIは時間の都合で最低限って感じです。時間さえあればZOZO大忘年会みたいオサレなUIが作りたかったですね。(来年か!?)
クライアントとテレビ画面間のメッセージングについては、Cloud FireStoreを利用しています。リアルタイムにDBの更新を検知出来るのでなかなか便利です。
ちなみに去年はAWS上に構築していたので、メッセージングは外部サービスのPubNubを利用していました。
イベントが発生するたびにコレクションをドキュメントを追加していって、TV側ではコレクションを監視する事でイベントを受け取っています。
コレクションの変更監視にonSnapShotをそのまま利用すると起動時に既存のドキュメントも全てaddイベントが発生してしまったので、作成日でフィルタをかけてみました。何か良い方法があれば情報待ってます。
class DB { constructor() { this.db = firebase.firestore(); } sendEvent(type, data) { const event = { type: type, data: data, createdAt: Date.now() } this.db.collection('events').add(event); } subscribeEvent(onEventChange) { this.unsubscribe = this.db.collection('events').where('createdAt', '>', Date.now()).onSnapshot(collection => { collection.docChanges().forEach(change => { if (change.type === 'added') { onEventChange(change.doc.data()); } }) }); } }
CM機能
上のクライアント画面にもありますが、YoutubeのURLを指定してCMを流せる機能を実装しました。CMだけでなくYoutube上の音楽の再生とか、推しコンテンツの布教にも使われていました。
Youtube動作の埋め込みにはreact-playerを使えば一瞬です。
視聴者からのお便り
昨年作ったLINE bot(くいすきーたん)に画像や動画を送るとTV画面上に表示される機能です。
昨年とほぼ同じ機能なのですが、昨年は単に画像の表示だけをしていたので、画像を投稿しても誰にも気付かれずに終わってしまいました。
そこで今年は画像を表示するタイミングで「視聴者からのおたよりです」という音声を出して注目を集めるようにしてみました。おかげで割と盛り上がって、今年は色んな画像が投稿されました。
昨年システムをそのまま流用しているので、ここだけメッセージングがPubNubになっていたりとコードがキメラ状態です。
音声画像化機能
テロップの機能だけではすぐ飽きられるかなと思い、みんながしゃべっている内容を音声認識してGoogle画像検索した結果を表示する機能も実験的に付けてみました。結果から言うと、複数人が雑談しているような環境では音声認識がうまく働かずほとんど機能しませんでした。
音声認識だけはフロントエンドのみで完結する事が出来ず、Webサーバーを立てて対応しました。
マイクで拾った音声データをそのままWebSocket経由でサーバーに送信し、サーバー側でGoogle Cloud Speech-to-Textでテキスト化して、テキストをCustom Search EngineでGoogle画像検索し、テキストと画像検索結果をクライアントに送っています。ちなみに音声認識は1時間あたり150円ほどかかるため、常時つけていると破産してしまうので、ボタンでオンオフ出来るようにしました。(そしてほとんど利用しなかった)
サーバーはnode.jsで書いていましたが、WebSocketはHTTPS経由でないと動作しないため、Let's encrypt!でSSL証明書を取得してnginxをリバースプロキシにしました。
あと地味なハマりポイントですが、HTTPS経由だとWebSocket URLは wss://servername とする必要があります。(ローカルでのテスト時はHTTP経由で ws://localhost だったので、本番環境に切り替えた途端に上手く動かなくなって少しハマった)
Cloud Speech-to-Textの音声ストリーム認識では60秒が経過すると強制的に接続が切断される仕様になっていますので、今回みたいにずっと認識させ続けるには、音声が途切れたタイミングや一定時間毎でセッションを張り直す必要があります。その部分については下記コードを参考にしました。
また、node.jsから画像検索する部分については下記コードを参考にしました。
遠隔参加機能
今回はスキー旅行に参加出来なかったメンバーも居たため、スマートフォンからテレビのワイプ(窓みたいなやつ)で参加出来る機能を実装しました。バラエティ番組でよくあるようなやつですね。
(画像再掲)
これはスキーに参加出来なかったメンバーが毎晩参加してくれて非常に盛り上がりました。やっぱり人間が一番のコンテンツなんだと実感しました。
あとは外出中に留守番しているメンバーと連絡を取るのにも使えて地味に便利でした。
一見ハードルの高そうなビデオ通話ですが、WebRTC(と便利なライブラリ)の登場によってハードルが一気に下がりました。今回はNTT CommunicationsのサービスであるSkyWayを利用し、Peer-to-Peerで接続する構成にしました。個人レベルで利用する場合には無料のプランで十分利用可能かと思います。(月50万回の接続まで無料)
コードについては公式のサンプルコードが一番参考になるかと思います。
ほんの数十行のコードでビデオ通話が出来るのはすごいですね。
ちなみにiPhone+Safariだと同時に一つのオーディオストリームしか再生出来ないため、複数人の通話時には注意です。
今回の反省とか知見
実際に作って現場で運用してみると色々な反省点や知見が見えてきました。
- シンプルな機能でもコミュニケーションを誘発するものは長時間遊んで貰える
- 見ていない人にも注目して貰いたい時にはサウンドを利用すると良い。ブラウザの読み上げ機能は結構使える。
- テレビに出力する際には解像度の都合で端が切れる事があるので、外周はあまり使わない方が良い(テロップの文字が切れたりした)
- タブレットのブラウザ画面だとアドレスバーを隠せる機能が悪さをしているのかCSSのvhや%の挙動がPCと若干違う
- 想定していたけど確率が低いからと放置していたバグは必ず発生する
- 時間の都合でビデオ通話機能を同時に1人に制限していたため、複数人でバッティングしてしまった
- 2週間で作るボリュームではなかった(色々とバグや妥協が発生した)
まとめ
開発は計画的に!