にじボイスとHonoでサーバーレスライブ配信ラジオを作った話
この記事はHono Advent Calendar 2024 7日目の記事です。
TL;DR
-
にじボイスAPIの先行テスト許可をもらったので、それを使って「ずっとしゃべり続けるラジオ」を作ろうと思った
-
実際に開発中にAPIが正式リリースされてしまったけれど、結果的に複数ユーザーで同期再生できるサーバーレスな「Niji Radio」を作成
デモ
※コスト面の都合で予告なく停止する可能性があります。ごめんなさい!
はじめに
先日、にじボイスAPIが正式公開されました。
にじボイスはAIを活用した音声生成プラットフォームで、感情豊かな音声を簡単に生成できるのが特徴です。
テストユーザーとして利用許可をもらったとき、「24時間ずーっとしゃべり続けるWebラジオ」を作ってみたいと思い立ちました。
実装経緯
ライブ配信を考えると、OBSやCloudflare Streamなどの配信サービスを使う手があります。しかし、配信用のPCを常に稼働させておくのは手間だし、できればサーバーレスで全部回したい。
そこで「音声を都度生成して、それを同期再生する仕組み」をCloudflare Workers+Durable Objectsを使って実現することにしました。ユーザー同士が同じ音声をほぼ同時に再生できるよう、WebSocketでタイミングを合わせています。
アーキテクチャ概要
フロントエンド :
-
単純なHTML+JavaScriptで構成し、Audio APIで音声再生をコントロール
-
WebSocketでバックエンドから同期情報(今どのトラックが何秒目かなど)を受け取って再生位置を合わせる
-
プレイリスト情報もリアルタイムに反映
バックエンド(MusicSyncObject) :
-
Durable Objectによるプレイリストと再生状態の一元管理
-
プレイリストの追加・置換・クリア機能
-
トラック終了時には次トラックへ自動的に切り替え
-
WebSocketで全クライアントへ同期状態を通知
-
Hibernation APIでセッション管理
技術スタック
-
Runtime : Cloudflare Workers
-
State Management : Durable Objects
-
Framework : Hono(軽量なWebフレームワーク)
主要機能の解説
プレイリスト管理
Niji Radioは、以下のエンドポイントを通じてプレイリストを操作します。
-
POST /api/setPlaylist
:新しいトラックをプレイリスト末尾に追加 -
POST /api/uploadTrack
:音声ファイルのアップロード。R2に保存し、保存したものをsetPlaylistで追加する
これらのリクエストを受けてDurable Objectが内部状態を更新し、必要なら再生を即時反映します。
再生中にsetPlaylist
が行われれば、現在の曲が終わったタイミングで次の曲へと自動的に進みます。
同期メカニズム
同期にはWebSocketを使っています。クライアントが接続すると、以下のような情報がサーバーから送られます。
{
"type": "sync",
"elapsedTime": <再生開始からの経過ミリ秒>,
"trackUrl": "<現在再生中のトラックURL>",
"duration": <トラックの全長ミリ秒>
}
クライアントは受け取ったelapsedTime
をもとに、Audio要素を指定の再生位置からスタートさせ、他のユーザーと再生タイミングを揃えます。曲が終わるとchangeTrack
メッセージが全クライアントに送られ、次の曲へと自動的に切り替わります。
残り時間表示と次曲再生
フロントエンド側では、この同期情報をもとに
-
今の曲があと何ミリ秒で終わるか
-
次の曲までどれくらいの時間があるか
といった情報をリアルタイムで表示できます。
まとめ
Niji Radioは、Cloudflare WorkersとDurable Objectsを活用することで、複数ユーザー間での同期音声再生をサーバーレスかつスケーラブルに実現した例です。
グローバルに展開できるWorkers、ステートフルなDurable Objectsを組み合わせることで、低レイテンシでリアルタイムな体験を提供できました。
時間があれば、今後は
- コメントを音声合成で読み上げて返信する機能
- 本当の24時間連続稼働
- 現状はニュースソースなどの問題で10曲のループになっている
などに挑戦してみたいと思います。
ソースコード
参考
Discussion