今年に入ってClubhouseという音声SNSが大ブームになっています。
技術のポイントとして、スピーカ同士が遅延なく通話が可能で、その通話を大勢のオーディエンスがリスニングできる点になります。
方法としてはスピーカ間はWebRTC、オーディエンスはHLSに変換されたデータを取得するという構成が考えられます。iOS/Androidアプリであれば独自プロトコルでの実装という事も考えられます。
今回はagora.ioの技術を用いてWeb版Clubhouseのようなアプリを作ってみようと思います。
WebアプリなのでベースはWebRTCとなります。又、agora.ioの技術を利用すれば大勢のオーディエンスもWebRTCで接続可能です。
サンプルはこちらに公開してます。
##画面イメージ
以下の機能が実装されています。
・ルームの作成
・他の人が作成されたルームの表示
・他の人のルームに入室
・退室
ルーム表示についてはサーバー側で管理する方法が一般的かもしれませんが、今回はAgoraRTMSDKを利用してリアルタイムメッセージで管理してみます。
AppIDはagora.ioにサインアップして取得する必要があります。
##実装の詳細
JoinボタンをクリックしたタイミングでAgoraRTMにログインします。
全員共通の"main"というチャンネルに接続し、新規でルームが発生した際、入室ボタンを表示する実装になっています。
async function joinRtm() {
rtmClient = AgoraRTM.createInstance(options.appid);
rtmClient.login({ token: '', uid: options.accountName}).then(() => {
console.log('AgoraRTM client login success');
//rtm channel
rtmChannel = rtmClient.createChannel(mainRtmChannel);
rtmChannel.join().then(()=>{
console.log("RTM Join Channel Success");
rtmChannel.on('ChannelMessage', function (message, memberId) {
console.log("Get channel message:"+memberId);
console.dir(message);
const information = JSON.parse(message.text);
if(information.type == "new"){
const room = $(
<div id="player-wrapper-${memberId}" >
<button id="" type="submit" class="btn btn-primary btn-sm" onclick="join('${information.channelName}','','host')">Join Speaker(Host:${memberId})</button>
<button id="" type="submit" class="btn btn-primary btn-sm" onclick="join('${information.channelName}','','audience')">Join Audience(Host:${memberId})</button>
<button id="" type="submit" class="btn btn-primary btn-sm" onclick="leave()">Leave</button>
</div>
<br />
);
$("#remote-playerlist").append(room);
}
});
});
}, function (err) {
console.log("AgoraRTC client init failed", err);
});
}
次にRoomに入室処理する処理です。
・新規Room作成か既存Roomに入室か
・スピーカーかオーディエンスか
で処理を分岐しています。
新規Room作成の場合は、他のユーザにRoom作成の通知をRTMSDKで実施しています。
スピーカー入室の場合は、マイクを取得してサーバーへPublishする実装をします。
async function join(channelName,type,role) {
console.log("channelName:" + channelName);
// add event listener to play remote tracks when remote user publishs.
client.on("user-published", handleUserPublished);
client.on("user-unpublished", handleUserUnpublished);
client.setClientRole(role);
const uid = await client.join(options.appid, channelName, null, null);
if(role == "host"){
localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
// Publish the local audio track to the channel.
await client.publish([localAudioTrack]);
}
if(type == "new"){
//新規ルームの通知
var info = JSON.stringify({uid: options.accountName, channelName: channelName, type: type});
rtmChannel.sendMessage({text: info});
console.log("send new channel info");
}
}
最後に他拠点からの音声取得部分になります。
こちらはスピーカーもオーディエンスも同様の処理になります。
async function subscribe(user, mediaType) {
const uid = user.uid;
// subscribe to a remote user
await client.subscribe(user, mediaType);
console.log("subscribe success");
if (mediaType === 'audio') {
user.audioTrack.play();
}
}
agora.ioを利用すればClubhouseのようなリアルタイム音声SNSの構築が簡単にできます。
現時点ではミニマムなサンプルですが、段階的にClubhouseに近づけるサンプルを公開していきたいと思います。
#参考リンク
agora.ioではiOS/Androidアプリ用のSDKも公開されています。より高性能な音声技術をアプリに組み込む事も可能です。
・ボイスチャット/カラオケアプリにおける音声配信(VoIP)技術について
・Raw Audio Data を使って自分の音声を可視化してみる