Help us understand the problem. What is going on with this article?

Twilio WebRTCハンズオン(WebRTC Go 編)

はじめに

本資料は、過去に作成した「Twilio WebRTC ハンズオン 2020年版」の2020年リメイク版になります。

対象者

  • Twilio が初めての方
  • 名前は知っているけど、触ったことがない方
  • WebRTC に興味のある方

カリキュラム

  • ハンズオンの準備と Twilio Video の概要
  • Twilio Videoの実装
  • グループルーム機能を利用して複数人によるビデオチャットを実装してみよう
  • 参考情報
  • まとめ

自己紹介

katsumi_small_1.jpg

髙橋克己(たかはしかつみ)

(株)KDDI ウェブコミュニケーションズ
Twilio 事業部 エバンジェリスト
Facebook: katsumi.takahashi
GitHub: mobilebiz

katsumi.takahashi@kddi-web.com

ハンズオン用ソースコード

こちらからtwilio-video.zipをローカルにダウンロードして展開して下さい。

ハンズオンの準備と Twilio Video の概要

このハンズオンの内容はトライアルアカウントでも利用できます。
トライアルアカウントの作成は、以下の URL から行ってください。
https://cloudapi.kddi-web.com/signup

手順1. Twilio の管理コンソールにログインします。

Programmable Video

  • WebRTC をベースに構築された音声・動画・データ通信サービス
  • JavaScript SDK / iOS SDK / Android SDK を提供
  • アクセストークン(JWT)を使った認証
  • ルームベースでの実装(呼び出し機能はありません)
  • P2Pベースの「Go」、「Peer-to-peer」と、SFUベースの「Group」「Group-small」の4種類のルームタイプが利用可能
  • 「Go」は最大2名、「Peer-to-peer」は最大10名まで(3名までを推奨)、「Group-small」は最大4名まで、「Group」は最大50名までの参加者を収容可能
  • TURN/STUN サービスも提供
  • 画面共有に対応(Chrome と Firefox、Edge)
  • サーバーサイドでの録画に対応(「Group」「Group-small」のみ)
  • メディアコーデックには、Opus(音声)、VP8 / H.264(映像)に対応

ルームの作成方法には、最初の参加者が参加したときに動的に生成される Ad-hoc 方式と、REST API を使って予めルームを作成しておく方式があります。Ad-hoc 方式では、管理コンソール上で設定しておいたルームタイプが自動的に選択されますが、REST API を使った方式では、都度ルームタイプを指定することができます。
今回のハンズオンでは、Ad-hoc 方式を使いますが、都度ルームを作成したい場合は以下の記事を参考にしてください。
Twilio Videoで都度Roomの設定を行う方法

<参考> P2P 方式と SFU 方式

  • P2P(Peer to Peer)方式
    • ブラウザ同士が直接映像などをやり取りする
    • もっとも高解像度で自由度が高い
    • WebRTC が前提としている接続方式でリアルタイム通信に向いている
    • 接続する相手が増えてくるとブラウザ側の負担が大きくなる(特にモバイル)
    • Twilio Video の「Go」と「Peer-to-peer」で利用される方式
  • SFU(Selective Forwarding Unit)方式
    • 映像や音声を中継するサーバーを配置
    • 録画などの付加機能を実施することが可能
    • 上りのコネクションは1本、下りは相手の数分必要
    • ブラウザが増えた場合に、ブラウザ側の負担が P2P に比べて少ない
    • SFU 側では画像の加工などは行わないため、SFU 自体の負担は低い
    • Twilio Video の「Group」「Group-small」で利用される方式

上級編SFUとP2P.png

<参考> TURN/STUN

  • NAT(Network Address Translation) 越えをするための仕組み
  • ICE(Interactive Connectivity Establishment)として説明されることもある
  • TURN
    • 直接通信できない場合に中継するサーバー
  • STUN
    • 自分が外部に出たときに、外部から見える情報を取得する
  • Twilioは、Network Traversal Service として TURN/STUN を単独でも提供(TURN のみ有料)
    • TURN/STUN の利用料は Twilio Video の利用料の含まれる(「Go」のみ、25GB/月以降は有料)
    • Twilio の TURN サーバーは9つのリージョンから最も近い場所を利用可能

スクリーンショット 2018-07-15 20.37.21.png

Twilio Video が利用できるかを確認するためには、以下のテストサイトに接続してみます。

https://networktest.twilio.com/?region=jp1

Twilio Video SDK が対応するブラウザ

CHROME MS EDGE FIREFOX SAFARI
Android - -
iOS -
Linux - -
macOS ✓※※
Windows ✓※※ -

※ iOS の Chrome と Firefox では、WebRTC API にはアクセスできません。
※※ Chromium-based Edgeブラウザにのみ対応しています。

ルームの設定を変更する

ここでは、同時参加者数が2名に限定されますが、無料で使える WebRTC Go の設定を行っていきます。この設定を行うことにより、以後 Ad-hoc 方式で作成されるルームは、「Go」タイプとなります。

  • Twilio管理コンソールにログインします。
  • サービスボタン(左側のドットが3つ表示されたアイコン)を押して、表示されたスライドメニューからProgrammable Videoを選択します。
  • さらに、部屋(Rooms)の設定(Setting)を選択します。
  • Room Topologyを以下のように設定します。
設定項目 設定値 備考
ROOM TYPE Go WebRTC Go
MAXIMUM PERTICIPANTS 2 最大参加者数を2に制限

「I acknowledge this change will result in a change in cost」にチェックをいれます。

スクリーンショット 2020-11-22 10.28.50.png

  • Saveボタンを押します。

Video APIの認証

  • ユーザがルームに入るためには、アクセストークンが必要(電車の切符のようなもの)
  • アクセストークンは、以下の情報を使って動的に生成する必要あり(デフォルトの有効期限は1時間)
    • ACCOUNT SID (Twilioのアカウントに紐付いたID)
    • API KEY
    • API SECRET
    • IDENTITY (セッション中にユーザを識別するための値)
  • アクセストークンに対して、接続可能なルームを指定しておくことも可能
  • アクセストークンは、通常はAPI経由で生成する(一時的なトークンでよければ、管理コンソール上でも生成が可能)

アクセストークンの生成プログラム(API経由)を作る

では最初に、アクセストークンを作成する部分を作ります。

シナリオ

Programmable Video で利用するアクセストークンを生成するプログラムを作りましょう。
今回は、実際にブラウザから動的に生成できるように、Twilioのサーバーレス環境であるTwilio Functionsを利用します。

手順1. APIキーを生成する

  • 管理コンソールのスライドメニューから、Programmable Video を選択します。
  • Programmable Video メニューのツール > API Keys を選択します。
  • 新しい API キーを作成するボタンを押すか、赤い+アイコンを押して、新しい API Key を作成します。
  • 名前欄に「Video」と入力し、キータイプは「Standard」を選択します。

スクリーンショット 2018-07-16 07.11.03.png

  • APIキーを作成するボタンを押します。
  • 表示されるSID(SK から始まる文字列)と、SECRETに表示されている文字列の両方をメモ帳に保存します(SECRET については、このタイミングでしか確認することができません)。
  • 完了しました!のチェックボックスにチェックを入れて、終了ボタンを押します。

上級編VideoAPIKey.png

手順2. アクセストークン発行用Functionの作成

Programmable Video を利用するためには、先程作成した API キーと SECRET を使って、アクセストークンを動的に生成する必要があります。
今回は、Twilio Functions を使って、特定のルームにだけアクセス可能なアクセストークンを生成してみましょう。

  • スライドメニューから、Functionsを選択します。
  • Overview 画面にある、青い Create Service ボタンを押します。
  • ダイアログ内の Service Name に「ds-hackathon」と入力ます。

スクリーンショット 2020-11-22 11.33.03.png
- Next ボタンを押します。
- 作成したサービスの Editor ページが表示されるので、Add + ボタンを押して、表示されるリストから Add Function を選択します。

スクリーンショット 2020-11-22 11.36.18.png
- パス名を入れる状態になるので、「/video-token」と入力してエンターキーを押します。

スクリーンショット 2020-11-22 11.37.37.png
- Protected が選択されているリストを選択し、 Public を選択し直します。

スクリーンショット 2020-11-22 11.39.37.png
- 作成されたコードをすべて削除し、以下のコードで置き換えましょう。

video-token.js
const uuid = require('uuid');
exports.handler = function(context, event, callback) {
    // ルーム名を取得
    const ROOM_NAME = event.roomName || '';
    if (ROOM_NAME === '') callback(new Error('roomName parameter was not set.'))

    // 環境変数から各種情報をセット
    const ACCOUNT_SID = context.ACCOUNT_SID;
    const API_KEY = context.TWILIO_VIDEO_KEY;
    const API_SECRET = context.TWILIO_VIDEO_SECRET;

    const IDENTITY = uuid.v4();  // ランダムにクライアント名を生成

    const AccessToken = Twilio.jwt.AccessToken;
    const VideoGrant = AccessToken.VideoGrant;

    // 参加できるルームをトークンで限定
    const videoGrant = new VideoGrant({
        room: ROOM_NAME
    });

    const accessToken = new AccessToken(
        ACCOUNT_SID,
        API_KEY,
        API_SECRET
    );

    accessToken.addGrant(videoGrant);
    accessToken.identity = IDENTITY;
    callback(null, {
        token: accessToken.toJwt()
    });
};
  • 最後に忘れずに、 Save ボタンを押してコードを保存します。

  • 次に、Editor 画面の Settings の中にある、Environment Variables を選択します。

スクリーンショット 2020-11-22 11.46.57.png

  • KEYVALUE に以下の2つの項目を追加します。それぞれの値を入力した後、Add ボタンを押すと追加されます。
KEY VALUE
TWILIO_VIDEO_KEY 先程メモしておいた API KEY (SKから始まる文字列)
TWILIO_VIDEO_SECRET 先程メモしておいた SECRET

スクリーンショット 2020-11-22 11.51.08.png

手順3. デプロイ

ではここまでの状態で、一度デプロイを行います。デプロイというのは、サーバー上で実行できる環境を構築する作業です。コードを修正してもデプロイするまではサーバー上では実行できません。

  • Editor 画面の下の方にある、青い Deploy All ボタンを押します。
  • デプロイ中のログが画面上に表示され、しばらくしてデプロイが完了すると Functions の /video-token に緑のチェックが入ります。

スクリーンショット 2020-11-22 11.58.44.png

では次に、正しくトークンが発行されるかをテストしてみたいと思います。

  • video-token の右側にあるドットが3つあるアイコンを押して、プルダウンから Copy URL を選択します。

スクリーンショット 2020-11-22 12.00.11.png

  • ブラウザで新しいタブを開いて、コピーしたURLを貼り付け、さらに ?roomName=xxx パラメータを付加します(ドメインのXXXXはご自分の環境によって異なります)。

スクリーンショット 2020-11-22 12.04.49.png

画面上に、次のような JSON が戻れば成功です。

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6InR3aWxpby1mcGE7dj0xIn0.eyJqdGkiOiJTSzE2YmMyNzI1MWNkNTE5NjQ0MzJkYWYxNTFiMmU4MDAzLTE2MDYwODEzNzkiLCJncmFudHMiOnsiaWRlbnRpdHkiOiI4NmI4ZTRjZi0xMGY5LTRmYjAtYTlkMS1hYWUzNzNiYmE1ZTciLCJ2aWRlbyI6eyJyb29tIjoieHh4In19LCJpYXQiOjE2MDYwODEzNzksImV4cCI6MTYwNjA4NDk3OSwiaXNzIjoiU0sxNmJjMjcyNTFjZDUxOTY0NDMyZGFmMTUxYjJlODAwMyIsInN1YiI6IkFDYTBlNTA0ZDVmOWVlZWRkZmJiNWQ3NzMyMWIxYzc0OTYifQ.zxj4EpyRIjZRO3XqziW03BnNR54sOALS8SIMITQyBho"}

このトークンの有効期限は1時間なので、通常はルームに参加する直前にこの URL を呼び出してトークンを取得します。

Twilio Videoの実装

ここからは、実際にHTMLとJavaScriptを使って、Twilio VideoのJavaScript SDKを使った実装をしていきます。

手順1. HTMLとJSを設定する

  • こちらからtwilio-video.zipをローカルにダウンロードして展開して下さい。
  • ダウンロードした ZIP ファイルを解凍し、2つのファイルがあることを確認します。
    • video.html
    • video.js
  • 管理コンソールの Functions 先程作成した ds-hackathon サービスを選択し、Editor 画面を開きます。
  • Add + ボタンを押します。
  • Upload File を選択します。
  • 先程解凍した2つのファイル(video.jsvideo.html)を選択します。
  • VISIBILITY を両ファイルとも、Public に設定して、画面下にある Upload ボタンを押します。

スクリーンショット 2020-11-23 10.00.37.png

  • アップロードが完了すると、Assets 内に2つのファイルが見えるようになります。

スクリーンショット 2020-11-23 10.06.02.png

この時点ではまだデプロイがされていないため、ファイル名の左側が●になっていることがわかります。
では早速デプロイをしてみましょう。

  • 画面下にある Deploy All ボタンを押します。
  • しばらくしてデプロイが完了すると、先程の2つのファイルに緑色のチェックが表示されます。

スクリーンショット 2020-11-23 10.10.55.png

手順2. HTML ファイルを修正して、JavaScript や Twilio Video SDK などを読み込む

Assets 内の video.html を選択すると、以下のような HTML ソースを見ることができます。

スクリーンショット 2020-11-23 10.38.57.png

この時点では、ボタンが2つ表示されるだけの HTML となっています。

STEP 1. 自分自身の画像を表示するエレメントを追加する

まずは、自分自身のカメラ映像を表示する <video> タグを追加したいと思います。

  • 9行目と10行目の間に以下のコードを記載します。

<video id="myStream" autoplay muted="true"></video>

スクリーンショット 2020-11-23 10.43.03.png

<video> タグを使うことで、映像再生に対応するメディアプレイヤーを埋め込むことができます。
<video> タグの詳細については、こちらをご覧ください。

STEP 2. HTML に外部スクリプトを読み込む

つぎに、video.js ファイルや Twilio Video SDKなどの外部 JavaScript ファイルを読み込む設定を行います。今回読み込むのは以下の3つのファイルです。

  1. Twilio Video JS SDK : Twilio が提供する Video 用のSDK
  2. axios : アクセストークンを JavaScript 内から呼び出すための HTTP クライアントライブラリ
  3. video.js : 今回作成する JavaScript ファイル
  • 14行目と15行目の間に以下の3行を追加します。
  <script src="//media.twiliocdn.com/sdk/js/video/releases/2.7.3/twilio-video.min.js"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script src="./video.js?version=1.00"></script>

スクリーンショット 2020-11-23 10.57.35.png

これで HTML ファイルの修正は完了となります。

  • Save ボタンを押して変更を保存します。

手順3. video.js を修正して、ビデオ会議ができるようにする

  • Editor 画面の Assets 内にある、video.js ファイルを選択します。

スクリーンショット 2020-11-23 11.02.17.png

STEP 1. プレビュー画面の表示

自分自身の映像を、<video> タグに表示する部分を作ります。

// STEP 1. から // STEP 1. End の間に以下のコードを記載します。

    // STEP 1. プレビュー画面の表示
    navigator.mediaDevices.getUserMedia({video: true, audio: true})
    .then(stream => {
        document.getElementById("myStream").srcObject = stream;
        localStream = stream;
    });
    // STEP 1. End

navigator.mediaDevices.getUserMedia() を利用することで、自身のPCに接続されているカメラとマイクを使うすることができます。

パラメータの説明

  • VideoとAudioの選択(使用するかどうか)

{video: true, audio: true}

カメラがサポートする解像度を指定することもできます。自身のカメラがサポートする解像度をみるためには、以下の URL を開いて、Video Capture タブを開きます。
chrome://media-internals/

  • 最低でも640x480以上、出来たら1280x720が良いけどだめならよしなにやってください

{ audio: true, video: { width: {min: 640, ideal: 1280}, height: {min: 480, ideal: 720} } }

  • 絶対に640x480がいいです…それ以外だったら動作しなくてもOK
    • min,maxの代わりに exact: 640 と指定することも可能

{ audio: true, video: { width: {min: 640, max: 640}, height: {min: 480, max: 480} } }

  • フレームレートの設定(一例/Chrome限定)

{ audio: true, video: { frameRate: { min: 10, max: 15 } } }

  • 初回は許可を求めるダイアログが出てきます。
  • 複数のカメラやマイクが接続されている場合は、適切なものを選択する必要があります。

navigator.mediaDevices.getUserMedia() の詳細については、こちらをご覧ください。

カメラやマイクを取得できると、Stream オブジェクトを取得することができます。
この Stream オブジェクトを <video> タグの srcObject に指定する(12行目)ことで、自身の映像が再生されます。

  • Save ボタンを押して変更を保存します。
  • 画面下の Deploy All ボタンを押して、デプロイします。
  • デプロイが完了したら、/video.htmlvideo.js ではないので注意)の右側のメニューアイコン(ドットが3つ)から、Copy URL を選択します。

スクリーンショット 2020-11-23 11.30.57.png

  • ブラウザの新しいタブを開いて、コピーした URL を貼り付けて実行します。
  • 初回は、マイクとカメラの許可ダイアログが表示されるので、許可を選択します。

スクリーンショット 2020-11-23 11.32.17.png

  • 自分自身のカメラ映像が表示されることを確認します。

スクリーンショット 2020-11-24 7.22.50.png

  • カメラが複数台あったり、仮想カメラをセットアップしている場合などは、そちらが選択されてしまうことがあります。その場合は、ブラウザのアドレス欄にあるカメラアイコンをクリックして、さらに管理ボタンを押します。

スクリーンショット 2020-11-23 11.36.23.png

  • 権限の中のカメラマイクを選択すると、デフォルトのデバイスが選択できます。

STEP 2. アクセストークンを取得

ここまでは、Twilio Video は利用されていません。ここからいよいよ Twilio Video を使って、ビデオルームに接続する部分を作っていきましょう。

video.js の // STEP 2. から // STEP 2. End の間に以下のコードを記載します。

        // STEP 2. アクセストークンを取得
        axios.get(`${document.location.protocol}//${TWILIO_DOMAIN}/video-token?roomName=${ROOM_NAME}`)
        .then(async body => {
            const token = body.data.token;
            console.log(`Token got. ${token}`); // 本番環境ではコメントアウトしましょう

            connectRoom(token); // ルームに接続
        });
        // STEP 2. End

ここでは、入室ボタンを押したときの処理として記述します。
先ほど作成したサーバー上の video-token Function を Axios 経由で取得します。取得時にルーム名を指定していて、取得されたトークンでは、そのルームにか入れないように制限をかけています。
取得したアクセストークンは JWT(JSON) 形式で返ってきます。

  • Save ボタンを押して修正を保存します。
  • Deploy All ボタンを押してデプロイします。
  • 先程開いたカメラ映像のタブに移動します。
  • ブラウザのアドレスバーの右端にあるメニューアイコンを押して、その他のツール > デベロッパー ツール を選択します。

スクリーンショット 2020-11-23 11.50.32.png
- Console タブを開いておきます。
- ブラウザを強制リロードします(Editor 画面でリロードしないように気をつけてください)。
- 入室ボタンを押します。
- コンソール画面上に以下のようなトークン情報が表示されれば、ここまでは成功です。

スクリーンショット 2020-11-23 11.55.08.png

ここで、403エラーが発生する場合は、video-token Function が Public になっていない可能性がありますので確認しましょう。

STEP 3. 部屋に入室

では次に、取得したアクセストークンを使って、ルームに入ってみましょう。

video.js の // STEP 3. から // STEP 3. End の間に以下のコードを記載します。

        // STEP 3. 部屋に入室
        Video.connect(token, { name: ROOM_NAME })
        .then(room => {
            console.log(`Connected to Room ${room.name}`);
            videoRoom = room;

            // すでに入室している参加者を表示
            room.participants.forEach(participantConnected);

            // 誰かが入室してきたときの処理
            room.on('participantConnected', participantConnected);

            // 誰かが退室したときの処理
            room.on('participantDisconnected', participantDisconnected);

            // 自分が退室したときの処理
            room.once('disconnected', error => room.participants.forEach(participantDisconnected));

            btnJoinRoom.disabled = true;
            btnLeaveRoom.disabled = false;
        })
        .catch(err => console.error(err));
        // STEP 3. End

ルームに入るためのメソッドは、Video.connect() です。第一オプションのアクセストークンは必須です。第2オプションでは、下記のパラメータを渡すことが可能です。

  • audio および video : これらが有効時(デフォルト)には、ルームに接続すると直ちにローカルのカメラおよびマイクからのオーディオおよびビデオのトラックが作成および公開されます。
  • region : シグナリングサーバのロケーションを指定します。日本リージョンを利用する場合は、jp1 を指定します。省略すると gll 最も遅延が少ないリージョンが自動選択されます。
  • name : 参加したいルームの名前を動的に指定できます。
  • iceServers および iceTransportPolicy : ルーム接続時に TURN/STUN サーバーを強制的に利用することができます。 オプションの詳細については、こちらをご覧ください。

ルームの名前には、参加したいルームを指定します。その名前のルームがまだ存在していない場合、接続に先立って作成されます(Ad-hoc 方式)。 ルームがすでにアクティブな場合は、自動的にそのルームに接続され、同じルームに接続されている他の参加者(participant)からの通知を受信します。ルーム名はアカウント内で一意です。
ルームに参加すると、room オブジェクトの中に、participants リストが返りますので、それを使ってすでに参加しているユーザの情報を取得しています(49行目)。

スクリーンショット 2020-11-23 13.21.07.png

また、ルームへの接続が完了するとルームのイベントが取得できるようになるので、イベントハンドラを指定します。

  • participantConnected イベント: 自分以外の参加者がルームに参加したとき(52行目)
  • participantDisconnected イベント: 自分以外の参加者がルームから退出したとき(55行目)
  • disconnected イベント: 接続が切れてしまったとき(58行目)

60〜61行目は、ボタンの有効化/無効化を指定しています。ルームに正しく入れたので、入室ボタンを無効化して、退室ボタンを有効化しています。

では、ここまでの状態で一度テストをしましょう。

  • Save ボタンを押して修正を保存します。
  • Deploy All ボタンを押してデプロイします。
  • 先程開いたカメラ映像のタブに移動します。
  • ブラウザを強制リロードします。
  • 入室ボタンを押してルームに入ります。
  • 以下のようにコンソールに表示されれば、無事にルームに接続できています(Connected to Room VideoRoom が表示されます)。

スクリーンショット 2020-11-23 13.26.31.png

STEP 4. 部屋から退室

STEP 3で指定した各イベントの処理は一旦おいておいて、まずは退出ボタンを押したときの処理を記述しておきましょう。

video.js の // STEP 4. から // STEP 4. End の間に以下のコードを記載します。

        // STEP 4. 部屋から退室
        videoRoom.disconnect();
        console.log(`Disconnected to Room ${videoRoom.name}`);
        btnJoinRoom.disabled = false;
        btnLeaveRoom.disabled = true;
        // STEP 4. End

ルームから退室するには、disconnect() メソッドを呼ぶだけです(37行目)。

スクリーンショット 2020-11-23 13.33.22.png

disconnect() をコールすると、STEP 3で指定した disconnect イベントが発火しますので、画面周りの処理はそちらに任せています。
39〜40行目は、ボタンの有効化/無効化の処理です。

では再び、ここまでの状態で一度テストをしましょう。

  • Save ボタンを押して修正を保存します。
  • Deploy All ボタンを押してデプロイします。
  • 先程開いたカメラ映像のタブに移動します。
  • ブラウザを強制リロードします。
  • 入室ボタンを押してルームに入ります。
  • 退室ボタンを押してルームから抜けます。
  • コンソール上に Disconnected to Room VideoRoom が表示されれば退室が完了しています。

スクリーンショット 2020-11-23 13.38.29.png

テストをしたときに、上記以外に Participant xxxxxx connected のメッセージが出ることがあります。これは、入室した状態でブラウザをリロードしてしまったため、サーバ上ではルームに入室した状態が残ってしまっており、それを検知して先に入室しているユーザがいるように反応してしまったためです。しばらくすると最初のユーザがサーバー上から消えます(Participant xxxxxx disconnected. が表示されます)。

STEP 5. 参加者を表示する

では次に、別の参加者の映像を画面に表示させてみたいと思います。

video.js の // STEP 5. から // STEP 5. End の間に以下のコードを記載します。

        // STEP 5. 参加者を表示する 
        const div = document.createElement('div');
        div.id = participant.sid;

        // 参加者のトラック(映像、音声など)を処理
        participant.tracks.forEach(publication => {
            if (publication.isSubscribed) {
                trackSubscribed(div, publication.track);
            }
        });

        // 参加者の映像が届いたとき
        participant.on('trackSubscribed', track => trackSubscribed(div, track));

        // 参加者の映像が切れたとき
        participant.on('trackUnsubscribed', trackUnsubscribed);

        document.body.appendChild(div);
        // STEP 5. End

新しい参加者を表示するための <div> タグを生成します(76行目)。
削除するときの識別子として利用するため、id に参加者(participant)の id を指定しています(77行目)。

スクリーンショット 2020-11-23 13.48.12.png

79〜84行目は、参加者のトラック(tracks)リストを使って、パブリケーション(publication)オブジェクトを取り出し、そこに含まれるトラックを使って画面の表示を行う処理です(画面の表示自体は、後述のSTEP 7で行います)。

WebRTC では、音声や映像をトラックと呼ばれる配送経路上で送受信します。すなわち、1参加者は複数のトラックを持つことになります。
Participant > Tracks リスト > Publication > Track

この時点では、画面上への表示の処理を記述していないので、実行しないで先にすすみましょう。

STEP 6. 他の参加者の画面を削除する

次は他の参加者が退出したときの処理です。具体的には、participantDisconnected イベントが発火したときに動作する処理となります。

video.js の // STEP 6. から // STEP 6. End の間に以下のコードを記載します。

        // STEP 6. 他の参加者の画面を削除する
        document.getElementById(participant.sid).remove();
        // STEP 6. End

STEP 5で、参加者用に作成した <div> タグの id を指定して、remove() メソッドを呼びます。これにより、<div> タグ自体がドキュメントから削除されるので、画面上から消えます。

STEP 7. トラックをアタッチする

では次に、先程の STEP 5でペンディングにしていた、トラックを画面上の <div> タグと結びつける処理を記述します。

video.js の // STEP 7. から // STEP 7. End の間に以下のコードを記載します。

        // STEP 7. トラックをアタッチする
        div.appendChild(track.attach());
        // STEP 7. End

コード自体は非常にシンプルです。
実際に、音声や映像のトラックを <div> タグにアタッチすると、以下のように <audio> タグや、<video> タグが追加されます。

スクリーンショット 2020-11-23 14.16.28.png

では再び、ここまでの状態で一度テストをしましょう。

  • Save ボタンを押して修正を保存します。
  • Deploy All ボタンを押してデプロイします。
  • 先程開いたカメラ映像のタブに移動します。
  • ブラウザを強制リロードします。
  • 入室ボタンを押してルームに入ります。
  • 別のタブを開いて、同じURLで接続してみます。

スクリーンショット 2020-11-24 7.23.24.png

このように相手側(今回は自分のカメラなので同じ画像です)が表示されれば成功です。
環境によっては、相手側の画像のサイズがだんだん大きくなるかもしれません。これは、Twilio が通信状況をリアルタイムで検知しながら最適なサイズを自動で選択するためです。

  • それぞれのタブで、退室ボタンを押してルームから抜けます。

STEP 8. トラックのデタッチ

では最後に、参加者が退出したときの処理を記述しましょう。

video.js の // STEP 8. から // STEP 8. End の間に以下のコードを記載します。

        // STEP 8. トラックのデタッチ
        track.detach().forEach(element => element.remove());
        // STEP 8 End

すでに、STEP 6で参加者が退出したときの <div> タグを削除する処理を記述しているので、このコード自体はあまり意味がないように見えますが、たとえば参加者の音声トラックだけが削除された場合(音声ミュート)などは、disconnect イベントは発生せず、trackUnsubscribed イベントのみが発火するため、こちらのコードが重要になります。

これでコードはすべて完成です。
完成したコードを載せておきます。

video.js
(() => {
    'use strict';

    const TWILIO_DOMAIN = location.host;    // 現在のURL
    const ROOM_NAME = 'VideoRoom';          // 部屋の名前
    const Video = Twilio.Video;             // Twilio Video JS SDK
    let videoRoom, localStream;

    // STEP 1. プレビュー画面の表示
    navigator.mediaDevices.getUserMedia({video: true, audio: true})
    .then(stream => {
        document.getElementById("myStream").srcObject = stream;
        localStream = stream;
    });
    // STEP 1. End

    // ボタンの準備
    const btnJoinRoom = document.getElementById("button-join");
    const btnLeaveRoom = document.getElementById("button-leave");

    // 入室ボタンが押されたときの処理
    btnJoinRoom.onclick = (() => {
        // STEP 2. アクセストークンを取得
        axios.get(`${document.location.protocol}//${TWILIO_DOMAIN}/video-token?roomName=${ROOM_NAME}`)
        .then(async body => {
            const token = body.data.token;
            console.log(`Token got. ${token}`); // 本番環境ではコメントアウトしましょう

            connectRoom(token); // ルームに接続
        });
        // STEP 2. End
    });

    // 退出ボタンが押されたときの処理
    btnLeaveRoom.onclick = (() => {
        // STEP 4. 部屋から退室
        videoRoom.disconnect();
        console.log(`Disconnected to Room ${videoRoom.name}`);
        btnJoinRoom.disabled = false;
        btnLeaveRoom.disabled = true;
        // STEP 4. End
    });

    // ルームに接続
    const connectRoom = (token) => {
        // STEP 3. 部屋に入室
        Video.connect(token, { name: ROOM_NAME })
        .then(room => {
            console.log(`Connected to Room ${room.name}`);
            videoRoom = room;

            // すでに入室している参加者を表示
            room.participants.forEach(participantConnected);

            // 誰かが入室してきたときの処理
            room.on('participantConnected', participantConnected);

            // 誰かが退室したときの処理
            room.on('participantDisconnected', participantDisconnected);

            // 自分が退室したときの処理
            room.once('disconnected', error => room.participants.forEach(participantDisconnected));

            btnJoinRoom.disabled = true;
            btnLeaveRoom.disabled = false;
        })
        .catch(err => console.error(err));
        // STEP 3. End
    };

    // 他の参加者が入室したとき
    const participantConnected = (participant) => {
        console.log(`Participant ${participant.identity} connected'`);

        // STEP 5. 参加者を表示する 
        const div = document.createElement('div');
        div.id = participant.sid;

        // 参加者のトラック(映像、音声など)を処理
        participant.tracks.forEach(publication => {
            if (publication.isSubscribed) {
                trackSubscribed(div, publication.track);
            }
        });

        // 参加者の映像が届いたとき
        participant.on('trackSubscribed', track => trackSubscribed(div, track));

        // 参加者の映像が切れたとき
        participant.on('trackUnsubscribed', trackUnsubscribed);

        document.body.appendChild(div);
        // STEP 5. End
    }

    // 他の参加者が退室したとき
    const participantDisconnected = (participant) => {
        console.log(`Participant ${participant.identity} disconnected.`);

        // STEP 6. 他の参加者の画面を削除する
        document.getElementById(participant.sid).remove();
        // STEP 6. End
    }

    // トラックの購読
    const trackSubscribed = (div, track) => {
        // STEP 7. トラックをアタッチする
        div.appendChild(track.attach());
        // STEP 7. End
    }

    // トラックの非購読
    const trackUnsubscribed = (track) => {
        // STEP 8. トラックのデタッチ
        track.detach().forEach(element => element.remove());
        // STEP 8 End
    }

})();

先程と同じように再度デプロイをしてテストをしてみましょう。

グループルーム機能を利用して複数人によるビデオチャットを実装してみよう

このステップでは、グループルームを利用して、最大50名までのビデオ会議に変更していきたいと思います。
しかし実際にはコードを変更する必要はありません。
管理コンソールの設定を変更するだけでグループルームになります。

手順1. 設定の変更

  • Twilioの管理コンソールを開き、スライドメニューからProgrammable Videoを選択します。
  • 部屋(Rooms)の設定(Setting)を選択します。
  • Room Topologyを以下のように設定します。
設定項目 設定値 備考
ROOM TYPE Group グループルーム
VIDEO CODEC VP8 & H.264 VP8が使えなければH.264
RECORDING DISABLED 録画は無効
MAXIMUM PARTICIPANTS 50 最大参加者数を50に設定
MEDIA REGION Japan - jp1 日本リージョンを指定

スクリーンショット 2020-11-23 14.37.42.png

  • Saveボタンを押します。

手順2. テスト

  • ブラウザを強制リロードして、3人以上でもビデオ会議ができることを確認します。

参考情報

今まで紹介してこなかったTwilio Videoの機能について、簡単に説明します。

  • データトラック
  • 画面共有
  • 録画
  • 料金

データトラック

  • 音声や動画以外に、データ通信用のトラックを利用することできます。
  • チャットのような文字列のやり取りや、マウスの動きを送信して共同編集が可能です。
  • データトラック用に、DataTrack APIが用意されています。
  • ネットワークの状態によって、パケットのロスが発生することがあります。これを調整するためのパラメータとして、以下の2つを用意しています。
    • maxPacketLifeTime : パケットの生存時間(この時間内であれば再送の対象とする。単位はms)
    • maxRetransmits : パケットの再送回数(この回数を越えても送信できない場合はドロップ)

画面共有

  • Chrome、 Firefox、 MS Edge (chromium 版)を利用する場合、画面共有が可能です。
  • Firefoxはバージョン52以上で利用可能です。
  • 画面共有のついては、こちらをご参照ください。

twilioVideo-screen-share2.gif

録画(Video Recording)

  • 録画をするためには、グループルームの初期値で録画を有効にするか、REST APIでルームの作成時に録画を有効にします。
  • 録画ファイルは、参加者ごとに、音声( mka ファイル)と動画( mkv ファイル)の2つが作成されます。
  • メディアファイルのダウンロードは、管理サイトから手動で行うか、以下のURLを使ってダウンロードすることができます。
    • https://video.twilio.com/v1/Recordings/RTXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Media
    • 上記URLにアクセスするためには、APIキーとSECRETによるBASIC認証が必要です。
  • メディアファイルを AWS S3 に直接保存させることも可能です(Enterprise Edition の契約が必要)。
  • Twilio 側にメディアファイルを保存する場合は、ユーザの秘密鍵で暗号化することができます(Enterprise Edition の契約が必要)。
  • これらの設定は、管理コンソール > Programmable Video > 録音 > 設定で行います。
  • Compositions APIを利用することで、複数の参加者のメディアデータをまとめて、1つの mp4 もしくは webm に加工することができます。

Programmable Video の料金

ルームタイプ 課金単位 料金(税込み)
Group 1 分あたり 0.61111 円
Group-small 1 分あたり 0.61111 円
Peer-to-peer 1 分あたり 0.22917 円
Go 無料※
録画料 分/参加者あたり 0.61111 円
録画データ保存料 1GB 毎/日あたり 0.25514 円

※ 月に25GB分のTURN料金が含まれます。

Compositions API を利用する場合には以下の追加料金が発生します。

  • コンポジット後の動画ファイル 1分あたりの料金: 1.52778円

データトラック機能は、ルーム料金に含まれます。

まとめ

Twilio Video は、WebRTC をベースに作られているため、ブラウザの機能をうまく組み合わせて色々なことができます。
今回の記事では触れませんが、字幕をつけたり、画像解析を入れるなど、通常のビデオ会議では実現できないような機能をもたせることも可能です。
WebRTC Go は、参加者あたりの料金がかからないプランとなっているため、ぜひ色々とチャレンジしてみてください。


Twilio(トゥイリオ)とは

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

自己紹介  
高橋克己(Katsumi Takahashi) 自称「赤い芸人
グローバル・インターネット・ジャパン株式会社 代表取締役
株式会社KDDIウェブコミュニケーションズ Twilio事業部エバンジェリスト

2001年より大手通信事業者の法人サービスの教育に携わり、企業における電話のしくみや重要性を研究。2016年よりTwilio事業部にジョインし、Twilioを使ったスマートコミュニケーションの普及活動を精力的に行っている。
2019 Twilio Champions
mobilebiz
フルスタックエンジニア。趣味は料理。 2014年7月に、留守番電話が文字で届く国内初の留守電サービス「TRANSREC」をリリース。 2015年4月にSmart Communication Award 2015で「自動電話リレーサービス」が最優秀賞を受賞。 2016年2月よりTwilioエヴァンジェリスト。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away