初めに
これは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の記事も書いているので、もしよければそちらもぜひ!