1.はじめに
googleAssistantにあるSOUNDBAR
デバイスとして振る舞うサービスを作り、音声指示によって送られてくる具体的なコマンドもだいたい把握できました。他にも操作指示はあると思うのですが、喋りつかれるので最低限の「再生」「停止」「セットリストの変更」ができれば個人的には十分です。それぞれの指示で送られてくるコマンドについては「OK、グーグル。ジュークボックス、プレイ」にまとめています。
あとはそれらのコマンドに応じた振る舞いをするサーバーを作成するのみとなります。また、今回は全体の概要説明に留めて個々の具体的な作りは稿を改めようと考えています。
2.全体の造り
google Homeへの音声指示はgoogleAssistantから「フルフィルメント」(図中では'smart device butler')と呼ばれるAPIに対して「インテント」というJSON形式の送信データに変換されます(①)。フルフィルメントは送られてきたインテントの内容に応じてジュークボックスサーバーに再生や停止、セットリスト変更などの指示を送ります(②)。再生・停止のためにGoogleHomePlayerを実行したり、停止したりします(③)。
また、ジュークボックスサーバーは動作状態情報やセットリスト情報をフルフィルメントへ戻します(④)。フルフィルメントはgoogleAssistantにジュークボックスサーバーの状態やセットリストの情報を返します(⑤)。
そしてまた、動作確認やセットリスト作成を容易にするため、コマンドラインからジュークボックスサーバーへコマンドを送れるようにしました(⑥)。
3.プロセスとプロセスの間には
システム(と呼んで構わないと思うのですが)全体は登場人物が多いのですが、ジュークボックスとしての機能に絞ると下図のような造りにしています。
作り方としてシンプルなモックアップを作って思いつくままに欲しい機能を拡張していくというノンプランアプローチを採用しました。
だいたい次のような段階を経ています。
- ジュークボックスサーバーがGoogle Home Playerをexecで実行できるようにする。
- CLIから再生指示や再生リストの送信をできるようにする。
- フルフィルメントから再生指示や再生リストの切り替え指示をできるようにする。
- ジュークボックスサーバーがGoogle Home Playerをfork&execで実行できるようにする。
面倒なのはプロセス間通信(IPC)とGoogle Home Playerの非同期実行でした。以下簡単に説明します。
3.1.IPCクラス
図中ではClass IPC
が担当します。ジュークボックスサーバーに再生リストを作らせるためにCLIからmp3ファイルのパスを連続して送ったりしますが、こういう処理ではサーバー側の処理がちょっと遅れると拾いこぼしがおきたりするのでバッファ(というかキュー)を持たせるのが安全です。あんまり大仰な仕掛けを持たせるのも避けたかったので、ここではPHPのセマフォ関数にある共有メモリ系の機能を使いました。セマフォ関数にはもっとキュー向けのメッセージキュー機能があるのですが、メッセージキューにはメッセージキュー全体のメモリ割り当てがあり、mp3ファイルのパス情報のような比較的長い文字列を扱うとちょいちょいメモリ制約に当たりがちだったりするので(もちろん割り当てサイズを増やせば回避できるのですが)そちらは使いませんでした。
やりとりのキューにはリングバッファとして使える配列構造を作成して、その配列を丸ごと共有メモリに押し込むというやり方をしています。サイズが大きくなるとコスト的にどうなのという気がしなくもないのですが、今回のようなかわいらしいサイズなら問題にはならないようです。
3.2.非同期実行クラス
非同期実行はフルフィルメントが再生要求後の制限時間内に応答を返すために必要でした。ジュークボックスサーバーがGoogle Home Playerを同期実行(exec関数とかsystem関数とか)させるとフルフィルメントからの要求にジュークボックスサーバーが(再生終了するまで)応答を返せないのでフルフィルメントが待ってしまいます。
Google Home Notifierのように即座に終了するものを使うと待ち時間は小さくなりますが、今度は曲の再生終了が拾えなくなり、リストに沿って順番に再生することができなくなります。
非同期実行はよくあるfork&execをpcntl_fork()、pcntl_exec()で実現するだけですが、ジュークボックスサーバーの子プロセスとなるGoogle Home PLayerの動作状態を拾うのが面倒でした。PCNTL関数にはpcntl_waitpid()があって、これを使えば子プロセスの状態を拾えそうですが、使い方にクセがありました(使い方を間違えているだけかもしれないのですが)。
4.ジュークボックスの造り
ジュークボックスそのものは送られてきたコマンドを解釈してそれぞれのロジックに分岐実行させるswitch~caseの塊になっています。
主な機能は再生、停止、再生リストの作成と再生リストをセットリストとして読み書きする機能、セットリストの切り替えといったものがあります。それらのロジックそのものは別に面白いものではないのですが、Google Home Playerはプロセスをkillしても再生が止まらないので、どうやって止めるかというのが多少の工夫どころでしょうか(単に無音のmp3を再生させれば良い)。
5.おわりに
ジュークボックスのシステムはGoogle Home Player以外はPHP(PHP8.1.10)で作成しました。ボリューム的にソースコードなど出していませんが、(参考になるのかどうかわかりませんが)部分的なものについては稿を改めて出そうと思っています。