29
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Unified Plan時代のWebRTC(半)手動シグナリング (2018.08)

Last updated at Posted at 2018-08-11

2018年8月のWebRTC API

WebRTC 1.0 に向けて、ゆっくりながらも着実にブラウザの実装が進んでいます。

これを踏まえて、いつもの手動シグナリングをアップデートしました。

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は未対応)で動きます。

シグナリングの仕組み

  • 最初は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現在修正済です。

Mac OS X 上のChrome 69どうし, Chrome Canary 70どうし, Firefox 61どうしで動作確認しています。異なるブラウザ間でも、「use BroadcastChannel」のチェックを外して手動でSDPをコピー&ペーストすれば、通信させることができます。

以下、Chrome Canary 70 どうしで使った場合の例を示します。

DataChannelの確立まで

BroadcastCannelを使ってシグナリングを行い、DataChannelの接続を確立します。

broadcast_signaling_for.png
    1. 片方のブラウザA(左)で [Connect Datachannel]ボタンをクリック
      Offer SDP が生成される
    1. BroadcastChannel経由でブラウザA(左)→ ブラウザB(右)Offerが、ブラウザB → ブラウザA にAnswerが交換される
    • この時、テキストエリアにSDPの内容が表示される
    1. DataChannelが確立すると、両方のブラウザのコンソールに"datachannel open" のログが表示される
    1. どちらかのブラウザの[Send Hello]ボタンをクリックすると datachannel でメッセージを送信
    1. 反対側のブラウザのコンソールにメッセージのログが表示され、メッセージを送り返す。メッセージを送った側のコンソールにもログが表示される

手動の場合は、こちらの手順を参照してください。

メディアの開始

どちらか一方、または両方のブラウザで、[Start Media]ボタンをクリックすると、ビデオ、オーディオが複数開始し、Local Mediaの領域に表示されます。全部で3つのMediaStreamが用意されます。

start_media.png
  • 一番左が、カメラとマイクで取得された映像と音声です(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が表示されることになります。
両方でトラックを追加すれば、双方向にも映像/音声を送信できます。

追加処理の流れ

    1. 送信元
    • peer.addTrack()でビデオ/オーディオトラックを追加、戻り値のsenderを保持
    • peer.onnegotiationnneded()が発火
    • DataChannel経由で、Offer/Answerが交換される
    • 相手側のブラウザに映像または音声が伝送される
    1. 送信先(受信側)
    • 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は削除されます。

除去処理の流れ

    1. 送信元
    • 除去するトラックに対応するsenderを取得し、peer.removeTrack()で除去
    • peer.onnegotiationnneded()が発火
    • DataChannel経由で、Offer/Answerが交換される
    • 相手側のブラウザで映像または音声が除去される
    1. 送信先(受信側)
    • mediastream.onremovetrack()が発火
    • 該当するmediastreamのトラック数がゼロになったら、対応するVideoElementの再生を停止、削除

終わりに

しょぼいサンプルですが、手軽にマルチストリームのWebRTC通信を行うことができます。お試しあれ。

29
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?