初めに
これはWebRTCの概要やWebRTCを使用したサービスであるSkyWayを見ていき、WebRTCについての理解を深めることを目的とした記事です。
WebRTCとは?
WebRTCについてはこちらの記事を読んでいただければそれの仕組みや特徴が分かると思います。
上記の記事をざっくりまとめると、WebRTC(Web Real-Time Communications、ウェブリアルタイムコミュニケーション)とはウェブアプリケーションなどにてブラウザ間で直接ユーザー同士のリアルタイムの音声や動画をやり取りすることのできる技術です。また、WebRTCの基本的な概念は、MediaStreamオブジェクト(あとで詳しく取り上げる)を利用してユーザーたちの音声・画像などを取得し、 RTCPeerConnectionインターフェースにより接続されたユーザー同士でそれらをやり取りする、というものです。
SkyWayとは?
SkyWayとはWebRTCの仕組みを利用したサービスで、APIとして利用できます。SkyWayの基本的な仕組みは、ユニークなIDを所有するユーザー同士が、PeerというユーザーたちとSkyWayが提供するサーバ(シグナリングサーバ、WebRTCの利用に必要な情報のやり取りを行う)との接続やユーザー同士の接続を管理するエージェントを通して、MediaStreamオブジェクトの情報をやり取りするものとなっています。
詳しくはこちらの記事に記載されています。
もしSkyWayで一対一のユーザー同士をする場合はこんな感じになります。

SkyWayの特徴は以下の点です。
- WebRTCの実装部分をコーディングする必要がないため、WebRTCについて知識がなくてもWebRTCを利用することができる。
- 多人数通話や多人数に対する映像配信ができる。
- 通常であればWebRTCが利用できない状況(UDP通信が利用できないなど)でもSkyWayならWebRTCを利用することができる。
- IOSやAndroidなどのブラウザ以外からの実行環境でもWebRTCの利用ができる。
参考資料
他のサービスにはない様々な特徴がSkyWayにはあるので、詳しくはSkyWayのホームページをご覧ください。
SkyWayチュートリアルからSkyWayの仕組みを見てみる
最後にSkyWayが提供しているチュートリアルからSkyWayの仕組みについてみていこうと思います。ko
のチュートリアル自体はhtmlとjavascriptのみから構成されていて、GoogleChrome、Firefox、MicrosftEdgeなどのブラウザーから実行できるのでぜひやってみてください。(今回見ていくチュートリアルのコードは見やすくするために表記が実際のものと異なる部分があります。)
ちなみにチュートリアルはこちらから閲覧できます。
SkyWayチュートリアルの処理の大まかな流れ
SkyWayチュートリアルの処理を図で表してみました。次の章でこの処理について詳しく見ていくので、ぜひそちらの方もご覧ください。

表示部分
表示部分であるhtmlのコードはこちら。
<html lang="ja"> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebRTC Test</title>
<script src="https://cdn.webrtc.ecl.ntt.com/skyway-4.4.1.js"></script>
</head>
<body>
<video id="my-video" width="400px" autoplay muted playsinline></video>
<p id="my-id"></p>
<textarea id="their-id"></textarea>
<button id="make-call">発信</button>
<video id="their-video" width="400px" autoplay playsinline></video>
<script src="./rtc.js"></script>
</body>
</html>
<script src="https://cdn.webrtc.ecl.ntt.com/skyway-4.4.1.js">によりSkyWayサービスを利用します。<script src="./rtc.js"></script>で表記されているrtc.jsはこのhtmlファイルと同じ階層にあるSkyWayを使用したWebRTCの実装を担うファイルで、表記部分と実装部分を分けたいがために勝手に作りました。なのでこの部分に直接WebRTCの実装プログラムを書き込んでも動きます。
実装部分
実装部分のrtc.jsの中身はこちらです。
//音声、カメラの取得
let localStream;
navigator.mediaDevices.getUserMedia({video: true, audio: true})
.then( stream => {
const videoElm = document.getElementById('my-video');
videoElm.srcObject = stream;
videoElm.play();
localStream = stream;
}).catch( error => {
console.error('mediaDevice.getUserMedia() error:', error);
return;
});
//Peer idの取得
const peer = new Peer({
key: '各々Skywayで登録したAPIキー',
debug: 2
});
peer.on('open', () => {
document.getElementById('my-id').textContent = peer.id;
});
//発信処理
document.getElementById('make-call').onclick = () => {
const theirId = document.getElementById('their-id').value;
const mediaConnection = peer.call(theirId, localStream);
setEventListener(mediaConnection);
};
setEventListener = mediaConnection => {
mediaConnection.on('stream', stream => {
const videoElm = document.getElementById('their-video');
videoElm.srcObject = stream;
videoElm.play();
});
}
//着信処理
peer.on('call', mediaConnection => {
mediaConnection.answer(localStream);
setEventListener(mediaConnection);
});
実装内容を部分ごとに見ていきます。
//音声、カメラの取得
let localStream;
navigator.mediaDevices.getUserMedia({video: true, audio: true})
.then( stream => {
const videoElm = document.getElementById('my-video');
videoElm.srcObject = stream;
videoElm.play();
localStream = stream;
}).catch( error => {
console.error('mediaDevice.getUserMedia() error:', error);
return;
});
まずはユーザーの音声、カメラの取得について見ていきます。
navigatorはあらかじめ定められたグローバルなプロパティです。このプロパティを呼び出すことにより、アプリのユーザーが使用するブラウザの状態や情報(ブラウザが使用するメディアの情報、そのブラウザが動いているデバイスの位置情報など)をアプリ側が取得できます。
参考記事
navigatorが所有する読み取り専用プロパティであるmediaDevicesはブラウザのカメラやマイクなどのメディア入力装置へのアクセスを提供するmediaDeviceオブジェクトを返します。
mediaDeviceオブジェクトが返された後、このオブジェクトが有するgetUserMediaメソッドが実行されます。このメソッドはブラウザのメディア入力の許可をユーザーに求め、最終的にMediaStreamオブジェクトを返すPromiseオブジェクトを生成し、ユーザーが許可した場合、thenメソッドの引数(今回はstream)からMediaStreamオブジェクトが利用できるようになります。getUserMediaメソッドの引数からmediaStreamにて扱うメディア入力の種類(トラック)を制約するMediaStreamConstraintsオブジェクトが作成され、もしユーザーの音声と映像が取得したい場合は、その引数は{ audio: true, video: true }となります(audio、videoのどちらかはtrueとして指定しなくてはErrorが発生し、正常に処理をこなせません)。audio、videoの両オブジェクトの値を変えることで映像の解像度を指定するなどのさらなる制約を付け加えることもできます。
この部分で扱っているMediaStreamオブジェクトはストリーム(リアルタイムの動画データなど)もデータを扱い、音声・映像などのストリームの要素はトラックという単位で管理されます。(MediaStream.getTracksメソッドなどでMediaStreamオブジェクトのトラックを取得することでストリームの音声・映像などの制御ができます。)
参考記事
今度はgetUserMediaメソッドの成功時に行われるthenメソッドを見てみます。
まずはid="my-video"で指定されたvideo要素のオブジェクトを変数videoElmに代入し、そのオブジェクトが有するsrcObjectプロパティにMediaStreamオブジェクトを代入します。srcObjectプロパティはメディアが利用できるHTMLElement(<video>など)に含まれるプロパティであり、HTMLElementで利用するメディアソースを提供するオブジェクトが入ります。ここに入るオブジェクトの種類は、MediaStream、MediaSource、Blob、Fileがありますが、注釈を見ると2017年11月のブラウザはMediaSrreamのみしかサポートしていないため、srcObjectにオブジェクトを代入するときはMediaStreamオブジェクトのみにした方がよさそうです。
変数videoElmがplayメソッドを実行し、先ほどsrcObjectに代入されたメディアソースが再生されます。playメソッドはHTMLElementにあらかじめ含まれているメソッドであり、最終的にPromiseを返すので、今回は実装してませんがcatchメソッドを用いて失敗時の処理を実装することもできます。ちなみに<video src="流したい動画">に対してplayメソッドを使用すると流したい動画を再生させることができます。なので言い換えると、このメソッドによりHTMLMediaElement.srcプロパティにあるメディアソースも再生できます。
最後にMediaStreamオブジェクトをグローバル変数であるlocalStreamに代入し、次の処理へ移ります。
もしこの処理の中でNotAllowedErrorまたはNotFoundErrorなどが発生した場合は、catchメソッド以降の処理へ移り、失敗を告げるコンソール出力とともにすべての処理をここで終えることとなります。
参考記事
//Peer idの取得
const peer = new Peer({
key: '各々Skywayで登録したAPIキー',
debug: 2
});
peer.on('open', () => {
document.getElementById('my-id').textContent = peer.id;
});
次に通信を管理するPeerを使用し、通信に必要なPeer IDを取得する部分について見ていきます。これ以降の処理はSkyWayをCDNなどでアプリ上で利用できる状態でないと作動しません。
PeerクラスはSkyWayを使用するうえで必要不可欠なクラス。new Peer(id, option)またはnew Peer(option)の二つからこのクラスの新スタンスが取得可能で、今回は後者の方法で取得しています。idはユーザーがほかのユーザーと通信を行う際に必要なPeer IDというもので、指定しない場合は自動で作成されます。optinonはSkywayの接続時のオプションを指定するオブジェクトで、このオブジェクト内のkeyプロパティにアプリ製作者がSkyWayを利用するために作成したAPIキーを代入しないとWebRTCの通信ができません。debugは通信時にブラウザのコンソールに何を出力するのか決められるもので、今回は通信時の情報の全てを出力するように指定しています。
Peerインスタンスに含まれるonメソッドは、SkyWayが提供する全てのクラスが継承するEventEmitterが所持するメソッドで、第一引数のイベント(今回はopen)の実行時に第二引数のクロージャが実行されます。openイベントとはPeerインスタンスの生成時にシグナリングサーバとの接続が鵜なくいった際に発生するイベントであり、今回は他のユーザーと通信する際に必要なユーザー自身のPeer IDをブラウザ上の決まった要素へ表示するようにしています。
ちなみにシグナリングサーバとは最初の方にも紹介したように、SkyWayが提供している、通信するユーザー同士のIPアドレスなどの通信に必要な情報をそのユーザー間で交換する機能を持つサーバです。
参考記事
//発信処理
document.getElementById('make-call').onclick = () => {
const theirId = document.getElementById('their-id').value;
const mediaConnection = peer.call(theirId, localStream);
setEventListener(mediaConnection);
};
setEventListener = mediaConnection => {
mediaConnection.on('stream', stream => {
const videoElm = document.getElementById('their-video');
videoElm.srcObject = stream;
videoElm.play();
});
}
次にほかのユーザーへMediaStreamオブジェクトを送信する際の発信処理について見ていきます。こちらは受信側のユーザーは行いません。
id='make-call'の要素をクリックした場合、以下の三つを実行します。
-
id="their-id"の要素の値を変数theirIdへ代入 -
Peerインスタンスのcallメソッドの結果を変数mediaConnectionへ代入。callメソッドは接続を管理するMediaConnectionインスタンスを返すメソッドであり、第一引数に接続先のユーザーが所有するPeer IDを、第二引数にブラウザのストリームの音声・映像の情報を有するMediaStreamオブジェクトが入り、第三引数に発信時のオプションを入れることもできる。 - 独自に作成した
setEventListenerメソッドに変数mediaConnectionを引数にとったものを実行する。(このメソッドは次に説明する)
setEventListenerメソッドの処理は以下の通りです。
引数のmediaConnectionオブジェクトのonメソッドにの第一引数にstreamを指定して実行します。もしstreamを指定した場合、MediaStreamを受信したときに第二引数のクロージャを実行します。ここで使用するクロージャの引数streamにはあとで見ていく着信処理の中のmediaConnectionオブジェクトのanswerメソッドから送られてきたMediaStreamオブジェクトが入ります。その後の処理は先ほど見ていったブラウザに自分のストリームを表示する処理と同じです。
参考記事
//着信処理
peer.on('call', mediaConnection => {
mediaConnection.answer(localStream);
setEventListener(mediaConnection);
});
最後に他のユーザーからMediaStreamの送信が来た際の着信処理について見ていきます。この処理は送信側は行いません。
Peerインスタンスが入っているpeerオブジェクトのonメソッドは第一引数にcallが指定された場合に、Peerインスタンスのcallメソッドの実行時に第二引数のMediaConnectionのインスタンスが代入されたmediaConnectionオブジェクトを引数にとったクロージャが実行されます。mediaConnectionオブジェクトのanswerメソッドはPeerIDを持ったほかのユーザーが自分のPeerIDへ接続があった場合に実行するもので、第一引数のMediaStreamオブジェクト(今回は受信側のストリームデータを持つlocalStream)を接続元のPeerIDを持つブラウザへ送信します。また、第二引数に応答時のオプションを指定することもできます。最後に送信元からのMediaStreanオブジェクトを含んだmediaConnectionオブジェクトを引数にとり、送信側と同様にsetEventListenerメソッドを実行し、送信元のストリームを受信側のブラウザにも映します。
参考記事
終わりに
この記事を見ていただきありがとうございます。
Javascriptの他にもPHPの記事も書いているので、もしよければそちらもぜひ!