🦁

Agoraを使って、Clubhouseの「もくもく作業部屋」のような音声チャット実装。トークン生成処理解説。

2021/02/15に公開
3

「Focus Cafe」を作っています

はじめまして、からまげです。個人開発が趣味で、Webアプリやネィティブアプリを作っています。
先日、個人開発で、「Focus Cafe」 という 作業集中アプリ をリリースしました!

https://focus-cafe.space/home

Agoraでボイスチャット機能を作った

今回、Focus Cafeに、Agoraを使って、 「ボイスチャット機能」 をリリースしましたので、Agoraについてまとめようと思い、この記事を書いています。

この記事が、誰かの助けになれば、幸いです。

Focus Cafe の紹介

Focus Cafeには、以下のような機能があります。

  • 25分集中 + 5分休憩、のタイマー機能
  • 一緒に集中している人のアイコンが リアルタイムに表示される
  • やりとげた作業の記録をつけられる
  • GitHubのアクティビティのような草を生やす機能
  • 集中した時間を競うランキング機能
  • リアルタイムテキストチャット機能

リモートワーク特有の孤独感を解消したい

わたしは、このアプリを、リモートワーク特有の孤独感を解消したいと思い、作りました。
在宅勤務をしていると、一人ぼっちでさびしいなぁー、と感じる瞬間が度々訪れませんか?

人間は社会的な動物なので、自分が 「孤立してる」 と感じると、ソワソワしたりして、集中力や生産性が落ちます。

カフェで集中していると、なぜか集中力が増しませんか?
これは周りに人がいることで、集中が高まる効果が生まれています。
PCで作業をする際、カフェには同じようにPCで作業をしている人が近くにいることが多いので、一緒に頑張っている人がいるという一体感が集中力を高めます。
周りの人がいると気が散ってしまい、邪魔だと思いがちですが、邪魔ではなく、むしろ自分の集中力を高める助けになります。

Focus Cafeは、家にいながら、カフェ作業っぽく集中したい方におすすめです。

ボイスチャット機能がほしい

この「Focus Cafe」に、ボイスチャット機能がほしいなぁーと思い始めました。
というのも、最近、Clubhouseが流行ってるじゃないですか。
Clubhouseを見ていると、「もくもく作業部屋」 みたいなルームが多いことに気づきました。
「もくもく作業部屋」では、みんな無言で作業していて、キーボード音だけがカタカタ聞こえる。
時折、雑談もしていているが、基本的には静か。
これも、カフェで作業するのと同じ現象ではないかなと推測します。
きっと、みんな、「みんなで作業している」という一体感を求めて、「もくもく作業部屋」に集まっているんだと思います。ひとりで家で作業している孤独感を払拭したいという欲求がそこにはあります。
つまり、Focus Cafeにも同様の機能を作れば、孤立感をやわらげることができ、需要はあるのかなと考えました。

音声チャットを作るならClubhouseでも使われているAgoraが良いらしい

ボイスチャット機能を作るにあたって、どのサービスを使えば良いのかと調べました。
いま、熱いのは、Clubhouseでも使われているAgoraが良いとの情報を得ました。

https://www.agora.io/en/

Agoraの使い方とドキュメント

Agoraの使い方は、以下の記事にわかりやすくまとめられているので、本記事では割愛します。
今回のボイスチャット機能を作るにあたり、大変参考になりました。ありがとうございます。

https://zenn.dev/ginpei/articles/agora-voice-chat

https://zenn.dev/arahabica/articles/0f54f2cdb1a29d

公式のドキュメントもわかりやすいので、ぜひ読みましょう。
https://docs.agora.io/en/Voice/landing-page?platform=Web

サーバサイドの接続トークン生成処理を解説

今回、ボイスチャット機能を作るにあたって、情報が少ないと感じたのは、「トークンの生成処理」についてです。

AgoraSDKを使って、音声チャットをするためには、自前のサーバ側の処理で、トークンを生成する必要があります。

https://docs.agora.io/en/Voice/token_server?platform=Web

Agoraで音声チャットにつなぐためには、トークンが必要

このトークン生成処理は、サーバサイドで行う必要があります。
理由は、トークンの生成には秘匿情報(証明書) が必要です。
この処理をクライントサイドで行ってしまうと、秘匿情報を暴露することになりますから、サーバ側で行う必要があります。

Cloud Functionを使ったトークン生成解説

今回は、Cloud Functionを用いて、トークンの生成処理を行いました。
言語はNodeJSを選択しました。

まずは、トークン生成に必要なライブラリをインストールしてください。

agora-access-tokenをインストール

npm install agora-access-token

モジュールインポート

つづいて、そのモジュールをインポートします。

const {RtcTokenBuilder, RtcRole} = require('agora-access-token')

APP_IDとCERTIFICATEの値を、管理画面から引っ張ってくる

Agoraの管理画面のAPPIDとCertificateの値をコピーして、コードに貼り付けてください。
この値は公開しないように注意してください。

const APP_ID = 'zzzzzzzzzzzzzzzzzz'
const APP_CERTIFICATE = 'xxxxxxxxxxxxxxxxx'

トークン生成に必要なパラメータ

その他、生成に必要なパラメータは、以下です。

  • uid ユーザーID(ユーザーを識別しないのであれば、0でよい)
  • channelName 接続するチャンネル名
  • role PUBLISHER(スピーカー) or SUBSCRIBER(聴衆)
  • privilegeExpireTime 期限切れまでの時間(sec)

これらの値は、requstのクエリーから受け取るか、デフォルト値で指定してください。

  // get uid 
  let uid = req.query.uid
  if(!uid || uid == '') {
    uid = 0
  }
  // get channel 
  let channelName = req.query.channel
  if(!channelName || channelName == '') {
    channelName = 'hogefuga'
  }
  // get role
  let role = RtcRole.SUBSCRIBER
  if (req.query.role == 'publisher') {
    role = RtcRole.PUBLISHER
  }
  // get the expire time
  let expireTime = req.query.expireTime
  if (!expireTime || expireTime == '') {
    expireTime = 3600
  } else {
    expireTime = parseInt(expireTime, 10)
  }
  // calculate privilege expire time
  const currentTime = Math.floor(Date.now() / 1000)
  const privilegeExpireTime = currentTime + expireTime

必要なパラメータは揃ったので、トークンを生成します。

const token = RtcTokenBuilder.buildTokenWithUid(APP_ID, APP_CERTIFICATE, channelName, uid, role, privilegeExpireTime);

あとは、生成したトークンをレスポンスのjsonに詰めて返せば終わりです。

generateToken の Cloud Function のコード

以下に、Cloud Functionのコードを載せておきます。

const functions = require('firebase-functions')
const cors = require('cors')({origin: true})
const {RtcTokenBuilder, RtcRole} = require('agora-access-token')

const APP_ID = 'zzzzzzzzzzzzzzzzzz'
const APP_CERTIFICATE = 'xxxxxxxxxxxxxxxxxxxxxxx'

exports.generateToken = functions.https.onRequest((req, res) => {
  // get uid 
  let uid = req.query.uid
  if(!uid || uid == '') {
    uid = 0
  }
  // get channel 
  let channelName = req.query.channel
  if(!channelName || channelName == '') {
    channelName = 'hogefuga'
  }
  // get role
  let role = RtcRole.SUBSCRIBER
  if (req.query.role == 'publisher') {
    role = RtcRole.PUBLISHER
  }
  // get the expire time
  let expireTime = req.query.expireTime
  if (!expireTime || expireTime == '') {
    expireTime = 3600
  } else {
    expireTime = parseInt(expireTime, 10)
  }
  // calculate privilege expire time
  const currentTime = Math.floor(Date.now() / 1000)
  const privilegeExpireTime = currentTime + expireTime
  const token = RtcTokenBuilder.buildTokenWithUid(APP_ID, APP_CERTIFICATE, channelName, uid, role, privilegeExpireTime)
  cors(req, res, () => {
    const json = {
      token
    }
    response.send(JSON.stringify(json))
  })
})

クライアント側のJoin処理

このあと、クライアント側で、このトークンを受け取り、以下のようなコードでAgoraSDKを使って、join処理を行えば、音声チャットを始める準備が整います。

const client = AgoraRTC.createClient({ mode: 'rtc', codec: 'vp8' })
const uid = await client.join(appId, voiceChatRoom.name, voiceChatRoom.token, null)

Agoraを使えば、Clubhouseに負けない音声サービスを生み出すことが可能になります。
一度試してみる価値はあるかと思います。
その際に、この記事が助けになれば、幸いです。

最後までお読みいただき、ありがとうございました。

Discussion

清水太一清水太一

初めまして。
Focus Cafeとても面白いサービスですね!!
1点質問なのですが、Agoraを使用するために代理店のVcubeさんなどは挟んでいますでしょうか。それとも直接Agora.io(アメリカ)を使用していますでしょうか。
意図としては僕のサービスにもAgoraを組み込んで見ようと思ったのですが、日本だと代理店(Vcube)を挟まないといけないなどの記事を見た事があったので質問です。

お手数をおかけしますが、ご回答のほど宜しくお願いいたします。

karamagekaramage

コメントありがとうございます。
代理店は、わたしは使ってないです。
日本だと代理店(Vcube)を挟まないといけないという話も初耳です。