Twilio Videoで全員が退室したらRoomを閉じる方法


はじめに

みなさん、こんにちは。
KDDIウェブコミュニケーションズのTwilio事業部エバンジェリストの高橋です。

前回の記事で、REST API経由でRoomを生成すると、全員が退室しても5分間はRoomが存在するという説明をしました。
Twilio Videoで都度Roomの設定を行う方法

そこで、この記事では5分待たずに全員がRoomから退室した時点でRoomを閉じる方法を説明します。

なぜ閉じる必要があるのか

Twilio Videoの会議は、Room単位でログが生成されます。以下は生成されたログです。

このログをみるとわかるように、ルームのUNIQUE NAME(Room名)は同じでも、SID(RMから始まる文字列)が異なるログが生成されています。
先程の5分が有効になっていると、一度全員が退室してから5分以内に誰かが入室してくると同じSIDをもつRoomログにまとまってしまいます。これだと、途中誰もいなかった時間も継続時間としてカウントされてしまうので会議時間としては正確ではないです。
また、そもそもこのログは、Completed Room Logsなので、完了していないと管理コンソールからは確認できません(継続中のRoomログを調べるにはREST APIを利用します)。

そこで、REST APIを使ってRoomを生成した場合も、全員が退出したタイミングでRoomデータを完了させる方法が必要になります。

StatusCallbackを使う

Twilio Videoには、Roomの属性としてStatusCallbackが用意されています。これは、Room上で発生した様々なイベントに応じて、Twilio側から発行される通知になります。
残念ながら通知されるイベントの中に、「全員が退出した」というイベントはないので、「誰かが退出した」イベント(participant-disconnected)が発火したタイミングで、Roomに残っている人を数えて、全員いなくなったときに強制的にRoomを閉じるようにすれば良さそうです。

では、さっそく実装しましょう。

まずは、StatusCallbackの指定ですが、こちらは前回の記事のコードを一部修正することで設定できます。

video-token.js
exports.handler = async function(context, event, callback) {

・・・ 省略 ・・・

        if (rooms.length === 0) {
            // 開催中の部屋がないので定義
            return client.video.rooms.create({
                maxParticipants: MAX_PARTICIPANTS,
                recordParticipantsOnConnect: RECORDING,
                type: ROOM_TYPE,
                uniqueName: roomName,
                mediaRegion: 'jp1',
                statusCallback: `https://${context.DOMAIN_NAME}/video-status-callback`, // <--ここを追加
            });
        } else {
            return null;
        }

・・・ 省略 ・・・

};

次に、StatusCallbackの受け側をつくります。

video-status-callback.js
exports.handler = function(context, event, callback) {
    console.log('Status Callback called.');
    console.log(event.RoomSid, event.RoomStatus, event.StatusCallbackEvent);

    // 誰かが退出したら、残りの参加者を確認し、誰も残ってなければルームを閉じる
    if (event.StatusCallbackEvent === 'participant-disconnected') {
        const ACCOUNT_SID = ''; TwilioのAccountSid;
        const API_KEY = ''; Twilio VideoのAPI Key;
        const API_SECRET = ''; Twilio VideoのSecret;
        const roomName = event.RoomName;
        const roomSid = event.RoomSid;
        const client = require('twilio')(API_KEY, API_SECRET, {accountSid: ACCOUNT_SID});
        let fAlive = false;  // 生存者フラグ
        client.video.rooms(roomSid).participants.each({
            status: 'connected',
            done: err => {
                if (err) callback(new Error(`Room closing error: ${error}`));
                if (!fAlive) {
                    // 誰も残ってないので、ルームを閉じる
                    client.video.rooms(roomSid).update({
                        status: 'completed'
                    })
                    .then(room => {
                        console.log(`Room close. ${room.sid}`);
                        callback(null, 'OK');
                    })
                    .catch(error => {
                        callback(new Error(`Room closing error: ${error}`));
                    });    
                } else {
                    callback(null, 'OK');
                }
            }
        }, participant => {
            console.log(`participant: ${participant.identity} is alive.`);
            fAlive = true;  // 生存者がいたのでフラグをセット
        });
    } else {
        callback(null, 'OK');
    }
}

Roomを強制的に閉じるには、RoomをUpdateしてStatusCompletedにすればOKです。
また、現在入室している(残っている)参加者を知るには、こちらのAPIを使えば良さそうです。
ところが、こちらのサンプルコードにある.eachというメソッドが曲者で、得られたリストが終了したときの処理の書き方がわかりにくく、callbackをどこで発行すればよいかで頭を悩ますことになります(callbackを発行しないとFunctionsがタイムアウトしてエラーになります)。
結論から言うと、上記コードにあるように、done:パラメータを使って、リスト終了後の処理を書きます。

以上で、REST API経由でRoomを作成した場合も、全員が退室時にRoomが閉じられるようになります。
なお、Roomが閉じられると、room-endedイベントが発火するので、会議の時間を計算したり、録画データの後処理を行うなどに利用するとよいでしょう。

まとめ

今後、BCP対策としてVideoを使ったサービスを開発したりする機会が増えてくるかと思いますので、ぜひ参考にしていただければと思います。


Twilio(トゥイリオ)とは

https://cloudapi.kddi-web.com
Twilioは音声通話、メッセージング(SMS/チャット)、ビデオなどの 様々なコミュニケーション手段をアプリケーションやビジネスへ容易に組み込むことのできるクラウドAPIサービスです。初期費用不要な従量課金制で、各種開発言語に対応しているため、多くのハッカソンイベントやスタートアップなどにも、ご利用いただいております。