2023年5月1日を持ちまして、株式会社KDDIウェブコミュニケーションズのTwilioリセール事業が終了したため、本記事に記載されている内容は正確ではないことを予めご了承ください。
はじめに
本資料は、過去に作成した「Twilio WebRTC ハンズオン 2020年版」の2020年リメイク版になります。
最新情報に更新(2022-11-15)
対象者
- Twilio が初めての方
- 名前は知っているけど、触ったことがない方
- WebRTC に興味のある方
カリキュラム
- ハンズオンの準備と Twilio Video の概要
- Twilio Videoの実装
- 参考情報
- まとめ
自己紹介
髙橋克己(たかはしかつみ)
(株)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の設定を行う方法
Twilio Video の基本概念
以下の図は、Twilio Video を使って、ビデオ会議を行うときの基本的な概念を図にしたものです。
- ビデオを開始したいデバイスは、まずはルームに入るためのトークンを取得する必要があります。
- 次に、取得したトークンを使ってルームに接続をします。このときに、自分自身のトラック情報を渡します。
- 接続が成功すると、ルームに入室している他の参加者の情報を取得することができます。
- 他の参加者のトラックを購読し、自身の画面上に表示させるようにします。
- ルームで発生する各種イベント通知(参加者の入退室など)を取得し、処理を行います。
<参考> WebRTC とは
WebRTC とは、Web の技術(わかりやすく言えばブラウザの機能)を使って、お互いにリアルタイム性のある情報交換をするための技術の総称になります。リアルタイムな情報交換データとして、音声や映像などが挙げられます。
WebRTC では情報交換する相手とどのような手法でデータを交換するかを決める必要があり、その作業をシグナリングと呼んでいます。
また、それぞれの端末はファイアーウォールやルータの内側にいることが多く、外部から自分自身を探してもらうための仕組みなどもシグナリングには含まれます。
<参考> P2P 方式と SFU 方式
- P2P(Peer to Peer)方式
- ブラウザ同士が直接映像などをやり取りする
- もっとも高解像度で自由度が高い
- WebRTC が前提としている接続方式でリアルタイム通信に向いている
- 接続する相手が増えてくるとブラウザ側の負担が大きくなる(特にモバイル)
- Twilio Video では「WebRTC Go」と「Peer-to-peer」で利用される方式
- SFU(Selective Forwarding Unit)方式
- 映像や音声を中継するサーバーを配置
- 録画などの付加機能を実施することが可能
- 上りのコネクションは1本、下りは相手の数分必要
- ブラウザが増えた場合に、ブラウザ側の負担が P2P に比べて少ない
- SFU 側では画像の加工などは行わないため、SFU 自体の負担は低い
- Twilio Video では「Group」「Group-small」で利用される方式
<参考> 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つのリージョンから最も近い場所を利用可能
Twilio Video が利用できるかを確認するためには、以下のテストサイトに接続してみます。
Twilio Video SDK が対応するブラウザ
CHROME | MS EDGE ※ | FIREFOX | SAFARI | WebView | |
---|---|---|---|---|---|
Android | ✓ | - | ✓ | - | - |
iOS | ✓ | - | - | ✓ | ✓ |
Linux | ✓ | - | ✓ | - | - |
macOS | ✓ | ✓ | ✓ | ✓ | - |
Windows | ✓ | ✓ | ✓ | - | - |
※ Chromium-based Edgeブラウザにのみ対応しています。
ルームの設定を変更する
ここでは、同時参加者数が2名に限定されますが、無料で使える WebRTC Go の設定を行っていきます。この設定を行うことにより、以後 Ad-hoc 方式で作成されるルームは、「Go」タイプとなります。
- Twilio管理コンソールにログインします。
- Explore ProductsからVideoを選択します。
- さらに、Manage > Room Settingsを選択します。
- Room Topologyを以下のように設定します。
設定項目 | 設定値 | 備考 |
---|---|---|
Room Type | Go | WebRTC Goを選択 |
Maximum Participants | 2 | 最大参加者数を2に制限 |
Maximum Participant Duration | 14400 | 最大滞在時間を14400秒(4時間)に制限 |
- Saveボタンを押します。
Video APIの認証
- ユーザがルームに入るためには、アクセストークンが必要(電車の切符のようなもの)
- アクセストークンは、以下の情報を使って生成する必要あり(デフォルトの有効期限は1時間)
- ACCOUNT SID (Twilioのアカウントに紐付いたID)
- API KEY
- API SECRET
- IDENTITY (セッション中にユーザを識別するための値)
- アクセストークンに対して、接続可能なルームを指定しておくことも可能
- アクセストークンは、通常はAPI経由で生成する(一時的なトークンでよければ、管理コンソール上でも生成が可能)
アクセストークンの生成プログラム(API経由)を作る
では最初に、アクセストークンを作成する部分を作ります。
シナリオ
Programmable Video で利用するアクセストークンを生成するプログラムを作りましょう。
今回は、実際にブラウザから動的に生成できるように、Twilioのサーバーレス環境であるTwilio Functionsを利用します。
手順1. APIキーを生成する
Authenticate to make changesが表示されたら、赤いボタンを押して認証します。
- Create API keyボタンを押して、新しい API Key を作成します。
- Friendly nameに「Video」と入力し、Regionは「US1」、Key typeは「Standard」を選択します。
- Create API Keyボタンを押します。
- 表示されるSID(SK から始まる文字列)と、SECRETに表示されている文字列の両方をメモ帳に保存します(SECRET については、このタイミングでしか確認することができません)。
- Got it! 〜 のチェックボックスにチェックを入れて、Doneボタンを押します。
API Key と API Secret が外部に漏洩すると、第三者から不正な利用をされてしまう可能性がありますので、取り扱いには十分注意してください。
手順2. アクセストークン発行用Functionの作成
Programmable Video を利用するためには、先程作成した API キーと SECRET を使って、アクセストークンを動的に生成する必要があります。
今回は、Twilio Functions を使って、特定のルームにだけアクセス可能なアクセストークンを生成してみましょう。
-
Explore Productsから、Functions and Assetsを選択します。
-
Overview 画面にある、青い Create Service ボタンを押します。
-
Next ボタンを押します。
-
作成したサービスの Editor ページが表示されるので、Add + ボタンを押して、表示されるリストから Add Function を選択します。
-
作成されたコードをすべて削除し、以下のコードで置き換えましょう。
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 を選択します。
- KEY と VALUE に以下の2つの項目を追加します。それぞれの値を入力した後、Add ボタンを押すと追加されます。
KEY | VALUE |
---|---|
TWILIO_VIDEO_KEY | 先程メモしておいた API KEY (SKから始まる文字列) |
TWILIO_VIDEO_SECRET | 先程メモしておいた SECRET |
- 次に、Editor 画面の Settings の中にある、Dependencies を選択します。
- MODULE と VERSION に以下の2つの項目を追加します。それぞれの値を入力した後、Add ボタンを押すと追加されます。
MODULE | VERSION |
---|---|
uuid | 9.0.0 |
手順3. デプロイ
ではここまでの状態で、一度デプロイを行います。デプロイというのは、サーバー上で実行できる環境を構築する作業です。コードを修正してもデプロイするまではサーバー上では実行できません。
- Editor 画面の下の方にある、青い Deploy All ボタンを押します。
- デプロイ中のログが画面上に表示され、しばらくしてデプロイが完了すると Functions の
/video-token
に緑のチェックが入ります。
では次に、正しくトークンが発行されるかをテストしてみたいと思います。
-
video-token
の右側にあるドットが3つあるアイコンを押して、プルダウンから Copy URL を選択します。
- ブラウザで新しいタブを開いて、コピーしたURLを貼り付け、さらに
?roomName=hoge
パラメータを付加します(ドメインのXXXXはご自分の環境によって異なります)。
https://video-XXXX.twil.io/video-token?roomName=hoge
画面上に、次のような 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 先程作成した video サービスを選択し、Editor 画面を開きます。
- Add + ボタンを押します。
- Upload File を選択します。
- 先程解凍した2つのファイル(
video.js
とvideo.html
)を選択します。 - VISIBILITY を両ファイルとも、Public に設定して、画面下にある Upload ボタンを押します。
- アップロードが完了すると、Assets 内に2つのファイルが見えるようになります。
この時点ではまだデプロイがされていないため、ファイル名の左側が●になっていることがわかります。
では早速デプロイをしてみましょう。
- 画面下にある Deploy All ボタンを押します。
- しばらくしてデプロイが完了すると、先程の2つのファイルに緑色のチェックが表示されます。
Assets は Twilio が提供しているストレージサービスです。
Public でアップロードすると、外部からもアクセスが可能になります。Proteced を指定すると、アクセスする際に認証が必要になります。
手順2. HTML ファイルを修正して、JavaScript や Twilio Video SDK などを読み込む
Assets
内の video.html
を選択すると、以下のような HTML ソースを見ることができます。
この時点では、ボタンが2つ表示されるだけの HTML となっています。
STEP 1. 自分自身の画像を表示するエレメントを追加する
まずは、自分自身のカメラ映像を表示する <video> タグを追加したいと思います。
- 9行目と10行目の間に以下のコードを記載します。
<video id="myStream" autoplay muted="true"></video>
<video> タグを使うことで、映像再生に対応するメディアプレイヤーを埋め込むことができます。
<video> タグの詳細については、こちらをご覧ください。
STEP 2. HTML に外部スクリプトを読み込む
つぎに、video.js ファイルや Twilio Video SDKなどの外部 JavaScript ファイルを読み込む設定を行います。今回読み込むのは以下の3つのファイルです。
- Twilio Video JS SDK : Twilio が提供する Video 用のSDK
- axios : アクセストークンを JavaScript 内から呼び出すための HTTP クライアントライブラリ
- video.js : 今回作成する JavaScript ファイル
- 14行目と15行目の間に以下の3行を追加します。
<script src="https://sdk.twilio.com/js/video/releases/2.22.1/twilio-video.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="./video.js?version=1.01"></script>
これで HTML ファイルの修正は完了となります。
- Save ボタンを押して変更を保存します。
手順3. video.js を修正して、ビデオ会議ができるようにする
- Editor 画面の Assets 内にある、
video.js
ファイルを選択します。
STEP 1. プレビュー画面の表示
自分自身の映像を、<video> タグに表示する部分を作ります。
// STEP 1. から // STEP 1. End の間に以下のコードを記載します。
// STEP 1. プレビュー画面の表示
localTracks = await Video.createLocalTracks();
const localVideo = document.getElementById('myStream');
localTracks.forEach((track) => {
if (track.kind === 'video' || track.kind === 'audio') {
track.attach(localVideo);
}
});
// STEP 1. End
Video.createLocalTracks()
を利用することで、自身のPCに接続されているカメラとマイクを使うすることができます。
取得したローカルトラックを <video> タグのvideoとaudioトラックに割り当てること(12〜16行目)で、自身の映像が再生されます。
- Save ボタンを押して変更を保存します。
- 画面下の Deploy All ボタンを押して、デプロイします。
- デプロイが完了したら、
/video.html
(video.js
ではないので注意)の右側のメニューアイコン(ドットが3つ)から、Copy URL を選択します。
- ブラウザの新しいタブを開いて、コピーした URL を貼り付けて実行します。
- 初回は、マイクとカメラの許可ダイアログが表示されるので、許可を選択します。
- 自分自身のカメラ映像が表示されることを確認します。
- カメラが複数台あったり、仮想カメラをセットアップしている場合などは、そちらが選択されてしまうことがあります。その場合は、ブラウザのアドレス欄にあるカメラアイコンをクリックして、さらに管理ボタンを押します。
- 権限の中のカメラやマイクを選択すると、デフォルトのデバイスが選択できます。
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 ボタンを押してデプロイします。
- 先程開いたカメラ映像のタブに移動します。
- ブラウザのアドレスバーの右端にあるメニューアイコンを押して、その他のツール > デベロッパー ツール を選択します。
- Console タブを開いておきます。
- ブラウザを強制リロードします(Editor 画面でリロードしないように気をつけてください)。
- 入室ボタンを押します。
- コンソール画面上に以下のようなトークン情報が表示されれば、ここまでは成功です。
ここで、403エラーが発生する場合は、video-token
Function が Public
になっていない可能性がありますので確認しましょう。
STEP 3. 部屋に入室
では次に、取得したアクセストークンを使って、ルームに入ってみましょう。
video.js
の // STEP 3. から // STEP 3. End の間に以下のコードを記載します。
// STEP 3. 部屋に入室
Video.connect(token, { name: ROOM_NAME, tracks: localTracks })
.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
リストが返りますので、それを使ってすでに参加しているユーザの情報を取得しています(51行目)。
また、ルームへの接続が完了するとルームのイベントが取得できるようになるので、イベントハンドラを指定します。
-
participantConnected
イベント: 自分以外の参加者がルームに参加したとき(54行目) -
participantDisconnected
イベント: 自分以外の参加者がルームから退出したとき(57行目) -
disconnected
イベント: 接続が切れてしまったとき(60行目)
62〜63行目は、ボタンの有効化/無効化を指定しています。ルームに正しく入れたので、入室ボタンを無効化して、退室ボタンを有効化しています。
では、ここまでの状態で一度テストをしましょう。
- Save ボタンを押して修正を保存します。
- Deploy All ボタンを押してデプロイします。
- 先程開いたカメラ映像のタブに移動します。
- ブラウザを強制リロードします。
- 入室ボタンを押してルームに入ります。
- 以下のようにコンソールに表示されれば、無事にルームに接続できています(
Connected to Room VideoRoom
が表示されます)。
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()
メソッドを呼ぶだけです(39行目)。
disconnect()
をコールすると、STEP 3で指定した disconnect
イベントが発火しますので、画面周りの処理はそちらに任せています。
41〜42行目は、ボタンの有効化/無効化の処理です。
では再び、ここまでの状態で一度テストをしましょう。
- Save ボタンを押して修正を保存します。
- Deploy All ボタンを押してデプロイします。
- 先程開いたカメラ映像のタブに移動します。
- ブラウザを強制リロードします。
- 入室ボタンを押してルームに入ります。
- 退室ボタンを押してルームから抜けます。
- コンソール上に
Disconnected to Room VideoRoom
が表示されれば退室が完了しています。
テストをしたときに、上記以外に 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> タグを生成します(78行目)。
削除するときの識別子として利用するため、id
に参加者(participant
)の id
を指定しています(79行目)。
82〜86行目は、参加者のトラック(tracks
)リストを使って、パブリケーション(publication
)オブジェクトを取り出し、そこに含まれるトラックを使って画面の表示を行う処理です(画面の表示自体は、後述のSTEP 7で行います)。
WebRTC では、音声や映像をトラックと呼ばれる配送経路上で送受信します。すなわち、1参加者は複数のトラックを持つことになります。
Participant > Tracks > 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> タグが追加されます。
では再び、ここまでの状態で一度テストをしましょう。
- Save ボタンを押して修正を保存します。
- Deploy All ボタンを押してデプロイします。
- 先程開いたカメラ映像のタブに移動します。
- ブラウザを強制リロードします。
- 入室ボタンを押してルームに入ります。
- 別のタブを開いて、同じURLで接続してみます。
このように相手側(今回は自分のカメラなので同じ画像です)が表示されれば成功です。
環境によっては、相手側の画像のサイズがだんだん大きくなるかもしれません。これは、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
イベントのみが発火するため、こちらのコードが重要になります。
これでコードはすべて完成です。
完成したコードを載せておきます。
(async() => {
'use strict';
const TWILIO_DOMAIN = location.host; // 現在のURL
const ROOM_NAME = 'VideoRoom'; // 部屋の名前
const Video = Twilio.Video; // Twilio Video JS SDK
let videoRoom, localTracks;
// STEP 1. プレビュー画面の表示
localTracks = await Video.createLocalTracks();
const localVideo = document.getElementById('myStream');
localTracks.forEach((track) => {
if (track.kind === 'video' || track.kind === 'audio') {
track.attach(localVideo);
}
});
// 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, tracks: localTracks })
.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
}
})();
先程と同じように再度デプロイをしてテストをしてみましょう。
参考情報
今まで紹介してこなかったTwilio Videoの機能について、簡単に説明します。
- データトラック
- 画面共有
- 録画
- 料金
データトラック
- 音声や動画以外に、データ通信用のトラックを利用することできます。
- チャットのような文字列のやり取りや、マウスの動きを送信して共同編集が可能です。
- データトラック用に、DataTrack APIが用意されています。
- ネットワークの状態によって、パケットのロスが発生することがあります。これを調整するためのパラメータとして、以下の2つを用意しています。
- maxPacketLifeTime : パケットの生存時間(この時間内であれば再送の対象とする。単位はms)
- maxRetransmits : パケットの再送回数(この回数を越えても送信できない場合はドロップ)
画面共有
- Chrome、 Firefox、 MS Edge (chromium 版)を利用する場合、画面共有が可能です。
- Firefoxはバージョン52以上で利用可能です。
- 画面共有のついては、こちらをご参照ください。
録画(Video Recording)
録画機能については、Peer-to-Peerルームと、WebRTC GOルームでは使えません。
- 録画をするためには、グループルームの初期値で録画を有効にするか、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 の料金(2022/11月現在)
ルームタイプ | 課金単位 | 料金(税込み) |
---|---|---|
Group | 1 分あたり | 0.54 円 |
Group-small | 1 分あたり | 0.54 円 |
Peer-to-peer | 1 分あたり | 0.21 円 |
WebRTC Go | 無料 | |
録画料 | 分/参加者あたり | 0.54 円 |
録画データ保存料 | 1GB 毎/日あたり | 0.25 円 |
Compositions API
を利用する場合には追加料金が発生します。
データトラック機能は、ルーム料金に含まれます。
まとめ
Twilio Video は、WebRTC をベースに作られているため、ブラウザの機能をうまく組み合わせて色々なことができます。
今回の記事では触れませんが、字幕をつけたり、画像解析を入れるなど、通常のビデオ会議では実現できないような機能をもたせることも可能です。
WebRTC Go は、参加者あたりの料金がかからないプランとなっているため、ぜひ色々とチャレンジしてみてください。
<参考> Twilio Video 関連の記事
Twilio(トゥイリオ)とは
https://cloudapi.kddi-web.com
Twilioは音声通話、メッセージング(SMS/チャット)、ビデオなどの 様々なコミュニケーション手段をアプリケーションやビジネスへ容易に組み込むことのできるクラウドAPIサービスです。初期費用不要な従量課金制で、各種開発言語に対応しているため、多くのハッカソンイベントやスタートアップなどにも、ご利用いただいております。