WebRTCブログ第一回

 

はじめまして!OISの自社製品『ReDois』開発担当です。

 

『ReDois』は、WebRTCと呼ばれる技術を利用し、リアルタイム配信を実現しています。

 

 

私からは、このWebRTCに関する情報について発信していこうと思います。

今回は、WebRTCの基本として、P2Pでの接続について解説します。

 

WebRTC公式より公開されているサンプルページを使い、ソースコードの解説も交えながら見ていこうと思います。

ソースコードはこちらになります。

このサンプルページでは、左上から順にボタンを押していけば自分同士でWebRTC接続ができるようになっています。

それぞれのボタンを押したとき、具体的にどのような動作をしているのか掘り下げていきましょう。

 


 

①[Get media]ボタン

最初に、マイクやカメラからストリームを取得し、それを画面に表示します

  async function getMedia() {
    (中略)
    try {
      const userMedia = await navigator.mediaDevices.getUserMedia(constraints);
      gotStream(userMedia);
    } catch (e) {
      console.log('navigator.getUserMedia error: ', e);
    }
  }

  function gotStream(stream) {
    console.log('Received local stream');
    localVideo.srcObject = stream;
    localStream = stream;
  }

具体的には、
navigator.mediaDevices.getUserMedia()メソッドでMediaStreamオブジェクトを取得し、これを画面上のvideo要素にセットすることによってマイクやカメラの音声・映像をブラウザ上で再生することができます。

(サンプルページでは、localVideoが示すvideo要素はmuted属性を持っているので音声は再生されません)

 

②[Create peer connection]ボタン

WebRTC接続の要となるオブジェクトである、RTCPeerConnectionオブジェクトを作成します。

  function createPeerConnection() {
    (中略)
    const servers = null;


    window.localPeerConnection = localPeerConnection = new RTCPeerConnection(servers);
    console.log('Created local peer connection object localPeerConnection');
    localPeerConnection.onicecandidate = e => onIceCandidate(localPeerConnection, e);
    sendChannel = localPeerConnection.createDataChannel('sendDataChannel', dataChannelOptions);
    sendChannel.onopen = onSendChannelStateChange;
    sendChannel.onclose = onSendChannelStateChange;
    sendChannel.onerror = onSendChannelStateChange;

    window.remotePeerConnection = remotePeerConnection = new RTCPeerConnection(servers);
    console.log('Created remote peer connection object remotePeerConnection');
    remotePeerConnection.onicecandidate = e => onIceCandidate(remotePeerConnection, e);
    remotePeerConnection.ontrack = gotRemoteStream;
    remotePeerConnection.ondatachannel = receiveChannelCallback;

    localStream.getTracks()
      .forEach(track => localPeerConnection.addTrack(track, localStream));
    console.log('Adding Local Stream to peer connection');
  }

今回は自分同士での接続なので、ローカル・リモート共にこの場でRTCPeerConnectionオブジェクトを作成しています。
RTCPeerConnectionオブジェクトを作成する他に、①で取得したMediaStreamオブジェクトからMediaStream.getTracks()メソッドでMediaStreamTrackオブジェクトを取得し、RTCPeerConnection.addTrack()メソッドでこれから送信する音声・映像をセットしています。

 

③[Create offer]ボタン

SDPと呼ばれる文書をoffer側(今回はローカル側)で作成します。SDPについての詳細な説明は今回省きますが、WebRTC接続にはお互いの内部情報をやりとりする必要があり、SDPはそれを表現するプロトコルです。

 async function createOffer() {
    try {
      const offer = await localPeerConnection.createOffer(offerOptions);
      gotDescription1(offer);
    } catch (e) {
      onCreateSessionDescriptionError(e);
    }
  }
function gotDescription1(description) {
    offerSdpTextarea.disabled = false;
    offerSdpTextarea.value = description.sdp;
  }

SDP offerはRTCPeerConnection.createOffer()メソッドで作成します。
ここでは更に、作成したSDPをtextarea要素で表示しています。

 

④[Set offer]ボタン

作成したSDP offerをRTCPeerConnectionオブジェクトにセットします。

async function setOffer() {
    let sdp = offerSdpTextarea.value;
    const offer = {
      type: 'offer',
      sdp: sdp
    };
    console.log(`Modified Offer from localPeerConnection\n${sdp}`);

    try {
      // eslint-disable-next-line no-unused-vars
      const ignore = await localPeerConnection.setLocalDescription(offer);
      onSetSessionDescriptionSuccess();
    } catch (e) {
      onSetSessionDescriptionError(e);
    }

    try {
      // eslint-disable-next-line no-unused-vars
      const ignore = await remotePeerConnection.setRemoteDescription(offer);
      onSetSessionDescriptionSuccess();
    } catch (e) {
      onSetSessionDescriptionError(e);
    }
  }

ローカル側はRTCPeerConnection.setLocalDescription()、リモート側はRTCPeerConnection.setRemoteDescription()でSDP offerをセットします。
今回は自分同士の接続なので気にしなくてよいのですが、本来リモートはSDP offerを知らないはずなので、SDPを伝達するためにシグナリングサーバーと呼ばれるものが必要になってきます。これについては次回以降で詳しく解説する、かも…?

また、SDP offerをセットした後、リモート側のonTrackイベントが発火します。イベント発火時の処理gotRemoteStream()は②でリモート側に設定しています。

function gotRemoteStream(e) {
    if (remoteVideo.srcObject !== e.streams[0]) {
      remoteVideo.srcObject = e.streams[0];
      console.log('Received remote stream');
    }
  }

 

onTrackイベントが発火すると、通信相手が送信するMediaStreamオブジェクトの配列を受け取れるので、それをリモート側のvideo要素にセットしています。
ただし、この時点ではWebRTC接続が確立されていないので、セットした音声・映像が再生されることはありません。

 

⑤[Create answer]ボタン

SDPをanswer側(今回はリモート側)で作成します。
offerとanswerについての説明も次回以降をお楽しみに…!!

async function createAnswer () {
   (中略)
    try {
      const answer = await remotePeerConnection.createAnswer();
      gotDescription2(answer);
    } catch (e) {
      onCreateSessionDescriptionError(e);
    }
  }
function gotDescription2(description) {
    answerSdpTextarea.disabled = false;
    answerSdpTextarea.value = description.sdp;
  }

SDP answerはRTCPeerConnection.createAnswer()メソッドで作成します。
③同様、作成したSDPをtextarea要素で表示しています。

 

⑥[Set answer]ボタン

作成したSDP answerをRTCPeerConnectionオブジェクトにセットします。

async function setAnswer() {
    let sdp = answerSdpTextarea.value;
    const answer = {
      type: 'answer',
      sdp: sdp
    };

    try {
      // eslint-disable-next-line no-unused-vars
      const ignore = await remotePeerConnection.setLocalDescription(answer);
      onSetSessionDescriptionSuccess();
    } catch (e) {
      onSetSessionDescriptionError(e);
    }

    console.log(`Modified Answer from remotePeerConnection\n${sdp}`);
    try {
      // eslint-disable-next-line no-unused-vars
      const ignore = await localPeerConnection.setRemoteDescription(answer);
      onSetSessionDescriptionSuccess();
    } catch (e) {
      onSetSessionDescriptionError(e);
    }
  }

ローカル側はRTCPeerConnection.setRemoteDescription()、リモート側はRTCPeerConnection.setLocalDescription()でSDP answerをセットします。
ここまで完了すると後はWebRTCが勝手に接続試行を行ってくれ、成功するとリモート側のvideo要素で音声・映像の再生が始まります。

接続成功!

 

⑦[Hang up]ボタン

WebRTC接続を終了します。

function hangup() {
    remoteVideo.srcObject = null;
    console.log('Ending call');
    localStream.getTracks().forEach(track => track.stop());
    sendChannel.close();
    if (receiveChannel) {
      receiveChannel.close();
    }
    localPeerConnection.close();
    remotePeerConnection.close();
    (以下略)

ローカル・リモート共にRTCPeerConnection.close()メソッドでWebRTC接続を閉じます。
お疲れ様でした。

 


 

さて、いかがでしたでしょうか。

単純なP2Pであれば、WebRTCは誰でも簡単に利用することができる優れものです。

今後も『ReDois』やWebRTCについて継続的に発信していきます。

お楽しみに!

 

オリエンタルインフォーメイションサービス(OIS)

\ 最新情報をチェック /