2018年8月のWebRTC API
WebRTC 1.0 に向けて、ゆっくりながらも着実にブラウザの実装が進んでいます。
- MediaStream単位から、MediaTrack単位のAPIに
- 参考:最新のWebRTCのtrack系処理を、手動+データチャネルシグナリングで観察してみる
- addStream()/removeStream()ではなく、addTrack()/removeTrack()を利用
- peer.onaddstream(), peer.onremovestream()イベントではなく、peer.ontrack(), stream.onremovetrack()イベントを利用
- Multi-streamが、もうすぐUnified Planに
- Firefoxだけでなく、ChromeでもM70からUnified Planに
- Edgeも対応予定、Safariは音沙汰なし
- 参考:WebRTC の未来 - Unified Plan
- 参考:SDP の Unified Plan と Plan B
これを踏まえて、いつもの手動シグナリングをアップデートしました。
MediaTrack 単位のメディアのハンドリング
ローカルメディアの追加
RTCPeerConnection.addStream()ではなく、RTCPeerConnection.addTrack()を使います。
let peer = new RTCPeerConnection();
let localStream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
// --- 従来のMediaStream の追加 ---
peer.addStream(localStream);
// --- 新しい MediaTrack単位の追加 ---
let videoSender = peer.addTrack(localStream.getVideoTracks()[0], localStream);
let audioSender = peer.addTrack(localStream.getAudioTracks()[0], localStream);
この時、addTrack()の戻り値のRTCRtpSender を保存して置いて、メディアの除去に使います。
ローカルメディアの除去
// --- 従来のMediaStream の除去 ---
peer.removeStream(localStream);
// --- 新しい MediaTrack単位の除去 ---
peer.removeTrack(videoSender);
peer.removeTrack(audioSender);
リモート側メディアの追加、除去のイベンント
これまでは、リモート側でメディアが追加になると、RTCPeerConnection.onaddstream() が発火していました。これが、RTCPeerConnection.ontrack() に変わります。また、リモート側のメディアが除去された場合のイベントは、MediaStream.onremovetrack() になります。 (RTCPeerConnection.onremovetrack() のようなイベントはありません)
// --- 従来のMediaStream の追加、除去 ---
peer.onaddstream = function(event) {
let stream = event.stream;
// ... 追加時の処理 ...
}
peer.onremovestream = function(event) {
let stream = event.stream;
// ... 除去時の処理 ...
};
// --- 新しい MediaTrack単位の追加、除去 ---
peer.ontrack = function(event) {
let track = event.track;
let stream = event.streams[0];
if (track.kind === 'video') {
// video追加時の処理
}
else if (track.kind === 'audio') {
// audio追加時の処理
}
stream.onremovetrack = function (evt) {
const track = evt.track;
if (track.kind === 'video') {
// video除去時の処理
}
else if (track.kind === 'audio') {
// audio除去時の処理
}
};
stream.onaddtrack = function (evt) {
// 2つ目以降のトラック追加では、このイベントも発火する
// 通常は peer.ontrack() を使えば、こちらでハンドリングする必要はなさそう
};
};
// --- トラック除去時に、このようなイベントは発火しない
// peer.onremovetrack = function(event) {};
Unified Plan の利用
1つのRTCPeerConnectionで、複数のメディアストリームを通すマルチストリームには、2種類のやり方があり、生成されるSDPが異なります。
- Unified Plan ... Firefoxが採用。WebRTC 1.0もこちら。Chrome 70でも対応予定
- Plan B ... Chrome, Safari が採用
- ※Edgeは、まだ2017年の段階ではマルチストリームが利用できませんでした。2018年8月現在の状況は未確認です。
Chrome 70 から、オプション指定でUnified Planも選べる様になります。
let peer = new RTCPeerConnection({sdpSemantics : "unified-plan"});
※このオプションを付けていても、Chrome 69やFireFox, Safariでもエラーにはなりません。効果もありませんが。
半手動で動かしてみる
MediaTrack単位の扱いや、Unified Planによるマルチストリームの動作が確認できるデモを用意しました。
Chrome Canary 70, Firefox 61, Chrome 69(Unified Planは未対応)で動きます。
- GitHub Pages で試す
- GitHub でソースを見る
シグナリングの仕組み
- 最初はDataChannelを確立する
- この際に BroadcastChannel を使って、Offer / Answer のSDPを交換 (参考)
- Vanilla ICE を使って、全ての ICE Candidate が揃ってから交換
- ※異なるブラウザ間で通信する場合は、BroadcastChannelの替わりに手動でコピー&ペーストする
- addTrack()/removeTrack()をした場合は、DataChannelを使って Offer / Answer のSDPを再交換
- peer.onnegotiationneeded() イベントの発火で Offer を投げる
- 最初のSDP交換でOffer を出した側、Answerを返した側のどちらでも、Offerを投げることができる
準備
※WebAudioを扱う部分にバグがあり正しく動きませんでした。2018.10.25現在修正済です。
- ブラウザで2つの別々のウィンドウを開き、横に並べてください
- この時ログが見れるように、デベロッパーコンソールを開いておくと分かりやすいです
- それぞれ https://mganeko.github.io/webrtcexpjp/basic2016/dc_signaling_multistream.html にアクセスしてください
Mac OS X 上のChrome 69どうし, Chrome Canary 70どうし, Firefox 61どうしで動作確認しています。異なるブラウザ間でも、「use BroadcastChannel」のチェックを外して手動でSDPをコピー&ペーストすれば、通信させることができます。
以下、Chrome Canary 70 どうしで使った場合の例を示します。
DataChannelの確立まで
BroadcastCannelを使ってシグナリングを行い、DataChannelの接続を確立します。
-
- 片方のブラウザA(左)で [Connect Datachannel]ボタンをクリック
Offer SDP が生成される
- 片方のブラウザA(左)で [Connect Datachannel]ボタンをクリック
-
- BroadcastChannel経由でブラウザA(左)→ ブラウザB(右)Offerが、ブラウザB → ブラウザA にAnswerが交換される
- この時、テキストエリアにSDPの内容が表示される
-
- DataChannelが確立すると、両方のブラウザのコンソールに"datachannel open" のログが表示される
-
- どちらかのブラウザの[Send Hello]ボタンをクリックすると datachannel でメッセージを送信
-
- 反対側のブラウザのコンソールにメッセージのログが表示され、メッセージを送り返す。メッセージを送った側のコンソールにもログが表示される
手動の場合は、こちらの手順を参照してください。
メディアの開始
どちらか一方、または両方のブラウザで、[Start Media]ボタンをクリックすると、ビデオ、オーディオが複数開始し、Local Mediaの領域に表示されます。全部で3つのMediaStreamが用意されます。
- 一番左が、カメラとマイクで取得された映像と音声です(Video + Audio の MediaStream)
- 真ん中が、Canvasで時刻を表示しています。captureStream()で映像を取り出してます(Video のみの MediaStream)
- 一番右が、WebAudioで作った音声です(Audio のみの MediaStream) ※音量はゼロです
メデイアトラックの追加/除去
トラックの追加
- [add Video]でカメラの映像が、[add Audio]でマイクの音声が追加されます (同じMediaStream)
- [add Video of Canvas]でキャンバスからの時刻の映像が追加されます (2番目のMediaStream)
- [add Audio of WebAudio]でWebAudioで作った和音の音声が追加されます(3番目のMediaStream)
送信先では、MediaStream毎にVideoElementが生成されます。手を抜いて音声だけの場合もVideoElementで再生しています。今回は最大3つのVideoElementが表示されることになります。
両方でトラックを追加すれば、双方向にも映像/音声を送信できます。
追加処理の流れ
-
- 送信元
- peer.addTrack()でビデオ/オーディオトラックを追加、戻り値のsenderを保持
- peer.onnegotiationnneded()が発火
- DataChannel経由で、Offer/Answerが交換される
- 相手側のブラウザに映像または音声が伝送される
-
- 送信先(受信側)
- peer.addTarck()イベントが発火
- 2つ目以降のトラックだった場合は、mediastream.onaddtrack()も発火(何もしていない)
- (MediaTrackが所属する)MediaSteamに対応するVideo要素がなかったら、新しく生成
- video要素で、映像または音声を再生
トラックの除去
- [remove Video]でカメラの映像を、[remove Audio]でマイクの音声を除去します (同じMediaStream)
- [remove Video of Canvas]でキャンバスからの映像を除去します (2番目のMediaStream)
- [remove Audio of WebAudio]でWebAudioで作った音声を除去します(3番目のMediaStream)
送信先では、トラック数がゼロになったMediaStreamに対応するVideoElementは削除されます。
除去処理の流れ
-
- 送信元
- 除去するトラックに対応するsenderを取得し、peer.removeTrack()で除去
- peer.onnegotiationnneded()が発火
- DataChannel経由で、Offer/Answerが交換される
- 相手側のブラウザで映像または音声が除去される
-
- 送信先(受信側)
- mediastream.onremovetrack()が発火
- 該当するmediastreamのトラック数がゼロになったら、対応するVideoElementの再生を停止、削除
終わりに
しょぼいサンプルですが、手軽にマルチストリームのWebRTC通信を行うことができます。お試しあれ。
- GitHub Pages で試す
- GitHub でソースを見る